Asembler x86/Architektura

Nadeszła pora na omówienie budowy procesora z punktu widzenia programisty.

Rejestry

edytuj

W procesorach z rodziny 80x86[1] wyróżniamy następujące rejestry (w nawiasach podano ich rozmiar):

  • 8 rejestrów ogólnego przeznaczenia [32 bity]
  • 6 rejestrów segmentowych [16 bitów]
  • rejestr flag [32 bity]
  • wskaźnik instrukcji - EIP [32 bity]
  • 8 rejestrów jednostki zmiennoprzecinkowej procesora (w procesorach nowszych od 80386), kiedyś koprocesora arytmetycznego [80 bitów]
  • w nowszych procesorach 8 rejestrów rozszerzeń MMX/3DNow/3DNow! [64 bity]
  • w nowszych procesorach 8 rejestrów XMM rozszerzeń SSE, SSE2, SSE3 [128 bitów]

Dokładny opis wszystkich rejestrów poniżej.

Rejestr EIP

edytuj

Rejestr EIP (ang. extended insertion point), dawniej po prostu IP (jeszcze 16-bitowy) wskazuje na adres aktualnie wykonywanej instrukcji. Procesor ma zapętlony sposób działania: pobiera instrukcję, wykonuje ją i, na koniec cyklu, zwiększa zawartość rejestru EIP o długość wykonanej instrukcji. W razie uruchomienia programu, początkowo rejestr EIP wskazuje na początek segmentu kodu.

Rejestry ogólnego przeznaczenia

edytuj

Jak sama nazwa wskazuje, służą do wykonywania przeróżnych, nieokreślonych z góry czynności, takich jak np.:

  • przechowywanie operandów i wyników obliczeń arytmetycznych i logicznych
  • służą jako liczniki (głównie rejestr ECX)
  • funkcjonują jako przechowalnie argumentów

Pisząc program w asemblerze, prawie na pewno będziesz korzystał z tej grupy rejestrów.


Rejestry ogólnego przeznaczenia
8 bitów
AH AL BH BL CH CL DH DL
AX BX CX DX SP BP SI DI
EAX EBX ECX EDX ESP EBP ESI EDI


EAX - używany głównie w czasie operacji arytmetycznych
EBX - rejestr bazowy, wraz z rejestrem segmentowym DS tworzą wskaźnik na przestrzeń danych
ECX - rejestr służący głownie jako licznik w zapętlonych operacjach
EDX - rejestr danych, używany do operacji arytmetycznych oraz obsługi wejścia-wyjścia
ESP - wskaźnik wierzchołka stosu
EBP - wskaźnik do danych w segmencie stosu
ESI - rejestr indeksowy, wskaźnik źródła
EDI - rejestr indeksowy, wskaźnik przeznaczenia

Jak widać powyżej, wszystkie 8 powyższych rejestrów dzieli się na mniejsze, rejestry składowe. Zmodyfikowanie wartości rejestru składowego modyfikuje kawałek rejestru, którego jest częścią (reszta bitów rejestru-rodzica pozostaje bez zmian).
Podział ten wywodzi się z procesorów 8086, gdzie rejestry procesora były 16-bitowe i nazywały się kolejno AX, BX itd. Umożliwienie korzystania z tych rejestrów wiąże się z zapewnieniem kompatybilności starszych programów z nowymi procesorami. Tworząc procesory z 32-bitowymi rejestrami dopisano do ich nazw literkę E (Extended).

Rejestry segmentowe

edytuj

W pamięci komputera przechowywane są instrukcje dla procesora oraz inne dane, które podlegają przetwarzaniu. Aby procesor "wiedział" co oznaczają poszczególne dane stosuje się tzw. rejestry segmentowe. To właśnie te rejestry umożliwiają wskazanie fragmentów pamięci zawierających kod maszynowy (tzn. te dane procesor może wykonać) oraz obszary danych. Tak naprawdę, ich rola aktualnie nie jest tak duża, jak była w czasach 80286 oraz wcześniejszych. Ponadto, w procesorze 8086 rejestry segmentowe dzieliły całą dostępną przestrzeń adresową na 64-kilobajtowe segmenty, czyli coś w rodzaju "okienek", przez które można było dostać się do pamięci.

Rejestry segmentowe:

  • CS - segment kodu
  • DS - segment danych
  • SS - segment stosu
  • ES - dodatkowy segment danych
  • FS - dodatkowy segment danych
  • GS - dodatkowy segment danych

Wszystkie z tych rejestrów istniały już w procesorach 8086 poza FS i GS, które zostały wprowadzone w procesorze 80386. Ich rola jest nieco odmienna w różnych modelach pamięci.


Segmentowy model pamięci

edytuj
procesor wielkość magistrali adresowej max. wielkość pamięci operacyjnej
8088 20 bitów 1 MB
8086 20 bitów 1 MB
80286 24 bity 16 MB
80386 32 bity 4 GB
80486 32 bity 4 GB
80586 32 bity 4 GB
80686 36 bitów 64 GB [2]
 

Segmentowy model pamięci jest wykorzystywany np. w systemie MS-DOS. Filozofia tegoż podziału polega na tym, iż program może mieć w pamięci operacyjnej wydzielone 3 obszary (segmenty): kodu, danych oraz stos. W segmencie kodu przechowywany jest kod naszego programu, w segmencie danych, jak mówi nazwa, program posiada własny obszar, gdzie przechowuje najróżniejsze dane; zaś stos jest to obszar pamięci, który zasługuje na dokładniejszy opis. Jest on opisany niżej, jeszcze w tym podrozdziale.

Wyżej wymienione rejestry CS, DS, SS, będą przechowywać adres w pamięci owych segmentów wg opisu. Jeśli zdecydujemy się na utworzenie kilku segmentów danych, ich adresy mogą być przechowywane w rejestrach ES, FS i GS.
Odwoływanie się do konkretnego adresu (w tym modelu organizacji pamięci) odbywa się przy użyciu dwóch liczb, których rozmiar jest zależny od naszego procesora (patrz: tabela po lewej). Pierwsza z nich wskazuje na adres segmentu, w którym znajduje się komórka; zaś druga liczba jest przesunięciem, czyli odległością pomiędzy adresem komórki i adresem początku segmentu, w który się znajduje. Wartość tę będziemy nazywać offsetem. Jego rozmiar jest również zależny od naszego procesora i, jak można się domyślić, proporcjonalnie do jego rozmiaru wzrasta maksymalny rozmiar pojedynczego segmentu w pamięci, gdyż musi dać dostęp do każdej komórki segmentu, licząc od jego początku (obecnie są to przynajmniej 32 bity, czyli maksymalny rozmiar segmentu będzie wynosić 4 GB). Aby określić konkretny fizyczny adres, używa się zapisu - x:y. Gdzie x to adres segmentu, zaś y to offset.

Płaski model pamięci

edytuj
 

Z ang. flat model. Jest to sposób organizacji pamięci wykorzystywany przez np. system Windows i systemy z rodziny Unix. Jest on odmianą segmentowego modelu pamięci, który jest rozwinięty o ochronę przed zapisem. Płaski model pamięci funkcjonuje wyłącznie na procesorach 32-bitowych i tylko w trybie chronionym. Ten model polega na tym, że cała pamięć dostępna dla systemu operacyjnego jest traktowana jako jeden wielki segment. Może on mieć rozmiar maksymalnie 4 GB (włącznie z wszystkimi fizycznymi i wirtualnymi zasobami RAM). Słusznie zatem można byłoby stwierdzić, że dostępna pamięć jest jedną, wielką, jednowymiarową tablicą jak w językach wysokiego poziomu, np C/C++ czy Pascal. Wspomniany segment nazywany jest przestrzenią adresową, zaś każdy uruchomiony program posiada własna taką przestrzeń. Dzięki jej istnieniu program nie może wyjść poza swój segment (przestrzeń) danych. Dodatkowo, segment ten, z perspektywy naszego programu, ma adres 0 (do adresowania używamy więc tylko offsetu). Co więcej, wszystkie wyżej wymienione rejestry segmentowe tj. CS, DS, SS, ES, FS i GS wskazują na początek segmentu danych przydzielonego naszemu programowi (który ma adres 0). Wyjście poza przydzieloną dla programu przestrzeń adresową w jakikolwiek sposób doprowadzi do wkroczenia systemu operacyjnego i wyłączenia naszego programu wraz ze zgłoszeniem komunikatu "Naruszenie ochrony pamięci". Dzięki płaskiemu modelowi pamięci, program jest wykonywany szybciej, gdyż procesor nie musi obliczać adresu komórki jak w adresowaniu segment:offset.

 
Stos,ogólne wyobrażenie

Stos jest wyjątkowym segmentem, którego struktura wymaga dokładniejszego opisu. Jak sama nazwa wskazuje, działa na zasadzie podobnej do stosu jakichś rzeczy, którą obrazuje rysunek (taki sposób organizacji nazywamy z ang. LIFO, last-in, first-out). Elementy w stosie kładziemy "jeden na drugim". Tzn. aby nałożyć element nr. 3, najpierw kładziemy elementy 1 i 2. Gdy chcemy się pozbyć 2. elementu ze stosu, nie możemy tego zrobić dopóki nie pozbędziemy się 3.
Jednakże trzeba koniecznie wspomnieć, że stos ma specyficzną budowę. Mianowicie rośnie w dół. Zatem jeśli chcesz go sobie wyobrazić jako poukładane książki jedna na drugiej jak robiłeś do tej pory to należy wprowadzić poprawkę na to wyobrażenie. Wyobraź sobie że stos tworzysz wbrew wszelkim prawom grawitacji na suficie. Jeśli coś dokładasz to rośnie on w dół czyli w kierunku podłogi pomieszczenia. Odwrotnie się dzieje gdy coś ze stosu zdejmujesz. Taka konstrukcja stosu w komputerze była po prostu ułatwieniem dla jego konstruktorów i projektantów. W obecnych modelach pamięci, na stos można umieszczać jedynie wartości 16- i 32-bitowe. W grupie rejestrów procesora x86 wyróżniamy trzy rejestry związane ze stosem: SS -przechowuje adres stosu (nie dotyczy płaskiego modelu pamięci!), ESP -będący adresem wierzchołka stosu oraz EBP, którego funkcję omówimy poniżej. Wywołania funkcji ingerują w specyficzny sposób w strukturę stosu. Opiszę ją poniżej.

 

Przed wywołaniem funkcji musimy najpierw umieścić na stosie argumenty dla niej, w odwrotnej kolejności niż są wymienione w pliku nagłówkowym/dokumentacji. Gdy już to zrobimy, mamy na stosie kilka argumentów ułożonych jeden na drugim. Następnie wywołujemy naszą funkcję. Po wywołaniu, jeszcze przed przeskokiem w miejsce pamięci (gdzie znajduje się kod naszej funkcji), automatycznie na stos odkładana jest wartość rejestru EIP (opisanego już wcześniej), która po tej operacji "leży" bezpośrednio na naszych argumentach. W następnej kolejności, na stos wrzucona zostaje zawartość rejestru EBP, po czym nadana zostaje mu nowa wartość (jego rola w całym procesie za chwilę zostanie opisana). Zapamiętaną wartość określamy skrótem SFP (ang. Stack Frame Pointer - wyjaśnienie poniżej). Na koniec, na szczyt naszego stosu wrzucane są kolejno wszystkie zmienne lokalne wykorzystywane przez naszą funkcję i cały proces kończy się. Końcowy efekt obrazuje grafika po prawej (oczywiście ilość argumentów i lokalnych zmiennych, czy ewentualnie buforów, jest tutaj zupełnie przykładowa).
Jak widać na niej, rejestr EBP wskazuje na adres SFP. Cały opisany obszar na rysunku nazywamy ramką stosu.
Gdy funkcja kończy swoje działanie, przywracana jest wartość rejestrów EIP oraz EBP, a następnie cała ramka zostaje wyrzucona ze szczytu stosu.
Jak wspomniałem należy również pamiętać, że stos rozpina się od górnych obszarów pamięci ku dolnym. Dołożenie czegokolwiek na stos powoduje zmniejszenie wartości rejestru ESP, zaś zabranie czegoś powoduje jego zwiększenie.




Rejestr flag

edytuj

Programista programujący w asemblerze ma wyjątkową możliwość kontrolowania ustawień samego procesora. Możliwe jest to dzięki tzw. rejestrowi flag. To w nim zapisane są takie ustawienia, jak np. możliwość korzystania z instrukcji wejścia-wyjścia przez nasz program. Ponadto, w rejestrze tym zapisywanych jest wiele informacji nt. ostatnio przeprowadzanej operacji arytmetycznej. Tak jak poprzednie rejestry, również posiada swoją starszą wersję (FLAGS), która stanowi jego pierwszą połowę (16 pierwszych bitów). Rejestr EFLAGS wygląda tak (rozkład bitów):

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
0 0 0 0 0 0 0 0 0 0 ID VIP VIF AC VM RF
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 NT IOPL OF DF IF TF SF ZF 0 AF 0 PF 1 CF

