首先,我们来了解一些简单的的汇编知识:
1:简单的汇编指令
mov 移值 mov dword ptr[ ebp - 4 ],14h
lea 移地址 lea eax , [ ebp - 4 ]
push 压栈 push 10
pop 出栈 pop eax
add 累加 add eax , 4
sub 累减 sub eax , 4
call ——> (a) 压入下一行指令的地址 (b)jump跳转到被调用方函数
2:寄存器
eax、ebx、ecx、edx
ebp 栈底指针寄存器
esp 栈顶指针寄存器
pc 下一行指针寄存器
有了简单的了解之后我们可以利用汇编语言了解函数堆栈调用的过程:
#include<stdio.h>
int myadd(int a,int b)
{
int c = a+b;
return c;
}
int main()
{
int a = 10;
int b = 20;
int c = myadd(a,b);
return 0;
}
一、形参是怎样开辟的?
int main()
{
00361410 push ebp
00361411 mov ebp,esp
00361413 sub esp,0E4h
00361419 push ebx
0036141A push esi
0036141B push edi
0036141C lea edi,[ebp-0E4h]
00361422 mov ecx,39h
00361427 mov eax,0CCCCCCCCh
0036142C rep stos dword ptr es:[edi]
int a = 10;
0036142E mov dword ptr [a],0Ah //给a开辟空间并赋值为10
int b = 20;
00361435 mov dword ptr [b],14h //给b开辟空间并赋值为20
int c = myadd(a,b);
0036143C mov eax,dword ptr [b] //将b的值赋给寄存器eax
0036143F push eax
00361440 mov ecx,dword ptr [a] //将a的值赋给寄存器ebx
00361443 push ecx
00361444 call myadd (036118Bh) //调用call指令做两件事
//1、压入下一行指令地址
//2、jump跳转到被调用方函数(myadd函数)
int c = myadd(a,b);
00361449 add esp,8 //esp栈顶指针移动
0036144C mov dword ptr [c],eax //eax的值赋给变量c
return 0;
0036144F xor eax,eax
}
首先我们来看主函数的反汇编代码:
int myadd(int a,int b)
{
003613D0 push ebp
003613D1 mov ebp,esp
003613D3 sub esp,0CCh
003613D9 push ebx
003613DA push esi
003613DB push edi
003613DC lea edi,[ebp-0CCh]
003613E2 mov ecx,33h
003613E7 mov eax,0CCCCCCCCh
003613EC rep stos dword ptr es:[edi]
int c = a+b;
003613EE mov eax,dword ptr [a] //将a的值给eax寄存器
003613F1 add eax,dword ptr [b] //将eax的值+= b
003613F4 mov dword ptr [c],eax //将eax的值赋给变量c
return c;
003613F7 mov eax,dword ptr [c] //将c的值赋给eax
}
一、形参是怎样开辟的?
形参由调用方开辟,调用方清理
二、形参的入栈顺序是怎样的?
从右向左,依次入栈(通常支持可变参数的语言都是由右向左依次入栈)
三、函数返回值是如何带出来的?
通常由寄存器带回
0 ~ 4个字节 寄存器带回
5 ~ 8个字节 两个寄存器带回
大于8个字节 临时量带回
四、函数调用结束后怎样退回到调用方?
压栈过程中压入了调用方的栈底指针(寄存器的位置)
五、函数调用结束为什么会沿着调用点继续执行?
call指令压入了下一行指令
六、清栈
被调用方开辟的空间;让ebp回到调用方;清理调用方开辟的形参