第05章下 加载内核

内核使用c语言编写,使用gcc编译

1 ELF文件

ELF文件是指可执行廉洁个事,最初由UNIX系统实验室作为应用程序二进制接口开发和发行的

EFL文件类型:

  1. 待重定向文件:待重定向文件是常说的目标文件,是源文件编译后单位完成连接的半成品,被常用语与其他目标文件合并连接以构建出二进制可执行文件或是动态链接库.因为在该文件中,如果引用了其他外部文件中定义的符号(变量或是函数),那么在编译阶段只能给出一个符号名,该符号名的地址还不确定,需要在连接过程完成地址的填充,因此成为待重定向
  2. 共享目标文件:常说的动态库文件
  3. 可执行文件:经过编译链接的,可以运行的程序文件
  4. 核心转出文件:当进程以外终止时,系统可以将该进程的空间地址内容级终止的一些其他信息转储到核心转储文件

1.1 ELF格式

程序中又很短的段,如代码段,数据段等,同样也有很多节,段是由节来组成的多个节经过连接过程,被合并成为一个段.

2 内核的加载

首先需要一个保护模式下,读取磁盘数据的函数,将内核的可执行文件的内容拷贝到内存.

然后需要一个函数,解析可执行文件的内容,将不同段中的内容,根据表头中定义的地址,拷贝到指定的地址上

最后,jmp到入口地址执行即可

2.1 保护模式下读取硬盘

与实模式下的几乎完全相同,唯一的区别是,在将读取的内容写入内存的时候,使用的是32位寄存器,不在是16位寄存器.

; 功能:保护模式下读取硬盘n个扇区
; 参数:
; eax:开始读取的磁盘扇区
; cx:读取的扇区个数
; bx:数据送到内存中的起始位置
rd_disk_m_32:
; 这里要保存eax 的原因在与,下面section count 寄存器需要一个8位的寄存器
; 只有acbd这四个寄存器能够拆分为高低8位来使用,而dx作为寄存器号,被占用了
; 因此需要个abc三个寄存器中一个来用,这里选择了 ax
    mov esi,eax
    mov di,cx

; 0x1f2 寄存器:sector count ,读写的时候都表示要读写的扇区数目
; 该寄存器是8位的,因此送入的数据位 cl
    mov dx,0x1f2
    mov al,cl 
    out dx,al 

; 恢复eax
    mov eax,esi 

; eax中存放的是要读取的扇区开始标号,是一个32位的值,因此 al 是低8位
; 0x1f3 存放0~7位的LBA地址,该寄存器是一个8位的
    mov dx,0x1f3
    out dx,al 

; 下面的 0x1f4 和5 分别是8~15,16~23位LBA地址,这俩寄存器都是8位的
; 因此是用shr,将eax右移8位,然后每次都用al取eax中的低8位
    mov cl,8 
    shr eax,cl 
    mov dx,0x1f4 
    out dx,al 

    shr eax,cl
    mov dx,0x1f5 
    out dx,al 

; 0x1f6 寄存器低4位存放 24~27位LBA地址,
; 0x1f6 寄存器是一个杂项,其第六位,1标识LBA地址模式,0标识CHS模式
; 上面使用的是LBA地址,因此第六位位1
    shr eax,cl 
    and al,0x0f 
    or al,0xe0    
    mov dx,0x1f6 
    out dx,al 
    
; 0x1f7 寄存器,读取该寄存器的时候,其中的数据是磁盘的状态
; 写到该寄存器的时候,写入的僵尸要执行的命令,写入以后,直接开始执行命令
; 因此需要在写该寄存器的时候,将所有参数设置号
; 0x20 表示读扇区,0x30写扇区
    mov dx,0x1f7 
    mov al,0x20
    out dx,al 

    .not_ready:
; 读 0x1f7 判断数据是否就绪,没就绪就循环等待.
        nop
        in al,dx
        and al,0x88 
        cmp al,0x08 
        jnz .not_ready

