上一期已经讲述了操作系统用到的相关寄存器和一些位操作

那么我们这一期来看看程序的实现吧!

bootloader目标代码是什么格式(操作系统开发loader程序编写)(1)

(GuEeOS启动界面)

链接之后,程序的代码段,数据段就保存好了。以上就是对ELF(32bits)的简单介绍,如果读者还想深入了解这个格式,可以去各种网站上一一了解。当然,笔者也找到了这个文献:http://www.skyfree.org/linux/references/ELF_Format.pdf(如果失效还可以在 Linux 系统的/usr/include/elf.h 中找到这些定义)。下面笔者将象征性地演示ELF文件的分析,首先需要一个二进制查看器,这种工具在Linux和windows上都非常易得,笔者用的是binary Editor(一个鬼子开发的软件)。打开编译后的kernel.bin文件:

bootloader目标代码是什么格式(操作系统开发loader程序编写)(2)

(开发底层软件研究二进制数据很重要)

上面都是16进制码,画蓝色细线的地方是ELF Header(Elf32_Ehdr),画红色粗线的地方是Program header table(Elf32_Phdr)下面我们逐字节分析:

第一行:

第二行:

第三行:

第四行:

第五行:

第六行:

得知内核的各个数据后,我们就可以加载内核了。目前我们强制设定了Loader程序大小为4096字节,占用了4096/512=8个扇区,那么我们就从第9扇区开始读取内核。

我们先修改Makefile:

1#现在该明白这儿为什么写1、8了吧 2LOAD_SECTOR_OFFSET=1 3LOAD_SECTORS=8 4 5#从第9扇区开始读,设为9 6KERNEL_SECTOR_OFFEST=9 7#内核占用扇区数,根据内核大小设置,写得足够大就行 8KERNEL_SECTORS=348 9 10#这是笔者的gcc的目录,请读者另外自行设置(可无) 11PREFIX=builder/ 12 13NASM=nasm 14CC=$(PREFIX)gcc 15LD=ld 16DD=dd 17QEMU=qemu-system-i386 18 19BOOT_BIN=boot.bin 20LOADER_BIN=loader.bin 21KERNEL_FILE=kernel.bin 22OS_IMG=os.img 23 24#编译参数 25ASM_KERNEL_FLAGS=-felf32 26#-fno-builtin指不使用gcc默认库,因为我们要自己实现所有功能-m32是32位模式-I是指头文件默认目录 27C_KERNEL_FLAGS=-I./include-c-fno-builtin-m32 28#内核_START(可自行设置,默认为_start)入口和地址 29LD_FLAGS=-melf_i386-e_START-Ttext0x80100000 30 31#默认执行os.img 32.all:os.img 33 34#注意这些缩进是制表符[--->],不是空格[] 35bootloader.bin: 36$(NASM)boot.asm-o$(BOOT_BIN) 37$(NASM)loader.asm-o$(LOADER_BIN) 38 39ASM_FILE: 40$(NASM)$(ASM_KERNEL_FLAGS)_Start.asm-o_Start.o 41 42C_FILE: 43$(CC)$(C_KERNEL_FLAGS)start.c-ostart.o 44 45kernel.bin:ASM_FILEC_FILE 46$(LD)$(LD_FLAGS)-o$(KERNEL_FILE)_Start.ostart.o 47 48#执行os.img前,应该生成boot.bin和loader.bin 49#seek为9,目的是跨过前9个扇区(第0~8个扇区),我们在第9个扇区写入。 50#count为348,目的是一次往参数of指定的文件中写入348个扇区。 51os.img:bootloader.binkernel.bin 52$(DD)if=$(BOOT_BIN)of=$(OS_IMG)bs=512count=1conv=notrunc 53$(DD)if=$(LOADER_BIN)of=$(OS_IMG)bs=512seek=$(LOAD_SECTOR_OFFSET)count=$(LOAD_SECTORS)conv=notrunc 54$(DD)if=$(KERNEL_FILE)of=$(OS_IMG)bs=512seek=$(KERNEL_SECTOR_OFFEST)count=$(KERNEL_SECTORS)conv=notrunc 55 56#运行前,应该生成os.img 57run:os.img 58$(QEMU)-boota-fda$(OS_IMG) 59 60clean: 61rm*.bin 62rm*.o

里面有些提到的文件后面会讲。目前文件较少,内核目录也较简单,下节会有较大的改动,这是目前的文件树:

bootloader目标代码是什么格式(操作系统开发loader程序编写)(3)

我们先把内核前奏的程序_Start.asm写好(可不写,记得设置内核入口函数名就行),这个内核是由另一个程序调用的,栈顶地址可自己按照需要修改:

1;File:_Start.asm 2;内核的栈顶地址 3KERNEL_STACK_TOPequ0x8009fc00 4 5[bits32] 6 7externmain 8 9[section.text] 10global_START 11 12_START: 13movax,0x10 14movds,ax 15moves,ax 16movfs,ax 17movgs,ax 18movss,ax 19movesp,KERNEL_STACK_TOP 20 21callmain;调用start.c的main() 22 23CPU_hlt: 24hlt 25jmpCPU_hlt

接下来我们修改一下loader.asm就可以了,这次修改内容不多,但是理解起来较为麻烦,笔者注释已经写清楚了。

1... 2jmpENTER_LOADER 3 4READ_SECTORequ9 5 6ENTER_LOADER: 7;该地址实际是0x10000当前处于实模式 8;一次只能加载128个扇区,一共384个扇区,因此分3次加载 9movax,0x1000 10movsi,READ_SECTOR 11movcx,128 12callload_file 13 14movax,0x2000 15movsi,READ_SECTOR 128 16movcx,128 17callload_file 18 19movax,0x3000 20movsi,READ_SECTOR 256 21movcx,128 22callload_file 23 24;跳过数据段 25jmpTest_0xE820 26... 27... 28 29;si:扇区逻辑区块地址,起点为0 30;cx:扇区数 31read_floppy_sector: 32pushax 33pushcx 34pushdx;保存缓冲内容 35pushbx 36 37movax,si 38xordx,dx 39movbx,18 40 41divbx 42incdx 43movcl,dl 44xordx,dx 45movbx,2 46 47divbx 48 49movdh,dl 50xordl,dl 51movch,al 52popbx 53.rp: 54moval,0x01 55movah,0x02 56int0x13 57jc.rp 58popdx 59popcx 60popax 61ret 62 63load_file: 64;段偏移 65moves,ax 66xorbx,bx 67.loop: 68callread_floppy_sector 69addbx,512 70incsi 71loop.loop 72ret 73 74[bits32] 75 76flush: 77... 78... 79point_in_paging_mode: 80;分页机制下寻址 81moveax,Page_Dir_Address 82movebx,Page_Table_Address 83addeax,ebx 84shleax,20 85addeax,0xb8000 86movdword[eax 160 2],'P' 87movdword[eax 160 3],0x6f 88movdword[eax 160 4],'a' 89movdword[eax 160 5],0x6f 90 91jmpenter_kernel 92 93KERNEL_BIN_BASE_ADDREQU0x10000 94KERNEL_ENTRYequ0x80100000 95 96enter_kernel: 97callinit_kernel 98;进入内核 99jmpKERNEL_ENTRY 100 101;这里引用胡同学的注释: 102;遍历每一个ProgramHeader,根据ProgramHeader中的信息来确定把什么放进内存,放到什么位置,以及放多少。 103init_kernel: 104xoreax,eax 105xorebx,ebx;记录每一个ProgramHeaderTable地址 106xorecx,ecx;记录每一个ProgramHeaderTable数量 107xoredx,edx;记录每一个ProgramHeaderTable的大小:e_phentsize 108 109movdx,[KERNEL_BIN_BASE_ADDR 42];偏移42字节:e_phentsize 110movebx,[KERNEL_BIN_BASE_ADDR 28];偏移28字节:e_phoff,第一个programheader偏移量 111addebx,KERNEL_BIN_BASE_ADDR 112movcx,[KERNEL_BIN_BASE_ADDR 44];偏移44字节:e_phnum 113 114;遍历每个段 115.EACH_SEGMENT: 116cmpbyte[ebx 0],0;PT_NULL=0 117je.PTNULL 118 119pushdword[ebx 16];p_filesz,memcpy第三个参数:size 120moveax,[ebx 4];p_offset,本段在文件起始的偏移字节 121addeax,KERNEL_BIN_BASE_ADDR;本程序段的起始地址 122pusheax;memcpy第二个参数:source 123pushdword[ebx 8];memcpy第一个参数:destination 124callmemcpy 125addesp,12;memcpy一共3个参数,故3*4=12 126 127.PTNULL: 128addebx,edx;Edxistheprogramheadersize,iee_phentsize,whereebxpointstothenextprogramheader 129loop.EACH_SEGMENT 130 131ret 132 133;逐字节拷贝 134memcpy: 135cld 136pushebp 137movebp,esp 138pushecx;保存ecx内值 139movedi,[ebp 8];dst 140movesi,[ebp 12];src 141movecx,[ebp 16];size 142repmovsb;逐字节拷贝 143 144popecx 145popebp 146 147ret 148...

接下来就是激动人心的一刻:make run

bootloader目标代码是什么格式(操作系统开发loader程序编写)(4)

编译非常流畅!运行结果也是成功的!

bootloader目标代码是什么格式(操作系统开发loader程序编写)(5)

当然,Loader程序还有很多值得优化的地方,进入图形模式,输出调试信息等,但是现在,我们已经真正地在开始编写我们的OS内核了!那么,Loader程序编写就告一段落了!

关注"GuEes"公众号,了解更多消息!

,