函数的调用过程
函数调用过程学习 笔记 主要是自己根据调用过程看汇编代码理解的知识,并不全面
栈帧介绍
在计算机的栈区大小在windows平台下位1M,在Linux平台下位8M
栈帧: 计算机把栈区分区域划分为栈帧,当函数需要调用栈空间计算机按栈帧进行分配栈空间
寄存器 函数调用的过程中需要使用的寄存器简介
esp寄存器:存放当前线程的栈顶指针 栈顶寄存器
ebp寄存器:存放当前线程的栈底指针 栈底寄存器
eip寄存器: 指向下一条指令的寄存器 指令寄存器
cal 调用指令
ret 返回指令
call:影响eip esp
ret:影响eip esp
源代码
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
int c;
c = Add(a, b);
cout << c;
return 0;
}
源代码经过编译连接后成为可执行文件,执行可执行文件(进程)就会给这个进程分配内存空间(栈,堆,数据区,代码区),在分配的时候会有一个前期函数的调用用来初始化栈区,堆区,数据区,然后在由这个函数调用主函数
1.主函数是否是入口函数?
主函数在逻辑上是入口函数,但是物理上并不是入口函数
2.主函数中的变量地址如何确定?
主函数当中变量的地址都是以ebp为基准来确定的
函数的调用过程中 主函数中的参数 由 ebp栈底指针进行操作
函数调用会什么会进行现场保护?
程序在main函数当中执行由call指令 转到 Add函数执行 在Add函数执行完毕程序需要回到调用点处继续运行 所以把call的地址 入栈进行现场保护,因为是回到后续指令执行,所以把后续指令地址 入栈进行保护
001A32A5 C7 45 F8 0A 00 00 00 mov dword ptr [a],0Ah
16: int b = 20;//主函数参数的定义
001A32AC C7 45 EC 14 00 00 00 mov dword ptr [b],14h
17: int c=0;
001A32B3 C7 45 E0 00 00 00 00 mov dword ptr [c],0
18: c = Add(a, b);
001A32BA 8B 45 EC mov eax,dword ptr [b]
001A32BD 50 push eax
//先把 b的值给寄存器 eax 然后压栈eax push 通过影响esp进行入栈
001A32BE 8B 4D F8 mov ecx,dword ptr [a]
001A32C1 51 push ecx
//把c的值给寄存器ecx 然后压栈exc
//这样就是Add函数的形参传递 从右先做传递 依次压栈
001A32C2 E8 E9 E1 FF FF call Add (01A14B0h)
//call命令影响 esp eip
001A32C7 83 C4 08 add esp,8 //被保护的值
//现场保护
//程序在main函数当中执行由call指令 转到 Add函数执行 在Add函数执行完毕程序需要回到调用点处继续运行 所以把call的地址 入栈进行现场保护,因为是回到后续指令执行,所以把后续指令地址 入栈进行保护
001A32CA 89 45 E0 mov dword ptr [c],eax
19: cout << c;
001A32CD 8B F4 mov esi,esp
001A32CF 8B 45 E0 mov eax,dword ptr [c]
001A32D2 50 push eax
001A32D3 8B 0D B0 00 1B 00 mov ecx,dword ptr [__imp_std::cout (01B00B0h)]
001A32D9 FF 15 C0 00 1B 00 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (01B00C0h)]
001A32DF 3B F4 cmp esi,esp
001A32E1 E8 EF DF FF FF call __RTC_CheckEsp (01A12D5h)
20: return 0;
001A32E6 33 C0 xor eax,eax
21: }
Add汇编代码
9: int Add(int a, int b)
10: {
004C2D40 55 push ebp
//入栈 ebp对ebp进行现场保护
004C2D41 8B EC mov ebp,esp
//把 esp 给ebp
004C2D43 81 EC C0 00 00 00 sub esp,0C0h
004C2D49 53 push ebx
004C2D4A 56 push esi
004C2D4B 57 push edi
//压栈主函数中使用的三个寄存器 需要进行保护
004C2D4C 8B FD mov edi,ebp
004C2D4E 33 C9 xor ecx,ecx
004C2D50 B8 CC CC CC CC mov eax,0CCCCCCCCh
//对 esp ebp的空间进行冲洗
004C2D55 F3 AB rep stos dword ptr es:[edi]
004C2D57 B9 E6 20 4D 00 mov ecx,offset _98E7F853_Day6_5@cpp (04D20E6h)
004C2D5C E8 8C E6 FF FF call @__CheckForDebuggerJustMyCode@4 (04C13EDh)
11: return a + b;
004C2D61 8B 45 08 mov eax,dword ptr [a]
// a的值给寄存器eax
004C2D64 03 45 0C add eax,dword ptr [b]
// b的值给寄存器eax 进行累加
12: }
004C2D67 5F pop edi
004C2D68 5E pop esi
004C2D69 5B pop ebx
//弹栈三个寄存器
004C2D6A 81 C4 C0 00 00 00 add esp,0C0h
004C2D70 3B EC cmp ebp,esp
004C2D72 E8 5E E5 FF FF call __RTC_CheckEsp (04C12D5h)
004C2D77 8B E5 mov esp,ebp
//esp恢复到 ebp现场保护的地址
004C2D79 5D pop ebp
//弹栈ebp 使得ebp继续成为栈底指针
004C2D7A C3 ret
//ret 影响esp eip寄存器
//把 ret弹出来 给给eip寄存器 这时候 弹出来的就是call(现场保护)后面的的一条指令
c 调用者完成 栈平衡
__stdcall __fastcall 回调函数约定 被调用者完成栈平衡
函数的调用过程
1.在主函数调用之前 ,主函数当中的变量 是通过ebp**(栈底指针)**作为基准值来确定的
2.当主函数调用被调用函数的时候,call会进行现场保护,把call的下一条指令入栈
3.先入栈ebp 对ebp进行现场保护
4.然后 把ebp 升到esp的位置
5.然后对esp-若干个字节 形成被调用函数的栈帧空间
6.入栈esi,edi等在主函数中使用到的寄存器
7.对esp – ebp内的若干字节空间进行冲洗 0xcccccccc进行填充
8.以ebp作为基准值 入栈被调用函数的变量
9.执行被调用函数
10.被调用函数有返回值的话,会由某一个寄存器来返回
11.pop 弹栈 esi edi等主函数需要使用的寄存器
12.esp 回到ebp的位置
13.弹栈 ebp (因为进行了现场保护) 弹栈使得ebp栈底指针回到主函数的栈底
14.ret 栈平衡操作
c的默认调用约定 调用者进行栈平衡
stdcall 回调函数调用约定 被调用者进行栈平衡