快捷搜索:  汽车  科技

操作系统脚本编程教程(操作系统开发loader程序编写)

操作系统脚本编程教程(操作系统开发loader程序编写)第三行:第二行:(开发底层软件研究二进制数据很重要)上面都是16进制码,画蓝色细线的地方是ELF Header(Elf32_Ehdr),画红色粗线的地方是Program header table(Elf32_Phdr)下面我们逐字节分析:第一行:

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

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

操作系统脚本编程教程(操作系统开发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文件:

操作系统脚本编程教程(操作系统开发loader程序编写)(2)

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

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

第一行:

  • 一整条线都(就)是e_ident[],每一个字节的意义对应了上面的表,读者可以用上面的表格进行查看,这里的意义为:0x7f 'E' 'L' 'F' 32位对象 小端字节序,有效版本,后面都为不需要设置的内容均为0。

第二行:

  • 第一条线就是e_type:可执行文件。
  • 第二条线就是e_machine:Intel 80386。
  • 第三条线就是e_version:有效版本。
  • 第四条线就是e_entry:程序的虚拟入口地址为0x80100000
  • 第五条线就是e_phoff:程序头表在文件中的偏移量是0x34。

第三行:

  • 第一条线就是e_shoff:节区头部表在文件内的偏移量,这里的值为0x10f8(如果没有节区头部表则为0)。
  • 第二条线就是e_flags:e_flags的具体属性。
  • 第三条线就是e_ehsize:ELF header的字节大小为0x34。
  • 第四条线就是e_phentsize:Elf32_Phdr 的字节大小0x20字节。
  • 第五条线就是e_phnum:程序头表中段的个数为2个段。
  • 第六条线就是e_shentsize:节区头部表中各个节的大小。

第四行:

  • 第一条线就是e_shnum:节头表中节的个数为8 。
  • 第二条线就是e_shstrndx:字符串表在节区头表中的索引为5。
  • 第三条线就是p_type:可加载程序段。
  • 第四条线就是p_offset:本段在文件起始的偏移字节为0x1000
  • 第五条线就是p_vaddr:本段被加载到内存后的起始虚拟地址为0x80100000

第五行:

  • 第一条线就是p_paddr:(此项暂且保留,未设定)。
  • 第二条线就是p_filesz:本段在文件中的字节大小为0x79。
  • 第三条线就是p_memsz:本段在内存中的字节大小(和p_filesz相等)。
  • 第四条线就是p_flags:该值为5=4 1=PF_R PF_X,表示可读,可执行。

第六行:

  • 第一条线就是p_align:本段对齐的方式。

得知内核的各个数据后,我们就可以加载内核了。目前我们强制设定了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

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

操作系统脚本编程教程(操作系统开发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

操作系统脚本编程教程(操作系统开发loader程序编写)(4)

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

操作系统脚本编程教程(操作系统开发loader程序编写)(5)

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

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

猜您喜欢: