从汇编角度分析函数调用

函数调用约定和函数调用的汇编级别实现原理对于日常的开发和调试工作都是及其重要的,本文将会对这两个方面的内容进行介绍。

一. 函数调用约定

函数调用约定主要影响两个方面的东西。第一个是函数参数的入栈顺序,第二个是函数堆栈的清除。最常用的就是 __cdecl,__stdcall,__fastcall 和 __thiscall

1. __cdecl

C语言的默认调用约定,又称C调用约定,是C/C++中默认的调用方式。参数由右向左入栈,函数调用者负责清理堆栈(由于这点的存在,所以 __cdecl 调用约定支持可变参数,因为可变参数传递时只有调用者才知道传了多少个参数),函数的返回值存放于EAX寄存器

2. __stdcall

参数由右向左入栈,函数本身负责清理堆栈,函数返回值存放于EAX寄存器中,WindowsAPI的默认调用方式就是它

3. __fastcall

其特点是快,函数的前两个参数由 ECX 和 EDX 传递,后续参数从右往左入栈函数本身负责清理堆栈,函数的返回值存放于EAX寄存器中

4. __pascal  与 __stdcall 一样,VC中已经废弃了 __pascal 改用 __stdcall

5. __thiscall

C++特有的调用约定,其并不像前面那些是关键字可在程序中使用,其有以下几个特征:

第一,this指针的值由 ECX 寄存器传递,函数自身清理堆栈。 如果是可变参数,调用者清理堆栈,this指针在所有参数入栈后再入栈

 第二,参数由右向左的顺序入栈

第三,返回值在EAX寄存器中

二. 函数调用过程分析 

2.1 函数调用步骤

1. 参数入栈: 根据函数调用约定将参数按照一定顺序依次压入系统栈中,最常用的就是从 右 往 左 压入栈中。这里可以思考下为什么要从右往左? 因为从右往左压入栈的话,被调函数里面取参数使用的时候,就是从左往右的顺序,一般而言 [EBP + 8] 就是第一个参数,[EBP+0xC]是第二个参数,比较方便

2. 返回地址入栈:将调用指令(CALL指令)的下一条指令的地址压入栈中。

3. 代码区跳转:处理器从当前代码区跳转到被调用函数的入口处

4. 栈帧调整: 第一步,保存当前栈帧状态值,以备后面恢复栈帧时使用(PUSH EBP, 这里的EBP实际上指的是其调用函数的栈帧的栈底位置)

第二步,将当前栈帧切换到新栈帧,(MOV EBP, ESP,  更新本函数栈帧的栈底位置)

第三步,给新栈帧分配空间(SUB ESP,8H, 把ESP减去所需空间(局部变量所占空间)的大小,更新栈顶)

上述步骤可用下述指令描述:

; 调用函数中指令
; 调用前的一堆指令
PUSH 参数3    ; (假设将要调用的函数有3个参数,从右往左入栈)
PUSH 参数2 
PUSH 参数1 
CALL  函数地址        ; CALL指令依次完成了两项工作
                        ; 第一项,将当前指令的下一条指令地址(返回地址)入栈
                        ; 第二项,跳转到所调用函数的入口处
                        ; 假设下一条指令地址是 0x4000 1234,被调用函数地址
                        ;                       为 0x40002345
                        ; 则相当于有,PUSH 0x40001234  ; JMP 0x40002345
; 被调函数中指令
PUSH EBP         ; 保存旧栈帧的底部
MOV  EBP, ESP  ; 设置新栈帧的底部  (上述两条指令顺利完成了栈帧切换)
SUB  ESP, XXX  ; 设置新栈帧的顶部  (为新栈帧开辟了空间)                                                                                   

2.2 函数返回步骤

1. 保存返回值: 将函数的返回值保存在EAX寄存器中

2. 弹出当前栈帧,恢复上一个栈帧:  第一步, 回收当前栈帧的空间(MOV ESP, EBP, 即将栈顶指针恢复到本栈帧的帧底位置)

第二步,将当前栈帧底部保存的前栈帧的EBP值弹入EBP寄存器,恢复出上一个栈帧

第三步,将函数返回地址弹给EIP寄存器

3. 跳转: 根据上面获取到的返回地址,跳转到母函数中继续执行

ADD ESP, XXX    ; 回收当前栈帧空间,或者直接( MOV ESP, EBP )
POP  EBP           ; 将上一个栈帧底部位置恢复到EBP
RETN               ;  1. 弹出返回地址
                   ;  2. 跳转到返回地址

 




发表评论

电子邮件地址不会被公开。 必填项已用*标注