当前位置:网站首页>X86函数调用模型分析
X86函数调用模型分析
2022-08-03 19:00:00 【mingjie】
相关: 《Postgresql中的pg_memory_barrier_impl和C的volatile》《X86函数调用模型分析》
函数A调用函数B,B执行完毕后继续执行函数A,如何实现这样的调用?
直接思考可能会存在以下几步:
- A的局部变量如果在寄存器,需要保存起来。
- 这些变量保存在栈中,栈中的位置需要记录。
- 多层调用的话记录堆栈位置的信息会有多组,也都需要记录。
- A调用完B后还需要继续执行,继续执行的位置需要保存起来。
下面分析x86的具体实现。 (资料汇编)
速查:
- 对于栈帧来说:栈帧顶部用bp指针(高地址),栈帧底部(低地址)用sp指针。
- 对于堆栈来说:整体堆栈的顶部为sp指针(堆栈生长到的最低地址)。
一、内存结构
二进制程序执行时的内存结构:
- code section:保存程序执行指令的机器码。
- static section:在程序执行期间不改变的常量和静态变量。
- heap:使用malloc申请的堆内存,向内存地址升序的方向生长:grows up。
- stack:保存函数局部变量和函数调用的控制信息,向内存地址降序的方向生长:grows down。
二、寄存器
- 程序的虚拟内存空间提供了
的空间保存数据,地址从0x00000000到0xFFFFFFFF(一个十六进制为对应4个二进制位,所以是2的32次方)。
- 寄存器提供了额外的存储空间,每个寄存器可以存一个字(4字节)。
和函数调用相关的寄存器(e表示扩展的意思):
- eip:指令指针,存储当前正在执行的机器指令的地址。也叫PC(程序计数器)。
- ebp:帧指针,保存当前栈帧顶部地址(高地址)。
- esp:堆栈指针,保存当前堆栈底部地址(低地址)。
下图便于理解:
|----------------------| high address
| ... |
|-------frame----------|
| ... |
| ... |
| ... |
|-------frame----------| # current frame <----- ebp
| ... |
| ... |
| ... | <----- esp
|----------------------| low address三、x86函数调用
- 当需要调用另一个函数时,栈空间需要生长,用来保存一些局部变量 或者 寄存器信息。
- 当调用函数发生时,caller执行逻辑会跳转到callee,拿到结果后,在跳转会caller。这就需要改变下面几个寄存器的值:
- eip指令指针,需要改成指向callee的指令。
- ebp 和 esp 当前分别指向caller栈帧的顶部和底部。两个寄存器都需要更新为 指向callee的新栈帧的顶部和底部。
- 当函数返回时,需要恢复寄存器中的旧值,才可以返回caller。所以更新寄存器的值,需要将它的旧值保存在堆栈中,以便在函数返回后恢复旧值。
下面是main调用foo的执行过程:
step0
step1:参数入栈
将参数压入堆栈。 x86将参数压入堆栈来传递参数。请注意,当我们将参数压入堆栈时,esp 会递减。参数以相反的顺序压入堆栈。(上面是高地址)
step2:旧的eip入栈
旧的eip(rip)压入堆栈。跳转到子函数执行eip需要指向子函数,所以这里先保存下。
step3:修改eip指向
已经保存了 eip 的旧值,可以安全地将 eip 更改为指向被callee的指令。
step4:将旧的ebp入栈
step5:ebp向下移动指向新栈帧顶部
这就是mov %esp %ebp的含义:
step6:esp向下移动
通过sub esp(esp地址–) 来为新栈帧分配新空间。编译器会根据函数的复杂度确定 esp 应该减少多少。
- 例如,只有几个局部变量的函数不需要太多的堆栈空间,因此 esp 只会减少几个字节。
- 例如,如果一个函数将一个大数组声明为一个局部变量,那么 esp 会减少很多来适应堆栈中的数组。
step7:执行callee
现在堆栈中已经保存了函数的局部变量和跳转控制信息;由于ebp指向栈帧的顶部,所以可以用ebp+8找到第一个参数的保存位置。
step8:返回esp回到堆栈顶部
step9:恢复旧的ebp
使用esp从堆栈中pop出一个值(old ebp),把old ebp的值赋给ebp。
step10:弹出eip
继续使用esp弹出old eip的值赋给eip。
step11:从堆栈中删除参数
继续讲堆栈上的参数弹出到寄存器,然后删除esp栈顶以下的元素。栈顶以下的元素已经不在栈中,没有意义。
四、实例分析
int main(void) {
foo(1, 2);
}
void foo(int a, int b) {
int bar[4];
}
gcc -O0 t.c -o t -gmain执行过程
(gdb) disassemble /rm
Dump of assembler code for function main:
3 int main(void) {
# 由_start调入main函数
0x0000000000401122 <+0>: 55 push %rbp # 栈帧顶部入栈
0x0000000000401123 <+1>: 48 89 e5 mov %rsp,%rbp # 栈帧顶部指针rbp指向新栈帧顶部
4 foo(1, 2);
=> 0x0000000000401126 <+4>: be 02 00 00 00 mov $0x2,%esi # 参数1入寄存器传递
0x000000000040112b <+9>: bf 01 00 00 00 mov $0x1,%edi # 参数2入寄存器传递
0x0000000000401130 <+14>: e8 07 00 00 00 callq 0x40113c <foo> # push %rip 然后 jmpq
# push %rip 等价与 sub $0x8, %rsp
# mov $rip, %rsp
0x0000000000401135 <+19>: b8 00 00 00 00 mov $0x0,%eax
5 }
0x000000000040113a <+24>: 5d pop %rbp # 先恢复rbp的值
0x000000000040113b <+25>: c3 retq # 在恢复rip的值 popq %rip
End of assembler dump.foo函数
(gdb) disassemble /rm
Dump of assembler code for function foo:
7 void foo(int a, int b) {
0x000000000040113c <+0>: 55 push %rbp # 帧顶位置 入栈
0x000000000040113d <+1>: 48 89 e5 mov %rsp,%rbp # rbp帧顶指针,指向新帧顶
0x0000000000401140 <+4>: 89 7d ec mov %edi,-0x14(%rbp) # 参数2入栈(先压最后一个参数入栈)
0x0000000000401143 <+7>: 89 75 e8 mov %esi,-0x18(%rbp) # 参数1入栈
8 int bar[4];
9 }
=> 0x0000000000401146 <+10>: 90 nop
0x0000000000401147 <+11>: 5d pop %rbp # 先恢复rbp的值
0x0000000000401148 <+12>: c3 retq # 在恢复rip的值 popq %rip
End of assembler dump.边栏推荐
- 6000 字+,帮你搞懂互联网架构演变历程!
- POJ 1465 Multiple(用BFS求能组成的n的最小倍数)
- 要想成为黑客,离不开这十大基础知识
- fatal error: jni.h: No such file or directory
- 软件测试回归案例,什么是回归测试?
- 分享即时通讯开发之WebSocket:概念、原理、易错常识、动手实践
- 基于DMS的数仓智能运维服务,知多少?
- Zhong Hua, senior architect of Ali: China-Taiwan strategic thinking and architecture practice; including internal implementation manual
- 【计网】二、物理层
- WEB 渗透之CSRF
猜你喜欢

