我试图让自己了解堆栈溢出,并使用这些-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. c-fno-stack-protected ector
。然后我唤起了gdb main
、b 4
、run
,从以下输出可以看出
(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
现在在rbp
之后有额外的11
字节,然后才是缓冲区。
rbp
开始对齐16个字节(16的倍数)吗?
x86-64 System VABI对于16字节或更大的本地或全局数组以及所有C99 VLA(始终为本地)需要16字节对齐。
数组使用与其元素相同的对齐方式,除了长度至少为16字节的局部或全局数组变量或C99可变长度数组变量始终具有至少16字节的对齐方式。4
4对齐要求允许在对数组进行操作时使用SSE指令。编译器一般无法计算可变长度数组(VLA)的大小,但预计大多数VLA将需要至少16个字节,因此要求VLA至少具有16字节对齐是合乎逻辑的。
小于一个SIMD向量(16字节)的固定大小数组没有这个要求,因此它们可以在堆栈布局中有效地打包。
请注意,这不适用于结构内的数组,仅适用于局部变量和全局变量。
(对于动态存储,malloc
返回值的对齐必须足够对齐,以容纳该大小的任何对象,并且由于x86-64 SysV的maxalign_t
为16字节,如果大小为16或更高,malloc
还必须返回16字节对齐的指针。对于较小的分配,如果需要,它可以仅返回8B对齐的8B分配。)
对本地数组的要求使得编写将其地址传递给需要16字节对齐的函数的代码变得安全,但这大多不是ABI本身真正需要指定的。
不同的编译器不必就将它们的代码链接在一起达成一致,结构布局或调用约定的方式(哪些寄存器是被调用的,或者用于arg传递…)。编译器基本上拥有它正在编译的函数的堆栈布局,其他函数不能假设或依赖于它。只有当您将指针作为函数args传递或将指针存储到全局变量中时,它们才会获得指向本地变量的指针。
但是,为全局变量指定它很有用:它使编译器生成的自动向量化代码可以安全地假设全局数组的对齐,即使它是另一个编译器编译的目标文件中的extern int[]
。