Czytając o assemblerze często spotykam ludzi, którzy piszą, że wciskają pewien rejestr procesora, a później ponownie go wyskakują , aby przywrócić jego poprzedni stan.
- Jak możesz pushować rejestr? Gdzie jest popychany? Dlaczego jest to potrzebne?
- Czy sprowadza się to do pojedynczej instrukcji procesora, czy jest to bardziej złożone?
assembly
x86
stack
terminology
Ars emble
źródło
źródło
b
,w
,l
, lubq
do określenia rozmiaru pamięci manipulowany. Np .:pushl %eax
ipopl %eax
%eax
zawsze ma rozmiar 32-bitowy.Odpowiedzi:
przesunięcie wartości (niekoniecznie przechowywanej w rejestrze) oznacza zapisanie jej na stosie.
popping oznacza przywrócenie do rejestru wszystkiego, co znajduje się na szczycie stosu . To są podstawowe instrukcje:
źródło
r/m
nie tylko register, więc możeszpush dword [esi]
. Lub nawetpop dword [esp]
załadować, a następnie zapisać tę samą wartość z powrotem pod tym samym adresem. ( github.com/HJLebbink/asm-dude/wiki/POP ). Wspominam o tym tylko dlatego, że mówisz „niekoniecznie rejestr”.pop
wejść w obszar pamięci:pop [0xdeadbeef]
Oto jak wypychasz rejestr. Zakładam, że mówimy o x86.
Jest odkładany na stos. Wartość
ESP
rejestru jest zmniejszana do rozmiaru przekazanej wartości, gdy stos rośnie w dół w systemach x86.Konieczne jest zachowanie wartości. Ogólne użycie to
A
push
to pojedyncza instrukcja w x86, która wewnętrznie wykonuje dwie rzeczy.ESP
rejestr o rozmiar przekazanej wartości.ESP
rejestru.źródło
Gdzie jest popychany?
esp - 4
. Dokładniej:esp
jest odejmowany przez 4esp
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:
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
isub
.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
ipop
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:
push
ipop
wykonują jedną mikrooperację.źródło
push
/pop
dekodować na Uops. Dzięki licznikom wydajności możliwe są testy eksperymentalne, a firma Agner Fog to zrobiła i opublikowała tabele instrukcji . Procesory Pentium-M i nowsze mają tryb single-uoppush
/pop
dzięki silnikowi stosu (patrz plik pdf mikroarch firmy Agner). Obejmuje to najnowsze procesory AMD, dzięki umowie o współużytkowaniu patentów Intel / AMD.mov
ładowaniami). W przypadku rozlanych zmiennych niebędących stałymi, cykliczne przekazywanie do sklepu wiąże się z dużym opóźnieniem (dodatkowe ~ 5c w porównaniu z przekazywaniem bezpośrednio, a instrukcje sklepu nie są tanie).ocperf.py
użyłbym skryptu opakowującego, aby uzyskać łatwe symboliczne nazwy dla liczników.Wypychanie i otwieranie rejestrów jest odpowiednikiem tego:
Zauważ, że jest to składnia x86-64 At & t.
Używany jako para, pozwala to zapisać rejestr na stosie i przywrócić go później. Są też inne zastosowania.
źródło
lea rsp, [rsp±8]
zamiastadd
/,sub
aby lepiej emulować efektpush
/pop
na flagach.Prawie wszystkie procesory używają stosu. Stos programu jest techniką LIFO z zarządzaniem obsługiwanym sprzętowo.
Stos to ilość pamięci programu (RAM) normalnie alokowana na szczycie stosu pamięci procesora i rośnie (w instrukcji PUSH wskaźnik stosu jest zmniejszany) w przeciwnym kierunku. Standardowym terminem wstawiania do stosu jest PUSH, a usuwania ze stosu - POP .
Stos jest zarządzany przez odpowiedni rejestr procesora stosu, zwany także wskaźnikiem stosu, więc gdy procesor wykonuje POP lub PUSH, wskaźnik stosu załaduje / zapisze rejestr lub stałą w pamięci stosu, a wskaźnik stosu zostanie automatycznie zmniejszony x lub zwiększony zgodnie z liczbą wprowadzonych słów lub wskoczył do (z) stosu.
Za pomocą instrukcji asemblera możemy przechowywać w stosie:
źródło