MIT 6.828 Lab1筆記
PART 1: PC Bootstrapr
這一部分主要是實驗環(huán)境的配置,跟隨教程操作即可
實模式下的地址轉(zhuǎn)換
物理地址 = 段基址*16 + 偏移量
PART 2: Boot Loader
在6.828課程的系統(tǒng)中,boot loader包含兩部分:一份匯編代碼 boot/boot.s 和一份c代碼 boot/main.c 。它們構(gòu)成的boot loader主要有兩個作用
1. 將處理器從實模式切換道32位保護(hù)模式。在實模式中,處理器只能訪問1MB物理空間,而在32位保護(hù)模式下,處理器可以訪問1MB以上的物理空間。切換模式后,邏輯地址向物理地址的映射方式也發(fā)生了變化,(段基址:偏移量)轉(zhuǎn)化需要的offset從16變?yōu)?2。
2. 借由x86的特殊I/O指令,直接從IDE硬盤中讀取內(nèi)核程序。
Exercise 3 的四個問題
Q: 處理器是何時開始執(zhí)行32位代碼的?是什么機(jī)制使得處理器從16位轉(zhuǎn)為32位?
A: 如下圖所示, 匯編操作將cr0寄存器的最后一位置為1,通過Intel手冊我們可以知道,當(dāng)cr0寄存器的最后一位為1時,開啟保護(hù)模式。
Lab1-1.png
Q: boot loader 執(zhí)行的最后一條指令是什么?它加載的kernel執(zhí)行的第一條指令是什么?
A: 如下圖所示,這是boot loader執(zhí)行的最后一條指令。這條指令轉(zhuǎn)到了kernel程序的入口。
Lab1-2.png
kernel執(zhí)行的第一條指令如下圖所示。
Lab1-3.png
Q: kernel的第一條指令在哪里?
A: 根據(jù)上一張圖,我們可以看到kernel第一條指令的地址在于 0x10000c
Q: boot loader在從硬盤讀取內(nèi)核時,它是怎樣決定讀取幾個扇區(qū)的?它是怎樣得知這個信息
A: bootmain函數(shù)如下圖所示,程序首先讀取足夠大的數(shù)據(jù)(程序中是SECTSIZE*8),判斷要讀取的數(shù)據(jù)是不是一個ELF數(shù)據(jù),如果是,則讀取程序頭,從表征程序頭的結(jié)構(gòu)體里面的e_phnum成員得知要讀取的數(shù)據(jù)數(shù)量。
Lab1-4.pngExercise 5
linker的地址改變,從結(jié)果上來看,kernel無法被加載進(jìn)內(nèi)存,現(xiàn)象如下圖所示
Lab1-5.png我在這里將鏈接地址從0x7c00改成了0x7cd0,用gdb跟蹤程序,會發(fā)現(xiàn)boot loader的起始地址變?yōu)榱宋覀兏某傻?x7cd0,如下圖所示
Lab1-6.png將鏈接地址修改成不同的值會出現(xiàn)不同的問題,但最終都會導(dǎo)致程序循環(huán),無法正確加載kernel程序
Exercise 6
將斷點打在boot loader的起始位置,觀察地址0x100000,可以看出這一地址的數(shù)據(jù)全部為0。當(dāng)程序進(jìn)入kernel后,發(fā)現(xiàn)指定的地址位置出現(xiàn)了數(shù)據(jù)。原因可能是boot loader 將kernel加載進(jìn)了內(nèi)存,所以相應(yīng)位置的數(shù)據(jù)出現(xiàn)了變化;
Exercise 7
在執(zhí)行指令 movl %eax, %cr0 之前, 兩個地址對應(yīng)的數(shù)值如下圖所示地址空間
Lab1-8.png在執(zhí)行指令過后, 兩個地址對應(yīng)的數(shù)值如下圖所示地址空間
Lab1-9.png執(zhí)行指令前兩個地址空間對應(yīng)的數(shù)據(jù)不同,而執(zhí)行指令后兩個地址空間對應(yīng)的數(shù)據(jù)相同,說明開啟分頁機(jī)制后這兩個虛擬地址被映射成了同一個物理地址。
注釋掉這條指令重新編譯,從結(jié)果上來看,硬件啟動失敗,直接退出
gdb跟蹤發(fā)現(xiàn)是一條jmp指令出錯了,因為跳轉(zhuǎn)過后的地址是一個無效地址。
PART 3: The Kernel
kern/printf.c, lib/printfmt.c 和 kern/console.c三個文件的關(guān)系
kern/console.c 最底層,用了大量匯編語言編寫,主要是適配于硬件的屏幕輸出;
kern/printf.c 使用了console.c中的底層函數(shù);
lib/printfmt.c 的函數(shù)最上層,封裝了內(nèi)核中的函數(shù),可以供上層使用。
Exercise 8
需要修改的代碼在lib/printfmt.c中,改變后的代碼為
case 'o': // Replace this with your code. /* putch('X', putdat); putch('X', putdat); putch('X', putdat); break; */ num = getuint(&ap, lflag); base = 8; goto number;模仿上方的代碼即可寫出,下面是問題的回答
1. console.c 導(dǎo)出了 cputchar 函數(shù)供 printf.c 使用,cputchar封裝在了 putch 函數(shù)中。
2. 下面代碼處理的情況是:屏幕上已經(jīng)充滿了字符,若現(xiàn)在的操作需要向屏幕再次輸入字符,則需要進(jìn)行的動作是:1.所有的字符向上移動一行 2.最后一行清空。這段代碼實現(xiàn)的就是這個功能。
if (crt_pos >= CRT_SIZE) {//*如果目前屏幕上的字符數(shù)超過了屏幕的容量 int i; memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));//* 將從第二行開始的內(nèi)容移動到第一行去 for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++) crt_buf[i] = 0x0700 | ' ';//*最后一行的內(nèi)容清零,由上文的內(nèi)容可知,對0x700進(jìn)行或操作與顯示的色彩有關(guān) crt_pos -= CRT_COLS; }3. 我將文中提及到的代碼段加在了 kern/init.c 中,因為前方有明顯的屏幕打印值,所以可以很方便地找到要追蹤地語句地位置。把斷點打在 cpirntf 上。
在 cprintf 函數(shù)內(nèi),fmt指向的是字符串 "x %d, y %x, z %d\n" 的地址。函數(shù)原型中沒有ap,所以這里ap也打印不出來,如下圖所示
Lab1-10.png
在 vcprintf 函數(shù)內(nèi),fmt指向的是字符串 "x %d, y %x, z %d\n" 的地址。ap指向一個地址,這個地址分別放有 x, y, z 的值,如下圖所示
Lab1-11.png
cons_putc 中,變量是一個char型
va_arg 是一個宏定義,gdb無法在之上直接打斷點。進(jìn)入vprintfmt逐步跟蹤后,可以看到每次va_arg后,ap都是指向下一個變量的地址
Lab1-12.png4. 輸出變成了 He110 World 。57616化成16進(jìn)制是e110, 72,6c,64分別是rld的ascii碼,如果在大端序的cpu上運行,則 Wo后面不會顯示字符。
5. 雖然輸入y沒有對應(yīng)的值,但還是會打印棧上的內(nèi)容,在信心安全領(lǐng)域這種叫做格式化字符串漏洞,用于泄露棧上信息。
6. 函數(shù)原型聲明為 cprintf(const char *fmt, va_list ap);
Exercise 11 12
要通過寄存器追溯函數(shù)調(diào)用棧,首先要弄懂一些細(xì)節(jié):
1.pop 和 push 指令的操作順序
push: 先減小esp寄存器里的值,再將要壓入的數(shù)據(jù)寫入到esp寄存器指向的地址;
pop: 先讀出esp寄存器指向地址的值,再增加esp寄存器的值
讀寫與寄存器值改變, 這兩個順序不要弄錯了
2.即將進(jìn)行函數(shù)調(diào)用時,進(jìn)行的壓棧操作
首先按照參數(shù)的聲明順序,將參數(shù)壓入棧中(關(guān)于壓棧的順序,可能根據(jù)編譯器有所不同;32位操作系統(tǒng)下函數(shù)傳參需要通過壓棧,64位cpu寄存器資源豐富,傳參直接用寄存器)。
然后壓入返回地址eip,傳參和壓入eip都是在call函數(shù)之前實現(xiàn)的
最后將esp地址傳遞給ebp,并將ebp的值壓入棧中。注意,這一部是在被調(diào)函數(shù)中實現(xiàn)的。
3.棧的生長方向,從高地址向低地址生長
關(guān)于在調(diào)用時顯示符號信息,這個實驗中的要求比較簡單,已經(jīng)寫好了 stab_binsearch 函數(shù),只需要利用即可,相應(yīng)的代碼中也有提示。
有一點需要注意的是,stab結(jié)構(gòu)體中有很多成員,哪一項顯示的是eip地址的行號呢,通過驗證,我們可以得知是 n_value 這個變量,如果使用了其他變量,make grade 也能通過,不過返回地址是不正確的。具體看程序吧
kern/kdebug.c中代碼如下圖所示
Lab1-13.pngkern/monitor.c中代碼如下圖所示
Lab1-14.pngmake qemu 的結(jié)果如下圖所示
Lab1-15.png通過查看 obj/kernel.asm ,我們判斷 test_backtrace 的返回地址應(yīng)該是基地址+0x28, 即十進(jìn)制的40, 與實驗結(jié)果相符。
make grade, 實驗通過??!
Lab1-16.png