Opiszę teraz poszczególne flagi:

znaczniki, których wartość zależna jest jedynie od ostatniego działania arytmetycznego
ID ang. nazwa równy 1 gdy
CF carry flag w wyniku dodawania/odejmowania przekroczono możliwy zakres wartości zmiennej
PF parity flag liczba jedynek w najmłodszym bajcie jest parzysta
AF auxiliary flag wystąpiło przeniesienie z bitu 3. na 4. lub pożyczka z 4. na 3.
ZF zero flag wynik ostatniego działania wyniósł 0
SF sign flag bit znaku jest równy 1 (liczba jest ujemna)
OF overflow flag bit znaku zmienił swoją wartość, ale nie doszło do przeniesienia poza zakres (tzn. CF=0)
pozostałe znaczniki ze zmienną wartością
NT nested task obecny proces nie wywodzi się z żadnego innego (nie wywołano go poprzez CALL; nie dotyczy JMP)
RF resume flag wystąpiło przerwanie zatrzymujące wykonanie programu (może wystąpić tylko, gdy TF=1)
VIP virtual interrupt pending trwa wirtualne przerwanie
ID ID flag jeśli można zmienić jej wartość oznacza to, że procesor obsługuje instrukcję CPUID


znaczniki, które mają wpływ na sposób działania procesora
ID ang. nazwa gdy równy 1
TF trap flag procesor wchodzi w tryb pracy krokowej, dzięki czemu po każdym wykonanym poleceniu generuje przerwanie i przechodzi do odpowiednich procedur obsługi (wykorzystywane przez np. debuggery)
IF interrupt flag przerwania są uwzględniane (w przeciwnym wypadku są ignorowane)
DF direction flag przetwarzanie łańcuchów odbywa się w normalny sposób, ku rosnącym adresom; w przeciwnym wypadku w dół, ku malejącym
IOPL input-output privilage level odczyt/zapis z/do wyjścia jest dozwolony; jest to 2-bitowa flaga
VM virtual 8086 mode przejście w tryb emulacji procesora 8086
VIF virtual interrupt flag przerwania są dozwolone (kopia IF w trybie wirtualnym); aby tryb wirtualny został aktywowany musi zostać włączona flaga VME w rejestrze CR4

Rejestry kontrolne

edytuj

Ponadto architektura i386 definiuje kilka rejestrów kontrolnych:

  • CR0

Ten rejestr ma długość 32 bitów na procesorze 386 lub wyższym. Na procesorze x86-64 analogicznie rejestr ten jak i inne kontrolne ma długość 64 bitów. CR0 ma wiele różnych flag, które mogą modyfikować podstawowe operacje procesora. Nas jednak będą interesowały szczególnie 6 bitów tego rejestru - dolne 5 (od PE do ET) oraz najwyższy bit (PG). Tabelka przedstawia rejestr CR0 (domyślnie dana operacja jest włączona gdy bit jest ustawiony, czyli ma wartość 1):

Bit Nazwa Nazwa angielska Opis
31 PG Paging Flag Jeśli ustawiony na 1, stronicowanie włączone. Jeśli bit ma wartość 0 to wyłączone
30 CD Cache disable Wyłącz pamięć cache
29 NW Not Write-Through Zapis do pamięci, czy przez cache
18 AM Aligment Mask Maska wyrównania. Aby ta opcja działała musi być ustawiona na 1, bit AC z rejestrów flag procesora również musi mieć wartość 1 oraz poziom uprzywilejowania musi wynosić 3.
16 WP Write Protection Ochrona zapisu
5 NE Numeric Error Numeryczny błąd, włącza wewnętrzne raportowanie błędów FPU gdy jest ten bit ustawiony
4 ET Extension Type Typ rozszerzenia. Ta flaga mówi nam jaki mamy koprocesor. Jeśli 0 to 80287, gdy 1 to 80387
3 TS Task switched Przełączanie zadań, pozwala zachować zadania x87
2 EM Emulate Flag Jeśli jest ustawiona nie ma żadnego koprocesora. W przeciwnym wypadku jest obecność jednostki x87
1 MP Monitor Coprocessor Monitor Koprocesora, kontroluje instrukcje WAIT/FWAIT
0 PE Protection Enabled Jeśli 1 system jest w trybie chronionym. Gdy PE ma wartość 0 procesor pracuje w trybie rzeczywistym
  • CR1

