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

Why does a stack overflow occur at varying stack usage each run instead of a fixed amount?

我正在 运行在 Debian OS 上编写一个带有递归调用的程序。我的筹码量是

-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 字节,加上第一个部分页面,其可用大小在每个 运行.1


  1. 所有这些都在 "regular" 偏移量非零的情况下;如果您非常幸运并且随机偏移量为零,您可能正好得到两页。