实验要求
编写一个C/C++程序例子,至少有三层的函数调用存在。通过GDB对编写的软件进行调试,需在第三层函数内设置断点使得程序暂停,然后进行如下的分析:
要求1
如何利用当前的栈基址和调试信息得到函数的调用关系,实现类似bt的显示结果,需进行解释说明;
要求2
在栈帧进行切换后,例如切换到第二层的函数上,分析如何根据当前的栈基址和调试信息进行获取变量的值,实现frame切换后利用print显示变量的效果,并说明其正确性。
实验原理
函数调用栈布局如图1所示:
函数调用的过程为主调函数首先将参数(从右向左)依次入栈,然后将指令指针EIP入栈以保存函数的返回地址(也是下一条待执行指令的地址)。然后被调函数将主调函数的栈帧基地址指针EBP入栈,将主调函数的栈顶指针ESP值赋给被调函数的EBP,然后改变ESP的值为被调函数的局部变量预留空间,之后将局部变量压栈。函数调用结束之后,将EBP指针的值赋给ESP从而释放局部变量,然后将已压栈的主调函数栈帧基址弹出到EBP,弹出返回地址到EIP。
从上面的分析中可以发现,函数调用过程中,被调用函数EBP指针所指向的内存里存储着主调函数的栈帧基地址,每层函数调用可通过当前EBP的值向栈底方向偏移(即EBP+4)得到返回地址,所以可以通过逐层递推找到最顶层的主调函数。根据EBP的值进行偏移可找到参数以及局部变量的地址,然后可以查找对应内存地址处所保存的数据。
实验过程
编写程序调试运行
编写一个C程序例子main.c,见附录,然后使用gcc -g -o main.exe main.c产生带有调试信息的可执行文件,然后使用命令gdb main.exe进行调试,首先使用命令b 18在第3层函数内设置一个断点,然后使用命令r运行程序,程序会在设置的断点处暂停,如图2所示,当前的函数调用栈是在max2函数那一层。
利用当前的栈基址和调试信息得到函数的调用关系,实现类似bt的显示结果。
使用命令info register ebp查看当前栈帧基地址,如图3所示,为0x60fec8,
使用命令x/2x 0x60fec8查看从内存地址0x60fec8开始8个字节内的内存数据,结果如图3所示。其中,前四个字节存放的的是主调函数的栈帧基地址值,为0x0060fee8,后四个字节存放的是返回地址,为0x004013c2。
使用命令查看指令地址0x004013c2所对应的在源代码中的位置,如图4所示,可以看到所对应的位置是在函数max3内。所 以可知:max3调用了max2函数。
然后利用上一步得到的栈帧基地址0x0060fee8继续回溯,结果如图5所示。可知是main函数那一层的栈帧基地址为0x0060ff18,main函数调用了max3函数。
实现frame切换后利用print显示变量的效果
如图7所示,使用命令f(1)切换到max3函数那一层。
附录
main.c程序为
#include<stdio.h>
int max2(int a,int b);
int max3(int a,int b,int c);
int g_1=2;
int g_2;
int main()
{
int a=5;
int b=7;
int c=6;
int result;
result=max3(a,b,c);
printf("%d\n",result);
return 0;
}
int max2(int a,int b)
{
if(a>=b)
{
return a;
}
else
{
return b;
}
}
int max3(int a,int b,int c)
{
int d;
d=max2(a,b);
return max2(c,d);
}