当前位置:网站首页>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.
边栏推荐
- c#反射和特性
- 突破边界,华为存储的破壁之旅
- 关于#oracle#的问题,如何解决?
- Event object, do you know it well?
- 学习笔记-支付宝支付
- Com多进程通信实现
- The ggbarplot function of the R language ggpubr package visualizes the grouped histogram, sets the add parameter to mean_se to visualize the histogram of the mean values of different levels and adds
- 太帅了!我用炫酷大屏展示爬虫数据!
- armv7与armv8的区别(v8和w12的区别)
- The ggline function of the R language ggpubr package visualizes grouped line graphs, the add parameter is mean_se and dotplot to visualize line graphs of different level averages, and adds error bars
猜你喜欢

npm ERR! 400 Bad Request - PUT xxx - Cannot publish over previously published version “1.0.0“.

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

4年手工测试被应届生取代了,用血与泪的教训给xdm一个忠告,该学自动化了...

Three.JS程序化建模入门

如何选择一块真正“好用的、性能高”的远程控制软件

Linux system uninstall, install, upgrade, migrate clickHouse database

Rear tube implements breadcrumb function

games202:三,实时环境光照IBL + PRT

Shell script realizes multi-select DNS simultaneous batch resolution of domain name IP addresses (new update)

Verilog's random number system task----$random
随机推荐
MySQL百万数据优化总结 一
阿里云数据存储生态计划发布,助力伙伴数据创新
How to encapsulate the wx.request() request of WeChat applet
Event object, do you know it well?
ECCV22|PromptDet:无需手动标注,迈向开放词汇的目标检测
bgp与mpls综合实验
LayaBox---TypeScript---JSX
云原生应用平台的核心模块有哪些
详细总结SoC、DSP、MCU、GPU和FPGA等基础概念
循环语句综合练习
21天学习挑战赛--第一天打卡(屏幕密度)
LayaBox---TypeScript---Symbols
多大数量级会出现哈希碰撞
npm ERR! 400 Bad Request - PUT xxx - Cannot publish over previously published version “1.0.0“.
TimerTask(addin timer语音)
The 38-year-old daughter is not in love and has no stable job, the old mother is crying
LayaBox---TypeScript---模块解析
How to choose a truly "easy-to-use, high-performance" remote control software
mysql清除binlog日志文件
Geoffery Hinton: The Next Big Thing in Deep Learning