为什么 x86-64 GCC 函数序言分配的堆栈少于局部变量?

2023-12-12

考虑以下简单的程序:

int main(int argc, char **argv)
{
        char buffer[256];

        buffer[0] = 0x41;
        buffer[128] = 0x41;
        buffer[255] = 0x41;

        return 0;
}

在 x86-64 机器上使用 GCC 4.7.0 编译。用 GDB 反汇编 main() 给出:

0x00000000004004cc <+0>:     push   rbp
0x00000000004004cd <+1>:     mov    rbp,rsp
0x00000000004004d0 <+4>:     sub    rsp,0x98
0x00000000004004d7 <+11>:    mov    DWORD PTR [rbp-0x104],edi
0x00000000004004dd <+17>:    mov    QWORD PTR [rbp-0x110],rsi
0x00000000004004e4 <+24>:    mov    BYTE PTR [rbp-0x100],0x41
0x00000000004004eb <+31>:    mov    BYTE PTR [rbp-0x80],0x41
0x00000000004004ef <+35>:    mov    BYTE PTR [rbp-0x1],0x41
0x00000000004004f3 <+39>:    mov    eax,0x0
0x00000000004004f8 <+44>:    leave  
0x00000000004004f9 <+45>:    ret    

当缓冲区为 256 字节时,为什么它只使用 0x98 = 152d 进行 sub rsp?当我将数据移动到 buffer[0] 时,它似乎只是使用分配的堆栈帧之外的数据并使用 rbp 来引用,那么 sub rsp,0x98 的意义是什么?

还有一个问题,这些线有什么作用?

0x00000000004004d7 <+11>:    mov    DWORD PTR [rbp-0x104],edi
0x00000000004004dd <+17>:    mov    QWORD PTR [rbp-0x110],rsi

为什么需要保存EDI而不是RDI?然而,我发现它将此移出了 C 代码中分配的缓冲区的最大范围。同样有趣的是为什么两个变量之间的差异如此之大。既然 EDI 只有 4 个字节,为什么两个变量需要 12 个字节分隔呢?


