为什么在每次运行而不是固定数量的堆栈使用量不同时发生堆栈溢出?

noh*_*hup 9 c linux stack-overflow out-of-memory rust

我在Debian OS上运行一个带递归调用的程序.我的堆栈大小是

-s: stack size (kbytes)             8192
Run Code Online (Sandbox Code Playgroud)

据我所知,堆栈大小必须是固定的,并且必须与每次运行时必须分配给程序的相同,除非明确更改它ulimit.

递归函数是递减给定的数字,直到达到0.这是用Rust写的.

fn print_till_zero(x: &mut i32) {
    *x -= 1;
    println!("Variable is {}", *x);
    while *x != 0 {
        print_till_zero(x);
    }
}
Run Code Online (Sandbox Code Playgroud)

并且值传递为

static mut Y: i32 = 999999999;
unsafe {
    print_till_zero(&mut Y);
}
Run Code Online (Sandbox Code Playgroud)

由于分配给程序的堆栈是固定的,理论上不能改变,我每次都希望堆栈溢出的值相同,但不是,这意味着堆栈分配是可变的.

运行1:

====snip====
Variable is 999895412
Variable is 999895411

thread 'main' has overflowed its stack
fatal runtime error: stack overflow
Run Code Online (Sandbox Code Playgroud)

运行2:

====snip====
Variable is 999895352
Variable is 999895351

thread 'main' has overflowed its stack
fatal runtime error: stack overflow
Run Code Online (Sandbox Code Playgroud)

虽然差别很小但是不应该理想地导致堆栈在同一个变量上溢出?为什么它会在不同的时间发生,这意味着每次运行的堆栈大小不同?这不是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
Run Code Online (Sandbox Code Playgroud)

输出:

运行1:

738551,738550,[1]    7052 segmentation fault
Run Code Online (Sandbox Code Playgroud)

运行2:

738438,738437,[1]    7125 segmentation fault
Run Code Online (Sandbox Code Playgroud)

Mat*_*lia 16

最有可能的原因是ASLR.

堆栈的基址在每次运行时随机化,以使某些类型的漏洞更加困难; 在Linux上,它的粒度为16个字节(这是x86和我所知道的几乎任何其他平台上最大的对齐要求).

另一方面,x86上的页面大小(通常)为4 KB,当您触摸第一个禁止页面时,系统会检测到堆栈溢出; 这意味着您将始终首先获得部分页面(偏移量取决于ASLR),然后在系统检测到堆栈溢出之前有两个完整页面.因此,总可用堆栈大小至少是您请求的8192个字节,加上第一个部分页面,其每次运行的可用大小不同.1


  1. 所有这些都在"常规"情况下,其中偏移是非零的; 如果你很幸运并且随机偏移量为零,你可能只得到两页.

  • 通过在`/ proc/sys/kernel/randomize_va_space`中禁用`ASLR`来验证它,结果现在是一致的.再次感谢. (4认同)
  • @nohup以防万一你在测试后没有重新启用ASLR,你肯定应该.ASLR是一项关键的安全保护,不应禁用. (3认同)