gpt4 book ai didi

c - x86_64:堆栈框架指针几乎没有用吗?

转载 作者:行者123 更新时间:2023-12-02 12:55:16 24 4
gpt4 key购买 nike

  • Linux x86_64。
  • gcc 5.x


  • 我正在研究使用-fomit-frame-pointer和不使用两个代码的输出(默认情况下,“-O3”处的gcc启用该选项)。
    pushq    %rbp
    movq %rsp, %rbp
    ...
    popq %rbp

    我的问题是:

    如果我全局禁用该选项,即使是在极端情况下编译操作系统,也有问题吗?

    我知道中断会使用该信息,那么该选项仅对用户空间有用吗?

    最佳答案

    编译器总是生成自一致的代码,因此只要您不使用对它进行某些假设的外部/手工代码即可(例如通过依赖rbp的值),禁用帧指针就可以了。
    中断不使用帧指针信息,它们可以使用当前的堆栈指针来保存最小的上下文,但这取决于中断的类型和OS(硬件中断可能使用Ring 0堆栈)。
    您可以查看英特尔手册,以了解更多信息。
    关于帧指针的作用:
    几年前,在编译了几个简单的例程并查看生成的64位汇编代码之后,我遇到了同样的问题。
    如果您不介意阅读我那时为自己写的大量笔记,就在这里。
    注意:询问某物的用途有点相对。为当前的主要64位ABI编写汇编代码时,我发现自己越来越少地使用堆栈框架。但这只是我的编码风格和意见。

    我喜欢使用框架指针,编写函数的序言和结尾,但是我也喜欢直接令人不快的答案,所以这是我的看法:
    是的,帧指针在x86_64 中几乎没有用
    注意它并不是完全没有用的,尤其是对于人类而言,但是编译器不再需要它。
    为了更好地理解为什么首先要有一个框架指针,最好回顾一些历史。
    回到实模式(16位)天
    当英特尔CPU仅支持“16位模式”时,如何访问堆栈存在一些限制,尤其是该指令(现在仍然是非法)

    mov ax, WORD [sp+10h]
    因为 sp不能用作基址寄存器。只有几个指定的寄存器可以用于此目的,例如 bx或更著名的 bp
    如今,这并不是每个人都关注的细节,但是 bp相对于其他基址寄存器具有优势,它隐式暗示将 ss用作段/选择器寄存器,就像 sp的隐式用法(通过 pushpop等)一样,并且像 esp在更高版本的32位处理器上一样。
    即使您的程序分散在内存中,每个段寄存器都指向不同的区域,但 bpsp的作用相同,这毕竟是设计者的意图。
    因此,通常需要一个堆栈框架,因此是一个框架指针。 bp有效地将堆栈分为四个部分:参数区域,返回地址,旧的bp(仅一个WORD)和局部变量区域。每个区域都由用于访问它的偏移量标识:参数和返回地址为正,旧 bp为零,局部变量为负。
    扩展有效地址
    随着Intel CPU的发展,增加了更广泛的32位寻址模式。
    特别是可以使用任何32位通用寄存器作为基址寄存器,包括 esp的使用。
    像这样的指示
    mov eax, DWORD [esp+10h]
    现在有效,堆栈帧和帧指针的使用似乎注定要结束。
    至少在开始时,情况可能并非如此。
    的确,现在可以完全使用 esp了,但是在上述四个区域中将堆栈分开仍然是有用的,特别是对人类而言。
    如果没有框架指针,则按入或 pop 按钮将更改参数或局部变量相对于 esp的偏移量,从而使形式的代码乍一看似乎不直观。考虑如何使用cdecl调用约定来实现以下C例程:
    void my_routine(int a, int b)
    {
    return my_add(a, b);
    }
    没有和有框架
    my_routine:      
    push DWORD [esp+08h]
    push DWORD [esp+08h]
    call my_add
    ret

    my_routine:
    push ebp
    mov ebp, esp

    push DWORD [ebp+0Ch]
    push DWORD [ebp+08h]
    call my_add

    pop ebp
    ret
    乍一看,第一个版本将相同的值两次压入。但是,实际上它会推两个单独的参数,因为第一次推降低esp,所以相同的有效地址计算会将第二次推指向另一个参数。
    如果添加局部变量(尤其是其中的很多变量),那么情况很快就会变得难以理解: mov eax, [esp+0CAh]是引用局部变量还是自变量?对于堆栈框架,我们为参数和局部变量设置了固定的偏移量。
    即使是起初,编译器仍然更喜欢使用帧基指针提供的固定偏移量。我看到这种行为首先随着gcc改变。
    在调试构建中,堆栈框架有效地增加了代码的清晰度,并使(熟练的)程序员可以轻松地了解正在发生的事情,并且如注释中所指出的,它们使他们可以更轻松地恢复堆栈框架。
    但是,现代的编译器擅长数学运算,可以轻松地计算堆栈指针的移动次数,并从 esp生成适当的偏移量,从而省略了堆栈框架以加快执行速度。
    当CISC需要数据对齐时
    与RISC兄弟相比,在引入SSE指令之前,英特尔处理器从未向程序员提出过太多要求。
    特别是他们从不要求进行数据对齐,我们可以在地址上访问32位数据,而不是4的倍数而不会引起大的抱怨(取决于DRAM数据宽度,这可能会导致延迟增加)。
    SSE使用了需要在16字节边界上访问的16字节操作数,因为SIMD范例已在硬件中高效实现,并且变得越来越流行,在16字节边界上的对齐变得很重要。
    现在主要的64位ABI需要它,堆栈必须按段落对齐(即16字节)。
    现在,我们通常被这样称呼,即在序言中对齐栈之后,但是假设我们没有这种保证,我们将需要执行其中一项
    push rbp                   push rbp
    mov rbp, rsp mov rbp, rsp

    and spl, 0f0h sub rsp, xxx
    sub rsp, 10h*k and spl, 0f0h
    在这些序言之后,堆栈以一种或另一种方式对齐,但是,由于帧指针本身未对齐,因此我们不能再使用 rbp的负偏移量来访问需要对齐的局部变量。
    我们需要使用 rsp,我们可以安排一个序言,其 rbp指向本地var的对齐区域的顶部,但是参数将处于未知的偏移量。
    我们可以安排一个复杂的堆栈框架(也许有多个指针),但是老式框架基本指针的关键是它的简单性。
    因此,我们可以使用帧指针访问堆栈上的参数,并使用局部变量的堆栈指针,这很公平。
    遗憾的是,减少了用于传递参数的堆栈的作用,对于少量参数(当前为四个),甚至没有使用它,将来可能会更少使用。
    因此,我们既不将帧指针用于局部变量(主要用于变量),也不将其用于参数(主要用于参数),那么我们将其用于什么呢?
  • 它保存了原始rsp的副本,因此要在函数导出处恢复堆栈指针,mov就足够了。如果堆栈与不可逆的and对齐,则需要原始副本。
  • 实际上,一些ABI保证在标准序幕之后对齐堆栈,从而使我们能够照常使用帧指针。
  • 某些变量不需要对齐,可以使用未对齐的帧指针进行访问,通常对于手工编写的代码来说是这样。
  • 一些函数需要四个以上的参数。

  • 概要
    帧指针是16位程序的一种残余范例,由于其在访问局部变量和参数时的简单性和清晰度,已被证明在32位计算机上仍然有用。
    但是,在64位计算机上,严格的要求几乎没有简单性和清晰度,但是帧指针仍然在 Debug模式下使用。

    基于框架指针可以用来使事情变得有趣的事实:确实,我想我从未见过这样的代码,但是我可以想象它如何工作。
    但是,我专注于帧指针的内部管理角色,因为这是我一直以来所看到的方式。
    所有疯狂的事情都可以通过将任何指针设置为帧指针的相同值来完成,我为后者赋予了更多“特殊”角色。
    例如,VS2013有时会使用 rdi作为“帧指针”,但是如果不使用 rbp/ebp/bp,我认为它不是真正的帧指针。
    对我来说, rdi的使用意味着帧指针省略优化:)

    关于c - x86_64:堆栈框架指针几乎没有用吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31417784/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com