Udostępnij za pośrednictwem


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 nvov 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:

  1. Przed wydaniem instrukcji procesor wypłukuje wszystkie oczekujące operacje pamięci w celu zapewnienia zgodności. Wszystkie przetwarzania wstępne danych zostają anulowane.

  2. 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

X86-64 Wikipedia