实现函数调用堆栈跟踪函数
我们需要在 lab1 中完成 kdebug.c 中函数 print_stackframe 的实现,可以通过函数 print_stackframe 来跟踪函数调用堆栈中记录的返回地址。
直接说代码吧:
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]*/uint32_t ebp = read_ebp();uint32_t ra = read_eip();int i;for(i = 0 ; i < STACKFRAME_DEPTH && ebp != 0 ; i++){cprintf("ebp: 0x%08x eip: 0x%08x ", ebp, ra);uint32_t *args = (uint32_t*)(ebp) + 2;cprintf("args: 0x%08x 0x%08x 0x%08x 0x%08x\n", args[0], args[1], args[2], args[3]);print_debuginfo(ra-1);ra = *((uint32_t*)(ebp) + 1);ebp = *(uint32_t*)(ebp);}
}
基本的思路在注释里写得很清楚了,关键就是要理解函数调用栈的递归调用关系,以及ebp和esp的运动过程。这部分建议直接看视频和课上的图,陈渝老师讲的很清楚。
这里并没有深入分析kdebug.c中的其他函数。
make qemu
结果如下:
接下来分别解释输出的含义:
- ebp表示ebp寄存器的值,这个值是上一层函数内ebp寄存器的值;
- eip表示当前函数的返回地址;
- args表示传给当前函数的参数;
- kern/debug/kdebug.c:306: print_stackframe 这种东西表示在kdebug.c的306行,print_stackframe函数内发生函数调用,而且上面这些都是print_stackframe函数的调用栈信息;
- +22之类的表示当前函数入口距离调用函数入口的字节数。
需要注意的是最后一行:<unknow>: -- 0x00007d71 --
。从整个函数调用栈的输出来看,不难推测应该是最“外层”的函数,即要进入 OS kernel 开始执行的那个函数,而我们之前分析过,bootmain函数的最后一行完成了这个操作,所以最后一行输出的是 bootmain 的栈帧信息。
// in obj/bootblock.asm function <bootmain>// call the entry point from the ELF header// note: does not return((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();7d66: a1 18 00 01 00 mov 0x10018,%eax7d6b: 25 ff ff ff 00 and $0xffffff,%eax7d70: ff d0 call *%eax
可以看到这个0x7d71
是进入 OS kernel 的 call 指令的最后一个字节。因为 stabs 中没有这个符号(好像),所以返回 unknow。
本节的分析其实比较粗略,因为并没有涉及到 kernel 整体的分析。