第05章下 加載內核

內核使用c語言編寫,使用gcc編譯ios

1 ELF文件

ELF文件是指可執行廉潔個事,最初由UNIX系統實驗室做爲應用程序二進制接口開發和發行的shell

EFL文件類型:bash

  1. 待重定向文件:待重定向文件是常說的目標文件,是源文件編譯後單位完成鏈接的半成品,被經常使用語與其餘目標文件合併鏈接以構建出二進制可執行文件或是動態連接庫.由於在該文件中,若是引用了其餘外部文件中定義的符號(變量或是函數),那麼在編譯階段只能給出一個符號名,該符號名的地址還不肯定,須要在鏈接過程完成地址的填充,所以成爲待重定向
  2. 共享目標文件:常說的動態庫文件
  3. 可執行文件:通過編譯連接的,能夠運行的程序文件
  4. 核心轉出文件:當進程之外終止時,系統能夠將該進程的空間地址內容級終止的一些其餘信息轉儲到核心轉儲文件

1.1 ELF格式

程序中又很短的段,如代碼段,數據段等,一樣也有不少節,段是由節來組成的多個節通過鏈接過程,被合併成爲一個段.函數

oop

2 內核的加載

首先須要一個保護模式下,讀取磁盤數據的函數,將內核的可執行文件的內容拷貝到內存.ui

而後須要一個函數,解析可執行文件的內容,將不一樣段中的內容,根據表頭中定義的地址,拷貝到指定的地址上指針

最後,jmp到入口地址執行便可code

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打上斷點,執行到後,開跟蹤.

相關文章
相關標籤/搜索