Ten rejestr jest zarezerwowany i nie mamy do niego żadnego dostępu.

  • CR2

CR2 zawiera wartość będącą błędem w adresowaniu pamięci (ang. Page Fault Linear Address). Jeśli dojdzie do takiego błędu, wówczas adres miejsca jego wystąpienia jest przechowywany właśnie w CR2.

  • CR3

Używany tylko jeśli bit PG w CR0 jest ustawiony.CR3 umożliwia procesorowi zlokalizowanie położenia tablicy katalogu stron dla obecnego zadania. Ostatnie (wyższe) 20 bitów tego rejestru wskazują na wskaźnik na katalog stron zwany PDBR (ang. Page Directory Base Register).

  • CR4

Używany w trybie chronionym w celu kontrolowania operacji takich jak wsparcie wirtualnego 8086, technologii stronicowania pamięci, kontroli błędów sprzętowych i innych.

Bit Nazwa Nazwa angielska Opis
13 VMXE Enables VMX Włącza operacje VMX
10 OSXMMEXCPT Operating System Support for Unmasked SIMD Floating-Point Exceptions Wsparcie systemu operacyjnego dla niemaskowalnych wyjątków technologii SIMD
9 OSFXSR Operating system support for FXSAVE and FXSTOR instructions Wsparcie systemu operacyjnego dla instrukcji FXSAVE i FXSTOR
8 PCE Performance-Monitoring Counter Enable Licznik monitora wydajności. Jeśli jest ustawiony rozkaz RDPMC może być wykonany w każdym poziomie uprzywilejowania. Zaś jeśli wartość tego bitu wynosi 0, rozkaz może być wykonany tylko w trybie jądra (poziom 0)
7 PGE Page Global Enabled Globalne stronicowanie
6 MCE Machine Check Exception Sprawdzanie błędów sprzętowych jeśli bit ten ma wartość 1. Dzięki temu możliwe jest wyświetlenie przez system operacyjny danych na temat tego błędu jak np w systemie Windows na "błękintym ekranie śmierci"
5 PAE Physical Address Extension Jeśli bit jest ustawiony to zezwalaj na użycie 36-bitowej fizycznej pamięci
4 PSE Page Size Extensions Rozszerzenie stronicowania pamięci. Jeśli 1 to stronice mają wielkość 4 MB, w przeciwnym przypadku 4 KB
3 DE Debugging Extensions Rozszerzenie debugowania
2 TSD Time Stamp Disable Jeśli ustawione, rozkaz RDTSC może być wykonany tylko w poziomie uprzywilejowania 0 (czyli w trybie jądra), zaś gdy równe 0 w każdym poziomie uprzywilejowania
1 PVI Protected Mode Virtual Interrupts Jeśli ustawione to włącza sprzętowe wsparcie dla wirtualnej flagi przerwań (VIF) w trybie chronionym
0 VME Virtual 8086 Mode Extensions Podobne do wirtualnej flagi przerwań


Rejestry jednostki zmiennoprzecinkowej

edytuj
 
Schemat stosu danych FPU

Zostały wprowadzone po raz pierwszy w koprocesorze arytmetycznym Intela (lata 80.), a następnie wrzucone do procesorów pod nazwą jednostki zmiennoprzecinkowej (ang. floating point unit (FPU)).Sam koprocesor zaś został zintegrowany z CPU począwszy od serii i486, co więcej tylko w droższym modelu DX. Liczby zmiennoprzecinkowe są składowane w rejestrach od R0 do R7 w postaci o podwójnej, rozszerzonej precyzji (ang. double extended-precision). Dane mogą być liczbami rzeczywistymi, całkowitymi, liczbami w kodzie BCD (upakowane lub nie), które zostaną automatycznie przekonwertowane do formatu FPU (jeśli wcześniej nie były). Jeśli zaś pewna liczba zapisana w rejestrze koprocesora ma zostać zapisana do komórki pamięci, może być w dowolnym formacie, np w postaci kodu BCD.

