您的位置:首页 > 其它

从汇编的角度分析函数调用过程(1)

2018-02-12 11:08 519 查看

一. 函数参数传递形式

函数的参数传递有2种方式:
堆栈方式
寄存器方式


如果是堆栈方式传递的,就需要定义函数参数在堆栈中的传递顺序,并约定函数被调用之后,由谁来平衡堆栈;

如果是寄存器方式传递的,就需要确定参数存放在哪个寄存器中。

每一种方式都有其优缺点,而且与使用的编程语言有关系,不存在哪种方式好与坏。

我们在开发中经常遇到
调用约定类型
,如
__cdecl
stdcall
PASCAL
fastcall
。这些调用约定类型就用来指定函数参数的传递方式的。上面几种约定类型,除了
fastcall
是使用寄存器方式传递参数外,其他的都是使用堆栈传递参数的。

Visual Studio中的C++工程,可以
C++
–>
高级
–>
调用约定
中进行调用约定的设置:



二. 使用堆栈方式传递函数参数

堆栈是一种“后进先出”的数据结构,
ESP
寄存器始终指向栈顶。栈中数据地址从底部到顶部依次减小,也就是说,栈底对应高地址,栈顶对应低地址。

调用函数时,调用者依次把参数压栈,然后调用函数,函数被调用之后,在堆栈中取得参数数据。函数调用结束以后,堆栈需要恢复到函数调用之前的样子,而到底是由调用者来恢复还是由函数自身来恢复,根据不同的调用约定类型采用不同的方式。

约定类型__cdeclstdcallPASCALfastcall
参数传递顺序从右到左从右到左从左到右使用寄存器
堆栈平衡者调用者函数自身函数自身函数自身
__cdcel
是C/C++/MFC程序默认的调用约定。

stdcall
是绝大多数Win32 API函数的约定方式,也有少部分使用
__cdcel
约定方式(如wsprintf等)。

在Windows C/C++开发中常用的就是
__cdecl
stdcall
这2种调用约定。

按照不同的
调用约定
来调用函数
int add(int a, int b)
。从调用者的视角来看,其汇编代码分别表示如下:

__cdecl

push b     ;参数按从右到左传递
push a
call add
add esp, 8 ;调用者在函数外部平衡堆栈


stdcall

push b     ;参数按从右到左传递
push a
call add   ;函数自己内部平衡堆栈,调用者不需要平衡堆栈


在函数调用过程中,参数入栈的过程如图:



上图中,
EBP
函数返回地址ret
都是32位地址。因为函数调用完之后会将
EBP
恢复为暂存在堆栈中的原
EBP
值,所以从调用者角度来看,在函数的一次调用过程中
EBP
是不会变化的。

我们可以在函数中通过新的
EBP
获取函数各个参数的值:

参数a = EBP + 0x8
参数b = EBP + 0xC
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: