我在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
答案 0 :(得分:16)
最有可能的原因是ASLR。
堆栈的基址在每次运行时随机化,以使某些类型的漏洞更加困难;在Linux上has a granularity of 16 bytes(这是x86和我所知道的几乎任何其他平台上最大的对齐要求)。
另一方面,the page size is (normally) 4 KB on x86,系统会在您触摸第一个禁止页面时检测到堆栈溢出;这意味着您将始终首先获得部分页面(偏移量取决于ASLR),然后在系统检测到堆栈溢出之前有两个完整页面。因此,总可用堆栈大小至少是您请求的8192个字节,加上第一个部分页面,每次运行时其可用大小不同。 1