Do rejestrów R0-R7 programista nie ma bezpośredniego dostępu, może się do nich odwoływać poprzez ich "aliasy" jakimi są rejestry ST0 - ST7. W związku z tym, że FPU ma budowę stosu rejestr ST0 jest jego szczytem i wcale nie oznacza to, że jest ustawiony na R0. Aliasy nie są przypisane na stałe poszczególnym rejestrom, wręcz przeciwnie są ruchome. ST0 raz może pokazywać na R3 raz na R2. W praktyce programistę w ogóle nie interesują rejestry R(X), dla nas istotne są tylko rejestry ST(X). Oprócz rejestrów przechowujących liczby, analogicznie jak w CPU są także rejestry stanu i flag które mają te same zastosowanie co flagi procesora - dzięki nim zachodzą odpowiednie instrukcje warunkowe.

Rejestry MMX

edytuj

Rozszerzenie MMX wraz ze swoimi rejestrami zostało wprowadzone w procesorach Pentium (1997). Rozszerzenie to pozwala na przeprowadzanie kilku obliczeń stałoprzecinkowych jednocześnie. Rejestry MMX są ponumerowane od MM0 do MM7 i wszystkie są 64-bitowe. Rejestry te nie są wydzielone, lecz są częścią składową rejestrów jednostki stałoprzecinkowej. Modyfikowanie rejestrów od R0 do R7 modyfikuje nasze rejestry MMX i na odwrót. Jako, że rejestry FPU były 80-bitowe a MMX są 64-bitowe, ostatnie 16 bitów przez MMX jest nieużywanych. Rejestry MMX wykorzystywane są również przez rozszerzenia stworzone przez AMD - 3DNow oraz 3DNow!, które z kolei umożliwiają przeprowadzanie obliczeń zmiennoprzecinkowych.

Rejestry XMM

edytuj

Rejestry XMM zostały wprowadzone wraz z rozszerzeniem SSE w procesorach Pentium III (1999). Są ponumerowane od XMM0 do XMM7, każdy po 128 bitów. Rozszerzenie SSE jest kontynuacją rozszerzenia MMX, gdyż założenia są podobne, lecz umożliwiają dodatkowo obliczenia na liczbach zmiennoprzecinkowych. Co więcej rejestry XMM nie są "aliasami" na rejestry FPU, zatem koprocesor i rejestry technologii SSE mogą być używane równolegle.

Pamięć

edytuj

Prezentacja danych w pamięci

edytuj

W architekturze 80x86 formą zapisu bajtów jest Little Endian. Oznacza to, że wielobajtowe wartości są zapisane w kolejności od najmniej do najbardziej znaczącego (patrząc od lewej strony), bardziej znaczące bajty będą miały "wyższe" (rosnące) adresy. Należy mieć na uwadze, że odwrócona zostaje kolejnośc bajtów a nie bitów.

Zatem 32-bitowa wartość B3B2B1B0 mogłaby by na procesorze z rodziny x86 być zaprezentowana w ten sposób:

Reprezentacja kolejności typu little-endian
Byte 0 Byte 1 Byte 2 Byte 3

Przykładowo 32-bitowa wartość 1BA583D4h (literka h w Asemblerze oznacza liczbę w systemie szesnastkowym, tak jak 0x w C/C++) mogłaby zostać zapisana w pamięci mniej więcej tak:

Przykład
D4 83 A5 1B

Zatem tak wygląda nasza wartość (0xD4 0x83 0xA5 0x1B) gdy zrobimy zrzut pamięci.

Tryby adresowania

edytuj

Tryb adresowania wyznacza sposób w jaki dostajemy się do adresu, czyli położenia naszego operandu (operandu czyli argumentu danego rozkazu). Na początek może Ciebie przerazić ilość tych trybów jednak zauważ, że wszystkie są dość do siebie podobne. Dodatkowo niektóre tryby są jakby modyfikacją poprzednich, jak na przykład trzy ostatnie. Czasem wystarczy tylko zmienić zapis operandu i mamy zupełnie inny tryb adresowania. Pisząc program nieraz będziesz korzystał z któregoś z sposobów określenia położenia operandu choć może sam nawet nie będziesz o tym wiedział, ani tym bardziej jak on się nazywa. Co więcej spora część z tych trybów jest całkiem intuicyjna, lecz czasem trzeba uważać na małe "kruczki".

