Architektura x86
Procesor Intel x86 używa złożonej architektury komputera zestawu instrukcji (CISC), co oznacza, że istnieje skromna liczba rejestrów specjalnych zamiast dużych ilości rejestrów ogólnego przeznaczenia. Oznacza to również, że złożone instrukcje o specjalnym przeznaczeniu będą dominować.
Procesor x86 śledzi swoje dziedzictwo co najmniej do 8-bitowego procesora Intel 8080. Wiele osobliwości w zestawie instrukcji x86 wynika ze wstecznej zgodności z tym procesorem (i jego wariantem Zilog Z-80).
Microsoft Win32 używa procesora x86 w trybie 32-bitowym płaskim. Ta dokumentacja koncentruje się tylko na trybie płaskim.
Rejestry
Architektura x86 składa się z następujących nieuprzywilejowanych rejestrów liczb całkowitych.
eax |
Akumulator |
ebx |
Rejestr podstawowy |
ecx |
Rejestr liczników |
edx |
Rejestrowanie danych — może służyć do uzyskiwania dostępu do portów we/wy i funkcji arytmetycznych |
esi |
Rejestr indeksu źródłowego |
edi |
Rejestr indeksu docelowego |
ebp |
Rejestr wskaźnika podstawowego |
esp |
Wskaźnik stosu |
Wszystkie rejestry całkowite są 32-bitowe. Jednak wiele z nich ma 16-bitowe lub 8-bitowe subregistery.
ax |
Niskie 16 bitów eax |
bx |
Niskie 16 bitów ebx |
cx |
Niskie 16 bitów ecx |
dx |
Niskie 16 bitów edx |
si |
Niskie 16 bita esi |
di |
Niskie 16 bitów edi |
bp |
Niskie 16 bitów ebp |
sp |
Niskie 16 bitów esp |
al |
Niskie 8 bitów eax |
ah |
Wysokie 8 bitów rejestru ax |
bl |
Niskie 8 bitów ebx |
bh |
Najwyższe 8 bitów bx |
cl |
Niskie 8 bitów ecx |
|
Najwyższe 8 bity cx |
dl |
Niskie 8 bitów edx |
dh |
Najwyższe 8 bitów dx |
Operowanie na podrejestrze ma wpływ tylko na podrejestr i żadną z części poza podrejestrem. Na przykład przechowywanie w rejestrze ax pozostawia wysokie 16 bity rejestru eax bez zmian.
W przypadku korzystania z polecenia ? (Evaluate Expression), rejestry powinny być poprzedzone znakiem '@' ( @ ). Na przykład należy użyć ? @ax, a nie ? ax. Dzięki temu debuger rozpoznaje osi jako rejestr, a nie symbol.
Jednak parametr (@) nie jest wymagany w poleceniu r (Rejestry). Na przykład r ax=5 zawsze będzie interpretowany poprawnie.
Dwa inne rejestry są ważne dla bieżącego stanu procesora.
eip |
wskaźnik instrukcji |
flagi |
Flagi |
Wskaźnik instrukcji jest adresem wykonywanej instrukcji.
Rejestr flag jest kolekcją flag jedno bitowych. Wiele instrukcji zmienia flagi, aby opisać wynik instrukcji. Te flagi można następnie przetestować za pomocą instrukcji skoku warunkowego. Zobacz flagi x86 , aby uzyskać szczegółowe informacje.
Konwencje wywoływania
Architektura x86 ma kilka różnych konwencji wywoływania. Na szczęście wszystkie one są zgodne z tymi samymi regułami zachowywania rejestru i zwracania funkcji:
Funkcje muszą zachować wszystkie rejestry, z wyjątkiem eax, ecxi edx, które można zmienić w wywołaniu funkcji i esp, które muszą zostać zaktualizowane zgodnie z konwencją wywołującą.
Rejestr eax odbiera wartości zwracane przez funkcję, jeśli wynik wynosi 32 bity lub mniejsze. Jeśli wynik wynosi 64 bity, wynik jest przechowywany w parze edx:eax.
Poniżej znajduje się lista konwencji wywoływania używanych w architekturze x86:
Win32 (__stdcall)
Parametry funkcji są przekazywane na stosie, wypychane od prawej do lewej, a obiekt wywoływany czyści stos.
Natywne wywołanie metody C++ (znane również jako thiscall)
Parametry funkcji są przekazywane na stosie, wypychane od prawej do lewej, wskaźnik "this" jest przekazywany w ecx rejestru, a obiekt wywoływany czyści stos.
COM (__stdcall dla wywołań metod języka C++)
Parametry funkcji są umieszczane na stosie od prawej do lewej, następnie wskaźnik "this" jest dodawany na stos, a później wywoływana jest funkcja. Funkcja wywoływana czyści stos.
__fastcall
Pierwsze dwa argumenty DWORD lub mniejsze są przekazywane do rejestrów ecx oraz edx. Pozostałe parametry są przekazywane na stosie, wypychane od prawej do lewej. Funkcja wywoływana czyści stos.
__cdecl
Parametry funkcji są przekazywane na stosie, umieszczane od prawej do lewej, a program wywołujący czyści stos. Konwencja wywoływania __cdecl jest używana dla wszystkich funkcji z parametrami o zmiennej długości.
Reprezentacja rejestrów i flag w debugerze
Oto przykładowy ekran rejestru debugera:
eax=00000000 ebx=008b6f00 ecx=01010101 edx=ffffffff esi=00000000 edi=00465000
eip=77f9d022 esp=05cffc48 ebp=05cffc54 iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000286
Podczas debugowania w trybie użytkownika możesz zignorować iopl oraz cały ostatni wiersz wyświetlacza debugera.
Flagi x86
W poprzednim przykładzie dwuliterowe kody na końcu drugiego wiersza są flagi. Są to rejestry jedno bitowe i mają różne zastosowania.
W poniższej tabeli wymieniono flagi x86:
Kod flagi | Nazwa flagi | Wartość | Stan flagi | Opis |
---|---|---|---|---|
z | Flaga przepełnienia | 0 1 | |
Brak przepełnienia — przepełnienie |
df | Flaga kierunkowa | 0 1 | updn | Kierunek w górę — kierunek w dół |
, jeśli | Flaga przerwania | 0 1 | diei | Przerwania wyłączone — przerwania włączone |
sf | Flaga podpisywania | 0 1 | plng | Dodatnie (lub zero) — ujemne |
zf | Flaga zerowa | 0 1 | nzzr | Niezerowe - zero |
af | Flaga przenoszenia pomocniczego | 0 1 | naac | Brak przewodu pomocniczego — przewody pomocnicze |
pf | Flaga parzystości | 0 1 | pepo | Parzystość nieparzysta — parzystość parzysta |
cf | Flaga przenoszenia | 0 1 | nccy | Brak przenoszenia — przewożąc |
tf | Flaga pułapki | Jeśli tf równa 1, procesor zgłosi wyjątek STATUS_SINGLE_STEP po wykonaniu jednej instrukcji. Ta flaga jest używana przez debuger do implementowania śledzenia jednoetapowego. Nie powinna być używana przez inne aplikacje. | ||
iopl | Poziom uprawnień wejścia/wyjścia | Poziom uprawnień we/wy Jest to dwu bitowa liczba całkowita z wartościami z zakresu od zera do 3. Jest on używany przez system operacyjny do kontrolowania dostępu do sprzętu. Nie należy stosować jej w aplikacjach. |
Gdy rejestry są wyświetlane w wyniku pewnych poleceń w oknie poleceń debugera, wyświetlany jest stan flagi . Jeśli jednak chcesz zmienić flagę przy użyciu polecenia r (Registers), należy odwołać się do niej za pomocą kodu flagi .
W oknie Rejestry systemu WinDbg kod flagi jest używany do wyświetlania lub zmieniania flag. Status flagi nie jest obsługiwany.
Oto przykład. Na poprzednim wyświetlaczu rejestru pojawia się stan flagi ng. Oznacza to, że flaga znaku jest obecnie ustawiona na 1. Aby to zmienić, użyj następującego polecenia:
r sf=0
Spowoduje to ustawienie flagi znaku na zero. Jeśli dokonasz ponownego wyświetlenia rejestru, kod stanu ng nie pojawi się. Zamiast tego zostanie wyświetlony kod stanu pl.
Flaga Znaku, Flaga Zero i Flaga Przenoszenia to najczęściej używane flagi.
Warunki
Warunek opisuje stan co najmniej jednej flagi. Wszystkie operacje warunkowe na x86 są wyrażane w warunkach.
Asembler używa skrótu jednej lub dwóch liter do reprezentowania warunku. Warunek może być reprezentowany przez wiele skrótów. Na przykład AE ("powyżej lub równe") jest tym samym warunkiem co NB ("nie poniżej"). W poniższej tabeli wymieniono niektóre typowe warunki i ich znaczenie.
Nazwa warunku | Flagi | Znaczenie |
---|---|---|
Z |
ZF=1 |
Wynik ostatniej operacji był zerowy. |
NZ |
ZF=0 |
Wynik ostatniej operacji nie był zerowy. |
C |
CF=1 |
Ostatnia operacja wymagała przeniesienia lub zapożyczenia. (W przypadku niepodpisanych liczb całkowitych oznacza to przepełnienie). |
NC |
CF=0 |
Ostatnia operacja nie wymagała przeniesienia ani pożyczania. (W przypadku niepodpisanych liczb całkowitych oznacza to przepełnienie). |
S |
SF=1 |
Wynik ostatniej operacji ma ustawiony najwyższy bit. |
NS |
SF=0 |
Wynik ostatniej operacji ma najbardziej znaczący bit wyzerowany. |
O |
OF=1 |
W przypadku traktowania jako podpisanej liczby całkowitej ostatnia operacja spowodowała przepełnienie lub niedopełnienie. |
NIE |
OF=0 |
Gdy operacja jest traktowana jako podpisana liczba całkowita, ostatnia operacja nie spowodowała przepełnienia ani niedopełnienia. |
Warunki mogą być również używane do porównywania dwóch wartości. Instrukcja cmp porównuje dwa operandy, a następnie ustawia flagi, jak gdyby jeden operand został odjęty od drugiego. Poniższe warunki mogą służyć do sprawdzania wyniku cmpwartość1, wartość2.
Nazwa warunku | Flagi | Znaczenie po operacji CMP. |
---|---|---|
E |
ZF=1 |
wartość1 == wartość2. |
NE |
ZF=0 |
wartość1 != wartość2. |
GE NL | SF = OF |
wartość1>= wartość2. Wartości są traktowane jako liczby całkowite ze znakiem. |
LE NG | ZF=1 lub SF!=OF |
wartość1<= wartość2. Wartości są traktowane jako liczby całkowite ze znakiem. |
G NLE | ZF=0 i SF=OF |
wartość1>wartość2. Wartości są traktowane jako liczby całkowite ze znakiem. |
L NGE | SF!=OF |
wartość1<wartość2. Wartości są traktowane jako liczby całkowite ze znakiem. |
AE NB | CF=0 |
wartość1>= wartość2. Wartości są traktowane jako niepodpisane liczby całkowite. |
BE NA | CF=1 lub ZF=1 |
wartość1<= wartość2. Wartości są traktowane jako niepodpisane liczby całkowite. |
A NBE | CF=0 i ZF=0 |
wartość1>wartość2. Wartości są traktowane jako niepodpisane liczby całkowite. |
B NAE | CF=1 |
wartość1<wartość2. Wartości są traktowane jako niepodpisane liczby całkowite. |
Warunki są zwykle używane, aby oddziaływać na wynik instrukcji cmp lub test. Na przykład
cmp eax, 5
jz equal
Porównuje rejestr eax z liczbą 5, poprzez obliczenie wyrażenia (eax - 5) i ustawia flagi zgodnie z wynikiem. Jeśli wynikiem odejmowania jest zero, zostanie ustawiona flaga zr, a warunek jz będzie spełniony, więc skok zostanie wykonany.
Typy danych
bajt: 8 bitów
słowo: 16 bitów
dword: 32 bitów
qword: 64 bity (łącznie z zmiennoprzecinkami)
dwa: 80 bitów (łącznie z zmiennoprzecinkami rozszerzonymi dubletami)
oword: 128 bitów
Notacja
W poniższej tabeli przedstawiono notację używaną do opisywania instrukcji dotyczących języka zestawu.
Notacja | Znaczenie |
---|---|
r, r1, r2... |
Rejestry |
m |
Adres pamięci (zobacz sekcję Tryby adresowania, aby uzyskać więcej informacji). |
#n |
Natychmiastowa stała |
r/m |
Rejestrowanie lub pamięć |
r/#n |
Rejestr lub stała natychmiastowa |
r/m/#n |
Rejestr, pamięć lub stała bezpośrednia |
cc |
Kod warunku wymieniony w poprzedniej sekcji Warunki. |
T |
"B", "W" lub "D" (bajt, słowo lub dword) |
accT |
Rozmiar T akumulator: al, jeśli T = "B", ax, jeśli T = "W", lub eax, jeśli T = "D" |
Tryby adresowania
Istnieje kilka różnych trybów adresowania, ale wszystkie mają postać T ptr [expr], gdzie T jest pewnym typem danych (zobacz poprzednią sekcję Typy danych) i wyrażenie jest wyrażeniem obejmującym stałe i rejestry.
Notacja dla większości trybów może być wywnioskowana bez większych trudności. Na przykład BYTE PTR [esi+edx*8+3] oznacza "weź wartość rejestru esi, dodaj do niej wartość rejestru edx pomnożoną przez osiem, dodaj trzy, a następnie uzyskaj dostęp do bajtu pod wynikowym adresem."
Rurociąg
Pentium jest dwuwątkowy, co oznacza, że może wykonać do dwóch operacji w jednym cyklu zegara. Jednak reguły dotyczące tego, kiedy możliwe jest wykonywanie dwóch czynności jednocześnie (znane jako parowanie) są bardzo skomplikowane.
Ponieważ x86 jest procesorem CISC, nie musisz martwić się o sloty opóźnienia skoku.
Synchronizowany dostęp do pamięci
Instrukcje ładowania, modyfikowania i przechowywania mogą otrzymać prefiks blokady, który modyfikuje instrukcję w następujący sposób:
Przed wydaniem instrukcji procesor wypłukuje wszystkie oczekujące operacje pamięci w celu zapewnienia zgodności. Wszystkie przetwarzania wstępne danych zostają anulowane.
Podczas wydawania instrukcji procesor będzie miał wyłączny dostęp do magistrali. Zapewnia to niepodzielność operacji ładowania/modyfikowania/przechowywania.
Instrukcja xchg automatycznie przestrzega poprzednich reguł za każdym razem, gdy wymienia wartość z pamięcią.
Wszystkie inne instrukcje są domyślnie niezgodne z blokadą.
Przewidywanie skoków
Przewiduje się podjęcie bezwarunkowych skoków.
Przewiduje się, że skoki warunkowe zostaną wykonane lub nie zostaną podjęte, w zależności od tego, czy zostały wykonane po raz ostatni. Pamięć podręczna do rejestrowania historii skoków jest ograniczona.
Jeśli CPU nie ma zapisu, czy skok warunkowy był ostatnio wykonany, czy nie, przewiduje, że wsteczne skoki warunkowe zostaną podjęte, a do przodu nie zostaną podjęte.
Wyrównanie
Procesor x86 automatycznie poprawi niezsynchronizowany dostęp do pamięci, kosztem wydajności. Nie zgłoszono wyjątku.
Dostęp do pamięci jest uznawany za wyrównany, jeśli adres jest całkowitą wielokrotnością rozmiaru obiektu. Na przykład wszystkie dostępne BYTE są wyrównane (wszystko jest wielokrotnością 1), dostęp WORD do parzystych adresów jest wyrównany, a adresy DWORD muszą być wielokrotnością 4, aby były wyrównane.
Prefiks blokady nie powinien być używany do nieosiąganych dostępów do pamięci.
Zobacz też
architektura x64