函数调用和汇编


1 函数调用和汇编

1.1 函数的调用约定

  • C函数的默认调用约定是__cdecl
  • C++全局函数和静态成员函数的默认调用约定是__stdcall
  • 类的成员函数的调用约定是__thiscall

函数生成符号不同
函数参数入栈顺序不同

void __stdcall swap(int a,int b)
{
  int tmp = a;
  a = b;
  b = tmp;
}

1.2 堆栈平衡

  • 因为函数调用过程中,参数需要压栈,所以在函数调用结束后,用于函数调用的压栈参数也需要退栈。
调用约定堆栈平衡方式
__stdcall函数自己平衡
__cdecl调用者负责平衡
__thiscall调用者负责平衡
__fastcall调用者负责平衡
__naked编译器不负责平衡,由编写者自己负责

1.3 汇编码

1.3.1 寄存器

  • 寄存器ebp(base pointer ):帧指针或基址指针,栈底指针
  • 寄存器esp(stack pointer):栈指针,栈顶指针

1.3.2 指令

  • lea eax,dword ptr [b]:将b地址的偏移量保存到寄存器eax中
  • mov ebp,esp:将esp指向内存中的数据保存到目的寄存器ebp中
  • call swap():将程序跳转到目标函数处执行。
    首先将call指令的下一条指令的地址(当前指令地址寄存器EIP中的地址+偏移量4个字节)压栈;
    然后执行jump指令跳转到调用函数的入口,即函数的开始地址。
  • ret:将栈顶保存的地址弹出到EIP中
#include<iostream>
using namespace std;

int swap(int a,int b)
{
  int tmp = a;
  a = b;
  b = tmp;
  
  return a;
}

int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);

	return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1.4 栈帧

  • 存储在用户栈上的,ebp栈底指针和esp栈顶指针之间的所有函数调用涉及的相关信息的记录单元;也叫做,过程活动记录。

1.4.1 开辟栈帧

在这里插入图片描述

1.4.2 栈帧回退

在这里插入图片描述

2 汇编解析

int main()
//当前栈帧:ESP = 00B5FD54 EBP = 00B5FD70
002D1920  push        ebp//ebp压栈,保存栈顶指针
//ESP = 00B5FD50 EBP = 00B5FD70
002D1921  mov         ebp,esp//将esp栈顶指针指向的地址保存到ebp中
//ESP = 00B5FD50 EBP = 00B5FD50
002D1923  sub         esp,0D8h//将esp的地址 - D8,预留字节给函数临时变量
//ESP = 00B5FC78 EBP = 00B5FD50 
002D1929  push        ebx//基址(base)寄存器,压栈
//ESP = 00B5FC74 EBP = 00B5FD50
002D192A  push        esi//源索引寄存器,压栈
//ESP = 00B5FC70 EBP = 00B5FD50  
002D192B  push        edi//目标索引寄存器,压栈
//ESP = 00B5FC6C EBP = 00B5FD50
002D192C  lea         edi,[ebp-18h]//将 ebp-18 的地址保存到edi中
002D192F  mov         ecx,6//计数器,用于重复rep指令
002D1934  mov         eax,0CCCCCCCCh//给eax寄存器赋予随机值
002D1939  rep stos    dword ptr es:[edi]//从edi的地址开始向上每4个字节,重复执行上述指令
002D193B  mov         ecx,offset _12508171_源文件名@cpp (02DC072h)//文件的地址
002D1940  call        @__CheckForDebuggerJustMyCode@4 (02D1339h)//检查错误

	int a = 10;
002D1945  mov         dword ptr [a],0Ah//将10写入4个字节到a的地址中
	int b = 20;
002D194C  mov         dword ptr [b],14h//将20写入4个字节到b的地址中

	swap(a, b);
002D1953  mov         eax,dword ptr [b]//从b的地址取4个字节到eax寄存器中
002D1956  push        eax//eax寄存器,压栈
//ESP = 00B5FC68 EBP = 00B5FD50

002D1957  mov         ecx,dword ptr [a]//从a的地址取4个字节到eax寄存器中
002D195A  push        ecx//ecx寄存器,压栈
//ESP = 00B5FC64 EBP = 00B5FD50

002D195B  call        swap (02D13E3h)//进入swap函数
//push EIP//将当前指令的下一条指令的地址压栈,(EIP = 002D195B + 4)
//ESP = 00B5FC60 EBP = 00B5FD50


---------------------------------------------------------------------------

//当前栈帧:ESP = 00B5FC60 EBP = 00B5FD50
//EIP = 002D13E3
002D13E3  jmp         swap (02D18D0h)//跳转到swap函数的入口,即开始地址
//EIP = 002D18D0

