把內核放入內存,究竟需做什么
寫滿實現內核功能的代碼的文件會被編譯成一個ELF文件。這個ELF文件不同於LOADER BIN文件。后者實質是一個沒有使用DOS命令的COM文件。因此,只需將它原封不動地從存儲設備讀入到內存中,然后跳轉到這個內存區域的開始,就將CPU的控制權交給了LOADER。
ELF文件是當前Linux系統上的可執行文件格式。寫一個C程序,然后編譯成可執行文件,使用 file 查看這個文件,能看到這個文件是ELF文件。
ELF文件由program header table、elf header、section header table、section組成。只有elf header的位置是固定的,在elf文件的開始位置。其他幾個成員的位置不固定。
將內核指令重新放置到內存中,需做兩件事情:一、把內核文件讀入內存中。二、把內存中的內核的程序段全部復制到規划好的內存位置。
第一件事情,熟悉FAT12文件系統,就能做到。我已經獨立寫代碼完成了這個功能並通過了測試。在本文不想再贅言。
第二件事情,要想完成它,需了解elf結構,需知道如何把數據從內存A位置復制到內存B位置,也就是說,需要實現一個函數,memcpy(int dest, int off, int size)。三個參數分別是:在內存中的虛擬地址、程序段在文件中的偏移量、程序段的長度。三個參數都能從ELF文件的elf頭和程序頭中獲取。
參照位置是elf文件的開頭。偏移量28個字節的內存位置,給它起個標記叫P,從P開始的若干個字節(忘記了具體數字)的內存存儲的是程序段的偏移量。文件開頭加上這個偏移量,是第一個程序頭的內存初始位置。
程序頭也存儲在一片內存中。用C語言中的struct幫助描述。程序頭是一個struct結構,成員變量有程序段的長度、程序段的偏移量、程序段在內存中的虛擬位置(也就是這個程序段將要被重新放置在內存中的位置)。這三個成員變量,就是函數memcpy需要的三個參數。
memcpy的實現
memcpy,有三個參數,分別是:數據要被復制到的內存地址dst,數據的原始地址src,數據的長度size。這個函數的功能是,把src處的size個字節的數據復制到dst處,返回值是src。
直接上代碼。
mempcy:
push ebp
mov ebp, esp
push esi
push edi
push ecx
mov esi, [ebp+12] ; src
mov edi, [ebp+8] ; dst
mov ecx, [ebp+16] ; size
.1
cmp ecx, 0
jz .2
mov al, [ds:esi]
mov [es:edi], al
inc esi
inc edi
dec ecx
jmp .1
.2
mov eax, [ebp+8]
pop ecx
pop edi
pop esi
pop ebp
ret
詳細解讀這個函數的實現。
在匯編中實現一個函數,模板是:
functionName:
; some code
; some code
ret
匯編函數必須用ret結尾。它的作用是在函數執行結束后,返回調用函數的上層代碼的下一條指令。
調用函數時,使用棧傳遞參數給函數。在函數內部,獲取參數時,再從棧中獲取參數。
mov esi, [ebp+12] ; src
mov edi, [ebp+8] ; dst
mov ecx, [ebp+16] ; size
ebp指向棧的開始位置棧頂,偏離棧頂4個字節的位置存儲的是調用函數指令的下一條指令的位置(大概就是這個意思),它是執行 call 指令時入棧的,是函數調用過程中最后一個入棧的數據。在它之前依次是函數的第一個、第二個、第三個參數入棧(在本函數中),相對於棧頂的偏移量依次是8個字節、12個字節、16個字節。
由於上面的代碼修改了esi、edi、ecx中的值,需要在修改之前將它們保存起來,在函數結束時再恢復它們原來的值,所以有下面的代碼:
push esi
push edi
push ecx
; some code
; some code
pop ecx
pop edi
pop esi
內存的最小單位是字節,本函數也按字節來復制數據,這不是必須的。復制數據使用
mov al, [ds:esi]
mov [es:edi], al
eax存儲2個字,4個字節;ax存儲1個字,2個字節;al存儲1個字節。ds是數據段,es是什么?有多少個字節,就需要重復多少次上面的復制操作。因此,需要一個循環。
.1
cmp ecx, 0
jz .2
mov al, [ds:esi]
mov [es:edi], al
inc esi
inc edi
dec ecx
jmp .1
在匯編中,loop指令能實現循環功能,本函數卻並未使用。這是為了規避恐怖的ecx陷阱。使用loop指令時,需ecx配合。在循環過程中,ecx的值會自動減少。當ecx的值是0時,循環結束。陷阱就出現在這里。具體是咋回事,我忘記了。但我遇到過,再加上在函數體中,可能會修改ecx。為了避免種種詭異的問題,本函數一般使用jmp指令,再配合手工遞減的ecx來實現循環功能。作者的其他匯編代碼都會如此,盡量不使用loop指令。