GreyZhang/g_unix: some basic learning about unix operating system. (github.com)
我不清楚MIT的学霸们学习的速度如何,看了一下他们的这个课程我的确是花费了不少时间的。中间,关于计算机本身的很多基础知识需要补充。在此之前,看了一些PC汇编代码编写的书籍也整了一份x86汇编与调用堆栈的学习笔记,主要也是为了服务于这一次的编码尝试的。
这个是课程中的要求,要实现的功能就是把函数调用产生的堆栈信息全都输出出来。前面的实验测试能够知道,在现在的环境中调用过程中会涉及到5个参数空间的处理。加上之前《1618_x86汇编以及函数调用堆栈》笔记中了解到的过程以及存储排布,这个功能实现就比较容易了。
这是我的一个实现方式,具体的代码会附加到笔记最后。
如何去理解并设计出来上面的代码呢?这得结合上面这一张图来看,这是之前的笔记中整理过的内容。从这里能够看得到存储的排布信息,而参数的数目在这里不是2个,应该是5个。局部量的空间也在这几个空间范围内,也就是5个uint32的空间内。再加上这个芯片的架构是小端,由此根据堆栈空间就可以不断获取出来堆栈空间的信息。
堆栈最下面是ebp以及eip,继续向上需要输出5个参数信息。那么如何获取下一块对战区域呢?当前ebp中存储的其实是上一次ebp的数值存储地址。由此,可以获取到下一块堆栈空间信息。理解起来比较绕,其中的技巧在于寄存器的引用访问。这种引用访问类似于地址,但是却没有地址而是通过名字来访问。
有了上面的信息,便可以设计初步的代码实现了。
这是新的系统运行的效果。
这是利用测试脚本环境进行测试的结果,看得出来这一次设计的信息是可以测试通过的。
最后这里补充了一段总结,其实是对堆栈的结构进行了一下说明。最初看这段的时候其实是不理解的,当整理完《1618_x86汇编以及函数调用堆栈》这一份笔记之后再看这一段感觉一切都清晰了。
相关代码:
int mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
// Your code here.
unsigned int *ebp = ((unsigned int *)read_ebp());
cprintf("Stack backtrace:\n");
while (ebp)
{
cprintf("ebp %08x ", ebp);
cprintf("eip %08x", ebp[1]);
cprintf(" args");
/* 当前环境中保存了5组参数 */
for (int i = 0; i < 5; i++)
{
cprintf(" %08x", ebp[i + 2]);
}
cprintf("\n");
/* ebp所指向的位置是栈顶,而栈顶的这个地址的存储存储的是老的ebp指向地址 */
ebp = (unsigned int *)(*ebp);
}
return 0;
}