; 到这一步表示数据就绪,设置各项数据,开始读取
; 一个扇区512字节,每次读2字节,因此读一个扇区需要256次从寄存器中读取数据
; di 中是最开始的cx也就是要读取的扇区数
; mul dx 是ax=ax * dx ,因此最终ax 中是要读取的次数
    mov ax,di
    mov dx,256 
    mul dx  
    mov cx,ax 

; 0x1f0 寄存器是一个16位寄存器,读写的时候,都是数据.
    mov dx,0x1f0

    .go_on_read:
        in ax,dx 
        mov [ebx],ax ;区别在这里,使用的是ebx
        add ebx,2  ;ax 是 16位寄存器,读出的也是2字节,因此读一次 dx+2
        loop .go_on_read
    ret 

.go_on_read中,使用的是[ebx]不在是实模式下的bx,因为保护模式下,寻址,需要32位的寄存器,不能再使用16位寄存器

2.2 解析内核ELF文件

3 代码

为了以后便于维护,现在将文件归类:

文件结构:

└── bochs
├── 02.tar.gz
├── 03.tar.gz
├── 04.tar.gz
├── 05a.tar.gz
├── 05b.tar.gz
├── 05c
   ├── boot
   │   ├── include
   │   │   └── boot.inc
   │   ├── loader.asm
    │   └── mbr.asm
   ├── build
   └── start.sh

  1. boot目录放和启动相关的mbr和loader
  2. kernel目录放和内核相关的文件
  3. build目录存放编译生成的临时文件

3.1 boot.inc

添加和加载内核文件,解析内核文件,以及跳转执行内核的常数的宏

; -------------------------------------loader.bin -------------------------------------
; 将要加载在内存的位置,和在虚拟磁盘的扇区位置
LOADER_IN_MEM equ 0x900 
LOADER_IN_DISK equ 2

; loader 执行的栈基址
LOADER_STACK_TOP equ LOADER_IN_MEM
; -------------------------------------loader.bin -------------------------------------

; ------------------------------------构造GDT需要的数据 ----------------------------------
; 16位为一组,最后通过位操作拼接.
GDT_48_G_4K  equ 00000000_10000000b
GDT_48_D_32  equ 00000000_01000000b
GDT_48_L_32  equ 00000000_00000000b
GDT_48_AVL   equ 00000000_00000000b
GDT_48_LEN_H equ 00000000_00001111b

GDT_32_P         equ 10000000_00000000b
GDT_32_DPL_0     equ 00000000_00000000b
GDT_32_DPL_3     equ 01100000_00000000b
GDT_32_S_SYS     equ 00000000_00000000b
GDT_32_S_USER    equ 00010000_00000000b
GDT_32_TYPE_CODE equ 00001000_00000000b
GDT_32_TYPE_DATA equ 00000010_00000000b 
;  -----------------------------------构造GDT需要的数据 ----------------------------------

;  -----------------------------------三个段描述符标表项 ----------------------------------
GDT_CODE_H32 equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+GDT_48_LEN_H)<<16)+ \
             (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_CODE+00000000b)

GDT_DATA_H32 equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+GDT_48_LEN_H)<<16)+ \
             (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_DATA+00000000b)
GDT_VGA_H32  equ (((00000000b<<8)+GDT_48_G_4K+GDT_48_D_32+GDT_48_L_32+GDT_48_AVL+00000000_00000000b)<<16)+ \
             (GDT_32_P+GDT_32_DPL_0+GDT_32_S_USER+GDT_32_TYPE_DATA+00001011b)

GDT_BASE equ 00000000b<<(24+32)
GDT_CODE equ (GDT_CODE_H32<<32)+0x0000FFFF
GDT_DATA equ (GDT_DATA_H32<<32)+0x0000FFFF
GDT_VGA  equ (GDT_VGA_H32<<32 )+0x80000007

