练习五:实现函数调用堆栈跟踪函数(需要编程) 需要在lab1中实现kdebug.c中的函数print_stackframe,可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址
- 函数堆栈的介绍
对于计算机中运行的每一个进程都有一个相应的栈来存储相应的信息,在进程中的每个函数被调用时都会占据栈中的一段区域,也就是栈帧。寄存器esp指向栈的栈顶,寄存器ebp指向的是当前帧的帧底,也就是说ebp在栈帧中是一个固定的值,而esp在函数调用的过程中随时处于变化。
- 栈帧在函数调用中的作用
栈帧中存放着函数调用与返回的各种信息
1、函数的参数值以及返回地址: 保存着函数调用前的断点信息,在函数调用完成后能够找到之前调用的地址
2、局部变量: 保存着函数的局部变量
3、上下文: 保存着函数调用前各个寄存器的值
4、栈帧的状态值: 保存前栈帧的顶部以及底部,在函数调用完成后恢复上一个栈帧。
- 寄存器esp以及ebp的作用:
在栈帧中寄存器ebp始终指向栈帧的底部,它是一个固定的值,相当于一个指针,那么就可以利用ebp的值来访问函数参数 函数的返回地址 函数的局部变量等。同时,函数调用上一级的帧底被压入当前ebp所指的地址,也就是说,当前栈帧的帧底保存着上一级调用者ebp的指针值
栈帧的示意图如下所示:
依据上图我们可知:
ss:[ebp+4] : 返回地址
ss:[ebp+8] : 第一个参数
ss:[ebp-4] : 寄存器的值
ss:[ebp] : 上一层栈帧的ebp值
print_stackframe()函数的编程如下:
下列是编写print_stackframe()时用到的函数:
read_eip():
read_eip(void) {
uint32_t eip;
/*
*asm表示后面的代码为汇编代码
*volatile 表示编译器不要优化代码,后面的指令 保留原样
*%0表示列表开始的第一个寄存器
*“=r”(eip)表示gcc让eip对应一个通用寄存器
*下面这条语句的作用是将ss:[ebp+4]对应的值保存到eip中,ss:[ebp+4]对应的值是
*函数的返回地址,也就是说将函数的返回地址保存到eip中,然后返回eip
*/
asm volatile("movl 4(%%ebp), %0" : "=r" (eip));
return eip;
}
read_ebp()函数也是同样的功能
print_debuginfo(): 打印eip和ebp相关的信息
print_debuginfo(uintptr_t eip) {
struct eipdebuginfo info;
if (debuginfo_eip(eip, &info) != 0) {
cprintf(" <unknow>: -- 0x%08x --\n", eip);
}
else {
char fnname[256];
int j;
for (j = 0; j < info.eip_fn_namelen; j ++) {
fnname[j] = info.eip_fn_name[j];
}
fnname[j] = '\0';
cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line,
fnname, eip - info.eip_fn_addr);
}
}
print_stackframe()函数的具体内容:
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(); //通过read_ebp()函数读取ebp的值
uint32_t eib = read_eip(); //通过read_eip()函数读取eip的值
int i;
for(i=0;ebp!=0 && i<STACKFRAME_DEPTH;i++) //STACKFRAME_DEPTH表示假设的栈的深度 通过一个for循环输出每一个栈帧中的各个参数
{
cprintf("ebp:0x%08x eip:0x%08x args:",ebp,eip);
uint32_t *args = (uint32_t)(ebp) + 2; //此时args指向了ss:[ebp+8]的位置 此处存放着参数
int j;
for(j=0;j<4;j++) //依次打印调用函数的参数1 2 3 4
cprintf("0x%08x ",args[j]);
cprintf("\n"); //输出换行符
print_debuginfo(eip-1); //打印eip以及ebp相关的信息
eip = ((uint32_t*)ebp)[1]; //此时eip指向了返回地址
ebp = ((uint32_t*)ebp)[0]; //ebp指向了原ebp的位置
}
}