[GCC入坑指南] -fomit-frame-pointer 和 -fno-omit-frame-pointer 编译标识

GCC编译器提供了 -fomit-frame-pointer-fno-omt-frame-pointer 两个相对的编译选项。

  • GCC手册[1]里对 -fomit-frame-pointer 的说明:

Omit the frame pointer in functions that don’t need one. This avoids the instructions to save, set up and restore the frame pointer; on many targets it also makes an extra register available.

On some targets this flag has no effect because the standard calling sequence always uses a frame pointer, so it cannot be omitted.

Note that -fno-omit-frame-pointer doesn’t guarantee the frame pointer is used in all functions. Several targets always omit the frame pointer in leaf functions.

Enabled by default at -O1 and higher.

该说明的大意就是如果函数不需要frame pointer,就不要将frame pointer保留在寄存器中。当打开优化选项:-O,-O2, -O3, -Os 时或者对某些平台不打开任何优化选项时,-fomit-frame-pointer会被默认打开,可以通过设置 -fno-omit-frame-pointer 关闭 -fomit-frame-pointer

  • 什么是 frame pointer

所谓的 frame pointer(FP)stack frame pointer 栈帧指针。每个进程的栈空间为一帧,FP指向当前进程栈空间的栈底(stack bottom)。而 stack pointer(SP) 即堆栈指针,总是指向栈顶(stack top)。

栈帧结构

图片来源 [2]

在多进程环境中,每个进程都有自己的栈空间,但所有进程的栈空间都在同一块存储空间,怎么确定各进程的栈呢?这就要看FP和SP,FP指向栈底,SP指向栈顶,这样,一个进程的栈空间就确定了。

通过回溯栈帧就可以追踪代码的调用过程和调用时的参数。

  • -fomit-frame-pointer 对编译结果的影响

GCC 版本 7.5.0 Target X86_64

示例代码:

int add(int a,int b){
    return a+b;
}
int mul(int a,int b){
    return a*b;
}
int ma(int a,int b,int c){
    return mul(add(a,b),c);
}

关闭 -fomit-frame-pointer 编译标识

编译命令:

-fno-omit-frame-pointer

编译结果:[3]

 add(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, DWORD PTR [rbp-8]
        add     eax, edx
        pop     rbp
        ret
mul(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     eax, DWORD PTR [rbp-4]
        imul    eax, DWORD PTR [rbp-8]
        pop     rbp
        ret
ma(int, int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     DWORD PTR [rbp-12], edx
        mov     edx, DWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, edx
        mov     edi, eax
        call    add(int, int)
        mov     edx, eax
        mov     eax, DWORD PTR [rbp-12]
        mov     esi, eax
        mov     edi, edx
        call    mul(int, int)
        leave
        ret

打开 -fomit-frame-pointer 编译标识

编译命令:

-fomit-frame-pointer

编译结果:[4]

add(int, int):
        mov     DWORD PTR [rsp-4], edi
        mov     DWORD PTR [rsp-8], esi
        mov     edx, DWORD PTR [rsp-4]
        mov     eax, DWORD PTR [rsp-8]
        add     eax, edx
        ret
mul(int, int):
        mov     DWORD PTR [rsp-4], edi
        mov     DWORD PTR [rsp-8], esi
        mov     eax, DWORD PTR [rsp-4]
        imul    eax, DWORD PTR [rsp-8]
        ret
ma(int, int, int):
        sub     rsp, 16
        mov     DWORD PTR [rsp+12], edi
        mov     DWORD PTR [rsp+8], esi
        mov     DWORD PTR [rsp+4], edx
        mov     edx, DWORD PTR [rsp+8]
        mov     eax, DWORD PTR [rsp+12]
        mov     esi, edx
        mov     edi, eax
        call    add(int, int)
        mov     edx, eax
        mov     eax, DWORD PTR [rsp+4]
        mov     esi, eax
        mov     edi, edx
        call    mul(int, int)
        add     rsp, 16
        ret

编译结果分析

从上面的编译结果中可以看到,是否忽略frame pointer 会导致编译生成的汇编代码有以下的区别:

func(int, ...):
        push    rbp
        mov     rbp, rsp
        ...
        pop     rbp
        ret

rbp:基址指针寄存器,用于提供堆栈内某个单元的偏移地
rsp:栈顶指针寄存器,提供堆栈栈顶单元的偏移地址

由这一点区别可以看到,当保留frame pointer 时,首先将保存在rbp寄存器中的FP入栈(push rbp);然后将rbp寄存器设置为本函数的rsp,即:将基址指针指向栈顶指针(mov rbp,rsp);最后在函数执行结束时恢复rbppop rbp


  1. GCC Command Options >> 3.11 Options That Control Optimization

  2. 《深入理解计算机系统(第三版)》3.7.1 The Run Time Stack

  3. https://gcc.godbolt.org/z/e6Wox6jqE

  4. https://gcc.godbolt.org/z/aovjb5qnv

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 引子 gcc and g++分别是gnu的c & c++编译器。gcc/g++在执行编译工作的时候,总共需要4步1...
    Alfie20阅读 8,319评论 2 0
  • 以下文章均为拜读公众号 源码游记 的笔记 http://mp.weixin.qq.com/mp/homepage?...
    lucasgao阅读 4,010评论 0 0
  • 前言 编译的主要任务是将源代码文件作为输入,最终输出目标文件,这期间发生了什么?便是我们本篇文章要介绍的。在开始之...
    沐灵洛阅读 8,536评论 0 2
  • 前言 编译的主要任务是将源代码文件作为输入,最终输出目标文件,这期间发生了什么?便是我们本篇文章要介绍的。在开始之...
    QiShare阅读 9,992评论 0 7
  • 静态分析是指对二进制包进行反编译,分析静态的代码逻辑。 本文内容包括:app 砸壳过程、工具和环境的坑、导出 OC...
    黑超熊猫zuik阅读 9,341评论 0 51

友情链接更多精彩内容