;  -----------------------------------三个段描述符标表项 ----------------------------------

;  -----------------------------------构造选择子需要的数据 ----------------------------------
SELECT_RPL_0 equ 00b 
SELECT_RPL_3 equ 11b 
SELECT_TI_GDT equ 000b 
SELECT_TI_LDT equ 100b 
;  -----------------------------------构造选择子需要的数据 ----------------------------------

;  -----------------------------------    分页机制       ----------------------------------
PAGE_DIR_TABLE_POS equ 0x100000
PAGE_P equ 1b 
PAGE_RW_R equ 00b 
PAGE_RW_W equ 10b 
PAGE_US_S equ 000b 
PAGE_US_U equ 100b 
;  -----------------------------------    分页机制       ----------------------------------


;  -----------------------------------    内核加载       ----------------------------------
KERNEL_BIN_IN_MEM equ 0x70000
KERNEL_BIN_IN_DISK equ 0x9
KERNEL_ENTRY equ 0xc0001500
ELF_PT_NULL equ 0
;  -----------------------------------    内核加载       ----------------------------------

3.2 mbr.asm

该文件无变化.

3.2 loader.asm

该文件变动较大:

  1. 首先在开启分页模式之前将整个内核文件从硬盘加载到内存中,因此新加了一个保护模式下,读取硬盘的函数
  2. 然后,一个kernel_init函数用来解析内存中的ELF文件,然后将各个段复制到指定的内存位置,因此新加了两个函数,一个kernel_initmemcpy
  3. 最后jmp

一共新添加了3段代码.

%include "boot.inc"

SECTION loader vstart=LOADER_IN_MEM
; 上来就跳转
    jmp 0:loader_start
    gdt_base: dq GDT_BASE 
    gdt_code: dq GDT_CODE 
    gdt_data: dq GDT_DATA 
    gdt_vga:  dq GDT_VGA

    gdt_size equ $-gdt_base

; 这里预留出 60 个段描述符的位置
    times 60 dq 0

; 界限,也就是全局段描述符表在内存中的地址位:gdt_base + 界限
; 因此界限=长度-1
    gdt_ptr dw (gdt_size-1)
            dd gdt_base 

;构建选择子
    select_code equ (0x1<<3)+SELECT_TI_GDT+SELECT_RPL_0
    select_data equ (0x2<<3)+SELECT_TI_GDT+SELECT_RPL_0
    select_vag  equ (0x3<<3)+SELECT_TI_GDT+SELECT_RPL_0

    ards_buf times 240 db 0 ; ARDS结构,这里定义了240/20个
    mem_total dd 0          ; 存放最终结果的内存区域
    ards_counts dw 0        ; 最后需要找到所有的ards结构中长度最大的那个内存最为物理内存,因此需要一个遍历

loader_start:
; --------------------------------获取物理内存大小--------------------------------

    xor ebx,ebx             ;初始ebx为0,告诉bios从头开始遍历每种类型的内存,后续该寄存器的值由bios更新,由bios使用
    mov edx,0x534d4150
    mov di,ards_buf

    .get_mem_size_e820:
        mov eax,0x0000e820 ; 子功能号
        mov ecx,20         ; 每次填充的大小,类似于指示可用buf区大小的意思

        int 0x15
        add di,cx          ; di中保存的是buf
        inc word [ards_counts] ;计数

        cmp ebx,0
        jnz .get_mem_size_e820

    ; 在所有的ards结构中找内存最大值
    mov ebx,ards_buf        ;ebx 中存放的是地址
    mov ecx,[ards_counts]    ;ecx 中存放的是次数,需要取地址

    xor edx,edx ;保存当前最大值

    .find_max_mem:
        mov eax,[ebx]
        add eax,[ebx+8]
        add ebx,20
        cmp edx,eax
        jge .next_ards
        mov edx,eax 
    .next_ards:
        loop .find_max_mem
        jmp .set_max_mem
        

    .set_max_mem:
        mov [mem_total],edx ;最终结果存放
