<深入浅出>根据函数调用过程谈栈回溯原理
通过分析函数调用过程的堆栈变化,可以看出在被调函数的EBP寄存器地址存放的是调用函数的EBP寄存器地址,EBP地址+4存放的是函数调用完成后的下一条指令存放地址,该指令的前一条指令则是调用函数的指令。说起来有点拗口,接下来代码分析一下:
分析使用的源码如下:
int _cdecl FunA(int a, int b) { int d; d = a + b; return d; } int _cdecl FunB(int a, int b) { return FunA(a, b); } int _cdecl FunC(int a, int b) { return FunB(a, b); } int _cdecl main(int argc, char* argv[]) { int r; r = FunC(2, 3); return 0; }
在函数FunA内的任意位置添加断点,然后执行代码,则会在该处停住,通过VS的cmd窗口分析步骤如下:
1、 查看寄存器值,得出EBP=0x002FF734
2、 根据该EBP,找到FuncA返回后的指令执行地址0x0005142B
3、 因为CALL函数执行的机器码=call机器码(1个字节)+函数地址(4个字节),得出call funcA这条指令的执行地址为0x00051426(0x0005142B-5)
4、 得出指令机器码为e8 6b fc ff ff
5、 机器码算出调用指令地址 0Xfffffc6B+5(五个字节)+0x00051426(call指令地址)=0x00051096
6、 查看0x00051096地址得出机器码 e9 25 0300 00,该机器码显示为jmp指令
7、 再通过计算0x000325+5+0x0005196=0x000513c0最终得出调用函数的地址。在VS2008窗口中分析命令如下:
通过实际查看FunA的函数入口位置如下所示:
通过以上分析可以得出FunA函数的调用地址,但是整个过程是没办法知道函数的名称的,知道函数名称需要使用.pdb(symbol文件:在编译过程中生成的符号文件)。这边就不分析了。
所以通过EBP加函数返回的指令地址可以一步一步的回溯整个过程的函数调用关系,也就是所谓的栈回溯原理了。
好吧,原理归原理,我们还是乖乖的命令窗口敲下>kb命令看看整个的函数调用过程吧。可以看出到后面VS也没法知道整个系统的符号文件,只能给出一个地址。
至此栈回溯原理分析结束,若有问题,请帮忙指出,不胜感激。