函数调用时的栈操作

本文详细解析了C++中函数调用的汇编实现,包括main函数调用add函数的过程,从参数压栈、栈帧建立到返回值处理,展示了函数调用的完整生命周期。通过对汇编代码的分析,理解了函数调用的底层机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

通过分析函数调用的汇编,解析一下时如何进行函数调用

示例代码

#include <iostream>
using namespace std;

int add(int a, int b)
{
	int d = 0;
	d = a + b;

	return d;
}

int main(int argc, char* argv[])
{
	int a = 1;
	int b = 2;

	int c = 0;
	c = add(a, b);
	return 0;
}

main函数的主要部分汇编代码

int main(int argc, char* argv[])
{
00642040  push        ebp  
00642041  mov         ebp,esp  
00642043  sub         esp,0E4h  
00642049  push        ebx  
0064204A  push        esi  
0064204B  push        edi  
0064204C  lea         edi,[ebp-24h]  
0064204F  mov         ecx,9  
00642054  mov         eax,0CCCCCCCCh  
00642059  rep stos    dword ptr es:[edi]  
0064205B  mov         ecx,offset _A7BC9FD1_source@cpp (064F066h)  
00642060  call        @__CheckForDebuggerJustMyCode@4 (0641393h)  
	int a = 1;
00642065  mov         dword ptr [a],1  
	int b = 2;
0064206C  mov         dword ptr [b],2  

	int c = 0;
00642073  mov         dword ptr [c],0  
	c = add(a, b);
0064207A  mov         eax,dword ptr [b]  
0064207D  push        eax  
0064207E  mov         ecx,dword ptr [a]  
00642081  push        ecx  
00642082  call        add (0641451h)  
00642087  add         esp,8  
0064208A  mov         dword ptr [c],eax  
	return 0;
0064208D  xor         eax,eax  
}
0064208F  pop         edi  
00642090  pop         esi  
00642091  pop         ebx  
00642092  add         esp,0E4h  
00642098  cmp         ebp,esp  
0064209A  call        __RTC_CheckEsp (064129Eh)  
0064209F  mov         esp,ebp  
006420A1  pop         ebp  
006420A2  ret

在main函数入口处打个断点,看一下当前各个寄存器的状态

执行以下语句后:

00642040  push        ebp              //将调用函数的栈底压栈
00642041  mov         ebp,esp          //将栈底指针指向调用函数的栈底
00642043  sub         esp,0E4h         //开辟0xE4的地址空间给main

 栈地址变成这样:

 执行这几条语句后,EBP指向原先的栈顶,ESP重新开辟一块地址0xE4H并指向新的栈顶。

0064204C  lea         edi,[ebp-24h]  
0064204F  mov         ecx,9  
00642054  mov         eax,0CCCCCCCCh  
00642059  rep stos    dword ptr es:[edi]  

上面把基址以上的0x24h用来存放临时变量,并初始化为0xCCCC。

接下来对临时变量进行初始化。

调用add函数

  

执行call 指令时,后面的0x0641451H其实时JMP指令的地址

JMP指令后的0x0641F20H时add函数的地址。

同时我们看到这个时候ESP向栈顶压入4个字节,通过下图发现,是将add函数返回后下一条指令地址(0x00642087)(EIP存储着下一条指令的地址)压栈

 

add函数的调用

int add(int a, int b)
{
push        ebp  
mov         ebp,esp  
sub         esp,0CCh  
push        ebx  
push        esi  
push        edi  
lea         edi,[ebp-0Ch]  
mov         ecx,3  
mov         eax,0CCCCCCCCh  
rep stos    dword ptr es:[edi]  
mov         ecx,offset _A7BC9FD1_source@cpp (047F066h)  
call        @__CheckForDebuggerJustMyCode@4 (0471393h)  
	int d = 0;
mov         dword ptr [d],0  
	d = a + b;
mov         eax,dword ptr [a]  
add         eax,dword ptr [b]  
mov         dword ptr [d],eax  

	return d;
mov         eax,dword ptr [d]  
}
pop         edi  
pop         esi  
pop         ebx  
add         esp,0CCh  
cmp         ebp,esp  
call        __RTC_CheckEsp (047129Eh)  
mov         esp,ebp  
pop         ebp  
ret  

 

 以下初始化d,同时可以看到这个时候取的a和b的地址正是入栈时的地址

 以上是整个加法运算过程,同时可以发现,执行return时,会将d的值赋给eax

以下执行返回操作

返回部分

 执行ret后,调用栈又回到main函数:

00642087  add         esp,8                  //将参数出栈
0064208A  mov         dword ptr [c],eax      //将返回值赋值给c

总结:

调用前

1)将参数从右到左将参数压入栈

2)将函数返回后的下一个指令地址入栈

3)先跳到JMP指令,根据JMP指令跳转到函数地址

调用中

1)将调用函数的基址指针入栈,将当前栈顶做为新的基址,同时开辟新的地址给当前函数操作

2)初始化一片地址给临时变量

3)进行函数操作

4)返回时,将操作结果存入相应的寄存器

5)恢复基址指针

调用结束

1)将参数出栈

2)将返回寄存器的值赋值给对应的变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值