; --------------------------------获取物理内存大小--------------------------------
    
; --------------------------------打印字符--------------------------------

    mov ax,0xb800
    mov gs,ax

    mov byte [gs:1120],'l'
    mov byte [gs:1121],00000111b
    mov byte [gs:1122],'o'
    mov byte [gs:1123],00000111b
    mov byte [gs:1124],'a'
    mov byte [gs:1125],00000111b
    mov byte [gs:1126],'d'
    mov byte [gs:1127],00000111b
    mov byte [gs:1128],'e'
    mov byte [gs:1129],00000111b
    mov byte [gs:1130],'r'
    mov byte [gs:1131],00000111b
    mov byte [gs:1132],'!'
    mov byte [gs:1133],00000111b
; --------------------------------打印字符--------------------------------

; --------------------------------开启分段模式--------------------------------

; 开启A20
    in al,0x92
    or al,000000010b 
    out 0x92,al
; 开启保护模式
    mov eax,cr0
    or eax,0x00000001
    mov cr0,eax
; 加载GDT
    lgdt [gdt_ptr]
; --------------------------------开启分段模式--------------------------------


; 流水线的原因,要强制刷新一次流水线
; 这里要用远跳转,因为进入了保护模式了,cs寄存器中存的不在是段基址,而是选择子
    jmp select_code:p_mode

; 主动告诉编译器下面的代码按照32位机器码编译
[bits 32]
p_mode:
; 注意这里ss段寄存器,也被赋值为 select_data
    mov ax,select_data
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov esp,LOADER_STACK_TOP

    mov ax,select_vag
    mov gs,ax
    mov byte [gs:1440],'p'

;  ----------------------------------- 加载内核文件到内存 -----------------------------------
    ; 加载内核的ELF文件
    mov eax,KERNEL_BIN_IN_DISK
    mov ebx,KERNEL_BIN_IN_MEM
    mov ecx,200 
    call rd_disk_m_32
    mov byte [gs:1442],'r'
    
;  ----------------------------------- 加载内核文件到内存 -----------------------------------


    ; 构建页表目录
    call setup_page

    ; 修改全局描述符表
    sgdt [gdt_ptr]
    mov ebx,[gdt_ptr+2]
    or dword [ebx+0x18+4],0xc0000000
    add dword [gdt_ptr+2],0xc0000000

    add esp,0xc0000000

    ; 将页表目录放入cr3
    mov eax,PAGE_DIR_TABLE_POS
    mov cr3,eax

    ; 打开cr0的pg位
    mov eax,cr0
    or eax,0x80000000
    mov cr0,eax 

    ; 重新加载 全局描述符表
    lgdt [gdt_ptr]
    mov byte [gs:1600],'V'

;  ----------------------------------- 解析内核文件 -----------------------------------
    ; 解析内核文件,病跳转执行
    call kernel_init
    ; 需要设置栈指针
    mov esp,0xc009f000
    mov byte [gs:1760],'K'
    jmp KERNEL_ENTRY
;  ----------------------------------- 解析内核文件 -----------------------------------
    
