《ucore lab1 exercise5》實驗報告


資源

  1. ucore在線實驗指導書
  2. 我的ucore實驗代碼

題目:實現函數調用堆棧跟蹤函數

我們需要在lab1中完成kdebug.c中函數print_stackframe的實現,可以通過函數print_stackframe來跟蹤函數調用堆棧中記錄的返回地址。如果能夠正確實現此函數,可在lab1中執行 “make qemu”后,在qemu模擬器中得到類似如下的輸出:

ebp:0x00007b28 eip:0x00100992 args:0x00010094 0x00010094 0x00007b58 0x00100096
kern/debug/kdebug.c:305: print_stackframe+22
ebp:0x00007b38 eip:0x00100c79 args:0x00000000 0x00000000 0x00000000 0x00007ba8
kern/debug/kmonitor.c:125: mon_backtrace+10
ebp:0x00007b58 eip:0x00100096 args:0x00000000 0x00007b80 0xffff0000 0x00007b84
kern/init/init.c:48: grade_backtrace2+33
ebp:0x00007b78 eip:0x001000bf args:0x00000000 0xffff0000 0x00007ba4 0x00000029
kern/init/init.c:53: grade_backtrace1+38
ebp:0x00007b98 eip:0x001000dd args:0x00000000 0x00100000 0xffff0000 0x0000001d
kern/init/init.c:58: grade_backtrace0+23
ebp:0x00007bb8 eip:0x00100102 args:0x0010353c 0x00103520 0x00001308 0x00000000
kern/init/init.c:63: grade_backtrace+34
ebp:0x00007be8 eip:0x00100059 args:0x00000000 0x00000000 0x00000000 0x00007c53
kern/init/init.c:28: kern_init+88
ebp:0x00007bf8 eip:0x00007d73 args:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8
<unknow>: -- 0x00007d72 –

請完成實驗,看看輸出是否與上述顯示大致一致,並解釋最后一行各個數值的含義。

提示:可閱讀小節“函數堆棧”,了解編譯器如何建立函數調用關系的。在完成lab1編譯后,查看lab1/obj/bootblock.asm,了解bootloader源碼與機器碼的語句和地址等的對應關系;查看lab1/obj/kernel.asm,了解 ucore OS源碼與機器碼的語句和地址等的對應關系。

要求完成函數kern/debug/kdebug.c::print_stackframe的實現,提交改進后源代碼包(可以編譯執行) ,並在實驗報告中簡要說明實現過程,並寫出對上述問題的回答。

補充材料:
由於顯示完整的棧結構需要解析內核文件中的調試符號,較為復雜和繁瑣。代碼中有一些輔助函數可以使用。例如可以通過調用print_debuginfo函數完成查找對應函數名並打印至屏幕的功能。具體可以參見kdebug.c代碼中的注釋。

解答

代碼實現

  1. 編程前,首先了解下當前情況:在Terminal下輸入make qemu,發現打印以下信息后就退出了:
along:~/src/ucore/labcodes/lab1$ sudo make qemu
WARNING: Image format was not specified for 'bin/ucore.img' and probing guessed raw.
         Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
         Specify the 'raw' format explicitly to remove the restrictions.
(THU.CST) os is loading ...

Special kernel symbols:
  entry  0x00100000 (phys)
  etext  0x001036f3 (phys)
  edata  0x0010e950 (phys)
  end    0x0010fdc0 (phys)
Kernel executable memory footprint: 64KB
  1. 分析print_stackframe的函數調用關系
kern_init ->
    grade_backtrace ->
        grade_backtrace0(0, (int)kern_init, 0xffff0000) ->
                grade_backtrace1(0, 0xffff0000) ->
                    grade_backtrace2(0, (int)&0, 0xffff0000, (int)&(0xffff0000)) ->
                        mon_backtrace(0, NULL, NULL) ->
                            print_stackframe ->
                                
  1. 找到print_stackframe函數,發現函數里面的注釋已經提供了十分詳細的步驟,基本上按照提示來做就行了。代碼如下所示。
    • 首先定義兩個局部變量ebp、esp分別存放ebp、esp寄存器的值。這里將ebp定義為指針,是為了方便后面取ebp寄存器的值。
    • 調用read_ebp函數來獲取執行print_stackframe函數時ebp寄存器的值,這里read_ebp必須定義為inline函數,否則獲取的是執行read_ebp函數時的ebp寄存器的值。
    • 調用read_eip函數來獲取當前指令的位置,也就是此時eip寄存器的值。這里read_eip必須定義為常規函數而不是inline函數,因為這樣的話在調用read_eip時會把當前指令的下一條指令的地址(也就是eip寄存器的值)壓棧,那么在進入read_eip函數內部后便可以從棧中獲取到調用前eip寄存器的值。
    • 由於變量eip存放的是下一條指令的地址,因此將變量eip的值減去1,得到的指令地址就屬於當前指令的范圍了。由於只要輸入的地址屬於當前指令的起始和結束位置之間,print_debuginfo都能搜索到當前指令,因此這里減去1即可。
    • 以后變量eip的值就不能再調用read_eip來獲取了(每次調用獲取的值都是相同的),而應該從ebp寄存器指向棧中的位置再往上一個單位中獲取。
    • 由於ebp寄存器指向棧中的位置存放的是調用者的ebp寄存器的值,據此可以繼續順藤摸瓜,不斷回溯,直到ebp寄存器的值變為0