//进入swap函数体
int swap(int a, int b)
002D18D0  push        ebp//ebp压栈,保存栈顶指针
//ESP = 00B5FC5C EBP = 00B5FD50  
002D18D1  mov         ebp,esp//将esp栈顶指针指向的地址保存到ebp中
//ESP = 00B5FC5C EBP = 00B5FC5C  
002D18D3  sub         esp,0CCh//将esp的地址 - CC,预留字节给函数临时变量
//ESP = 00B5FB90 EBP = 00B5FC5C EFL = 00000206(标志寄存器),执行地址运算指令时,标记位变化  
002D18D9  push        ebx//ebx寄存器压栈
//ESP = 00B5FB8C EBP = 00B5FC5C  
002D18DA  push        esi//esi寄存器压栈
//ESP = 00B5FB88 EBP = 00B5FC5C  
002D18DB  push        edi//edi寄存器压栈
//ESP = 00B5FB84 EBP = 00B5FC5C  
002D18DC  lea         edi,[ebp-0Ch]
002D18DF  mov         ecx,3
002D18E4  mov         eax,0CCCCCCCCh  
002D18E9  rep stos    dword ptr es:[edi]  
002D18EB  mov         ecx,offset _12508171_源文件名@cpp (02DC072h)  
002D18F0  call        @__CheckForDebuggerJustMyCode@4 (02D1339h)
 
	int tmp = a;
002D18F5  mov         eax,dword ptr [a]//从a的地址取4个字节到eax寄存器中
002D18F8  mov         dword ptr [tmp],eax//将eax寄存器中的数据写入4个字节到tmp的地址中  
	a = b;
002D18FB  mov         eax,dword ptr [b]//从b的地址取4个字节到eax寄存器中
002D18FE  mov         dword ptr [a],eax//将eax寄存器中的数据写入4个字节到a的地址中
	b = tmp;
002D1901  mov         eax,dword ptr [tmp]//从tmp的地址取4个字节到eax寄存器中
002D1904  mov         dword ptr [b],eax//将eax寄存器中的数据写入4个字节到b的地址中

	return a;
002D1907  mov         eax,dword ptr [a]//从a的地址取4个字节到eax寄存器中
002D190A  pop         edi//edi寄存器出栈
//ESP = 00B5FB88 EBP = 00B5FC5C  
002D190B  pop         esi//esi寄存器出栈
//ESP = 00B5FB8C EBP = 00B5FC5C  
002D190C  pop         ebx//ebx寄存器出栈
//ESP = 00B5FB90 EBP = 00B5FC5C  
002D190D  add         esp,0CCh//将esp的地址 + CC,回退释放临时变量的内存空间,平衡堆栈
//ESP = 00B5FC5C EBP = 00B5FC5C
002D1913  cmp         ebp,esp//检查堆栈平衡
002D1915  call        __RTC_CheckEsp (02D1253h)//判断esp和ebp是否相等
002D191A  mov         esp,ebp  
002D191C  pop         ebp//ebp出栈,ebp返回到进入函数之前的地址
//ESP = 00B5FC60 EBP = 00B5FD50  
002D191D  ret//复位,EIP出栈,栈帧回退到main函数
//pop EIP
//ESP = 00B5FC64 EBP = 00B5FD50

---------------------------------------------------------------------------


//退出函数
//当前栈帧:ESP = 00B5FC64 EBP = 00B5FD50
//EIP = 002D1960

002D1960  add         esp,8//esp栈顶指针+8,回退释放两个参数占用的内存空间
//ESP = 00B5FC6C EBP = 00B5FD50
	return 0;
002D1963  xor         eax,eax//eax异或,寄存器清零
002D1965  pop         edi//edi寄存器出栈
//ESP = 00B5FC70 EBP = 00B5FD50  
002D1966  pop         esi//esi寄存器出栈
//ESP = 00B5FC74 EBP = 00B5FD50  
002D1967  pop         ebx//ebx寄存器出栈
//ESP = 00B5FC78 EBP = 00B5FD50  
002D1968  add         esp,0D8h//将esp的地址 + D8,回退释放临时变量的内存空间,平衡堆栈
//ESP = 00B5FD50 EBP = 00B5FD50  
002D196E  cmp         ebp,esp//检查堆栈平衡
002D1970  call        __RTC_CheckEsp (02D1253h) 
002D1975  mov         esp,ebp  
002D1977  pop         ebp//ebp出栈,ebp返回到main函数之前的地址
//ESP = 00B5FD54 EBP = 00B5FD70  
002D1978  ret//复位,栈帧回退
//调用main函数,mainCRTStartup()

2.1 函数调用流程

进入main函数
push ebp保存栈底指针
push 寄存器保存现场
push 参数压参
call 函数调用函数
进入被调用函数
push ebp保存栈底指针
push 寄存器保存现场
执行函数运算
pop 寄存器恢复现场
add esp释放临时变量占用空间,平衡堆栈
cmp ebp,esp检查堆栈平衡
pop ebp回退栈底指针
ret复位,栈帧回退
退出被调用函数
add esp释放参数占用空间
xor eax寄存器清零
pop 寄存器恢复现场
add esp释放临时变量占用空间,平衡堆栈
cmp ebp,esp检查堆栈平衡
pop ebp回退栈底指针
ret复位,栈帧回退