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

时间:2017-03-24 07:05:09

标签: c linux rust out-of-memory stack-overflow

我在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

1 个答案:

答案 0 :(得分:16)

最有可能的原因是ASLR

堆栈的基址在每次运行时随机化,以使某些类型的漏洞更加困难;在Linux上has a granularity of 16 bytes(这是x86和我所知道的几乎任何其他平台上最大的对齐要求)。

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

  1. 所有这些都是在“常规”情况下,偏移量非零;如果你很幸运,随机偏移量为零,你可能会得到两页。