Ale*_*aux 6 c stack-overflow recursion operating-system
这个简单的C程序很少在相同的调用深度终止:
#include <stdio.h>
#include <stdlib.h>
void recursive(unsigned int rec);
int main(void)
{
recursive(1);
return 0;
}
void recursive(unsigned int rec) {
printf("%u\n", rec);
recursive(rec + 1);
}
Run Code Online (Sandbox Code Playgroud)
这种混乱行为背后的原因是什么?
我正在使用fedora(16GiB ram,堆栈大小为8192),并使用cc编译而没有任何选项.
编辑
问题是更多,因为在Linux上,线程堆栈大小是固定的,并且给出了ulimit -s
什么会影响可用的堆栈大小,以便堆栈溢出并不总是出现在相同的调用深度?
编辑2 @BlueMoon总是在他的CentOS上看到相同的输出,而在我的Fedora上,堆栈为8M,我看到不同的输出(最后打印的整数261892或261845,或261826,或......)
将printf调用更改为:
printf("%u %p\n", rec, &rec);
Run Code Online (Sandbox Code Playgroud)
这会强制gcc将rec放在堆栈上,并为您提供其地址,这可以很好地指示堆栈指针发生了什么.
运行您的程序几次,并注意最后打印的地址发生了什么.我的机器上的一些运行显示:
261958 0x7fff82d2878c
261778 0x7fffc85f379c
261816 0x7fff4139c78c
261926 0x7fff192bb79c
Run Code Online (Sandbox Code Playgroud)
首先要注意的是堆栈地址始终以78c
或结尾79c
.这是为什么?我们应该在跨越页面边界时崩溃,页面长度为0x1000字节,每个函数占用0x20字节的堆栈,因此地址应以00X或01X结尾.但仔细观察,我们崩溃了libc.因此堆栈溢出发生在libc内部,从这里我们可以得出结论,调用printf和其他调用的其他东西至少需要0x78c = 1932(可能加上X*4096)字节的堆栈才能工作.
第二个问题是为什么需要不同数量的迭代才能到达堆栈的末尾?一个提示是,我们获得的地址在程序的每次运行中都是不同的.
1 0x7fff8c4c13ac
1 0x7fff0a88f33c
1 0x7fff8d02fc2c
1 0x7fffbc74fd9c
Run Code Online (Sandbox Code Playgroud)
堆栈在内存中的位置是随机的.这样做是为了防止整个系列的缓冲区溢出攻击.但由于内存分配,特别是在此级别,只能在多个页面(4096字节)中完成,所有初始堆栈指针都将在0x1000处对齐.这将减少随机堆栈地址中随机位的数量,因此通过在堆栈顶部浪费随机数量的字节来添加额外的随机性.
操作系统只能在整个页面中计算您使用的内存量,包括堆栈限制.因此,即使堆栈以随机地址开始,堆栈上的最后一个可访问地址也始终是以0xfff结尾的地址.
简短的回答是:为了增加随机存储器布局中的随机性,堆栈顶部的一堆字节被故意浪费,但堆栈的末尾必须以页面边界结束.