为什么函数堆栈框架中的参数,变量和框架指针之间存在间隙?

时间:2018-10-01 20:57:08

标签: c stack frame

我有以下c程序:

void function(int a, int b, int c) {
  char buffer1[]="aaaaa";
  char buffer2[]="bbbbbbbbbb";
}
int main() {
  function(1,2,3);
  return 0;

}

当我在执行函数时打印框架信息时,会得到以下gdb输出:

(gdb) info frame
Stack level 0, frame at 0x7fffffffe1c0:
 rip = 0x40119b in function (ss1.c:4); saved rip = 0x4011ca
 called by frame at 0x7fffffffe1d0
 source language c.
 Arglist at 0x7fffffffe1b0, args: a=1, b=2, c=3
 Locals at 0x7fffffffe1b0, Previous frame's sp is 0x7fffffffe1c0
 Saved registers:
  rbp at 0x7fffffffe1b0, rip at 0x7fffffffe1b8
(gdb) 

在打印函数参数和局部变量的地址时,我得到:

(gdb) p/x &c
$65 = 0x7fffffffe184
(gdb) p/x &b
$66 = 0x7fffffffe188
(gdb) p/x &a
$67 = 0x7fffffffe18c
(gdb) p/x &buffer1
$68 = 0x7fffffffe197
(gdb) p/x &buffer2
$69 = 0x7fffffffe19d
  1. 为什么arg a的地址和var buffer1的地址之间有11个字节的间隙-而不仅仅是4个字节的间隙(即a的大小)?

  2. 为什么buffer2的地址和帧指针(0x7fffffffe1b0)之间有19个字节的间隙-而不仅仅是11个字节的间隙(即buffer2的大小)?

谢谢

3 个答案:

答案 0 :(得分:0)

只需运行简单程序:

#include <stdio.h>

void function(int a, int b, int c) 
{
  char buffer1[]="aaaaa";
  char buffer2[]="bbbbbbbbbb";

  printf("%p = &a\n", &a);
  printf("%p = &b\n", &b);
  printf("%p = &c\n", &c);
  printf("%p = &buffer1 sizeof(buffer1) = %zu\n", buffer1, sizeof(buffer1));
  printf("%p = &buffer2 sizeof(buffer2) = %zu\n", buffer2, sizeof(buffer2));
  printf("%zu = &buffer - &a\n", (char *)buffer1 - (char *)&a);
}

int main() 
{
  function(1,2,3);
  return 0;
}

结果完全符合预期。

0x7fff9d9d830c = &a                                                                                                                                                                                                                                           
0x7fff9d9d8308 = &b                                                                                                                                                                                                                                           
0x7fff9d9d8304 = &c                                                                                                                                                                                                                                           
0x7fff9d9d8310 = &buffer1 sizeof(buffer1) = 6                                                                                                                                                                                                                 
0x7fff9d9d8320 = &buffer2 sizeof(buffer2) = 11                                                                                                                                                                                                                
4 = &buffer - &a 

尝试在您的系统上运行它。

答案 1 :(得分:0)

这应该使您走上正确的道路,但不能解决实际差距:

  • 堆栈向下生长,您正尝试向上读取
  • 您看到的&a&b&c不是传递的参数,而是如果(例如)您说a=1时可以使用的本地存储function()。我相信,如果您不执行-O0,则这些将得到优化
  • abc通过寄存器而不是堆栈通过寄存器传递给函数,因此您不会在其中两次找到它们。即调用者不会将它们推入堆栈。
  • buffer1buffer2在堆栈中未对齐,并作为字符串打包在一起。

例如在buffer2之后(之前),您可以找到保存的RBP值,然后找到返回地址。对我来说:

(gdb) p &buffer1
$102 = (char (*)[6]) 0x7fffffffde82
(gdb) p &buffer2
$103 = (char (*)[11]) 0x7fffffffde77

({buffer10x7fffffffde87结尾)

然后保存的RBP

(gdb) p/x (char [8]) *0x7fffffffde88
$104 = {0xb0, 0xde, 0xff, 0xff, 0xff, 0x7f, 0x0, 0x0}

然后是寄信人地址:

(gdb) p/x (char [8]) *0x7fffffffde90
$105 = {0x80, 0x51, 0x55, 0x55, 0x55, 0x55, 0x0, 0x0}

您还可以从gdb中看到以下内容:

(gdb) info frame
Stack level 0, frame at 0x7fffffffde98:
 rip = 0x55555555513f in function (c.c:3); saved rip = 0x555555555180
                                                       ^^^^^^^^^^^^^^
 called by frame at 0x7fffffffdec0
 source language c.
 Arglist at 0x7fffffffde88, args: a=1, b=2, c=3
 Locals at 0x7fffffffde88, Previous frame's sp is 0x7fffffffde98
 Saved registers:
  rbp at 0x7fffffffde88, rip at 0x7fffffffde90
  ^^^^^^^^^^^^^^^^^^^^^^

您还可以通过查看汇编代码来查看此内容:

gcc -S c.c -o c.s

或者,如果您更喜欢英特尔:

gcc -masm=intel -S c.c -o c.s

我不知道为什么gcc会留下这个空白:

    mov     DWORD PTR -36[rbp], edi
    mov     DWORD PTR -40[rbp], esi
    mov     DWORD PTR -44[rbp], edx
    mov     DWORD PTR -6[rbp], 1633771873  <-- aaaa
    mov     WORD PTR -2[rbp], 97           <-- a\0
    movabs  rax, 7089336938131513954       <-- bbbbbbbb
    mov     QWORD PTR -17[rbp], rax
    mov     WORD PTR -9[rbp], 25186        <-- bb
    mov     BYTE PTR -7[rbp], 0            <-- \0

答案 2 :(得分:0)

编译器通常会遵循效率定义的ABI规范,以便针对代码内可能的深层嵌套表达式优化通过寄存器,对齐方式和空间的参数。例如,intel ABI规范说,在函数调用中使用16字节倍数的参数扩展堆栈指针,因此可以进行所有对齐。因此,很正常的情况是,在进入时,只用一个SP减就保留了局部变量的空间,然后开始执行代码,直到需要另一块空间为止。 ABI规范指出哪些寄存器用于传递参数,哪些必须在调用中遵守,哪些可以销毁,哪些用于链接堆栈帧(通常是intel的EBP)等。这允许编译器相互依赖(接口之间语言),并同时实现优化的代码并确保程序效率。这就是为什么您在进入/退出过程调用中看到一些明显的堆栈内存丢失的原因。