针对不同缓冲区大小的不同内存对齐方式

时间:2018-09-09 23:11:00

标签: linux gcc gdb x86-64 memory-alignment

我正在尝试教育自己有关堆栈溢出的问题,并使用这些-fno-stack-protector标志进行了一些尝试,并试图了解进程中的内存管理方式。

我编译了以下代码(使用Ubuntu 18.04.1 LTS(x86_64),gcc 7.3.0。,禁用ASLR)

int main (int argc, char *argv[])
{
    char    buff[13];
    return 0;
}

如下:gcc -g -o main main.c -fno-stack-protector。然后我想到了gdb mainb 4run,从下面的输出中可以看到

(gdb) print &buff
$2 = (char (*)[13]) 0x7fffffffd963

0x7fffffffd963: 0xff    0xff    0x7f    0x00    0x00    0x00    0x00    0x00
0x7fffffffd96b: 0x00    0x00    0x00    0x00    0x00    0x10    0x46    0x55
0x7fffffffd973: 0x55    0x55    0x55    0x00    0x00    0x97    0x5b    0xa0
0x7fffffffd97b: 0xf7    0xff    0x7f    0x00    0x00    0x01    0x00    0x00

(gdb) info frame 0
Stack frame at 0x7fffffffd980:
 [...]
 Saved registers:
 rbp at 0x7fffffffd970, rip at 0x7fffffffd978

为缓冲区分配的13字节紧随已保存的基本指针rbp之后。

将缓冲区大小从13增加到21后,我得到了以下结果:

(gdb) print &buff   
$3 = (char (*)[21]) 0x7fffffffd950

(gdb) x/48bx buff
0x7fffffffd950: 0x10    0x46    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffd958: 0xf0    0x44    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffd960: 0x50    0xda    0xff    0xff    0xff    0x7f    0x00    0x00
0x7fffffffd968: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffd970: 0x10    0x46    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffd978: 0x97    0x5b    0xa0    0xf7    0xff    0x7f    0x00    0x00

(gdb) info frame 0
Stack frame at 0x7fffffffd980:   
 [...]
 Saved registers:
  rbp at 0x7fffffffd970, rip at 0x7fffffffd978

现在在缓冲区之后的11之后还有rbp个字节。

  • 在第二种情况下,为什么还要增加11个字节?这是由于堆栈对齐,例如从rbp开始,缓冲区必须对齐16个字节(16的倍数)吗?
  • 为什么在第一种情况下内存布局不同,似乎没有对齐?

1 个答案:

答案 0 :(得分:2)

x86-64 System V ABI要求16字节或更大的本地或全局阵列以及所有C99 VLA(始终是本地的)的16字节对齐。

  

数组使用与元素相同的对齐方式,只是局部数组或全局数组   长度至少为16个字节的数组变量或C99可变长度的数组变量   始终至少对齐16个字节。 4

     

4 对齐要求允许在对阵列进行操作时使用SSE指令。   编译器通常无法计算可变长度数组(VLA)的大小,但可以预期   大多数VLA至少需要16个字节,因此逻辑上要求VLA具有   至少16字节对齐。

小于一个SIMD向量(16个字节)的固定大小的数组没有此要求,因此可以在堆栈布局中高效打包。

请注意,此 不适用于内部结构 中的数组,仅适用于局部变量和全局变量。

(对于动态存储,返回值malloc的对齐方式必须足够对齐,以容纳最大至该大小的任何对象,并且由于x86-64 SysV的maxalign_t为16字节,因此{{ 1}}如果大小为16或更大,还必须返回16字节对齐的指针。对于较小的分配,如果需要,它只能返回8B对齐的8B对齐。)


对本地数组的要求使得可以安全地编写将其地址传递给需要16字节对齐的函数的代码,但这绝不是ABI本身真正需要指定的内容。

不同的编译器不必同意将它们的代码链接在一起,结构布局或调用约定的方式(调用寄存器被调用,或用于arg传递...)都不需要达成共识。编译器基本上拥有其正在编译的函数的堆栈布局,而其他函数则不能假定或依赖于此。如果您将指针作为函数args传递或将指针存储到全局变量中,则它们只会获取指向本地var的指针。


尽管为全局变量指定它很有用:它使编译器生成的自动向量化代码可以安全地假定全局数组的对齐方式,即使它是另一个编译器编译的目标文件中的malloc。 / p>