實模式到保護模式:第13章讀書筆記
這一章的內容是加載內核程序和用戶程序的基本流程
關于程序的相關信息一般是放在文件的開頭,程序本來就是從最開頭的讀取,所以放在開頭也就符合一般的思維習慣,這本章中,作者創建了一個簡單的文件頭,用于存儲該程序的信息,用于加載程序的使用
在這一章中,作者代碼演示的加載內核和用戶程序的方式可能與真正的操作系統不同,但是這個過程大體是相似的,只是真實操作系統的頭部更為復雜
1. 接下來我來讀一下內核加載的程序代碼:
;代碼清單13-2
;文件名:c13_core.asm
;文件說明:保護模式微型核心程序
;創建日期:2011-10-26 12:11
;以下常量定義部分。內核的大部分內容都應當固定
core_code_seg_sel equ 0x38 ;內核代碼段選擇子 core_data_seg_sel equ 0x30 ;內核數據段選擇子 sys_routine_seg_sel equ 0x28 ;系統公共例程代碼段的選擇子 video_ram_seg_sel equ 0x20 ;視頻顯示緩沖區的段選擇子 core_stack_seg_sel equ 0x18 ;內核堆棧段選擇子 mem_0_4_gb_seg_sel equ 0x08 ;整個0-4GB內存的段的選擇子 ;-------------------------------------------------------------------------------
;以下是系統核心的頭部,用于加載核心程序
core_length dd core_end ;核心程序總長度#00 sys_routine_seg dd section.sys_routine.start ;系統公用例程段位置#04 core_data_seg dd section.core_data.start ;核心數據段位置#08 core_code_seg dd section.core_code.start ;核心代碼段位置#0c core_entry dd start ;核心代碼段入口點#10 dw core_code_seg_sel ;==============================================================================
內核程序聲明了段選擇子的內容,所以引導程序在讀取內核,創建描述符時,便應該根據這個標準來創建內核的描述符
[bits 32] flush: mov eax,0x0008 ;加載數據段(0..4GB)選擇子 mov ds,eax mov eax,0x0018 ;加載堆棧段選擇子 mov ss,eax xor esp,esp ;堆棧指針 <- 0 mov edi,core_base_address ;讀取出來的扇區將存儲于何處
初始化ss和ds的值,將其初始化對應的段選擇子
mov eax,core_start_sector ;內核的起始扇區號 mov ebx,edi ;起始地址 call read_hard_disk_0 ;以下讀取程序的起始部分(一個扇區)
;以下判斷整個程序有多大 ;內核程序將它所具有的大小填入到了第一個扇區的第一個字節處,只需要讀取出來,將
;其與512相除,即能得到扇區的個數
mov eax,[edi] ;核心程序尺寸
首先,將內核將存入的內存的位置加載到edi寄存器中,將待讀取扇區加載eax中去,調用read_hard_disk_0例程讀取內核的第一個扇區,讀取完畢之后,因為內核的首地址便存儲了內核程序的長度,所以通過mov eax,[edi]能夠將內核程序的長度存放到eax中
xor edx,edx mov ecx,512 ;512字節每扇區 div ecx or edx,edx jnz @1 ;未除盡,因此結果比實際扇區數少1 dec eax ;已經讀了一個扇區,扇區總數減1 @1: or eax,eax ;考慮實際長度≤512個字節的情況 jz setup ;EAX=0 ? ;讀取剩余的扇區 mov ecx,eax ;32位模式下的LOOP使用ECX mov eax,core_start_sector inc eax ;從下一個邏輯扇區接著讀 @2: call read_hard_disk_0 inc eax loop @2 ;循環讀,直到讀完整個內核
div 除數:指令默認被除數為edx:eax,商存儲在eax中,edx存儲余數
首先將edx的值清零,將512存入ecx中,作為除數,因為一個扇區存儲512個字節
上面的代碼成功得到總扇區數目,并且成功讀取了剩余的扇區
;到此處便將內核程序全部從硬盤中讀取出來了,也是位于內存0x40000處的內存開始位置處
setup: mov esi,[0x7c00+pgdt+0x02] ;不可以在代碼段內尋址pgdt,但可以 ;通過4GB的段來訪問 ;建立公用例程段描述符 mov eax,[edi+0x04] ;公用例程代碼段起始匯編地址 mov ebx,[edi+0x08] ;核心數據段匯編地址 sub ebx,eax dec ebx ;公用例程段界限 add eax,edi ;公用例程段基地址,edi是內核加載的位置,而eax是公用例程段定義的基地址 mov ecx,0x00409800 ;字節粒度的代碼段描述符 call make_gdt_descriptor mov [esi+0x28],eax mov [esi+0x2c],edx ;建立核心數據段描述符 mov eax,[edi+0x08] ;核心數據段起始匯編地址 mov ebx,[edi+0x0c] ;核心代碼段匯編地址 sub ebx,eax dec ebx ;核心數據段界限 add eax,edi ;核心數據段基地址 mov ecx,0x00409200 ;字節粒度的數據段描述符 call make_gdt_descriptor mov [esi+0x30],eax mov [esi+0x34],edx ;建立核心代碼段描述符 mov eax,[edi+0x0c] ;核心代碼段起始匯編地址 mov ebx,[edi+0x00] ;程序總長度 sub ebx,eax dec ebx ;核心代碼段界限 add eax,edi ;核心代碼段基地址 mov ecx,0x00409800 ;字節粒度的代碼段描述符 call make_gdt_descriptor mov [esi+0x38],eax mov [esi+0x3c],edx mov word [0x7c00+pgdt],63 ;描述符表的界限 lgdt [0x7c00+pgdt]
上面的代碼創建了屬于內核的數據段,代碼段,公共例程段的值,放入gdt表的位置與內核聲明的常數的值一致,在最后將新的gdt表的界限寫入對應的內存區域中,lgdt指令將新的gdt表的基地址和表的大小加載到GDT寄存器中去
jmp far [edi+0x10]
可以看一下上面內核頭部的定義,內核中偏移量為0x10中存儲的值為
core_entry dd start ;核心代碼段入口點#10 dw core_code_seg_sel
因此通過遠跳轉指令最終跳轉到內核標號為start的位置繼續執行
2. 進入內核程序的start開始運行,接下來進入內核加載用戶程序的步驟
mov esi,50 ;用戶程序位于邏輯50扇區
call load_relocate_program ;加載用戶程序
將用戶程序所在扇區放入esi中,開始加載用戶程序,接下來調用load_relocate_program來執行加載用戶程序
load_relocate_program: push ebx push ecx push edx push esi push edi push ds push es mov eax,core_data_seg_sel mov ds,eax mov eax,esi ;讀取程序頭部數據 mov ebx,core_buf call sys_routine_seg_sel:read_hard_disk_0 ;讀取出來的將會被放到core_buf緩沖區中,讀第一個扇區 ;以下判斷整個程序有多大 mov eax,[core_buf] ;程序尺寸,用戶程序的第一個字節記錄了該程序的大小 mov ebx,eax and ebx,0xfffffe00 ;使之512字節對齊(能被512整除的數, add ebx,512 ;低9位都為0 test eax,0x000001ff ;程序的大小正好是512的倍數嗎? cmovnz eax,ebx ;不是。使用湊整的結果
首先,ds指向內核數據段的選擇子,加載用戶程序的第一個扇區到內核數據段中的緩沖區中,通過讀取第一個字節,得到該用戶程序的長度,其中用到了cmovnz指令,避免了跳轉指令
mov ecx,eax ;實際需要申請的內存數量,eax存儲了該程序大小,而且該大小是512的整數倍 call sys_routine_seg_sel:allocate_memory ;返回時,ecx返回了分配內存的首地址 mov ebx,ecx ;ebx -> 申請到的內存首地址 push ebx ;保存該首地址,被分配內存的首地址 xor edx,edx mov ecx,512 div ecx mov ecx,eax ;總扇區數 mov eax,mem_0_4_gb_seg_sel ;切換DS到0-4GB的段 mov ds,eax
上面得到的eax是用戶程序的長度,并且是512字節的倍數,將其除以512,得到用戶程序所占據的扇區數,接下來就要將硬盤中的數據加載到內存中去,而通過allocate_memory分配的內存是線性地址,所以將ds切換到0-4GB段
mov eax,esi ;起始扇區號,那么第一個扇區將會被連續讀取兩次,eax指向要被讀取的扇區號 .b1: call sys_routine_seg_sel:read_hard_disk_0 inc eax loop .b1 ;循環讀,直到讀完整個用戶程序
讀取所有的扇區到為它分配的內存處,接下來就可以讀取用戶程序的頭部段,創建相對應的描述符,為用戶程序使用的例程進行重定位到正確的位置
;建立程序頭部段描述符
pop edi ;恢復程序裝載的首地址 mov eax,edi ;程序頭部起始線性地址 mov ebx,[edi+0x04] ;段長度 dec ebx ;段界限 mov ecx,0x00409200 ;字節粒度的數據段描述符 call sys_routine_seg_sel:make_seg_descriptor call sys_routine_seg_sel:set_up_gdt_descriptor mov [edi+0x04],cx
;建立程序代碼段描述符 mov eax,edi add eax,[edi+0x14] ;代碼起始線性地址 mov ebx,[edi+0x18] ;段長度 dec ebx ;段界限 mov ecx,0x00409800 ;字節粒度的代碼段描述符 call sys_routine_seg_sel:make_seg_descriptor call sys_routine_seg_sel:set_up_gdt_descriptor mov [edi+0x14],cx ;建立程序數據段描述符 mov eax,edi add eax,[edi+0x1c] ;數據段起始線性地址 mov ebx,[edi+0x20] ;段長度 dec ebx ;段界限 mov ecx,0x00409200 ;字節粒度的數據段描述符 call sys_routine_seg_sel:make_seg_descriptor call sys_routine_seg_sel:set_up_gdt_descriptor mov [edi+0x1c],cx ;建立程序堆棧段描述符 mov eax,edi ;得到程序加載的基地址 add eax,[edi+0x08] ;得到棧段的基地址,可是對于棧是要得到其高端地址作為基地址 ; add eax,[edi+0x0c] ;得到堆棧的高端地址 mov ebx,0xffffffff sub ebx,[edi+0x0c] ;兩者相減得出棧的界限值,esp > ebx mov ecx,0x00409600 call sys_routine_seg_sel:make_seg_descriptor call sys_routine_seg_sel:set_up_gdt_descriptor mov [edi+0x08],cx
根據用戶程序的頭部的內容,為用戶程序創建了代碼段,數據段,堆棧段,將所創建的描述符選擇子,并且將其放回用戶程序的頭部中去,用戶程序可以直接引用這些選擇子
;重定位SALT mov eax,[edi+0x04] mov es,eax ;es -> 用戶程序頭部 mov eax,core_data_seg_sel mov ds,eax cld mov ecx,[es:0x24] ;用戶程序的SALT條目數 mov edi,0x28 ;用戶程序內的SALT位于頭部內0x2c處 .b2: push ecx push edi mov ecx,salt_items mov esi,salt .b3: push edi push esi push ecx mov ecx,64 ;檢索表中,每條目的比較次數 repe cmpsd ;每次比較4字節 jnz .b4 mov eax,[esi] ;若匹配,esi恰好指向其后的地址數據 mov [es:edi-256],eax ;將字符串改寫成偏移地址 mov ax,[esi+4] mov [es:edi-252],ax ;以及段選擇子 .b4: pop ecx pop esi add esi,salt_item_len pop edi ;從頭比較 loop .b3 pop edi add edi,256 pop ecx loop .b2 mov ax,[es:0x04] ;將用戶頭部的選擇子放到ax中返回 pop es ;恢復到調用此過程前的es段 pop ds ;恢復到調用此過程前的ds段 pop edi pop esi pop edx pop ecx pop ebx ret
為用戶程序使用到的系統調用進行重定位,將例程段在內核中的選擇子和實際例程的偏移量放置到用戶程序的salt中去
此時已經成功加載完成了用戶程序,返回內核程序
總結:這一章通過作者通過一個實例來說明了用戶程序加載和內核程序加載的大致流程。

浙公網安備 33010602011771號