實際上,我們要做的工作是根據內核的Program header table的信息進行類似下麵這個C語言語句的記憶體複製: memcpy(p_vaddr, BaseOfLoaderPhyAddr+p_offset, p_filesz); 複製可能不止一次,如果Program header有n個,複製就進 ...
實際上,我們要做的工作是根據內核的Program header table的信息進行類似下麵這個C語言語句的記憶體複製:
memcpy(p_vaddr, BaseOfLoaderPhyAddr+p_offset, p_filesz);
複製可能不止一次,如果Program header有n個,複製就進行n次。
每一個Program header都描述一個段,語句中的P_offset為段在文件中的偏移,p_filesz為段在文件中的長度,p_vaddr為段在記憶體中的虛擬地址。
由ld生成的可執行文件中p_vaddr的值總是一個類似於0x8048XXX的值,至少我們的例子中是一個這樣的值。可是我們啟動分頁機制時地址都是對等映射的,記憶體地址0x8048XXX已經處在128MB記憶體以外(128MB的十六進位表示是0x8000000),如果電腦的記憶體小於128MB的話,這個地址顯然已經超出了記憶體大小。
即便電腦有足夠大的記憶體,顯然,我們也不能讓編譯器來決定內核載入到什麼地方。解決它有兩個辦法,一是通過修改頁表讓0x8048XXX映射到較低的地址,另一種方法就是通過修改ld的選項讓它生成的可執行代碼中p_vaddr的值變小。
nasm -f elf -o kernel.o kernel.asm
ld -m elf_i386 -s -Ttext 0x30400 -o kernel.bin kernel.o
程式的入口地址就變成0x30400了,ELF header等信息會位於0x30400之前。此時的ELF header和Program header table的情況如下表所示:
根據上表,我們應該這樣放置內核:
memcpy(30000h, 90000h+0, 40Dh);
也就是說,我們應該把文件從開頭開始40Dh位元組的內容放到記憶體30000h處。由於程式的入口在30400h處,所以從這裡就可以看出,實際上代碼只有0Dh+1個位元組。下麵是Kernel.bin的內容:
上面被星號省去的部分都是0.從中可以看出,從400h到40Dh是僅有的代碼,0xEBFE正是代碼最後的“jmp $”。
下麵的代碼實現了將Kernel.bin根據ELF文件信息轉移到正確的位置。它很簡單,找出每個Program header,根據其信息進行記憶體複製:
; InitKernel --------------------------------------------------------------------------------- ; 將 KERNEL.BIN 的內容經過整理對齊後放到新的位置 ; 遍歷每一個 Program Header,根據 Program Header 中的信息來確定把什麼放進記憶體,放到什麼位置,以及放多少。 ; -------------------------------------------------------------------------------------------- InitKernel: xor esi, esi mov cx, word [BaseOfKernelFilePhyAddr+2Ch];`. ecx <- pELFHdr->e_phnum movzx ecx, cx ;/ mov esi, [BaseOfKernelFilePhyAddr + 1Ch] ; esi <- pELFHdr->e_phoff add esi, BaseOfKernelFilePhyAddr;esi<-OffsetOfKernel+pELFHdr->e_phoff .Begin: mov eax, [esi + 0] cmp eax, 0 ; PT_NULL jz .NoAction push dword [esi + 010h] ;size ;`. mov eax, [esi + 04h] ; | add eax, BaseOfKernelFilePhyAddr; | memcpy((void*)(pPHdr->p_vaddr), push eax ;src ; | uchCode + pPHdr->p_offset, push dword [esi + 08h] ;dst ; | pPHdr->p_filesz; call MemCpy ; | add esp, 12 ;/ .NoAction: add esi, 020h ; esi += pELFHdr->e_phentsize dec ecx jnz .Begin ret ; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
接下來就是向內核跳轉
;*************************************************************** jmp SelectorFlatC:KernelEntryPointPhyAddr ; 正式進入內核 * ;***************************************************************
KernelEntryPointPhyAddr定義在頭文件load.inc中,其值為0x30400.當然,它必須跟我們的ld的參數-Ttext指定的值是一致的。將來如果我們想將內核放在另外的位置,只需改動這兩個地方就可以了。
運行結果如下:
成功了,出現字元“K”,這表明我們的內核在執行了。Loader的使命圓滿結束。
【源碼】