2017-03-24 76 views
10

Uruchomiłem program z wywołaniem rekurencyjnym w systemie operacyjnym Debian. Mój rozmiar stosu jestDlaczego występuje przepełnienie stosu przy różnym zużyciu stosu przy każdym uruchomieniu zamiast ustalonej kwoty?

-s: stack size (kbytes)    8192 

ile nauczyłem, rozmiar stosu musi być stałe i powinny być takie same, które muszą zostać przypisane do programu na każdym biegu, chyba że jest to wyraźnie zmieniło ulimit.

Funkcja rekurencyjna jest zmniejszana o podaną liczbę aż do 0. To jest napisane w Rust.

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

i wartość jest przekazywana jako

static mut Y: i32 = 999999999; 
unsafe { 
    print_till_zero(&mut Y); 
} 

Ponieważ stos przydzielone do programu jest stała, i teoretycznie nie musi się zmienić, spodziewałem przepełnienie stosu przy tej samej wartości za każdym razem, ale nie jest, co oznacza, że ​​alokacja jest variadic.

przejazd 1:

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

thread 'main' has overflowed its stack 
fatal runtime error: stack overflow 

uruchomić 2:

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

thread 'main' has overflowed its stack 
fatal runtime error: stack overflow 

chociaż różnica jest subtelna, nie powinien on być najlepiej powodując przepełnienie stosu w tej samej wielkości? Dlaczego dzieje się to w różnym czasie, sugerując różne rozmiary stosu w każdym biegu? To nie jest specyficzne dla Rust; Podobne zachowanie obserwuje się w 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 

wyjściowa:

przejazd 1:

738551,738550,[1] 7052 segmentation fault 

uruchomić 2:

738438,738437,[1] 7125 segmentation fault 
+6

Przepełnienie nastąpi tylko wtedy, gdy błędy strony stosu. Oznacza to, że wskaźnik stosu przechodzi na stronę niezaładowaną/odłożoną.Miejsce rozpoczęcia stosu nie musi znajdować się na dokładnej granicy strony, ale może zależeć od miejsca załadowania programu, więc wyzwalacz warunku przepełnienia (błąd strony) będzie inny. –

+1

Czy [this] (http://stackoverflow.com/questions/31180563/why-are-stackoverflow-errors-chaotic) wygląda podobnie? – Art

+0

@RichardCritten Czy więc strona poza przyznanym rozmiarem stosu musi być niezobowiązującą stroną w prawo? Proszę popraw mnie jeżeli się mylę. – nohup

Odpowiedz

16

Najprawdopodobniej jest to spowodowane ASLR.

Podstawowy adres stosu jest losowy przy każdym uruchomieniu, aby utrudnić niektóre rodzaje exploitów; na Linuksie to has a granularity of 16 bytes (co jest największym wymogiem wyrównania na X86 i prawie każdej innej platformie jaką znam).

Z drugiej strony, the page size is (normally) 4 KB on x86, a system wykryje przepełnienie stosu po dotknięciu pierwszej zabronionej strony; Oznacza to, że zawsze będziesz mieć dostępną pierwszą stronę (z offsetem zależną od ASLR), a następnie dwie pełne strony, zanim system wykryje przepełnienie stosu. W związku z tym całkowity rozmiar stosu wynosi co najmniej 8192 bajty, o które prosiłeś, plus pierwsza strona częściowa, której dostępny rozmiar jest inny przy każdym uruchomieniu.


  1. Wszystko to w przypadku "zwykłej", gdzie offset jest niezerowe; jeśli masz dużo szczęścia, a przesunięcie losowe wynosi zero, prawdopodobnie dostaniesz dokładnie dwie strony.
+0

16 bajtów jest najbardziej restrykcyjnym warunkiem wyrównania dla A86. –

+0

Przepraszam Przypadkowo usunąłem mój komentarz, więc dodam go tutaj ponownie. Pytałem, czy stos jest losowy, czy nie powinien być wyrównany do strony w celu uzyskania efektywnego dostępu? Dziękuję za aktualizację. – nohup

+1

Należy go wyrównać tylko dla najbardziej restrykcyjnego typu danych. –