該篇文章爲知識總結 , 大部分的內容來自 : https://www.cnblogs.com/whileskies/p/13427861.html , 非原創
題目
練習5:實現函數調用堆棧跟蹤函數 (需要編程)
我們需要在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 –
……
請完成實驗,看看輸出是否與上述顯示大致一致,並解釋最後一行各個數值的含義。
知識點
知識點總結來自 : https://www.cnblogs.com/whileskies/p/13427861.html , 非原創
堆棧是函數運行時的內存空間,由高地址向低地址增長,ebp寄存器存儲棧底地址,esp寄存器存儲棧頂地址,始終指向棧頂元素;棧從高地址向地址增長。
入棧指令:push S,相當於R[%esp] = R[%esp] - 4; M[R[%esp]] = S
出棧指令:pop D,相當於D = M[R[%esp]]; R[%esp] = R[%esp] + 4
函數調用時主要經過以下步驟:
調用者:
- 將被調用函數的參數從右向左依次入棧
- 執行call命令,將call命令的下一條指令地址,也即返回地址,壓棧,同時跳轉到被調用函數執行
被調用函數: - pushl %ebp:將調用者的棧底地址入棧,便於返回到調用者繼續執行
- movl %esp, %ebp:%ebp指向當前棧頂,也即與%esp指向相同,此函數的堆棧就此建立
- 函數執行,臨時變量壓入堆棧
- movl %ebp, %esp:%esp指向該函數的棧底
- popl %ebp:將%ebp棧底指針重新指向調用者函數的棧底地址
- ret:被調用函數從棧頂彈出返回地址,返回到調用函數繼續執行
堆棧示意圖如下所示:
+| 棧底方向 | 高位地址
| ... |
| ... |
| 參數3 |
| 參數2 |
| 參數1 |
| 返回地址 |
| 上一層[ebp] | <-------- [ebp]
| 局部變量 | 低位地址
上面講的知識點總結可以結合上面的來說明 .
以上爲了實際上一直在講的一個東西就是 : 我們可以通過 ebp
的位移推導出入參
和返回地址
, 也即是 :
ss:[ebp]處爲上一級函數的ebp地址,ss:[ebp+4]爲返回地址;
ss:[ebp + 8]爲函數第一個參數地址,ss:[ebp + 12]爲第二個參數地址;
解題
代碼
void print_stackframe(void) {
/* 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]
*
* 這裏解釋一下爲什麼需要 eip , 我們的目的是爲了打印調用函數的堆棧信息 , 要是知道每一層的 eip 就好了, 我們就可以知道堆棧信息 ,而如何知道
* eip 信息的, 一個重要的信息來自 ebp , 爲什麼? 因爲調用函數的時候會把 “返回地址” 壓棧
*
*/
uint32_t ebp = read_ebp();
uint32_t eip = read_eip();
// 同樣的方法可以讀到 esp
// uint32_t esp = read_esp();
// for 循環 , 向上打印堆棧信息
for (int i = 0; i < STACKFRAME_DEPTH; i++) {
if (ebp == 0) break;
// 打印這兩個值
cprintf("ebp:0x%08x eip:0x%08x ", ebp, eip);
// 打印參數
cprintf("args:0x%08x 0x%08x 0x%08x 0x%08x", *(uint32_t *)(ebp + 8),
cprintf("\n");
// 這裏打印的是該 eip , ebp 對應的函數名稱和代碼函數
print_debuginfo(eip - 1);
// 指針要指向上一層
eip = *(uint32_t *)(ebp + 4); // ebp + 4 就是上一次調用的地方,即"返回地址"
ebp = *(uint32_t *)(ebp); // ebp 裏面存放的是上一層的 ebp
}
}
首先通過函數讀取ebp、eip寄存器值,分別表示指向棧底的地址、當前指令的地址;
ss:[ebp + 8]爲函數第一個參數地址,ss:[ebp + 12]爲第二個參數地址;
ss:[ebp]處爲上一級函數的ebp地址,ss:[ebp+4]爲返回地址;
可通過指針索引的方式訪問指針所指內容。
獲取當前的eip值較爲巧妙,代碼如下:
static __noinline uint32_t
read_eip(void) {
uint32_t eip;
asm volatile("movl 4(%%ebp), %0" : "=r" (eip));
return eip;
}
在調用該函數時會創建相應堆棧,通過創建函數時壓入的上一級函數返回地址來間接得到當前的eip。
參考資料
看這一個
- https://www.cnblogs.com/whileskies/p/13427861.html (有人已經完成的答案)
- https://objectkuan.gitbooks.io/ucore-docs/content/lab1/lab1_2_1_5_ex5.html (題目地址)
- https://www.cs.cmu.edu/afs/cs/academic/class/15213-f09/www/lectures/04-machine-basics.pdf (彙編語言基礎)
- https://www.one-tab.com/page/YtHf9_uyTTO4Vdi0Hd7yLw (c 調用ASM 語法 )
- http://esofar.gitee.io/cnblogs-theme-silence/#/?id=cnblogs-theme-silence(博客主題)
- http://cr.openjdk.java.net/~sundar/8022483/webrev.01/raw_files/new/src/share/classes/com/sun/tools/hat/resources/oqlhelp.html (MAT OQ 查詢語句-無關)