即使没有对齐,GCC 分配的堆栈空间也比本地所需的更多。空间有啥用?

Posted

技术标签:

【中文标题】即使没有对齐,GCC 分配的堆栈空间也比本地所需的更多。空间有啥用?【英文标题】:GCC allocates more stack space than needed for locals, even without alignment. What's it using the space for?即使没有对齐,GCC 分配的堆栈空间也比本地所需的更多。空间有什么用? 【发布时间】:2021-07-17 12:16:24 【问题描述】:

我有这样的代码:

#include <stdio.h>
#include <string.h>

void overflow_me(char* dizi)
    char buff_array[100];
    strcpy(buff_array,dizi);
    printf("Hosgeldin %s",buff_array);



int main(int argc, char *argv[])
    overflow_me(argv[1]);
    return 0;

我使用gcc -g -o overflow overflow.c -m32 -mpreferred-stack-boundary=2 this 编译它。 然后我用gdb打开溢出文件,反汇编overflow_me函数。

                      endbr32
 0x00001211 <+4>:     push   %ebp
 0x00001212 <+5>:     mov    %esp,%ebp
 0x00001214 <+7>:     push   %ebx
 0x00001215 <+8>:     sub    $0x6c,%esp

我想知道为什么堆栈分配 108 个字节。我预计会是 0x64 而不是 0x6c。

整个反汇编函数:

 0x0000120d <+0>:     endbr32
 0x00001211 <+4>:     push   %ebp
 0x00001212 <+5>:     mov    %esp,%ebp
 0x00001214 <+7>:     push   %ebx
 0x00001215 <+8>:     sub    $0x6c,%esp
 0x00001218 <+11>:    call   0x1110 <__x86.get_pc_thunk.bx>
 0x0000121d <+16>:    add    $0x2db3,%ebx
 0x00001223 <+22>:    mov    0x8(%ebp),%eax
 0x00001226 <+25>:    mov    %eax,-0x70(%ebp)
 0x00001229 <+28>:    mov    %gs:0x14,%eax
 0x0000122f <+34>:    mov    %eax,-0x8(%ebp)
 0x00001232 <+37>:    xor    %eax,%eax
 0x00001234 <+39>:    pushl  -0x70(%ebp)
 0x00001237 <+42>:    lea    -0x6c(%ebp),%eax
 0x0000123a <+45>:    push   %eax
 0x0000123b <+46>:    call   0x10b0 <strcpy@plt>
 0x00001240 <+51>:    add    $0x8,%esp
 0x00001243 <+54>:    lea    -0x6c(%ebp),%eax
 0x00001246 <+57>:    push   %eax
 0x00001247 <+58>:    lea    -0x1fc8(%ebx),%eax
 0x0000124d <+64>:    push   %eax
 0x0000124e <+65>:    call   0x1090 <printf@plt>
 0x00001253 <+70>:    add    $0x8,%esp
 0x00001256 <+73>:    nop
 0x00001257 <+74>:    mov    -0x8(%ebp),%eax
 0x0000125a <+77>:    xor    %gs:0x14,%eax
 0x00001261 <+84>:    je     0x1268 <overflow_me+91>
 0x00001263 <+86>:    call   0x1320 <__stack_chk_fail_local>
 0x00001268 <+91>:    mov    -0x4(%ebp),%ebx
 0x0000126b <+94>:    leave
 0x0000126c <+95>:    ret

【问题讨论】:

因为它将ebx 推入堆栈。 是的,但是 ebx 有 4 字节大小不是吗? 我认为这是因为 buff_array 地址必须为 printf 调用堆叠。 嗯...是的,ebp 我认为您的编译器正在减去 2 个额外的双字来将参数传递给函数。 【参考方案1】:

看起来额外的空间用于堆栈 cookie(-fstack-protector=strong 默认开启,-fpie 也使代码复杂化),以及无缘无故地将堆栈 arg 从 EBP 上方复制到下方。

使用-fno-stack-protector -fno-pie 来简化汇编。默认情况下,它们在 Godbolt 上是关闭的,并且较新的 GCC 不会浪费从 EBP+8 复制到另一个本地的指令,因此https://godbolt.org/z/7bMzxGKsd 说明当您使用较新的版本进行不同编译时,您确实只保留了 100 字节的堆栈空间海湾合作委员会。我还使用 -fverbose-asm 用 var 名称评论 asm。 32 位 PIE 代码很糟糕(PC 相对寻址是 x86-64 64 位模式中的新功能),因此很难阅读,这就是我提到 -fno-pie 的原因,即使它不影响堆栈使用。 (除了调用 thunk 将当前 EIP 放入整数寄存器时。)


