当前位置:网站首页>X86函数调用模型分析
X86函数调用模型分析
2022-08-02 10:38:00 【mingjie73】
相关:
《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。

- (32位系统)程序的虚拟内存空间提供了 2 32 2^{32} 232的空间保存数据,用户地址空间3G从
0x0000000到0xC0000000,内核空间1G从0xC0000000到0xFFFFFFFF。 - (64位系统)程序的虚拟内存空间提供了 2 64 2^{64} 264的空间保存数据,用户地址空间128T从
0x0000 0000 0000 0000到0x0000 7FFF FFFF F0000,内核空间128T从0xFFFF 8000 0000 0000到0xFFFF FFFF FFFF FFFF。
二、寄存器
- 寄存器提供了额外的存储空间,每个寄存器可以存一个字(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 -g
main执行过程
(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.
边栏推荐
- Three.JS程序化建模入门
- org.apache.ibatis.binding.BindingException Invalidbound statement (not found)的解决方案和造成原因分析(超详细)
- 4年手工测试被应届生取代了,用血与泪的教训给xdm一个忠告,该学自动化了...
- LayaBox---TypeScript---Advanced Type
- 配置mysql失败了,这是怎么回事呢?
- 如何搭建威纶通触摸屏与S7-200smart之间无线PPI通信?
- 8年软件测试工程师的感悟:与薪资相匹配的永远是实力
- [Science of Terminology] For those difficult words about the integrated workbench, read this article to understand in seconds!
- 循环语句综合练习
- LayaBox---TypeScript---模块解析
猜你喜欢

Jay Chou's new song is released, crawl the "Mojito" MV barrage, and see what the fans have to say!

阿里云数据存储生态计划发布,助力伙伴数据创新

WPF 截图控件之文字(七)「仿微信」

Why use BGP?

MSYS2 QtCreator Clangd code analysis can not find mm_malloc.h problem remedy

多大数量级会出现哈希碰撞

AlphaFold又放大招,剑指整个生物界!

MySQL模糊查询性能优化

如何在技术上来保证LED显示屏质量?

全新荣威RX5,27寸大屏吸引人,安全、舒适一个不落
随机推荐
LayaBox---TypeScript---装饰器
博云入选Gartner中国DevOps代表厂商
如何搭建威纶通触摸屏与S7-200smart之间无线PPI通信?
3D激光slam:LeGO-LOAM---地面点提取方法及代码分析
Com多进程通信实现
初探zend引擎
Oracle查询提示 ORA-00933 SQL command not properly ended 原因排查
21天学习挑战赛--第一天打卡(屏幕密度)
云原生应用平台的核心模块有哪些
众城优选系统开发功能
循环结构--do-while循环
LayaBox---TypeScript---三斜线指令
AlphaFold又放大招,剑指整个生物界!
LayaBox---TypeScript---模块
5G基础学习1、5G网络架构、网络接口及协议栈
学习笔记-支付宝支付
win10打印服务无法启动(运行时错误automation)
多线程之生产者与消费者
Shell脚本实现多选DNS同时批量解析域名IP地址(新更新)
LayaBox---TypeScript---JSX