;  ----------------------------------- 构建内核页表目录 -----------------------------------
setup_page:
    ; 首先使用 clear_page 将页表目录所在的那一页 PAGE_DIR_TABLE_POS 开始
    ; 的4K空间清零
    mov ecx,4096
    mov esi,0
    .clear_page:
        mov byte [PAGE_DIR_TABLE_POS+esi],0
        inc esi
        loop .clear_page
    
    ; 将内核页表目录的第一个页表和内核在3G处的页表设为同一个,为了是在一进入分页机制后,虚拟地址映射正常
    .create_pde:
        mov eax,PAGE_DIR_TABLE_POS
        add eax,4096                        ; 页表目录后面紧跟着就是第一个页表 eax+4K,
        mov ebx,eax                         ; 因此eax中存的是第一个页表的地址
        or eax,PAGE_US_U|PAGE_RW_W|PAGE_P   ; eax中是第一个页表的地址,然后设置属性,让他成为第一个页表项
        
        mov [PAGE_DIR_TABLE_POS],eax        ; 将第一个页表放在页表目录的第0个表项和高1G内存开始的地方.

        mov [PAGE_DIR_TABLE_POS+768*4],eax    ;768=3*1024*1024 /(4*1024),所以高1G是在第768个页表,*4是因为,每个表项4字节

        sub eax,4096                        ; eax是第一个页表的地址,减去4KB,就是 PAGE_DIR_TABLE_POS
        mov [PAGE_DIR_TABLE_POS+4092],eax   ; 将页表目录最后一项设为自己的地址

    ; 填充第一个页表
        mov ecx,256             
        mov esi,0
        mov edx,PAGE_US_U|PAGE_RW_W|PAGE_P  ; edx现在是第一个页表中的第一个表项,他代表的是物理内存的第一页.

    .create_pte:
        mov [ebx+esi*4],edx                 ; 将物理内存从0开始依次映射到第一个页表中
        add edx,4096
        inc esi 
        loop .create_pte

    ; 填充高1G的其他页表目录项,页表目录所在内存页的后面页就放着页表,也就是说这些页表,在物理内存上是连续的.
    mov eax,PAGE_DIR_TABLE_POS
    add eax,4096*2                      ; eax 中现在是第一个页表的地址
    or eax,PAGE_US_U|PAGE_RW_W|PAGE_P 
    mov ebx,PAGE_DIR_TABLE_POS
    mov ecx,254                         ;第一个和最后一个已经创建完了,所以是256-2个
    mov esi,769                         ;是高1G的第2个页表

    .create_kernel_pde:
        mov [ebx,esi*4],eax
        inc esi 
        add eax,4096 
        loop .create_kernel_pde
        ret 

;  ----------------------------------- 读取磁盘文件 -----------------------------------

; 功能:保护模式下读取硬盘n个扇区
; 参数:
; eax:开始读取的磁盘扇区
; cx:读取的扇区个数
; bx:数据送到内存中的起始位置
rd_disk_m_32:
; 这里要保存eax 的原因在与,下面section count 寄存器需要一个8位的寄存器
; 只有acbd这四个寄存器能够拆分为高低8位来使用,而dx作为寄存器号,被占用了
; 因此需要个abc三个寄存器中一个来用,这里选择了 ax
    mov esi,eax
    mov di,cx

; 0x1f2 寄存器:sector count ,读写的时候都表示要读写的扇区数目
; 该寄存器是8位的,因此送入的数据位 cl
    mov dx,0x1f2
    mov al,cl 
    out dx,al 

; 恢复eax
    mov eax,esi 

; eax中存放的是要读取的扇区开始标号,是一个32位的值,因此 al 是低8位
; 0x1f3 存放0~7位的LBA地址,该寄存器是一个8位的
    mov dx,0x1f3
    out dx,al 

; 下面的 0x1f4 和5 分别是8~15,16~23位LBA地址,这俩寄存器都是8位的
; 因此是用shr,将eax右移8位,然后每次都用al取eax中的低8位
    mov cl,8 
    shr eax,cl 
    mov dx,0x1f4 
    out dx,al 

    shr eax,cl
    mov dx,0x1f5 
    out dx,al 

; 0x1f6 寄存器低4位存放 24~27位LBA地址,
; 0x1f6 寄存器是一个杂项,其第六位,1标识LBA地址模式,0标识CHS模式
; 上面使用的是LBA地址,因此第六位位1
    shr eax,cl 
    and al,0x0f 
    or al,0xe0    
    mov dx,0x1f6 
    out dx,al 
    
