跳转至

程序的执行过程

  • 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

汇编 - 理解函数调用栈 - 简书 (jianshu.com)

从汇编语言看 C/C++函数调用 - 知乎 (zhihu.com)