Adresowanie rejestrowe
(wartość operandu zawarta jest w jakimś rejestrze ogólnego przeznaczenia)
mov ax, bx
Adresowanie bezpośrednie (nazywane także natychmiastowym)
(wartość danego operandu jest już podana)
mov ax, 1

lub

mov ax, 010Ch
Adresowanie pośrednie
(wartość dla danego rozkazu znajduje się w komórce o adresie podanym w operandzie)
mov ax, [102h]; Adres końcowy to DS:0 + 102h

Wartość w tych dwóch nawiasach jest odpowiednikiem "to co znajduje się w komórce o adresie 102h"). Jeżeli programujesz lub programowałeś w języku C bądź C++ to ten sposób adresowania jest podobny do operatora wyłuskania. Oto odpowiednik w C(++):

int ax = *0x102;
Adresowanie pośrednie z przemieszczeniem
(używa się operacji arytmetycznych na danym adresie)
byte_tbl db 12,15,16,22,..... ;tablica bajtów
mov al,byte_tbl+2
mov al,byte_tbl[2] ;to samo lecz w innej formie

Co w C/C++ byłoby niemal takie same. Aby dostać się do elementu położonego o dwa bajty od początku należy dodać 2 lub odwołać się do elementu trzeciego (licząc od zera) jak w wspomnianych językach wysokiego poziomu.

Adresowanie rejestrowe, pośrednie
(w danym rejestrze zawarty jest adres komórki z pożądaną wartością)
mov ax,[di]

Nie myl tego z adresowaniem rejestrowym. Zobacz przykład:

mov dx,si ;skopiuj do DX wartość rejestru SI
mov dx,[si] ;skopiuj do rejestru DX coś co jest w komórce pamięci o adresie zapisanym w SI. Jeśli SI
;ma np. wartość 0D7h to do rejestru DX zostanie skopiowane to, co znajduje się właśnie w tej komórce

Rejestry używane do tego typu adresowania to: BX, BP, SI, DI

Adresowanie bazowe z przemieszczeniem
mov ax, arr[bx]; gdzie BX jest przemieszczeniem wewnątrz tablicy ARR

Ten tryb adresowania jest analogiczny do docierania do danego elementu tablicy w C. Porównaj sobie powyższy kod z poniższym:

int AX,BX; //nazwa zmiennej taka aby kod był jak najbardziej podobny do asemblerowego
int arr[4]= {6,5,34,22}; //zakładam oczywiście że BX ma wartość od 0 do 3
AX = arr[BX];
Adresowanie bazowo-indeksowe
mov ax,[bx + di]
Przykładowo, jeśli mówimy o tablicy, BX jest początkiem tablicy (zawiera adres jej pierwszego elementu), zaś DI jest indeksem w danej tablicy.
Adresowanie bazowo-indeksowe z przemieszczeniem
mov ax,[bx + di + 10]

Wejście - wyjście

edytuj

Programy komputerowe przetwarzają dane, jednak aby te dane móc przetworzyć należy najpierw załadować je do pamięci. Źródłem danych w systemie komputerowym są urządzenia wejścia, czyli najczęściej: mysz, klawiatura, napęd cd-rom czy dysk twardy. Do komunikacji programu z tymi urządzeniami służą instrukcje wejścia i wyjścia operujące na ponumerowanych portach. Służą one niskopoziomowemu programowaniu tychże urządzeń i to właśnie z tych instrukcji korzystają sterowniki urządzeń w systemach operacyjnych. Działanie tych instrukcji jest dość proste: mają za zadanie skopiować dane z rejestrów procesora do odpowiednich portów wyjściowych procesora (w przypadku instrukcji out) lub też pobrać dane z portów wejściowych procesora do rejestrów procesora (instrukcja in). Porty wejścia i wyjścia są elektronicznie połączone z odpowiednimi rejestrami sterującymi konkretnego sprzętu. Sprzęt reaguje na zmiany rejestrów sterujących oraz sam je modyfikuje w sposób opisany w swojej dokumentacji.


Przypisy

  1. Poczynając od 80386.
  2. Możliwość taką daje rozszerzenie PAE, musi być ono jednak obsługiwane także przez system operacyjny. W praktyce nie stosuje się tego rozwiązania w komputerach domowych.