您的位置:首页 > 编程语言 > C语言/C++

深入理解C语言----函数调用过程浅析

2013-09-22 23:00 471 查看
读了韩宏老师的《老码识途》第一章,忍不住自己动手试一下,利用反汇编来查看函数调用过程

#include<stdio.h>

typedef struct
{
int i1;
int i2;
int i3
}myrd;

myrd myfunc(int a, int b, int c)
{
myrd r1;
r1.i1 = a;
r1.i2 = b;
r1.i3 = c;
return r1;
}

void main(int count, char **args)
{
myrd r;
r = myfunc(1,2,3);
}
在第22行处设置断点,调试

调用myfunc() 附近的代码如下:



红框部分为传参数入栈和执行调用

单步执行三步后,根据ESP位置查看栈中数据如下:



可以看到三个参数被从右往左依次压入栈中(C语言调用惯例),栈的生长方向是从高地址向低地址(X86)

eax此时是用来存储保存函数返回值的内存区的起始地址

此时EIP,ESP,EBP的值如下;



epb+FFFFF1Ch即为ebp-E4(负数用反码表示)=001CFB4C ,此时以它开始的连续4个字节均为cc,将它赋值给eax后,把eax压入栈中



如图,矩形框所示位置即为[epb+FFFFF1Ch],圆框所示即为eax的值,也是矩形框的起始地址。

接下来执行call指令,调用函数

先看其机器码:E8 25 FD FF FF,E8是call的代码,后面4个字节是地址的偏移量(补码表示)。

故要跳转到的指令的地址 0x012D1195 = 0x012D146B + 5 (指令所占字节)- 0X02DB (0xFFFFFD25的补码)

PS:call语句计算偏移量不是从自己语句的首部计算,而是从自己的尾部,或者说是它的下一条语句的起始地址作为基准(jmp也是如此)

执行后跳转到如图:


与此同时,函数的返回地址也被压入栈中,esp的值也相应减4



继续单步执行,进入函数:



其中,前两步

012D13A0 55               push        ebp  

012D13A1 8B EC            mov         ebp,esp 

是将ebp的值压入栈中备份,然后用当前esp的值刷新ebp,从而便于函数利用ebp通过栈上的地址偏移获取参数(如1,2,3)

接下来可以清晰地看到,通过ebp的定位,局部变量的空间在栈上被分配(越靠前定义,地址越低),同时实参得以传入



此时栈上的结构如图:



注意到局部变量定义的区域和存放ebp的内存并不相邻,这是VS2008为了应对溢出攻击的措施

Tips:监视窗口查看变量

1、要看变量值的十六进制表示直接如:(void*)a 即可

2、查看地址内的值,先地址强制转换为指针的指针,如 *(void**)(ebp + 4)

然后,如下图,我们看到,在第一行,存放返回值内存区的首地址被赋值给eax,接着通过eax的定位依次把,i1,i2,i3拷贝到该临时内存



最后恢复esp和ebp,执行ret,函数返回。

可见,直到ret执行前,eax的值都没有发生变化,故主调方可通过eax寄存器获取函数返回值。

PS:返回值传递机制随函数返回值的类型变化,比如,返回值大小在4个字节以内,一般是在函数里写入到eax里即可;两个整形,就再用一个寄存器写入;上述情况是针对返回值占内存空间较大的情况分析的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  反汇编