Alokacja pamięci na stosie

0

Witam.
Usiłowałem dowiedzieć się jak wygląda kwestia zarządzania stosem w Linuxie ale nie znalazłem odpowiedzi więc szukam pomocy tutaj. Moje pytanie brzmi - na jakiej podstawie Linux rozstrzyga czy odwołanie do adresu z zakresu dno stosu - RLIMIT_STACK jest odwołaniem poza przestrzeń adresową prcesu?

Poniżej przedstawiam listingi dwóch prościutkich programików w C oraz odpowiadający im kod w asemblerze. Wykonanie prog2 przebiega bez zakłóceń, podczas dy wykonanie prog1 zawsze powoduje "segmentation fault". Nie rozumiem dlaczego gdyż kody w asemblerze są niemalże identyczne.

Prog1.c:
[code]
int main()
{
char a;
char* b = &a;
b = b - 999999;
*b = '\0';
}
[/code]

Prog1.s:
[code]
.file "prog1.c"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
leal -5(%ebp), %eax
movl %eax, -4(%ebp)
subl $999999, -4(%ebp)
movl -4(%ebp), %eax
movb $0, (%eax)
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.4.3"
.section .note.GNU-stack,"",@progbits
[/code]

Prog2.c:
[code]
int main()
{
char a[1000000];
char* b = &a[0];
*b = '\0';
}
[/code]

Prog2.s:
[code]
.file "prog2.c"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $1000016, %esp
leal -1000004(%ebp), %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
movb $0, (%eax)
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.4.3"
.section .note.GNU-stack,"",@progbits
[/code]

0

Przepraszam, użyłem zych znaczników więc wklejam ponownie:

Prog1.c:

int main()
{
        char a;        
        char* b = &a;
        b = b - 999999;
        *b = '\0';
}

Prog1.s:

        .file        "prog1.c"
        .text
.globl main
        .type        main, @function
main:
        pushl        %ebp
        movl        %esp, %ebp
        subl        $16, %esp
        leal        -5(%ebp), %eax
        movl        %eax, -4(%ebp)
        subl        $999999, -4(%ebp)
        movl        -4(%ebp), %eax
        movb        $0, (%eax)
        leave
        ret
        .size        main, .-main
        .ident        "GCC: (GNU) 4.4.3"
        .section        .note.GNU-stack,"",@progbits

Prog2.c:

int main()
{
        char a[1000000];
        char* b = &a[0];
        *b = '\0';
}

Prog2.s:

        .file        "prog2.c"
        .text
.globl main
        .type        main, @function
main:
        pushl        %ebp
        movl        %esp, %ebp
        subl        $1000016, %esp
        leal        -1000004(%ebp), %eax
        movl        %eax, -4(%ebp)
        movl        -4(%ebp), %eax
        movb        $0, (%eax)
        leave
        ret
        .size        main, .-main
        .ident        "GCC: (GNU) 4.4.3"
        .section        .note.GNU-stack,"",@progbits
0

movb $0, (%eax)

to mi sie nie podoba

sub esp,16
lea eax,[ebp-5] ;eax = adres X
mov [ebp-4],eax ;ebp-4 = adres X
sub [ebp-4],999999 ;ebp-4 = adres X - 999999. Watpie czy jeszcze jest poprawny
mov eax,[ebp-4] ;eax = niepoprawny adres
mov byte [eax],0 ; general protection fault.

0

System rozstrzyga, czy jest to przestrzen procesu, czy moze nie.
Przy uruchamianiu jakiegos programu najpierw jest mapowany potrzebny obszar(czasami na podstawie naglowka elf sa okreslane adresy), za pomoca mmap2, tu juz jest okreslane, czy to strona prywatna, czy do oczytu i takie tam, potem jeszcze sa wywolywane instrukcje mprotect, ktore takze sie przyczyniaja do okreslenia, czy strona/y pamieci sa dostepne, a jeszcze jest wywolywana set_thread_area, robi to na co nazwa wskazuje ;)
To tak pokrotce jak sie robi procesy.

Co do tego programu to wiadomo, ze bedzie segfault i nic w tym dziwnego, poniewaz wskaznik chcesz, aby wskazywal na adres 999999 nizszy niz wskazuje.
Watpie, zeby stos jakiegokolwiek procesu byl tego rozmiaru, wiec nawet na stos juz pointer nie wskazuje.

0

No właśnie moje pytanie dotyczy tego na jakiej podstawie jest ustalany rozmiar stosu? Czy jest on zapisywany przez kompilator w pliku wykonywalnym?
Obydwa programy odwołują się do tego samego adresu (pomijając losowość podstawy stosu) tyle, że w przypadku pierwszego z nich stos przydzielany na starcie jest znacznie mniejszy (12 KB i 980 KB - można dodać jakieś polecenie zawieszające proces i sprawdzić przy użyciu pmap). Z czego to wynika? Poza tym słyszałem, że w Linuxie stos może dynamicznie rosnąć a polecenie ulimit -a wskazuje, że limit wielkości stosu to 8192 KB na moim komputerze. Adres do którego się odwołuje nie przekracza wielkości dno_stosu - 8192 KB. Stąd moje pytania.

