为什么gcc有时会为本地分配额外的空间,但有时却没有?

时间:2017-03-31 16:08:10

标签: c gcc

这是我的代码

#include <stdio.h>                                                              

char func_with_ret()
{
    return 1;
}
void func_1()
{
    char buf[16];
    func_with_ret();
}
void func_2()
{
    char buf[16];
    getchar();
}
int main()
{
    func_1();
    func_2();
}
  1. 我声明了16字节的本地缓冲区,以保持堆栈指针对齐(对于x86)。
  2. 我写了两个函数&#34; func_1&#34;,&#34; func_2&#34;,它们看起来几乎相同 - 分配16字节的本地缓冲区并调用一个带有char返回值且没有参数的函数,但是一个是自定义的,另一个是getchar()。
  3. 使用gcc参数编译&#34; -fno-stack-protector&#34;(所以堆栈上没有金丝雀)和&#34; -O0&#34;避免意外的优化行为。
  4. 以下是gdb对func_1和func_2的反汇编代码。

    Dump of assembler code for function func_1:
       0x08048427 <+0>:     push   ebp
       0x08048428 <+1>:     mov    ebp,esp
       0x0804842a <+3>:     sub    esp,0x10
       0x0804842d <+6>:     call   0x804841d <func_with_ret>
       0x08048432 <+11>:    leave  
       0x08048433 <+12>:    ret 
    
    Dump of assembler code for function func_2:
       0x08048434 <+0>:     push   ebp
       0x08048435 <+1>:     mov    ebp,esp
       0x08048437 <+3>:     sub    esp,0x18
       0x0804843a <+6>:     call   0x80482f0 <getchar@plt>
       0x0804843f <+11>:    leave  
       0x08048440 <+12>:    ret
    

    在func_1中,缓冲区被分配为0x10(16)字节, 但在func_2中,它被分配为0x18(24)字节,为什么?

    编辑: @Attie弄清楚两者的缓冲区大小实际上是相同的,但是有 func_2中奇怪的8字节堆栈空间不知道它来自何处。

1 个答案:

答案 0 :(得分:1)

我刚尝试重现这一点,见下文:

编译x86-64(没有快乐):

$ gcc p.c -g -o p -O0 -fno-stack-protector
$ objdump -d p

p:     file format elf64-x86-64

[...]

0000000000400538 <func_1>:
  400538:       55                      push   %rbp
  400539:       48 89 e5                mov    %rsp,%rbp
  40053c:       48 83 ec 10             sub    $0x10,%rsp
  400540:       b8 00 00 00 00          mov    $0x0,%eax
  400545:       e8 e3 ff ff ff          callq  40052d <func_with_ret>
  40054a:       c9                      leaveq
  40054b:       c3                      retq

000000000040054c <func_2>:
  40054c:       55                      push   %rbp
  40054d:       48 89 e5                mov    %rsp,%rbp
  400550:       48 83 ec 10             sub    $0x10,%rsp
  400554:       e8 c7 fe ff ff          callq  400420 <getchar@plt>
  400559:       c9                      leaveq
  40055a:       c3                      retq

编译i386(成功):

$ gcc p.c -g -o p -O0 -fno-stack-protector -m32
$ objdump -d p

p:     file format elf32-i386

[...]

08048427 <func_1>:
 8048427:       55                      push   %ebp
 8048428:       89 e5                   mov    %esp,%ebp
 804842a:       83 ec 10                sub    $0x10,%esp
 804842d:       e8 eb ff ff ff          call   804841d <func_with_ret>
 8048432:       c9                      leave
 8048433:       c3                      ret

08048434 <func_2>:
 8048434:       55                      push   %ebp
 8048435:       89 e5                   mov    %esp,%ebp
 8048437:       83 ec 18                sub    $0x18,%esp
 804843a:       e8 b1 fe ff ff          call   80482f0 <getchar@plt>
 804843f:       c9                      leave
 8048440:       c3                      ret

这似乎与以下任何内容无关:

  • getchar()是一个库函数,因此我们调用PLT(过程链接表)。
  • 与函数的返回类型相关
  • main()
  • 中的来电顺序
  • 二进制文件中函数的顺序
  • 动态/静态恭维

但是,如果您将缓冲区的大小增加1,则17,则堆栈使用量分别增加到32和40字节(从16和24字节)。差异是16个字节,用于对齐,如回答here

我无法回答为什么在输入func_2()后堆栈对齐似乎关闭了8个字节。

如果更新func_1()func_2()以获得15字节缓冲区和单字节变量,并将数据写入它们,那么您可以看到这些项在堆栈框架中的位置:

void func_1(void) {
    char buf[15];
    char x;
    buf[0] = 0xaa;
    x = 0x55;
    func_with_ret();
}

void func_2(void) {
    char buf[15];
    char x;
    buf[0] = 0xaa;
    x = 0x55;
    getchar();
}
08048434 <func_1>:
 8048434:       55                      push   %ebp
 8048435:       89 e5                   mov    %esp,%ebp
 8048437:       83 ec 10                sub    $0x10,%esp
 804843a:       c6 45 f0 aa             movb   $0xaa,-0x10(%ebp)
 804843e:       c6 45 ff 55             movb   $0x55,-0x1(%ebp)
 8048442:       e8 d6 ff ff ff          call   804841d <func_with_ret>
 8048447:       c9                      leave
 8048448:       c3                      ret

08048449 <func_2>:
 8048449:       55                      push   %ebp
 804844a:       89 e5                   mov    %esp,%ebp
 804844c:       83 ec 18                sub    $0x18,%esp
 804844f:       c6 45 e8 aa             movb   $0xaa,-0x18(%ebp)
 8048453:       c6 45 f7 55             movb   $0x55,-0x9(%ebp)
 8048457:       e8 94 fe ff ff          call   80482f0 <getchar@plt>
 804845c:       c9                      leave
 804845d:       c3                      ret