; 0x1f7 寄存器,读取该寄存器的时候,其中的数据是磁盘的状态
; 写到该寄存器的时候,写入的僵尸要执行的命令,写入以后,直接开始执行命令
; 因此需要在写该寄存器的时候,将所有参数设置号
; 0x20 表示读扇区,0x30写扇区
    mov dx,0x1f7 
    mov al,0x20
    out dx,al 

    .not_ready:
; 读 0x1f7 判断数据是否就绪,没就绪就循环等待.
        nop
        in al,dx
        and al,0x88 
        cmp al,0x08 
        jnz .not_ready

; 到这一步表示数据就绪,设置各项数据,开始读取
; 一个扇区512字节,每次读2字节,因此读一个扇区需要256次从寄存器中读取数据
; di 中是最开始的cx也就是要读取的扇区数
; mul dx 是ax=ax * dx ,因此最终ax 中是要读取的次数
    mov ax,di
    mov dx,256 
    mul dx  
    mov cx,ax 

; 0x1f0 寄存器是一个16位寄存器,读写的时候,都是数据.
    mov dx,0x1f0

    .go_on_read:
        in ax,dx 
        mov [ebx],ax ;区别在这里,使用的是ebx
        add ebx,2  ;ax 是 16位寄存器,读出的也是2字节,因此读一次 dx+2
        loop .go_on_read
    ret 
;  ----------------------------------- 读取磁盘文件 -----------------------------------

;  ----------------------------------- 解析内核ELF -----------------------------------
; 功能:解析内核ELF文件
; 不需要参数,因为参数是 KERNEL_BIN_IN_MEM ,而且代码只是用一次
; 因此参数直接写死,不更改
kernel_init:
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    xor edx,edx

    ; 偏移42字节处是 e_phentsize是每个程序头结构的大小
    mov dx,[KERNEL_BIN_IN_MEM+42]
    ; 偏移28字节处是 e_phoff,是第一个 程序头结构在文件中的偏移
    mov ebx,[KERNEL_BIN_IN_MEM+28]

    ; 加上 KERNEL_BIN_IN_MEM ,就是第一个程序头在文件中的偏移地址
    add ebx,KERNEL_BIN_IN_MEM
    ; 偏移44字节处是 e_phnum ,是程序头结构的个数
    mov cx,[KERNEL_BIN_IN_MEM+44]

    ; 遍历每个程序头结构,按照其 p_offset,加载到最内存的指定位置
    .each_segment:
        ; p_type =0 则该程序头未使用,跳过
        cmp byte [ebx+0],ELF_PT_NULL
        je .pt_null 

        ; 程序头结构的偏移16 字节是 p_filesz 是结构的大小 
        push dword [ebx+16]
        ; 程序头结构的偏移16 字节是 p_offset 是该结构在ELF文件中的偏移地址
        mov eax,[ebx+4]
        ; 加上 KERNEL_BIN_IN_MEM 
        add eax,KERNEL_BIN_IN_MEM
        push eax 
        ; 程序头结构的偏移4 字节是 p_vaddr 是该结构最终 加载在内存中地址
        push dword [ebx+8]
        call memcpy

        add esp,12 
    .pt_null:
        add ebx,edx 
        loop .each_segment 
    ret
;  ----------------------------------- 解析内核ELF -----------------------------------

;  ----------------------------------- 批量拷贝 -----------------------------------
memcpy:
    cld 
    push ebp 
    mov ebp,esp 

    push ecx 
    mov edi,[ebp+8]
    mov esi,[ebp+12]
    mov ecx,[ebp+16]
    rep movsb 
    pop ecx 
    pop ebp 
    ret
;  ----------------------------------- 批量拷贝 -----------------------------------

memcpy函数的解释如下:

