MIT6.828-Lab1-Part3-Exercise11-完善mon_backtrace的代码

 

打印栈内的信息,需要使用在 /lab/inc/x86.h 中的 read_ebp() 来读取 ebp 的值。

完善mon_backtrace的代码

test_backtrace 会调用 mon_backtrace,它已经在 /lab/kern/monitor.c 中有一个原型了,完善它,并使它产生如下输出(需要使用在 /lab/inc/x86.h 中的 read_ebp() 来读取 ebp 的值):

Stack backtrace:
  ebp f0109e58  eip f0100a62  args 00000001 f0109e80 f0109e98 f0100ed2 00000031
  ebp f0109ed8  eip f01000d6  args 00000000 00000000 f0100058 f0109f28 00000061
  ...

在每一行中,ebp后面的值代表的是被这个函数所使用的ebp寄存器的值,这个值也是这个函数的栈帧的最高地址。而eip后面的值代表的是函数的返回地址。最后的五个列在args之后的16进制的值,是传递给这个函数的头五个输入参数,当然,有可能输入参数不到五个。


上面说了那么多,直接用图1来解释(有一处错误:帧指针ebp下面的地址应该是ebp-4):

img

被调用者的ebp指向的地址保存着调用者的ebp

被调用者的ebp的上面保存着返回地址(因为当被调用者逐渐退栈后,esp也指向图中的ebp的位置,然后通过 pop %ebp 出栈,ebp就变成指向调用者的栈帧的底部,而后esp要再+4。

ebp+8/12/16…. 保存着参数,注意这些参数都是被调用者的输入参数

因此结合博主的代码2,完善代码(博主有部分代码笔者觉得有误,欢迎讨论):

monitor.c

lab手册上写着一些GCC优化给read_ebp()带来的问题,就是说read_ebp()有可能在执行mon_backtrace的 `push %ebp; mov %esp, %ebp` 之前执行,如果是这样的话read_ebp()读取的其实是调用mon_backtrace的函数的栈帧的基地址,会出现mon_backtrace没被跟踪的情况。 另外要注意read_ebp的限定词 `static inline uint32_t read_ebp()`

,尤其是inline,说明并不会单独为read_ebp产生新的栈帧。所以它读取的就是调用它的函数的栈帧的基地址,而不是它自身的栈帧,它根本没有栈帧

int
mon_backtrace(int argc, char **argv, strcut Trapframe *tf)
{
  // 博主给出的是三行代码,笔者认为是错的,它实现的非inline的情况下,read_ebp具有独立的栈帧,通过read_ebp的ebp指向的地址保存着mon_backtrace的ebp的值来获取mon_backtrace的ebp
  int *ebp = (int *)read_ebp();  // 使用gdb查看指令,得知读取发生在push %ebp; mov %esp, %ebp。因此读取的mon_backtrace的栈帧的基地址ebp
  
  cprintf("Stack backtrace:\n");
	//If only we haven't pass the stack frame of i386_init
	while((int)ebp != 0x0) {
		cprintf("  ebp %08x", (int)ebp);
		cprintf("  eip %08x", *(ebp+1));
		cprintf("  args");
		cprintf(" %08x", *(ebp+2));
		cprintf(" %08x", *(ebp+3));
		cprintf(" %08x", *(ebp+4));
		cprintf(" %08x", *(ebp+5));
		cprintf(" %08x\n", *(ebp+6));
		ebp = (int *)(*ebp);  // 将ebp指向的地址内的调用者的 ebp值 赋予新的ebp
	}
  return 0;
}

最终运行后得到的结果为:

01

结合Exercise10的表格内的内容,可以验证:

  1. 0xf010ff18 是 mon_backtrace 的 ebp
  2. 0xf010ff38 是 test_backtrace(0) 的 ebp

参考资料

  1. cnblog-fatsheep9146
  2. github-fatsheep9146