《自己動手寫作業系統》第三章的第一個程式中,作者沒有把可作為開機磁區的程式拿出來,編譯通過,但是在bochs 中執行時,bochs會提示沒有找到啟動盤,然後反組譯碼bin檔案後,看到檔案末尾並沒有啟動盤第510和511位元組的0xaa55標誌,因此要想法將0xaa55標誌寫入啟動盤中。
試了挺多方法,為了節省篇幅,我就把最後使用的方法貼出來吧。這種方法模仿作者在第一章第一個程式中貼出來的程式,即首先計算出之前實打實的代碼的長度,然後接下來(510-長度)的內容填充0,最後兩個位元組寫上0xaa55。完整的程式最後附上,下面分析原理。
times 510-(4+(GDTLen1+3)/4*4+(SegCode16Len+3)/4*4+(SegCode32Len)) db 0dw 0xAA55
當然,第三章這個程式比第一章的複雜,一是第三章程式中涉及到了分節,因此第一章中程式的 510 -($-$$)就失去了效用,因為$$表示的當前節的節首,因此需要分別統計這三個節的長度。二是因為分節,產生了節首的對齊問題。下面分別闡述這兩個問題的解決方案。
得到每個節的長度,這個問題很簡單,只要在節首和節尾記錄一下即可,如下所示,不再贅述。
[SECTION .gdt]LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致程式碼片段LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址GdtLenequ$ - LABEL_GDT; GDT長度GdtPtrdwGdtLen - 1; GDT界限dd0; GDT基地址SelectorCode32equLABEL_DESC_CODE32- LABEL_GDTSelectorVideoequLABEL_DESC_VIDEO- LABEL_GDTGDTLen1 equ $-LABEL_GDT;END of [SECTION .gdt]
下面討論節首的對齊問題。第一個問題解決之後,我曾想著直接用各個節的長度和來代表程式的長度,結果出錯,還是找不到啟動盤。通過反組譯碼我發現如下兩個問題,一是補充的0的個數總是過多,說明程式的長度我統計少了,二是反組譯碼後程式每個節首的代碼總是與來源程式不同,說明在兩個節之間產生了一些額外的代碼,使反組譯碼器被騙。如下兩段代碼,其中左面代碼是來源程式中16位部分與32位部分交界處的一段,右邊代碼為相同位置的反組譯碼代碼,其中紅色地區的反組譯碼的代碼,是可疑代碼。
mov cr0, eaxjmp dword SelectorCode32:0 , [SECTION .s32][BITS 32]LABEL_SEG_CODE32: mov ax, SelectorVideo mov gs, ax mov edi, (80 * 11 + 79) * 2 |
00007C72 0F22C0 mov cr0,eax00007C75 66EA000000000800 jmp dword 0x8:0x000007C7D 0000 add [bx+si],al00007C7F 0066B8 add [bp-0x48],ah00007C82 1000 adc [bx+si],al00007C84 8EE8 mov gs,ax00007C86 BF7E07 mov di,0x77e |
其中可疑的是,在反組譯碼後的紅色代碼處,出現了連續的幾個0,因此我馬上想到了是不是為了節首的對齊。為了驗證這種思路,再看一下本程式的第一條指令的反組譯碼指令
00007C00 E92100 jmp word 0x7c24
這條指令要求程式跳轉到0x7c24處,據原始碼,這個地方是16位代碼的首地址。下面貼出來0x7c24附近的反組譯碼代碼,左圖是原反組譯碼代碼,中圖是我調整後的反組譯碼代碼,右圖是原程式中相應部分的代碼。0x7c24是程式的起點了,那麼0x7c24以上部分是什麼呢?還是一群0。分析右圖,其中藍色地區為虛擬碼,也就是說綠色地區是緊接著0x7c24的地區,藍色地區是什嗎?是資料,經計算,這個6 byte的資料應該是23,轉化成十六進位,正好是0x0000000017,如中圖綠色標註部分。好了,中圖中紅和綠之間還夾了兩個位元組,我們更有理由相信這是為了對齊的設定了,因為這個地區沒有代碼,沒有資料,唯一的特點是處於兩個節的相交處,我們離真相只有一步之遙。做如下計算,計算出16代碼區的位元組總數,0x52,發現,0x52不是4的倍數,而0x52+2=0x54是4的倍數,加的這個2正好是中圖中紅色地區和綠色地區之間夾的2個位元組。實驗其他地區,也成立。
真相大白了,NASM在編譯這個程式時是這樣處理的:每個節都是4對齊的。因此如果僅簡單的把所有節的長度加在一起得到程式的長度必然使得求得的長度偏小。我們應該考慮那些為了對齊作出犧牲的地區,所以有了公式:節實際占位元組數=(有效位元組數+3)/4*4,開頭所提公式即為該式應用。
00007C1C 17 pop ss00007C1D 0000 add [bx+si],al00007C1F 0000 add [bx+si],al00007C21 0000 add [bx+si],al00007C23 008CC88E add [si-0x7138],cl00007C27 D88EC08E fmul dword [bp-0x7140]00007C2B D0BC0001 sar byte [si+0x100],100007C2F 6631C0 xor eax,eax00007C32 8CC8 mov ax,cs00007C34 66C1E004 shl eax,0x4 |
00007C1C 17 pop ss00007C1D 0000 add [bx+si],al00007C1F 0000 add [bx+si],al00007C21 0000 add [bx+si],al00007C23 0000007C24 8CC88E 00007C27 D88EC08E fmul dword [bp-0x7140]00007C2B D0BC0001 sar byte [si+0x100],100007C2F 6631C0 xor eax,eax00007C32 8CC8 mov ax,cs00007C34 66C1E004 shl eax,0x4 |
GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT GDTLen1 equ $-LABEL_GDT[SECTION .s16][BITS 16]LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h xor eax, eax mov ax, cs shl eax, 4 |
至此,寫開機磁區的一大難題解決了,下面貼出代碼3.1的更新,希望能對同樣困惑的朋友有所協助。
%include"pm.inc"; 常量, 宏, 以及一些說明org07c00hHAHA_begin:jmpLABEL_BEGINQianlen equ $-HAHA_begin[SECTION .gdt]LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致程式碼片段LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址GdtLenequ$ - LABEL_GDT; GDT長度GdtPtrdwGdtLen - 1; GDT界限dd0; GDT基地址SelectorCode32equLABEL_DESC_CODE32- LABEL_GDTSelectorVideoequLABEL_DESC_VIDEO- LABEL_GDTGDTLen1 equ $-LABEL_GDT[SECTION .s16][BITS16]LABEL_BEGIN:movax, csmovds, axmoves, axmovss, axmovsp, 0100hxoreax, eaxmovax, csshleax, 4addeax, LABEL_SEG_CODE32movword [LABEL_DESC_CODE32 + 2], axshreax, 16movbyte [LABEL_DESC_CODE32 + 4], almovbyte [LABEL_DESC_CODE32 + 7], ahxoreax, eaxmovax, dsshleax, 4addeax, LABEL_GDT; eax <- gdt 基地址movdword [GdtPtr + 2], eax; [GdtPtr + 2] <- gdt 基地址lgdt[GdtPtr]cliinal, 92horal, 00000010bout92h, almoveax, cr0oreax, 1movcr0, eaxjmpdword SelectorCode32:0; 執行這一句會把 SelectorCode32 裝入 cs,; 並跳轉到 Code32Selector:0 處SegCode16Len equ $-LABEL_BEGIN[SECTION .s32][BITS32]LABEL_SEG_CODE32:movax, SelectorVideomovgs, ax; 視頻段選擇子(目的)movedi, (80 * 11 + 79) * 2; 螢幕第 11 行, 第 79 列。movah, 0Ch; 0000: 黑底 1100: 紅字moval, 'A'mov[gs:edi], axjmp$SegCode32Lenequ$ - LABEL_SEG_CODE32; END of [SECTION .s32]times 510-(4+(GDTLen1+3)/4*4+(SegCode16Len+3)/4*4+(SegCode32Len)) db 0dw 0xAA55