程序的执行过程¶
- gcc -E HelloWorld.c 预处理:加入头文件,替换宏。(preprocessing/excute)
- gcc -c -S HelloWorld.c 编译:包含预处理,将 C 程序转换成汇编程序。(compile)
- gcc -c HelloWorld.c 汇编:包含预处理和编译,将汇编程序转换成可链接的二进制程序。(compilation)
- gcc -o HelloWorld.c 链接:包含以上所有操作,将可链接的二进制程序和其它别的库链接在一起,形成可执行的程序文件。(link->object)
函数调用顺序¶
栈帧¶
栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。简言之,栈帧就是利用 EBP
(栈帧指针,请注意不是 ESP)寄存器访问局部变量、参数、函数返回地址等的手段。
;栈帧结构
PUSH EBP ;函数开始(使用EBP前先把已有值保存到栈中)
MOV EBP, ESP ;保存当前ESP到EBP中
... ;函数体
;无论ESP值如何变化,EBP都保持不变,可以安全访问函数的局部变量、参数
MOV ESP, EBP ;将函数的起始地址返回到ESP中
POP EBP ;函数返回前弹出保存在栈中的值
RETN ;函数终止
每一次函数的调用,都会在 调用栈
(call stack)上维护一个独立的 栈帧
(stack frame)。每个独立的栈帧一般包括:
- 函数的返回地址和参数
- 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量
- 函数调用的上下文
栈是从高地址向低地址延伸,一个函数的栈帧用 EBP 和 ESP 这两个寄存器来划定范围。EBP
指向当前栈帧的底部,ESP
始终指向栈帧的顶部。
EBP 寄存器又被称为 帧指针
(Frame Pointer)
ESP 寄存器又被称为 栈指针
(Stack Pointer)
一个很常见的活动记录示例如图所示
汇编角度¶
函数栈的地址是从高到低的
- rbp(base pointer):栈基指针/栈帧指针
- rsp(stack pointer):栈顶指针
- edi:函数参数(指令寄存器)
- rsi/esi:函数参数(指令寄存器)
- eax:累加器或返回值调用
需要注意的点
- 栈变量的申请,是同时发生的,所以在函数后申请,可能导致错误发生在函数前。
- 函数的释放
- 从理论上来说,leaveq
应该正好是入栈的逆向过程 mov %rbp %rsp; pop %rbp
.
=> 0x0000000000400504 <+29>: leaveq
0x0000000000400505 <+30>: retq
End of assembler dump.
(gdb) info registers rbp rsp
rbp 0x7fffffffe370 0x7fffffffe370
rsp 0x7fffffffe368 0x7fffffffe368
(gdb) si
0x0000000000400505 9 } //对应retq语句
(gdb) info registers rbp rsp
rbp 0x7fffffffe380 0x7fffffffe380
rsp 0x7fffffffe378 0x7fffffffe378
(gdb) disassemble foo