0%

gcc 编译选项 -fomit-frame-pointer

编译选项 -fomit-frame-pointer-fno-omit-frame-pointer 对汇编及调试影响

  • 栈是向下生长的。所谓向下生长是指从 内存高地址 -> 低地址 的路径延伸
  • 有两个重要的指针用于维护栈信息
    • ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。 由于栈的地址大小是从上到下从大到小,所以ESP指在栈的最底端
    • EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。指在栈的最顶端

man

手册中对 -fomit-frame-pointer 描述如下

Don’t keep the frame pointer in a register for functions that don’t need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register available in many functions. It also makes debugging impossible on some machines.
Enabled at levels -O, -O2, -O3, -Os.

当使用 -O0 时不会开启上述选项,编译器会在栈空间中保存额外信息用于调试,即 frame pointer

Example

int add(int a, int b)
{
    return a + b ;
}

汇编对比如下

$ gcc -fno-omit-frame-pointer test.c
$ csky-elf-objdump -dS a.out
00008934 <add>:
    8934:    2470          subi    r0, r0, 8
    8936:    9800          st    r8, (r0, 0)
    8938:    2470          subi    r0, r0, 8
    893a:    1208          mov    r8, r0
    893c:    9208          st    r2, (r8, 0)
    893e:    9318          st    r3, (r8, 4)
    8940:    8608          ld    r6, (r8, 0)
    8942:    8718          ld    r7, (r8, 4)
    8944:    1c67          addu    r7, r7, r6
    8946:    1272          mov    r2, r7
    8948:    1280          mov    r0, r8
    894a:    2070          addi    r0, r0, 8
    894c:    8640          ld    r6, (r0, 16)
    894e:    8800          ld    r8, (r0, 0)
    8950:    2070          addi    r0, r0, 8
    8952:    00cf          jmp    r15

$ gcc -fomit-frame-pointer test.c
$ objdump -dS a.out
00008934 <add>:
    8934:    2470          subi    r0, r0, 8
    8936:    9200          st    r2, (r0, 0)
    8938:    9310          st    r3, (r0, 4)
    893a:    8600          ld    r6, (r0, 0)
    893c:    8710          ld    r7, (r0, 4)
    893e:    1c67          addu    r7, r7, r6
    8940:    1272          mov    r2, r7
    8942:    2070          addi    r0, r0, 8
    8944:    8640          ld    r6, (r0, 16)
    8946:    00cf          jmp    r15

可以看到在栈上多开辟一些空间用于存储 frame pointer

不开启优化时更能反映出压栈及出栈的操作,在这个过程中不会有 fp 操作

00008934 <add>:
    # 开辟栈空间 8B
    8934:    2470          subi    r0, r0, 8
    # 第一个参数 r2 入栈
    8936:    9200          st    r2, (r0, 0)
    # 第二个参数 r3 入栈
    8938:    9310          st    r3, (r0, 4)
    # 加载并执行子程序 a + b
    893a:    8600          ld    r6, (r0, 0)
    893c:    8710          ld    r7, (r0, 4)
    893e:    1c67          addu    r7, r7, r6
    # 赋值给 r2 作为函数返回值
    8940:    1272          mov    r2, r7
    # 释放栈
    8942:    2070          addi    r0, r0, 8
    8944:    8640          ld    r6, (r0, 16)
    # 返回调用函数栈
    8946:    00cf          jmp    r15

Ref

  1. 关于-fno-omit-frame-pointer与-fomit-frame-pointer
  2. C函数调用和汇编代码分析
  3. Linux下Call Stack追溯的实现机制