• 第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打上断点,执行到后,开跟踪.

  • 相关阅读:
    C# log4net
    C# compare different Encoding pattern between UTF8 and UTF32 based on Md5
    C# extract img url from web content then download the img
    C# while loop Running until user press key
    C# GZip Compress DeCompress
    C# get md5 from bytes
    transition结合:after,:before实现动画
    http跟https的区别
    window,getComputedStyle,letter-spacing
    inline-block,vertical-align:middle
  • 原文地址:https://www.cnblogs.com/perfy576/p/9119145.html
Copyright © 2020-2023  润新知