x86_64:堆栈帧指针几乎没有用处吗? Linux x86_64 gcc 5.x

x86_64:堆栈帧指针几乎没有用处吗? Linux x86_64 gcc 5.x,c,gcc,assembly,x86-64,stack-frame,C,Gcc,Assembly,X86 64,Stack Frame,我正在研究两个代码的输出,使用-fomit帧指针,不使用(默认情况下,“-O3”处的gcc启用该选项) 我的问题是: 如果我全局禁用该选项,即使是在极端情况下编译操作系统,也会有问题吗 我知道中断会使用这些信息,所以该选项仅适用于用户空间吗?编译器总是生成自一致的代码,因此禁用帧指针是可以的,只要您不使用外部/手工编写的代码对其进行一些假设(例如,通过依赖rbp的值) 中断不使用帧指针信息,它们可能使用当前堆栈指针来保存最小上下文,但这取决于中断类型和操作系统(硬件中断可能使用环0堆栈)。 有关

我正在研究两个代码的输出,使用-fomit帧指针,不使用(默认情况下,“-O3”处的gcc启用该选项)

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


我知道中断会使用这些信息,所以该选项仅适用于用户空间吗?

编译器总是生成自一致的代码,因此禁用帧指针是可以的,只要您不使用外部/手工编写的代码对其进行一些假设(例如,通过依赖
rbp
的值)

中断不使用帧指针信息,它们可能使用当前堆栈指针来保存最小上下文,但这取决于中断类型和操作系统(硬件中断可能使用环0堆栈)。
有关这方面的更多信息,请参阅英特尔手册

关于帧指针的用途:
几年前,在编译了两个简单的例程并查看生成的64位汇编代码后,我遇到了与您相同的问题。
如果你不介意读很多我当时为自己写的笔记,就在这里

注意:询问某物的有用性是相对的。为当前主64位ABI编写汇编代码时,我发现自己使用的堆栈帧越来越少。然而,这只是我的编码风格和观点


我喜欢使用帧指针,写函数的序言和尾声,但我也喜欢直接的不舒服的答案,所以我是这样看的:

是的,帧指针在x86_64中几乎没有用处

请注意,它并不是完全无用的,特别是对人类来说,但编译器不再需要它了。 为了更好地理解为什么我们首先有一个帧指针,最好回忆一下历史

返回实模式(16位)天 当Intel CPU仅支持“16位模式”时,对如何访问堆栈存在一些限制,尤其是该指令是(现在仍然是)非法的

因为
sp
不能用作基址寄存器。只有少数指定寄存器可用于此目的,例如
bx
或更著名的
bp

如今,这并不是每个人都关注的一个细节,但与其他基址寄存器相比,
bp
有一个优势,即它隐含地暗示了将
ss
用作段/选择器寄存器,就像
sp
的隐含用法一样(通过
push
pop
等),就像esp在后来的32位处理器上所做的那样。
即使您的程序分散在内存中,每个段寄存器都指向不同的区域,
bp
sp
的作用是相同的,毕竟这是设计者的意图

因此,通常需要堆栈帧,因此需要帧指针。
bp
有效地将堆栈划分为四个部分:参数区域、返回地址、旧bp(仅一个字)和局部变量区域。由用于访问的偏移量标识的每个区域:参数和返回地址为正,旧的
bp
为零,局部变量为负

扩展有效地址 随着Intel CPU的发展,添加了更广泛的32位寻址模式。
特别是可以将任何32位通用寄存器用作基址寄存器,这包括使用
esp

是这样的吗

mov eax, DWORD [esp+10h]
现在,堆栈帧和帧指针的使用似乎注定要结束。
很可能情况并非如此,至少在一开始是这样。
诚然,现在完全可以使用
esp
,但在上述四个区域中分离堆栈仍然有用,特别是对人类

如果没有帧指针,推送或弹出将改变参数或相对于
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需要数据对齐时 在引入SSE指令之前,与他们的RISC兄弟相比,英特尔处理器从未向程序员提出过太多要求。
特别是,他们从未要求数据对齐,我们可以访问地址上的32位数据,而不是4的倍数,没有重大投诉(取决于DRAM数据宽度,这可能会导致延迟增加)。
SSE使用需要在16字节边界上访问的16字节操作数,如SIMD范例beco
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 
push rbp                   push rbp
mov rbp, rsp               mov rbp, rsp             

and spl, 0f0h              sub rsp, xxx
sub rsp, 10h*k             and spl, 0f0h