bs 程序用户个性化设置保存_操作系统开发——loader程序编写(9)

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

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

98834a056ff85f539ebecaca5b11a55e.png

(GuEeOS启动界面)

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

9e3f151222be8c5cc14293c450cc60d2.png

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

上面都是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 910#这是笔者的gcc的目录,请读者另外自行设置(可无)11PREFIX      = builder/1213NASM = nasm14CC   = $(PREFIX)gcc15LD   = ld16DD   = dd17QEMU = qemu-system-i3861819BOOT_BIN    = boot.bin20LOADER_BIN  = loader.bin21KERNEL_FILE = kernel.bin22OS_IMG      = os.img2324#编译参数25ASM_KERNEL_FLAGS = -f elf3226#-fno-builtin指不使用gcc默认库,因为我们要自己实现所有功能 -m32是32位模式 -I是指头文件默认目录27C_KERNEL_FLAGS = -I ./include -c -fno-builtin -m3228#内核_START(可自行设置,默认为_start)入口和地址29LD_FLAGS = -m elf_i386 -e _START -Ttext 0x801000003031#默认执行os.img32.all: os.img3334#注意这些缩进是制表符[--->],不是空格[    ]35bootloader.bin:36    $(NASM) boot.asm -o $(BOOT_BIN)37    $(NASM) loader.asm -o $(LOADER_BIN)3839ASM_FILE:40    $(NASM) $(ASM_KERNEL_FLAGS) _Start.asm -o _Start.o4142C_FILE:43    $(CC) $(C_KERNEL_FLAGS) start.c -o start.o4445kernel.bin: ASM_FILE C_FILE46    $(LD) $(LD_FLAGS) -o $(KERNEL_FILE) _Start.o start.o4748#执行os.img前,应该生成boot.bin和loader.bin49#seek为9,目的是跨过前 9 个扇区(第0~8个扇区),我们在第9个扇区写入。50#count为348,目的是一次往参数 of 指定的文件中写入348个扇区。51os.img: bootloader.bin kernel.bin52    $(DD) if=$(BOOT_BIN) of=$(OS_IMG) bs=512 count=1 conv=notrunc53    $(DD) if=$(LOADER_BIN) of=$(OS_IMG) bs=512 seek=$(LOAD_SECTOR_OFFSET) count=$(LOAD_SECTORS) conv=notrunc54    $(DD) if=$(KERNEL_FILE) of=$(OS_IMG) bs=512 seek=$(KERNEL_SECTOR_OFFEST) count=$(KERNEL_SECTORS) conv=notrunc5556#运行前,应该生成os.img57run: os.img58    $(QEMU) -boot a -fda $(OS_IMG)5960clean:61    rm *.bin62    rm *.o

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

e4dfb4772fdc69f80716c425ffb1dd12.png

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

 1;File: _Start.asm 2;内核的栈顶地址 3KERNEL_STACK_TOP equ 0x8009fc00 4 5[bits 32] 6 7extern main 8 9[section .text]10global _START1112_START:13    mov ax,0x1014    mov ds,ax15    mov es,ax16    mov fs,ax17    mov gs,ax18    mov ss,ax19    mov esp,KERNEL_STACK_TOP2021    call main    ;调用start.c的main()2223CPU_hlt:24    hlt25    jmp CPU_hlt

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

  1...  2    jmp ENTER_LOADER  3  4READ_SECTOR equ 9  5  6ENTER_LOADER:  7    ;该地址实际是0x10000当前处于实模式  8    ;一次只能加载128个扇区,一共384个扇区,因此分3次加载  9    mov ax, 0x1000 10    mov si, READ_SECTOR 11    mov cx, 128 12    call load_file 13 14    mov ax,0x2000 15    mov si,READ_SECTOR+128 16    mov cx,128 17    call load_file 18 19    mov ax,0x3000 20    mov si,READ_SECTOR+256 21    mov cx,128 22    call load_file 23 24    ;跳过数据段 25    jmp Test_0xE820 26... 27... 28 29;si: 扇区逻辑区块地址,起点为0 30;cx: 扇区数 31read_floppy_sector: 32    push ax  33    push cx  34    push dx ;保存缓冲内容 35    push bx  36 37    mov ax, si  38    xor dx, dx  39    mov bx, 18 40 41    div bx  42    inc dx  43    mov cl, dl  44    xor dx, dx  45    mov bx, 2 46 47    div bx  48 49    mov dh, dl 50    xor dl, dl  51    mov ch, al  52    pop bx  53.rp: 54    mov al, 0x01 55    mov ah, 0x02  56    int 0x13  57    jc .rp  58    pop dx 59    pop cx  60    pop ax 61    ret  62 63load_file: 64    ;段偏移 65    mov es, ax 66    xor bx, bx  67.loop: 68    call read_floppy_sector 69    add bx, 512 70    inc si  71    loop .loop 72    ret  73 74[bits 32] 75 76flush: 77... 78... 79point_in_paging_mode: 80    ;分页机制下寻址 81    mov eax,Page_Dir_Address 82    mov ebx,Page_Table_Address 83    add eax,ebx 84    shl eax,20 85    add eax,0xb8000 86    mov dword [eax+160+2],'P' 87    mov dword [eax+160+3],0x6f 88    mov dword [eax+160+4],'a' 89    mov dword [eax+160+5],0x6f 90 91    jmp enter_kernel 92 93KERNEL_BIN_BASE_ADDR EQU 0x10000 94KERNEL_ENTRY equ 0x80100000 95 96enter_kernel: 97    call init_kernel 98    ;进入内核 99    jmp KERNEL_ENTRY100101;这里引用胡同学的注释:102;遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。103init_kernel:104   xor eax,eax105   xor ebx,ebx                          ;记录每一个Program Header Table地址106   xor ecx,ecx                          ;记录每一个Program Header Table数量107   xor edx,edx                          ;记录每一个Program Header Table的大小:e_phentsize108109   mov dx,[KERNEL_BIN_BASE_ADDR+42]     ;偏移42字节:e_phentsize110   mov ebx,[KERNEL_BIN_BASE_ADDR+28]    ;偏移28字节:e_phoff,第一个program header偏移量111   add ebx,KERNEL_BIN_BASE_ADDR112   mov cx,[KERNEL_BIN_BASE_ADDR+44]     ;偏移44字节: e_phnum113114;遍历每个段115.EACH_SEGMENT:116   cmp byte [ebx+0],0                   ;PT_NULL = 0117   je .PTNULL118119   push dword [ebx+16]                  ;p_filesz,memcpy第三个参数: size120   mov eax,[ebx+4]                      ;p_offset, 本段在文件起始的偏移字节121   add eax,KERNEL_BIN_BASE_ADDR         ;本程序段的起始地址122   push eax                             ;memcpy第二个参数: source123   push dword [ebx+8]                   ;memcpy第一个参数: destination124   call memcpy125   add esp,12                           ;memcpy一共3个参数,故3*4=12126127.PTNULL:128   add ebx,edx                          ;Edx is the program header size, ie e_phentsize, where ebx points to the next program header129   loop .EACH_SEGMENT130131   ret132133;逐字节拷贝134memcpy:135   cld136   push ebp137   mov ebp,esp138   push ecx         ;保存ecx内值139   mov edi,[ebp+8]  ;dst140   mov esi,[ebp+12] ;src141   mov ecx,[ebp+16] ;size142   rep movsb        ;逐字节拷贝143144   pop ecx145   pop ebp146147   ret148...

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

362a906b61725b2e0a6c9f29159e6a08.png

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

1c26d2c89d7aecf93f1ddb47113668a4.png

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值