C 堆栈上静态变量的空间分配

C 堆栈上静态变量的空间分配,c,stack,buffer,disassembly,C,Stack,Buffer,Disassembly,下面的代码包含一个8字节的缓冲区 void vuln() { char buffer[8]; gets(buffer); printf("%s",buffer); } int main() { vuln(); return 0; } 因此,该缓冲区预计只保留8个字节的堆栈。但是反汇编显示保留了16个字节 (gdb) Dump of assembler code for function vuln: 0x000000000040057d <

下面的代码包含一个8字节的缓冲区

void vuln() {
    char buffer[8];
    gets(buffer);
    printf("%s",buffer);
}

int main() {
    vuln();
    return 0;
}
因此,该缓冲区预计只保留8个字节的堆栈。但是反汇编显示保留了16个字节

(gdb) 
Dump of assembler code for function vuln:
   0x000000000040057d <+0>:     push   %rbp
   0x000000000040057e <+1>:     mov    %rsp,%rbp
   0x0000000000400581 <+4>:     sub    $0x10,%rsp
   0x0000000000400585 <+8>:     lea    -0x10(%rbp),%rax
   0x0000000000400589 <+12>:    mov    %rax,%rdi
   0x000000000040058c <+15>:    callq  0x400480 <gets@plt>
   0x0000000000400591 <+20>:    lea    -0x10(%rbp),%rax
   0x0000000000400595 <+24>:    mov    %rax,%rsi
   0x0000000000400598 <+27>:    mov    $0x400644,%edi
   0x000000000040059d <+32>:    mov    $0x0,%eax
   0x00000000004005a2 <+37>:    callq  0x400450 <printf@plt>
   0x00000000004005a7 <+42>:    leaveq 
   0x00000000004005a8 <+43>:    retq   
End of assembler dump.
(gdb)
函数vuln的汇编程序代码转储:
0x000000000040057d:推送%rbp
0x000000000040057e:mov%rsp,%rbp
0x0000000000400581:子$0x10,%rsp
0x0000000000400585:lea-0x10(%rbp),%rax
0x0000000000400589:mov%rax,%rdi
0x000000000040058c:callq 0x400480
0x0000000000400591:lea-0x10(%rbp),%rax
0x0000000000400595:mov%rax,%rsi
0x0000000000400598:mov$0x400644,%edi
0x000000000040059d:mov$0x0,%eax
0x00000000004005a2:callq 0x400450
0x00000000004005a7:LEVEQ
0x00000000004005a8:retq
汇编程序转储结束。

在自动脚本中,某些操作将根据堆栈上缓冲区的预期大小执行。但这削弱了剧本。我可以知道为什么为缓冲区分配了16个字节,以便将其合并到脚本中吗?

您无法控制编译器将在堆栈上执行什么操作。您也无法控制堆栈上变量的顺序。编译器还允许引入填充,以便更好地对齐内存中的内容(尽管不是在数组中:这是严格禁止的)。在某些情况下,它甚至不会在堆栈上分配变量,而是在寄存器中分配

如果您对程序集级别有特殊需求,请在程序集中执行。

要求堆栈指针对齐:第3.2.2节(“堆栈框架”)中说

。。。输入参数区域的末端应在16字节边界上对齐。 换句话说,值(
%rsp
− 8) 在启用控制时,始终是16的倍数 转移到函数入口点。堆栈指针
%rsp
,始终指向 最新分配的堆栈帧的结尾

函数在堆栈上分配8个字节,然后调用一个单参数函数,
获取
;该函数的一个参数在寄存器中传递,因此为了保持ABI要求,编译器必须在进行函数调用之前将堆栈指针再向下移动8个字节。

正如他的回答所述,x86-64 ABI需要16个字节的堆栈对齐。如果在x86或x86_64上使用
gcc
,默认情况下,堆栈是16字节对齐的

gcc
文档:

-mpc堆栈边界=num 尝试使堆栈边界与提升为num字节的2边界对齐。如果未指定-mprefered stack boundary,则默认值为4(16字节或128位)


进一步查看文档,如果禁用SSE,gcc可能会将堆栈对齐到8个字节(从而违反ABI要求)。

我用GNU C-O0编译了它,并在内存中分配了16个字节;使用-O6,它增长到32字节。请看下面我的答案。您可以添加“根据堆栈上缓冲区的预期大小执行”的这些操作吗?也许整个问题都有答案。那么,在(%rsp-8)中,值8取决于什么?当我使用时,编译器在堆栈中分配了48个字节。我找不到其中的数学知识。如果按照我最初的计算,48字节是完美的,因为内存是以字(4字节)分配的,48是12个字。但是在这种情况下ABI需求如何呢?在该示例中,在进入函数时,堆栈指针的值
(k*16-8)
表示一些未知的k。
推%rbp
后,其值为
(k-1)*16
;在
子$0x30之后,%rsp
,其值为
(k-4)*16
;然后
调用gets@plt
隐式执行
推%rip
,使进入
的堆栈指针
(k-4)*16-8
获取
。这有助于你理解它是如何工作的吗?