0

Stos jako calosc jest segmentem, procesy tylko korzystaja z pewnego miejsca w tym segmencie. Skoro masz taki limit to znaczy, ze standardowo proces maksymalnie moze zajmowac 8129kB stosu.
Obszar przydzielony procesowi na stosie jest wielokrotnoscia rozmiaru strony, u Ciebie pewnie 4kB.
Przy kazdym alokowaniu pamieci na zmienna lokalna jest coraz to wiekszy zajmowany obszar stosu przez proces.
W pierwszym przykladzie miales tylko wskaznik typu char i zmienna char, wiec 8 bajtow, z racji wyrownania do 4, do tego dochadza argumenty main, adres powrotny kazdej wywolanej funkcji i wychodzi pewien rozmiar, ktory po wyrownaniu do strony da Ci rozmiar pamieci jaka proces wykorzystuje na stosie.

Pliki elf masz podzielone do tego na sekcje, .bss, .data, ctors, dtors, text,, fini, init czyli dane nie zainicjowane, dane zainicjowane, konstruktory, destruktory, kod, koncowka, poczatek, struktura opisujaca te sekcje zawiera pole z rozmiarem sekcji, wiec to tez moze byc brane pod uwage.
Dane zainicjowane moga byc na stosie, chociaz nie zawsze, poniewaz moga to byc dane zainicjowane globalne, wiec na stercie, jesli globalne/statyczne to juz w segmencie bss, a jesli lokalne to na stosie.

Mam nadzieje, ze nie namieszalem ;)

0

Nie, nie namieszales. Dzieki za trud opisywania.
To co mnie zastanawia to gdzie na poziomie binarnym odbywa sie alokacja stosu. Bo z tego co napisales wynika ze wielkosc sekcji kodu, sekcji danych statycznych itd. jest zapisana w ELF-ie ale nie jest tam zapisana wielkosc sekcji stosu...

0

Zreszta chyba nie moglaby byc zapisana bo nigdy nie wiadomo jak przebiegnie lancuch wywolan podczas wykonywania programu. Wiec alokacja stosu musi odbywac sie jakos inaczej. A kody asemblerowe tych dwoch programow roznia sie tylko tym ze w przypadku Prog2 wskaznik stosu jest przesuwany o odpowiednia wielkosc. Ale wydaje mi sie ze samo to nie moze wplywac na przydzielona procesowi przestrzen adresowa. Tego wlasnie nie rozumiem.

0

Poczytaj sobie manuale intela, <ort>na pewno </ort>wiele wyjasnia, a co do tego sub to jest tak, ze <ort>po prostu </ort>odejmujesz od wskaznika wiercholka stosu, np. 4 dla zmiennej int i wtedy wskaznik wskazuje na to wolne miejsce, wiec przestrzen na te 4 bajty jest wolna i mozna jej bez problemu uzyc, nie mieszajac z esp oraz nie ruszajac poprzednich danych.
Przy przechodzeniu w tryb chroniony sa ustawiane odpowiednie segmenty, czyli kodu, danych dla ring0, dla ring3 itd. zaleznie od systemu, potem katalogi stron, tablica stron itd.
Duzo jest tego ;)

0

Chyba jednak kluczowa kwestia jest pozycja wskaznika stosu. Prog1 nie powoduje już segmentation fault jeśli dolozyc jedna mala wstawke asemblerowa przesuwajaca wskaznik stosu:

#include <stdio.h>

int main()
{
	char a;	
	char* b = &a;
	b = b - 999999;
	asm ("subl $999999, %esp;");
	*b = '\0';
}

Wnioskuje z tego, ze albo przesuniecie wskaznika stosu jest powiazane z automatyczna alokacja dodatkowej przestrzeni adresowej dla procesu, albo - co wydaje mi sie bardziej prawdopodobne - jesli wystepuje blad strony wynikajacy z odwolania do nieobecnej w pamieci strony stosu, sprawdzana jest pozycja wskaznika stosu i na tej podstawie jadro podejmuje dalsze dzialania. Jest to mozliwe?

0

Ostatecznie rozwiklalem te sprawe. Jest tak jak napisalem powyzej - blad strony zwiazany ze stosem jest obslugiwany z uwzglednieniem wskaznika stosu. W architekturach x86 adres nie moze byc mniejszy niz ESP-32 aby zostal wlaczony do przestrzeni adresowej procesu.

0

Ostatecznie dowiedzialem sie ze jest tak jak napisalem powyzej: jesli wystepuje blad strony wynikajacy z odwolania do nieobecnej w pamieci strony stosu, sprawdzana jest pozycja wskaznika stosu i przykladowo w x86, stos jest powiekszany tylko jesli adres do ktorego nastapilo odwolanie nie przekracza limitu wielkości stosu i nie jest mniejszy niz ESP-32. W przeciwnym wypadku nastepuje wyslanie do procesy sygnalu SIGSEGV.

1 użytkowników online, w tym zalogowanych: 0, gości: 1