Lab1

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.png

Exercise 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.png

4. 輸出變成了 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.png

kern/monitor.c中代碼如下圖所示

Lab1-14.png

make qemu 的結(jié)果如下圖所示

Lab1-15.png

通過查看 obj/kernel.asm ,我們判斷 test_backtrace 的返回地址應(yīng)該是基地址+0x28, 即十進(jìn)制的40, 與實驗結(jié)果相符。

make grade, 實驗通過??!

Lab1-16.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容