Gdzie jest popychany?
esp - 4
. Dokładniej:
esp
jest odejmowany przez 4
- wartość jest przenoszona do
esp
pop
odwraca to.
ABI Systemu V mówi Linuksowi, aby rsp
wskazał rozsądną lokalizację stosu, gdy program zacznie działać: Jaki jest domyślny stan rejestru podczas uruchamiania programu (asm, linux)? który jest tym, czego zwykle powinieneś używać.
Jak możesz pushować rejestr?
Przykład minimalnego GNU GAS:
.data
/* .long takes 4 bytes each. */
val1:
/* Store bytes 0x 01 00 00 00 here. */
.long 1
val2:
/* 0x 02 00 00 00 */
.long 2
.text
/* Make esp point to the address of val2.
* Unusual, but totally possible. */
mov $val2, %esp
/* eax = 3 */
mov $3, %ea
push %eax
/*
Outcome:
- esp == val1
- val1 == 3
esp was changed to point to val1,
and then val1 was modified.
*/
pop %ebx
/*
Outcome:
- esp == &val2
- ebx == 3
Inverses push: ebx gets the value of val1 (first)
and then esp is increased back to point to val2.
*/
Powyższe na GitHubie z działającymi asercjami .
Dlaczego jest to potrzebne?
Prawdą jest, że instrukcje te mogą być łatwo realizowane za pośrednictwem mov
, add
i sub
.
Powodem ich istnienia jest to, że te kombinacje instrukcji są tak częste, że Intel zdecydował się je nam dostarczyć.
Powodem, dla którego te kombinacje są tak częste, jest to, że ułatwiają tymczasowe zapisywanie i przywracanie wartości rejestrów do pamięci, aby nie zostały nadpisane.
Aby zrozumieć problem, spróbuj ręcznie skompilować kod C.
Główną trudnością jest podjęcie decyzji, gdzie będzie przechowywana każda zmienna.
Idealnie, wszystkie zmienne pasowałyby do rejestrów, które są najszybszymi dostępnymi pamięcią (obecnie około 100x szybciej niż pamięć RAM).
Ale oczywiście możemy z łatwością mieć więcej zmiennych niż rejestrów, szczególnie dla argumentów funkcji zagnieżdżonych, więc jedynym rozwiązaniem jest zapisywanie w pamięci.
Moglibyśmy pisać na dowolny adres pamięci, ale ponieważ zmienne lokalne i argumenty wywołań funkcji i zwrotów pasują do ładnego wzorca stosu, który zapobiega fragmentacji pamięci , jest to najlepszy sposób radzenia sobie z tym. Porównaj to z szaleństwem pisania alokatora sterty.
Następnie pozwalamy kompilatorom zoptymalizować alokację rejestrów za nas, ponieważ jest to NP zakończone i jedna z najtrudniejszych części pisania kompilatora. Ten problem nazywa się alokacją rejestrów i jest izomorficzny do kolorowania grafów .
Kiedy alokator kompilatora jest zmuszony do przechowywania rzeczy w pamięci, a nie tylko w rejestrach, jest to znane jako wyciek .
Czy sprowadza się to do pojedynczej instrukcji procesora, czy jest to bardziej złożone?
Jedyne, co wiemy na pewno, to to, że Intel dokumentuje push
i pop
instrukcję, więc są one jedną instrukcją w tym sensie.
Wewnętrznie można go rozszerzyć na wiele mikrokodów, jeden do modyfikacji, esp
a drugi do wykonywania operacji we / wy pamięci i wykonywania wielu cykli.
Ale możliwe jest również, że pojedyncza push
instrukcja jest szybsza niż równoważna kombinacja innych instrukcji, ponieważ jest bardziej szczegółowa.
Jest to głównie nieudokumentowane:
b
,w
,l
, lubq
do określenia rozmiaru pamięci manipulowany. Np .:pushl %eax
ipopl %eax