MOVSB(MOVe String Byte):即字符串传送指令,这条指令按字节传送数据。通过SI和DI这两个寄存器控制字符串的源地址和目标地址,比如DS:SI这段地址的N个字节复制到ES:DI指向的地址,复制后DS:SI的内容保持不变。

而REP(REPeat)指令就是“重复”的意思,术语叫做“重复前缀指令”,因为既然是传递字符串,则不可能一个字(节)一个字(节)地传送,所以需要有一个寄存器来控制串长度。这个寄存器就是CX,指令每次执行前都会判断CX的值是否为0(为0结束重复,不为0,CX的值减1),以此来设定重复执行的次数。因此设置好CX的值之后就可以用REP MOVSB了。

CLD(CLear Direction flag)则是清方向标志位,也就是使DF的值为0,在执行串操作时,使地址按递增的方式变化,这样便于调整相关段的的当前指针。这条指令与STD(SeT Direction flag)的执行结果相反,即置DF的值为1。

3.3 main.c内核文件(新加)

暂时比较简单,直接是一个循环

int main( int argc, char const* argv[] )
{
    while ( 1 )
    {
    }
    return 0;
}

但是其编译过程不简单:

  1. 首先要编译,需要按照32位进行编译,因此需要加上-m32参数
  2. 链接:首先,需要执行程序的执行入口:-e mainmain函数作为程序的入口,然后指定入口地址-Ttext 0xc0001500,再然后,因为gcc -c编译结果位32位程序,因此链接的时候需要带上-m elf_i386参数

因此最终为:

gcc -o kernel.o -m32 -c ./kernel/main.c 
ld -Ttext 0xc0001500 -m elf_i386 -e main -o kernel.bin ./kernel.o

3.4 start.sh

改动较大,因为改变路目录结构,又新加了文件,因此,需要变动的比较多:

  1. 所有的文件要加上路径.临时文件直接在根目录生成,这样删除,使用-o命令,指定输出的路径和文件名
  2. mbr.asmloader.asm文件引入的boot.inc文件移动.因此,在编译的时候要加上-I ./boot/inlucde
  3. 新添加main.c文件的编译,链接,刻录
#! /bin/bash

# 编译mbr.asm
echo "----------nasm mbr.asm----------"
if !(nasm -o ./build/mbr.bin ./boot/mbr.asm -I./boot/include/);then
    echo "nasm error"
    exit
fi

# 刻录mbr.bin
echo "----------dd mbr.bin  ----------"
if !(dd if=./build/mbr.bin of=../hd60m.img bs=512 count=1 conv=notrunc);then
    echo "dd error"
    exit
fi

# 编译 loader.asm
echo "----------nasm loader.asm----------"

if !(nasm -o ./build/loader.bin ./boot/loader.asm -I./boot/include/);then
    echo "nasm error"
    exit
fi

# 刻录loader.bin
echo "----------dd loader.bin  ----------"
if !(dd if=./build/loader.bin of=../hd60m.img bs=512 count=4 seek=2 conv=notrunc);then
    echo "dd error"
    exit
fi


# 编译内核
echo "----------gcc -c kmain.c ----------"
if !(gcc -o ./build/kernel.o -m32 -c ./kernel/main.c );then
    echo "dd error"
    exit
fi

# 链接 
echo "----------ld kernel.o ----------"
if !(ld -Ttext 0xc0001500 -m elf_i386 -e main -o ./build/kernel.bin ./build/kernel.o);then
    echo "dd error"
    exit
fi

# 刻录 kernel.bin
echo "----------dd kernel.bin  ----------"
if !(dd if=./build/kernel.bin of=../hd60m.img bs=512 count=40 seek=9 conv=notrunc);then
    echo "dd error"
    exit
fi

# 删除临时文件
sleep 1s  
rm -rf ./build/*.*

# 运行bochs
cd ..
bochs

3.5 运行

0x1500打上断点,执行到后,开跟踪.

猜你喜欢

转载自www.cnblogs.com/perfy576/p/9119145.html