神秘的内存管理
Mysterious memory management
我正在玩简单的缓冲区溢出。但是,我发现这样的编译器行为非常有趣:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(char *arg1) {
int authenticated = 0;
char buffer[4];
strcpy(buffer, arg1);
if(authenticated) {
printf("HACKED !\n");
} else {
printf("POOR !\n");
}
return;
}
int main() {
char* mystr = "abcdefghijkl";
func(mystr);
printf("THANK YOU!\n");
return 0;
}
让我感到奇怪的是,我需要将 13 元素的缓冲区分配给 arg1,而不是 5 元素的缓冲区才能覆盖已验证的变量。
GDB 确认:
(gdb) print &authenticated
= (int *) 0x7fffffffe75c
(gdb) print &buffer
= (char (*)[4]) 0x7fffffffe750
地址之间相差12。
为什么在这种情况下编译不是最优的?
在重构这个函数的情况下,差异在变化,但为什么差异不总是 4,这似乎是最佳解决方案。
谢谢
您正在调用未定义的行为。任何事情都可能发生。
优化编译器会注意到在strcpy之后没有使用buffer,所以strcpy操作可以去掉。如果没有未定义的行为,它不会有任何可检测的副作用。
优化(或非优化)编译器会注意到 "authenticated" 始终为 0 并且永远不会更改,除非存在未定义的行为,并且编译器始终可以假设没有未定义的行为。所以总是打印 "POOR !\n".
绝对没问题
因此,在存在未定义行为的情况下,您尝试从实验中得出的任何结论都是 100% 没有根据的。
变量之间的距离比您预期的要大,因为它们 aligned to optimize performance。一些操作要求变量的内存位置是某个数字(通常是变量的大小)的整数倍。因此,例如,一个 8 字节 double
可以放在内存中的位置 0x1000 或 0x1008,但不能放在 0x1004。这是您的堆栈最终的样子(缺少优化等),数字表示距堆栈底部的偏移量:
-16: char[] buffer (4 bytes)
-12: padding (8 bytes)
-4: int authenticated (4 bytes)
int 以 4 字节对齐是可以理解的,但为什么 char 缓冲区以 16 字节对齐?为了能够利用 SSE instructions 进行字符串操作。这些需要 16 位内存对齐。在禁用 SSE 的情况下编译程序(-mno-sse
使用 gcc)导致此布局:
-8: char[] buffer (4 bytes)
-4: int authenticated (4 bytes)
所以这证实了额外的填充是由于 SSE。
我正在玩简单的缓冲区溢出。但是,我发现这样的编译器行为非常有趣:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(char *arg1) {
int authenticated = 0;
char buffer[4];
strcpy(buffer, arg1);
if(authenticated) {
printf("HACKED !\n");
} else {
printf("POOR !\n");
}
return;
}
int main() {
char* mystr = "abcdefghijkl";
func(mystr);
printf("THANK YOU!\n");
return 0;
}
让我感到奇怪的是,我需要将 13 元素的缓冲区分配给 arg1,而不是 5 元素的缓冲区才能覆盖已验证的变量。
GDB 确认:
(gdb) print &authenticated
= (int *) 0x7fffffffe75c
(gdb) print &buffer
= (char (*)[4]) 0x7fffffffe750
地址之间相差12。 为什么在这种情况下编译不是最优的?
在重构这个函数的情况下,差异在变化,但为什么差异不总是 4,这似乎是最佳解决方案。
谢谢
您正在调用未定义的行为。任何事情都可能发生。
优化编译器会注意到在strcpy之后没有使用buffer,所以strcpy操作可以去掉。如果没有未定义的行为,它不会有任何可检测的副作用。
优化(或非优化)编译器会注意到 "authenticated" 始终为 0 并且永远不会更改,除非存在未定义的行为,并且编译器始终可以假设没有未定义的行为。所以总是打印 "POOR !\n".
绝对没问题因此,在存在未定义行为的情况下,您尝试从实验中得出的任何结论都是 100% 没有根据的。
变量之间的距离比您预期的要大,因为它们 aligned to optimize performance。一些操作要求变量的内存位置是某个数字(通常是变量的大小)的整数倍。因此,例如,一个 8 字节 double
可以放在内存中的位置 0x1000 或 0x1008,但不能放在 0x1004。这是您的堆栈最终的样子(缺少优化等),数字表示距堆栈底部的偏移量:
-16: char[] buffer (4 bytes)
-12: padding (8 bytes)
-4: int authenticated (4 bytes)
int 以 4 字节对齐是可以理解的,但为什么 char 缓冲区以 16 字节对齐?为了能够利用 SSE instructions 进行字符串操作。这些需要 16 位内存对齐。在禁用 SSE 的情况下编译程序(-mno-sse
使用 gcc)导致此布局:
-8: char[] buffer (4 bytes)
-4: int authenticated (4 bytes)
所以这证实了额外的填充是由于 SSE。