The Linux 使用的 x86-64 ABI(以及其他一些操作系统,尽管值得注意的是notWindows(有自己不同的 ABI)在堆栈指针下方定义了一个 128 字节的“红色区域”,保证信号或中断处理程序不会触及该区域。 (参见图 3.3 和§3.2.2。)

因此,叶子函数(即不调用其他任何东西的函数)可以使用该区域来完成它想要的任何事情 - 它不会做任何像call这会将数据放置在堆栈指针处;并且任何信号或中断处理程序都将遵循 ABI,并在存储任何内容之前将堆栈指针至少再减少 128 个字节。

(较短的指令编码可用于带符号的 8 位位移,因此红色区域的要点是它增加了叶函数可以使用这些较短的指令访问的本地数据量。)

这就是这里发生的事情。

但是......这段代码没有利用那些较短的编码(它使用的是来自rbp而不是rsp)。为什么不?也算是节省了edi and rsi完全没有必要——你问为什么它可以节省edi代替rdi,但为什么它要保存它呢?

答案是编译器正在生成非常糟糕的代码,因为没有启用优化。如果您启用任何优化,您的整个功能可能会崩溃为:

mov eax, 0
ret

因为这确实是它需要做的全部:buffer[]是本地的,因此对其所做的更改永远不会对其他任何东西可见,因此可以进行优化;除此之外,该函数所需要做的就是返回 0。


所以,这是一个更好的例子。这个函数完全是无稽之谈,但是使用了一个类似的数组,同时做了足够的事情来确保事情不会全部被优化掉:

$ cat test.c
int foo(char *bar)
{
    char tmp[256];
    int i;

    for (i = 0; bar[i] != 0; i++)
      tmp[i] = bar[i] + i;

    return tmp[1] + tmp[200];
}

经过一些优化编译后,您可以看到红色区域的类似用途, 但这次它确实使用了来自的偏移量rsp:

$ gcc -m64 -O1 -c test.c
$ objdump -Mintel -d test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <foo>:
   0:   53                      push   rbx
   1:   48 81 ec 88 00 00 00    sub    rsp,0x88
   8:   0f b6 17                movzx  edx,BYTE PTR [rdi]
   b:   84 d2                   test   dl,dl
   d:   74 26                   je     35 <foo+0x35>
   f:   4c 8d 44 24 88          lea    r8,[rsp-0x78]
  14:   48 8d 4f 01             lea    rcx,[rdi+0x1]
  18:   4c 89 c0                mov    rax,r8
  1b:   89 c3                   mov    ebx,eax
  1d:   44 28 c3                sub    bl,r8b
  20:   89 de                   mov    esi,ebx
  22:   01 f2                   add    edx,esi
  24:   88 10                   mov    BYTE PTR [rax],dl
  26:   0f b6 11                movzx  edx,BYTE PTR [rcx]
  29:   48 83 c0 01             add    rax,0x1
  2d:   48 83 c1 01             add    rcx,0x1
  31:   84 d2                   test   dl,dl
  33:   75 e6                   jne    1b <foo+0x1b>
  35:   0f be 54 24 50          movsx  edx,BYTE PTR [rsp+0x50]
  3a:   0f be 44 24 89          movsx  eax,BYTE PTR [rsp-0x77]
  3f:   8d 04 02                lea    eax,[rdx+rax*1]
  42:   48 81 c4 88 00 00 00    add    rsp,0x88
  49:   5b                      pop    rbx
  4a:   c3                      ret    

现在让我们稍微调整一下,通过插入对另一个函数的调用, 以便foo()不再是叶函数:

$ cat test.c
extern void dummy(void);  /* ADDED */

int foo(char *bar)
{
    char tmp[256];
    int i;

    for (i = 0; bar[i] != 0; i++)
      tmp[i] = bar[i] + i;

    dummy();  /* ADDED */

    return tmp[1] + tmp[200];
}

现在红色区域无法使用,所以你看到的东西更像你 原本预期:

$ gcc -m64 -O1 -c test.c
$ objdump -Mintel -d test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <foo>:
   0:   53                      push   rbx
   1:   48 81 ec 00 01 00 00    sub    rsp,0x100
   8:   0f b6 17                movzx  edx,BYTE PTR [rdi]
   b:   84 d2                   test   dl,dl
   d:   74 24                   je     33 <foo+0x33>
   f:   49 89 e0                mov    r8,rsp
  12:   48 8d 4f 01             lea    rcx,[rdi+0x1]
  16:   48 89 e0                mov    rax,rsp
  19:   89 c3                   mov    ebx,eax
  1b:   44 28 c3                sub    bl,r8b
  1e:   89 de                   mov    esi,ebx
  20:   01 f2                   add    edx,esi
  22:   88 10                   mov    BYTE PTR [rax],dl
  24:   0f b6 11                movzx  edx,BYTE PTR [rcx]
  27:   48 83 c0 01             add    rax,0x1
  2b:   48 83 c1 01             add    rcx,0x1
  2f:   84 d2                   test   dl,dl
  31:   75 e6                   jne    19 <foo+0x19>
  33:   e8 00 00 00 00          call   38 <foo+0x38>
  38:   0f be 94 24 c8 00 00    movsx  edx,BYTE PTR [rsp+0xc8]
  3f:   00 
  40:   0f be 44 24 01          movsx  eax,BYTE PTR [rsp+0x1]
  45:   8d 04 02                lea    eax,[rdx+rax*1]
  48:   48 81 c4 00 01 00 00    add    rsp,0x100
  4f:   5b                      pop    rbx
  50:   c3                      ret    

(注意tmp[200]在第一种情况下位于带符号的 8 位位移范围内,但不在本例中。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 x86-64 GCC 函数序言分配的堆栈少于局部变量? 的相关文章

  • 局部变量在栈中的顺序是怎样的?

    我目前正在尝试对缓冲区溢出漏洞进行一些测试 这是易受攻击的代码 void win printf code flow successfully changed n int main int argc char argv volatile in
  • 在 OllyDbg 和 Assembler 中,EBP+8 是什么意思?

    我正在学习 OllyDbg 中的汇编和调试技巧 以便学习如何使用未记录的函数 现在我遇到以下问题 我有以下代码部分 来自 OllyDbg MOV EDI EDI PUSH EBP MOV EBP ESP MOV EAX DWORD PTR
  • 解释一下 AF 标志在 x86 指令中如何工作?

    我有一个小型 8086 模拟器 并且我已经有一个长期存在的错误了大约 2 年 因为 AF 在 sub 和 add 指令内无法正常运行 我当前计算其值的方法是 8 位数字和减法 uint8 t base subt base base 0xF
  • 了解近调用指令编码

    通过反汇编一些二进制代码 我发现了近调用指令call 0x8ae编码为e8 97 08 00 00 查看指令集参考 我发现这些指令被编码为 call XX XX XX XX lt gt e8 XX XX XX XX being XX XX
  • 问题与 Assembly x86-32 RET n 指令有关

    我知道 Ret n 的含义 但我无法弄清楚它在维护程序运行时堆栈方面的作用 我在那里有点困惑 ret 和栈有关系吗 ret n就好像ret add esp n 您可以将其用于 caller pops 调用约定 Plain ret就好像pop
  • 在运行时检查 GCC 版本

    我需要找出 C 程序执行过程中 运行时 可用的 系统中安装的 GCC 版本 主要版本和次要版本 意思是 以编程方式提取可用 gcc 的版本 就像我在 shell 中输入 gcc version 一样 但在 c 程序中 The GNUC an
  • gcc - 如何从静态库输出 (.a) 中删除所有绝对路径

    我正在使用 GCC for ARM 红色套件 4 恩智浦 http www code red tech com red suite 4 nxp php 我正在编译用于最终应用程序的静态库文件 a 我需要配置项目 以便任何开发人员都可以生成完
  • 为什么调用本地函数需要重定位?

    我写了下面的C代码 int foo int x int y return x int main return foo 1 2 然后我跑了 gcc c my file c objdump S my file o and got text 节的
  • 无法在 Mac OS X 中链接 GSL 库

    我正在尝试在简单的 C 代码中使用 gsl 函数 我已经使用自制程序安装了 gsl 但是当我使用 g J0 test cpp lgsl lgslcblas 编译时 我得到 J0 test cpp 34 10 fatal error gsl
  • 在Windows 64位上将mod_wsgi模块加载到apache中的问题

    我正在尝试安装 mod wsgi 模块操作说明 http code google com p modwsgi wiki InstallationOnWindows 我从以下位置下载了 mod wsgi so这个来源 http www lfd
  • GCC 优化对位操作的有效性

    以下是在 x86 64 上设置 C 中单个位的两种方法 inline void SetBitC long array int bit Pure C version array 1 lt
  • 为什么 GCC 不自动矢量化这个循环?

    我正在尝试优化一个占用程序大量计算时间的循环 但是 当我使用 O3 ffast math ftree vectorizer verbose 6 GCC 输出打开自动矢量化时 它无法对循环进行矢量化 我正在使用海湾合作委员会4 4 5 代码
  • OSX - 将 gcc 版本 4.2.1 替换为通过 Homebrew 安装的 4.9

    这已经困扰我一段时间了 我正在尝试编译一个巨大的 C 文件 我知道它可以工作 因为它在我工作的 Arch Linux 计算机上工作得很好 当我在 mac 上检查 GCC 版本时 它返回以下内容 Configured with prefix
  • JE/JNE 和 JZ/JNZ 之间的区别

    在 x86 汇编代码中 有JE and JNE完全一样JZ and JNZ JE and JZ只是完全相同的事物的不同名称 条件跳转时ZF 零 标志 等于 1 相似地 JNE and JNZ只是条件跳转的不同名称 什么时候ZF等于 0 Yo
  • 从 GP regs 加载 xmm

    假设您的价值观是rax and rdx你想加载到xmm登记 一种方法是 movq xmm0 rax pinsrq xmm0 rdx 1 虽然速度相当慢 有没有更好的办法 在最近的 Intel 或 AMD 上 你不会在延迟或 uop 计数方面
  • 使用 GCC 预编译头文件

    如何获得与 GCC 一起使用的预编译头 我的尝试并不顺利 也没有看到很多关于如何设置它的好例子 我试过了Cygwin https en wikipedia org wiki CygwinGCC 3 4 4 并使用 4 0Ubuntu htt
  • C++ init-list:使用未初始化的成员来初始化其他成员不会给出警告

    g 4 4 和 4 6 clang 3 2 和 coverity 都没有 使用 Wall 和 Wextra 其他一些 或 Weverything 分别给我以下代码片段的警告 class B char t2 char t public B t
  • 修改字符数组,修改部分向后显示

    我刚刚开始学习汇编 我正在尝试修改字符数组 这是我的汇编代码 data data byte Five 0 code Asm proc lea rax data mov dword ptr rax Four ret Asm endp end
  • 为什么这个 IA32 汇编代码有 3 个 leaal 指令?

    我编译了这个C函数 int calc int x int y int z return x 3 y 19 z 我在 calc s 中得到了这个 我正在注释正在发生的事情 file calc c text globl calc type ca
  • .so 文件的 objdump?需要帮助来理解消息

    我正在开发一个与流相关的本机应用程序 我在我的 c 模块中遇到了一些问题 我收到的是来自 Logcat 的一些消息 这是 Logcat 消息 INFO DEBUG 28 Build fingerprint generic sdk gener

随机推荐

  • 复制到剪贴板在 Android 上不起作用

    使用此视图创建标准移动应用程序 public class DebugView extends View ListView
  • 为什么 sys.getrefcount 给出巨大的值?

    import sys a 10 b a print sys getrefcount a b 1 print sys getrefcount b output 22 614 我的Python解释器有问题吗 为什么这会给出像 614 这样巨大的
  • Android:Proguard 的推荐配置是什么?

    我正在为 Android 开发应用程序并使用 Proguard 来混淆代码 目前我正在使用 ProGuard 配置 optimizationpasses 5 dontusemixedcaseclassnames dontskipnonpub
  • MVC4 & IClientValidatable - 自动 AJAX 调用服务器端验证

    我正在寻找在 MVC4 中实现自定义客户端验证 我目前让它与标准属性配合得很好 例如我的模型中的这个 public class UploadedFiles StringLength 255 ErrorMessage Path is too
  • 使用 std::for_each 和 std::views::iota 的并行 for 循环

    我想使用以下方法为基于并行索引的 for 循环设置一个简单的解决方法std views 为了按顺序运行 代码如下所示 int main pseudo random numbers random device rd default rando
  • 使多维数组唯一的php

    我的数组如下所示 大批 1 gt stdClass Object id gt 225 user id gt 1 name gt Blue Quilted Leather Jacket by Minusey 499 2 gt stdClass
  • 添加行时数据表中的重复列

    我创建一个 DataTable 并将其绑定到 DataGrid 我的数据源由一个表 FooTable 组成 该表由一列 FooName 组成 以下代码运行良好 除了每次添加新行时 都会有一个重复的列填充相同的数据 我不知道如何摆脱它 请参阅
  • 使用 XPATH 或 CSS 选择器在 Selenium 中查找元素

    我试图在 C 中使用 Selenium Webdriver 查找元素 导入 已尝试以下代码但没有找到它 driver FindElement By XPath class menu bg ul li 3 Click driver FindE
  • 正确删除单例

    我有以下代码 我的类 h static MyMutex instanceMutex static MyClass getInstance static void deleteInstance 我的类 c MyMutex MyClass in
  • 卷曲发送内容长度标头时出现问题

    我需要将文件上传到 wupload 这是我的功能 public function doPost link postfields cookie ref nobody false upload false header true headers
  • 当类型参数之一应该为 Nothing 时,为什么 Scala 的隐式类不起作用?

    Update 我修改了示例以便可以编译和测试 我有一个定义丰富方法的隐式类 case class Pipe I O R f I gt O R object Pipe The problematic implicit class implic
  • Docker - 名称已被容器使用

    运行docker使用以下命令的注册表总是会抛出错误 dev tmp me docker run d name registry v1 e SETTINGS FLAVOR local e STORAGE PATH registry e SEA
  • PowerShell Tee-Object 未捕获文件中的调试行

    我有一个通过自动化运行的 PowerShell 脚本 因此我需要将脚本的输出捕获到文件中 但我还想捕获运行的命令 为输出提供一些上下文 我会使用set x在 Linux shell 脚本中 不过 我不知道如何将这些命令捕获到 Windows
  • Typescript 实例化通用对象

    我遇到了这个问题 搜索了几个小时却找不到合适的解决方案 我正在尝试创建一个通用函数来恢复类型和对象并将对象转换为该特定类型 但我在实例化泛型类型时遇到了麻烦 我想知道 c Activator CreateInstance 中是否有类似的东西
  • 从 spring mvc 设置选择框值

    如何从控制器在 jsp 中设置选择框值 Employee employee new Employee 我为实体创建了新对象Employee然后设置值 用这个代码指定 employee setEmpDesignation addEmploye
  • 使用 Windows PowerShell 设置 .SSH 密钥时出错

    我已经让 GIT BASH shell 与 SSH 密钥一起正常工作 所以我知道我的基本配置步骤是正确的 但我更喜欢 Windows powershell 实际上我更喜欢 Mac 或 Linux 终端 但没有可用的选项 Anyways 我的
  • 看不懂背包解决方案

    在维基百科中 Knapsack 的算法如下 for i from 1 to n do for j from 0 to W do if j gt w i then T i j max T i 1 j T i 1 j w i v i 18 el
  • 需要一组基于 DefinePlugin 常量的模块

    我正在尝试使用 Webpack 构建一个 Web 应用程序 但我在设计的特定部分遇到了一些困难 希望这里有人有做类似事情的经验 或者足够了解告诉我我是这样做完全错误 基本上 我们有一个 Angular 仪表板应用程序 它由一个 shell
  • Swift 三元语法错误

    我以前一直用 Objective C 编程 现在对 Swift 还很陌生 Xcode 给我的这个错误真的让我很困惑 func renderBufferAreaBAUp yOffset CGFloat amount CGFloat ifLef
  • 为什么 x86-64 GCC 函数序言分配的堆栈少于局部变量?

    考虑以下简单的程序 int main int argc char argv char buffer 256 buffer 0 0x41 buffer 128 0x41 buffer 255 0x41 return 0 在 x86 64 机器