系统太多,多账号互通如何实现?

Bytes to beat three sides take offer: network + GC + + IO + redis + JVM red-black tree + data structure, to help you quickly into the giant!!!!!

go语言实现导出string字符串到文件中

Higher mathematics - chapter ten infinite series - constant term series

阿里巴巴政委体系-第六章、阿里政委体系运作

异常与智能指针

在线监控机房内的UPS电源及运行环境,解决方案来了

高数---级数

实现博客营销有哪些技巧

MySQL 啥时候用表锁,啥时候用行锁?这些你都应该知道吧
随机推荐
普通用户如何利用小红书赚钱呢?小红书的流量是真的吗?
基于DMS的数仓智能运维服务,知多少?
pytest接口自动化测试框架 | Jenkins集成初探
Web项目中简单使用线程池
APT级全面免杀与企业纵深防御体系的红蓝对抗
Higher mathematics - chapter ten infinite series - constant term series
201712-3 CCF Crontab满分题解
阿里巴巴政委体系-第六章、阿里政委体系运作
fatal error: jni.h: No such file or directory
WEB 渗透之RCE
使用range-based for循环的注意事项
Arduino实验三:继电器实验
关于2022年度深圳市技术攻关重大项目的申报通知
With the help of Kubernetes kubekey speed installation
MySQL如何一劳永逸的永久支持输入中文
if/else或switch替换为Enum
Blender script 删除所有幽灵对象
字节跳动三面拿offer:网络+IO+redis+JVM+GC+红黑树+数据结构,助你快速进大厂!!
flex布局
在线监控机房内的UPS电源及运行环境,解决方案来了