Linux wykorzystuje system V ABI dla architektury x86-64 (AMD64); patrz: System V ABI at OSDev Wiki w celu uzyskania szczegółowych informacji.
Oznacza to, że stos rośnie; mniejsze adresy są "wyżej" w stosie. Typowe funkcje C są kompilowane do
pushq %rbp ; Save address of previous stack frame
movq %rsp, %rbp ; Address of current stack frame
subq $16, %rsp ; Reserve 16 bytes for local variables
; ... function ...
movq %rbp, %rsp ; \ equivalent to the
popq %rbp ;/'leave' instruction
ret
The ilość pamięci zarezerwowanej dla zmiennych lokalnych jest zawsze wielokrotnością 16 bajtów, aby utrzymać wyrównany stos do 16 bajtów. Jeśli nie ma miejsca na stosie dla zmiennych lokalnych, nie ma żadnej instrukcji.
(Należy zauważyć, że adres zwrotny i poprzedni %rbp
popychany na stos są zarówno 8 bajtów, 16 bajtów Łącznie).
Podczas %rbp
wskazuje na bieżącej ramce stosu, %rsp
wskazuje na górze stos. Ponieważ kompilator zna różnicę między wartościami %rbp
i %rsp
w dowolnym punkcie tej funkcji, może użyć dowolnego z nich jako podstawy dla zmiennych lokalnych.
Ramka stosu to tylko plac zabaw lokalny: obszar stosu, którego używa aktualna funkcja.
Obecne wersje GCC wyłączają ramkę stosu, gdy używane są optymalizacje. Ma to sens, ponieważ w przypadku programów napisanych w języku C klatki stosu są najbardziej przydatne do debugowania, ale nie wiele więcej. (Można użyć np. -O2 -fno-omit-frame-pointer
, aby zachować klatki stosu, jednocześnie umożliwiając optymalizację.)
Mimo że ta sama ABI dotyczy wszystkich plików binarnych, bez względu na język, w którym są napisane, niektóre inne języki wymagają ramek stosu dla "rozwinięcia "(na przykład, aby" wyrzucić wyjątki "do dzwoniącego przodka bieżącej funkcji); tj. "odwijać" ramki stosów, że jedna lub więcej funkcji może zostać przerwanych, a sterowanie przekazane do funkcji nadrzędnej, bez pozostawiania niepotrzebnych rzeczy na stosie.
Kiedy ramki stosu są pomijane - dla GCC - zmienia implementacją funkcji zasadniczo
subq $8, %rsp ; Re-align stack frame, and
; reserve memory for local variables
; ... function ...
addq $8, %rsp
ret
Ponieważ nie ma ramki stosu (%rbp
jest używany do innych celów, a jego wartość nie jest wciśnięty do stosu), każde wywołanie funkcji przesuwa tylko adres powrotu do stosu, który jest 8-bajtową ilością, więc musimy odjąć 8 od %rsp
, aby zachować wielokrotność 16. (Ogólnie rzecz biorąc, wartość odjęta od i dodana to %rsp
jest nieparzystą wielokrotnością liczby 8.)
Parametry funkcji są zwykle przekazywane w rejestrach. Patrz odnośnik ABI na początku tej odpowiedzi o szczegóły, ale w skrócie, integralne typy i wskaźniki są przekazywane w rejestrach %rdi
, %rsi
, %rdx
, %rcx
, %r8
i %r9
z argumentów zmiennoprzecinkowych w %xmm0
do %xmm7
rejestrów.
W niektórych przypadkach pojawi się rep ret
zamiast rep
. Nie mylić: rep ret
oznacza dokładnie to samo, co ret
; prefiks rep
, chociaż zwykle używany w instrukcjach ciągów (powtarzające się instrukcje), nic nie robi po zastosowaniu do instrukcji ret
. Po prostu niektóre predyktory gałęzi procesorów AMD nie lubią przeskakiwania do instrukcji ret
, a zalecanym rozwiązaniem jest użycie zamiast tego rep ret
.
Na koniec pominąłem red zone powyżej górnej części stosu (128 bajtów pod adresami mniejszymi niż %rsp
). Dzieje się tak dlatego, że nie przydaje się to w przypadku typowych funkcji: w przypadku normalnej ramki stosu, będziesz chciał, aby lokalny materiał znajdował się w ramce stosu, aby umożliwić debugowanie. W przypadku omit-frame-frame wymagania dotyczące wyrównania stosu oznaczają już, że musimy odjąć 8 od %rsp
, więc uwzględnienie pamięci potrzebnej dla zmiennych lokalnych w tym odejmowaniu nic nie kosztuje.
Nie zapomniałeś włączyć optymalizacji. Co do ilości odejmowania, która zależy od wymagań wyrównania i czy możesz użyć czerwonej strefy. – Jester
@Jester Włączenie optymalizacji niekoniecznie oznacza, że pominięto wskaźnik ramki. –
Możliwy duplikat [Czym dokładnie jest wskaźnik bazowy i wskaźnik stosu? Do czego oni wskazują?] (Http://stackoverflow.com/questions/1395591/what-is-exactly-the-base-pointer-and-stack-pointer-to-what-do-they-point). IOW jest taki sam jak w kodzie x86_32. – usr2564301