我通过使用 |n| 查找 n(%ebp) 发现了问题所在> 0x6c,所以我发现了mov %eax,-0x70(%ebp)(前面是来自8(%ebp) 的加载,即arg。)和pushl -0x70(%ebp),这也表明这是dizi 的副本被推送为arg用于 printf。

另外,mov %gs:0x14,%eaxcall 0x1320 &lt;__stack_chk_fail_local&gt; 很明显这是用某种形式的-fstack-protector 编译的,所以我查看并发现它将堆栈cookie 存储到mov %eax,-0x8(%ebp)。 (就在保存的EBX下方,它本身在保存的EBP下方,EBP帧指针在费心设置后指向它。)


评论提到了 EBX,但保存的 EBP 和 EBX 的空间由 push %ebppush %ebx 分配,它们本身修改 ESP,而不是 sub $0x6c,%esp 的一部分。

请注意,GCC 确实 有时分配的堆栈空间比本地 + 对齐 (Why does GCC allocate more space than necessary on the stack, beyond what's needed for alignment?) 所需的要多,但这不是它在这里所做的:它正在使用它保留的堆栈空间的每个字节.没用,但是您要求它不要优化,所以它编写了愚蠢的代码。 :P

【讨论】:

我用“gcc -g -o buff buff.c -m32 -fno-stack-protector”编译了简单的代码。函数包含 buffer1[5] 和 buffer2[10] 我预计第一个缓冲区占用 8 个字节,第二个缓冲区占用 12 个字节,总共 20 个字节。但是,当我用 gdb 反汇编代码时,我发现它只需要 16 个字节,但为什么呢? void function(int a, int b, int c) char buffer1[5]; char buffer2[10]; void main() function(1,2,3); @ogulcanhgul: alignof(char) = 1 所以 GCC 不需要对齐数组。而且它们每个都小于 16 字节,因此即使没有 -m32(x86-64 SysV ABI“要求”16 字节的本地数组对齐,它也不会选择或更大,即使这是函数内部的,所以 ABI 在那里真的没有业务。我忘记了 i386 SysV ABI 是否说了同样的话。但无论如何,GCC 的启发式当然可以选择何时对齐或不对齐任何数组。)无论如何,他们可以从头到尾打包。【参考方案2】:

查看您的反汇编代码,它似乎正在检查strcpy 函数中的数据溢出。在 和 处,它将一个幻数 gs:0x14 写入刚刚超过 buff_array 末尾的地址。然后在strcpy 返回后,在-,它会检查这个幻数是否没有被覆盖。如果有,它知道复制了超过 100 个字节,并使用__stack_chk_fail_local 发出错误信号。

【讨论】:

支票的位置实际上是在返回之前(当它真正重要时),而不是在 strcpy 之后。 GCC -fstack-protector=strong 不关心你调用什么函数,只关心你有一个数组或堆栈局部变量的大小限制。 (我忘记了它对何时使用堆栈 cookie 的启发式是否包括您将指向它们的指针传递给函数;我认为有时即使所有数组访问都在当前函数中,它也会创建一个堆栈 cookie。) @PeterCordes:是的,看起来你是对的。但这仍然留下四个字节下落不明。那大概是在 8 字节边界上对齐 SP——你同意吗? 不,看我的回答:P GCC 在 this 的情况下似乎没有留下任何未使用的空间。 -mpreferred-stack-boundary=2 告诉它只维护 2^2 = 4 字节的堆栈对齐,它尊重这一点。 (没有 alignas(x) > 4 的本地人,所以它不需要对齐,也不会选择这样做。) (额外的 4 个字节是传入堆栈 arg 的副本,位于数组下方 -0x70(%ebp))。

以上是关于即使没有对齐,GCC 分配的堆栈空间也比本地所需的更多。空间有啥用?的主要内容,如果未能解决你的问题,请参考以下文章

函数所需的堆栈空间是否会影响C / C ++中的内联决策?

使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)

如何使用堆栈视图自动布局标签和按钮?

堆栈内存分配的区别

为啥我的编译器保留的空间比函数堆栈帧所需的空间多?

如何在 GCC 的 32 字节边界处对齐堆栈?