void print_stackframe(void) {
    uint32_t *ebp = 0;
    uint32_t esp = 0;

    ebp = (uint32_t *)read_ebp();
    esp = read_eip();

    while (ebp)
    {
        cprintf("ebp:0x%08x eip:0x%08x args:", (uint32_t)ebp, esp);
        cprintf("0x%08x 0x%08x 0x%08x 0x%08x\n", ebp[2], ebp[3], ebp[4], ebp[5]);
        
        print_debuginfo(esp - 1);

        esp = ebp[1];
        ebp = (uint32_t *)*ebp;
    }
     /* LAB1 YOUR CODE : STEP 1 */
     /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t);
      * (2) call read_eip() to get the value of eip. the type is (uint32_t);
      * (3) from 0 .. STACKFRAME_DEPTH
      *    (3.1) printf value of ebp, eip
      *    (3.2) (uint32_t)calling arguments [0..4] = the contents in address (uint32_t)ebp +2 [0..4]
      *    (3.3) cprintf("\n");
      *    (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc.
      *    (3.5) popup a calling stackframe
      *           NOTICE: the calling funciton's return addr eip  = ss:[ebp+4]
      *                   the calling funciton's ebp = ss:[ebp]
      */
}
  1. 編碼完成后,執行make qemu,打印結果如下所示,與實驗指導書的結果類似。
ebp:0x00007b38 eip:0x00100bf2 args:0x00010094 0x0010e950 0x00007b68 0x001000a2
    kern/debug/kdebug.c:297: print_stackframe+48
ebp:0x00007b48 eip:0x00100f40 args:0x00000000 0x00000000 0x00000000 0x0010008d
    kern/debug/kmonitor.c:125: mon_backtrace+23
ebp:0x00007b68 eip:0x001000a2 args:0x00000000 0x00007b90 0xffff0000 0x00007b94
    kern/init/init.c:48: grade_backtrace2+32
ebp:0x00007b88 eip:0x001000d1 args:0x00000000 0xffff0000 0x00007bb4 0x001000e5
    kern/init/init.c:53: grade_backtrace1+37
ebp:0x00007ba8 eip:0x001000f8 args:0x00000000 0x00100000 0xffff0000 0x00100109
    kern/init/init.c:58: grade_backtrace0+29
ebp:0x00007bc8 eip:0x00100124 args:0x00000000 0x00000000 0x00000000 0x0010379c
    kern/init/init.c:63: grade_backtrace+37
ebp:0x00007be8 eip:0x00100066 args:0x00000000 0x00000000 0x00000000 0x00007c4f
    kern/init/init.c:28: kern_init+101
ebp:0x00007bf8 eip:0x00007d6e args:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8
    <unknow>: -- 0x00007d6d --

解釋最后一行各個參數的含義

最后一行是 ebp:0x00007bf8 eip:0x00007d6e args:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8,共有ebp,eip和args三類參數,下面分別給出解釋。

  1. ebp:0x0007bf8 此時ebp的值是kern_init函數的棧頂地址,從obj/bootblock.asm文件中知道整個棧的棧頂地址為0x00007c00,ebp指向的棧位置存放調用者的ebp寄存器的值,ebp+4指向的棧位置存放返回地址的值,這意味着kern_init函數的調用者(也就是bootmain函數)沒有傳遞任何輸入參數給它!因為單是存放舊的ebp、返回地址已經占用8字節了。

  2. eip:0x00007d6e eip的值是kern_init函數的返回地址,也就是bootmain函數調用kern_init對應的指令的下一條指令的地址。這與obj/bootblock.asm是相符合的。

    7d6c:	ff d0                	call   *%eax
    7d6e:	ba 00 8a ff ff       	mov    $0xffff8a00,%edx
  1. args:0xc031fcfa 0xc08ed88e 0x64e4d08e 0xfa7502a8 一般來說,args存放的4個dword是對應4個輸入參數的值。但這里比較特殊,由於bootmain函數調用kern_init並沒傳遞任何輸入參數,並且棧頂的位置恰好在boot loader第一條指令存放的地址的上面,而args恰好是kern_int的ebp寄存器指向的棧頂往上第2~5個單元,因此args存放的就是boot loader指令的前16個字節!可以對比obj/bootblock.asm文件來驗證(驗證時要注意系統是小端字節序)。
00007c00 <start>:
    7c00:	fa                   	cli    
    7c01:	fc                   	cld    
    7c02:	31 c0                	xor    %eax,%eax
    7c04:	8e d8                	mov    %eax,%ds
    7c06:	8e c0                	mov    %eax,%es
    7c08:	8e d0                	mov    %eax,%ss
    7c0a:	e4 64                	in     $0x64,%al
    7c0c:	a8 02                	test   $0x2,%al
    7c0e:	75 fa                	jne    7c0a <seta20.1>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM