最近抽空在看自制操作系统相关的书籍,比如《自己动手写操作系统》、《Orange’S:一个操作系统的实现》、《一个64位操作系统的设计与实现》、《30天自制操作系统》等等,只有《30天自制操作系统》是可以完全在Windows下编译、链接、生成镜像的(使用的自制的非标准工具),其它几个全部都是在虚拟机中安装Linux系统,在Windows下编写源码,Linux下进行源码的编译链接,然后生成镜像。这就导致需要在Windows与Linux之间来回切换。
笔者尝试改写书中Makefile,用于MinGW环境下完全编译链接,生成镜像文件,遇到过太多坑。最主要的问题是MinGW中的GCC不支持elf文件格式,而是只能生成PE格式,LD也只能链接PE格式。但是我们需要链接成平面(Flat)二进制或者ELF格式,有一个–oformat binary
选项,但是使用它会遇到的问题是:
cannot perform PE operations on non PE output file
网上很多资料都是建议构建一个交叉编译的GCC套件。
笔者后面写的《在MinGW中构建GCC交叉编译器》一文有介绍使用MinGW构建GCC交叉编译器。
就在快要放弃的时候,遇到一个玩具操作系统示例,它就是完全在MinGW下编译、链接生成镜像的,这让我顿时来了兴趣,下面以一个实例来说明。
新建一个目录,在其中创建一个main.c,内容如下:
void start()
{
loop:
goto loop;
}
再创建一个Makefile文件,内容如下:
CC := gcc
RM := rm
CFLAGS := -Wall -fno-builtin -nostdlib -ffreestanding -nostdinc -m32 -fno-pic
all: main.bin
main.exe: main.o
$(CC) $(CFLAGS) -o main.exe main.o
main.bin: main.exe
objcopy -O binary main.exe main.bin
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
clean:
$(RM) *.o *.exe *.bin
在MinGW控制台中执行make命令,可以看到完全正常编译链接,没有任何错误:
使用命令
ndisasm main.bin > a.txt
反汇编来看一下:
可以看到生成了相应的代码,jmp short 0x3就是一直在死循环。
还有一种使用ld的脚本命令作为参数来编译链接,建立一个link.ld文件,内容如下:
OUTPUT_FORMAT("pei-i386")
ENTRY(_start)
phys = 0x00020000;
SECTIONS
{
.text phys : AT(phys) {
code = .;
*(.text)
*(.rodata)
. = ALIGN(4096);
}
.data : AT(phys + (data - code))
{
data = .;
*(.data)
. = ALIGN(4096);
}
.bss : AT(phys + (bss - code))
{
bss = .;
*(.bss)
. = ALIGN(4096);
}
end = .;
}
然后修改Makefile中的生成命令:
$(CC) $(CFLAGS) -o main.exe main.o -T link.ld
注意输出格式是"pei-i386",而不是ld支持的格式。ld支持的格式为:
再看一下生成的汇编代码:
可以看到,两种方式生成的自己编写的代码是完全一样的,只是后面自动填充的代码不一样。
由于ld默认的入口函数为start所以C文件中也是定义的start函数,不能直接使用main函数作为入口,否则会导致链接失败:
这里有一个技巧,就是不直接使用ld命令来链接,而是让gcc来调用,然后使用objcopy来生成我们想要的平面二进制文件或者ELF文件,当然,如果你想要生成32位elf文件,可以将objcopy的参数改为elf32-i386
,比如:
objcopy -O elf32-i386 main.exe main.elf
然后可以通过命令objdump -f main.elf
查看刚才生成的文件格式:
$ objdump -f main.elf
main.elf: file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00020000
原来MinGW环境下要这么玩,终于可以完全在MinGW下编写自己的操作系统了,希望本文对你有所帮助!