我在Debian操作系统上运行一个递归调用的程序.我的堆栈大小是
-s: stack size (kbytes) 8192
据我了解,堆栈大小必须是固定的,并且应该是相同的,必须分配给每个运行的程序,除非用ulimit显式更改.
递归函数是递减一个给定的数字,直到它达到0.这是写在Rust中.
fn print_till_zero(x: &mut i32) { *x -= 1; println!("Variable is {}",*x); while *x != 0 { print_till_zero(x); } }
并且该值被传递
static mut Y: i32 = 999999999; unsafe { print_till_zero(&mut Y); }
由于分配给程序的堆栈是固定的,理论上不能改变,我期望每次堆栈溢出在相同的值,但不是,这意味着堆栈分配是可变的.
运行1:
====snip==== Variable is 999895412 Variable is 999895411 thread 'main' has overflowed its stack fatal runtime error: stack overflow
运行2:
====snip==== Variable is 999895352 Variable is 999895351 thread 'main' has overflowed its stack fatal runtime error: stack overflow
虽然区别是微妙的,不应该理想地导致堆栈溢出在同一个变量?为什么在不同的时间发生,这意味着每次运行的堆栈大小不一样?这不属于Rust;在C中观察到类似的行为:
#pragma GCC push_options #pragma GCC optimize ("O0") #include<stdio.h> void rec(int i){ printf("%d,",i); rec(i-1); fflush(stdout); } int main(){ setbuf(stdout,NULL); rec(1000000); } #pragma GCC pop_options
输出:
运行1:
738551,738550,[1] 7052 segmentation fault
运行2:
738438,738437,[1] 7125 segmentation fault
解决方法
很可能是由于
ASLR.
堆栈的基地址在每次运行时被随机化,以使某些类型的攻击更加困难;在Linux这个has a granularity of 16 bytes(这是x86和几乎任何其他平台我知道的最大的对齐要求).
另一方面,the page size is (normally) 4 KB on x86,当您触摸第一个禁止的页面时,系统检测到堆栈溢出;这意味着您始终可以首先使用部分页面(偏移量取决于ASLR),然后系统检测到堆栈溢出之前的两个完整页面.因此,总可用堆栈大小至少为您请求的8192个字节,加上第一个部分页面,其可用大小在每次运行时都不同.
>所有这些在“正常”情况下,偏移量不为零;如果你很幸运,随机偏移量为零,你可能会得到两页.