深入理解C语言----函数调用过程浅析
2013-09-22 23:00
471 查看
读了韩宏老师的《老码识途》第一章,忍不住自己动手试一下,利用反汇编来查看函数调用过程
调用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里即可;两个整形,就再用一个寄存器写入;上述情况是针对返回值占内存空间较大的情况分析的。
#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里即可;两个整形,就再用一个寄存器写入;上述情况是针对返回值占内存空间较大的情况分析的。
相关文章推荐
- 深入理解C语言的函数调用过程
- 深入理解C语言的函数调用过程
- 深入理解C语言的函数调用过程
- 深入理解C语言的函数调用过程
- 深入理解C语言的函数调用过程
- 深入理解C语言的函数调用过程
- 深入理解C语言的函数调用过程 【转】
- 深入理解C语言的函数调用过程
- 深入理解计算机系统(3.7)------过程(函数的调用原理)
- 深入理解计算机系统(3.7)------过程(函数的调用原理)
- 深入理解函数的调用过程——栈帧
- 深入理解递归函数的调用过程
- 基于C语言sprintf函数的深入理解
- [李景山php] 深入理解PHP内核[读书笔记]--第四章:函数的实现 --函数的调用和执行
- 浅析c语言的函数调用
- 深入汇编语言来理解C语言中的传值和传址调用
- 深入理解C语言中的指向函数的指针!
- 深入理解递归函数的调用过程
- 理解函数调用实现过程 栈结构 栈过程