不同缓冲区大小的不同内存对齐方式
Different memory alignment for different buffer sizes
我正在努力自学堆栈溢出并尝试使用这些 -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 main
、b 4
、run
并且从以下输出中可以看出
(gdb) print &buff
= (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
= (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
个字节,然后是缓冲区。
- 第二种情况,为什么多了11个字节?这是由于堆栈的对齐,例如缓冲区是否必须从
rbp
开始对齐 16 个字节(16 的倍数)?
- 为什么第一种情况内存布局不一样,好像没有对齐?
x86-64 System V ABI 要求 16 字节或更大的局部或全局数组以及所有 C99 VLA(始终是局部数组)进行 16 字节对齐。
An array uses the same alignment as its elements, except that a local or global
array variable of length at least 16 bytes or a C99 variable-length array variable
always has alignment of at least 16 bytes.4
4 The alignment requirement allows the use of SSE instructions when operating on the array.
The compiler cannot in general calculate the size of a variable-length array (VLA), but it is expected
that most VLAs will require at least 16 bytes, so it is logical to mandate that VLAs have at
least a 16-byte alignment.
小于一个 SIMD 向量(16 字节)的固定大小数组没有此要求,因此它们可以在堆栈布局中高效打包。
请注意,此不适用于内部结构的数组,仅适用于局部变量和全局变量。
(对于动态存储,malloc
return 值的对齐方式必须足以容纳任何达到该大小的对象,并且由于 x86-64 SysV 具有 maxalign_t
16 字节,如果大小为 16 或更大,malloc
还必须 return 16 字节对齐的指针。对于较小的分配,它可以 return 只有 8B 对齐的 8B 分配,如果它想要。)
本地数组的要求使得编写将其地址传递给需要 16 字节对齐的函数的代码变得安全,但这通常不是 ABI 本身真正需要指定的内容。
这不是不同的编译器必须就 link 他们的代码达成一致的东西,结构布局或调用约定的方式(哪些寄存器被调用破坏,或用于 arg 传递.. .).编译器基本上拥有它正在编译的函数的堆栈布局,其他函数不能假设或依赖它的任何内容。如果您将指针作为函数参数传递,或者将指针存储到全局变量中,它们只会获得指向您本地变量的指针。
不过,为全局指定它是有用的:它使编译器生成的自动矢量化代码可以安全地假设全局数组对齐,即使它是另一个编译器编译的目标文件中的 extern int[]
.
我正在努力自学堆栈溢出并尝试使用这些 -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 main
、b 4
、run
并且从以下输出中可以看出
(gdb) print &buff
= (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
= (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
个字节,然后是缓冲区。
- 第二种情况,为什么多了11个字节?这是由于堆栈的对齐,例如缓冲区是否必须从
rbp
开始对齐 16 个字节(16 的倍数)? - 为什么第一种情况内存布局不一样,好像没有对齐?
x86-64 System V ABI 要求 16 字节或更大的局部或全局数组以及所有 C99 VLA(始终是局部数组)进行 16 字节对齐。
An array uses the same alignment as its elements, except that a local or global array variable of length at least 16 bytes or a C99 variable-length array variable always has alignment of at least 16 bytes.4
4 The alignment requirement allows the use of SSE instructions when operating on the array. The compiler cannot in general calculate the size of a variable-length array (VLA), but it is expected that most VLAs will require at least 16 bytes, so it is logical to mandate that VLAs have at least a 16-byte alignment.
小于一个 SIMD 向量(16 字节)的固定大小数组没有此要求,因此它们可以在堆栈布局中高效打包。
请注意,此不适用于内部结构的数组,仅适用于局部变量和全局变量。
(对于动态存储,malloc
return 值的对齐方式必须足以容纳任何达到该大小的对象,并且由于 x86-64 SysV 具有 maxalign_t
16 字节,如果大小为 16 或更大,malloc
还必须 return 16 字节对齐的指针。对于较小的分配,它可以 return 只有 8B 对齐的 8B 分配,如果它想要。)
本地数组的要求使得编写将其地址传递给需要 16 字节对齐的函数的代码变得安全,但这通常不是 ABI 本身真正需要指定的内容。
这不是不同的编译器必须就 link 他们的代码达成一致的东西,结构布局或调用约定的方式(哪些寄存器被调用破坏,或用于 arg 传递.. .).编译器基本上拥有它正在编译的函数的堆栈布局,其他函数不能假设或依赖它的任何内容。如果您将指针作为函数参数传递,或者将指针存储到全局变量中,它们只会获得指向您本地变量的指针。
不过,为全局指定它是有用的:它使编译器生成的自动矢量化代码可以安全地假设全局数组对齐,即使它是另一个编译器编译的目标文件中的 extern int[]
.