我在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个字节,加上第一个部分页面,其可用大小在每次运行时都不同.
>所有这些在“正常”情况下,偏移量不为零;如果你很幸运,随机偏移量为零,你可能会得到两页.