Asembler x86/Architektura
Nadeszła pora na omówienie budowy procesora z punktu widzenia programisty.
Rejestry
edytujW 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
edytujRejestr 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
edytujJak 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.
|
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
edytujW 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
edytujprocesor | 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
edytujZ 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
edytujStos 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.
Opcjonalnie, między SFP a zmiennymi lokalnymi mogą pojawić się wartości rejestrów zachowane na czas wykonania funkcji (praktyczna strona zagadnienia - w rozdziale Funkcje). |
Rejestr flag
edytujProgramista 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:
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 |
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
edytujPonadto 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ń |
W przygotowaniu: Niech ktoś to po mnie nieco poprawi/skoryguje - Doles |
Rejestry jednostki zmiennoprzecinkowej
edytujZostał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
edytujRozszerzenie 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
edytujRejestry 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.
W przygotowaniu: Znacznie rozwinąć opisy 3 powyższych rejestrów |
Pamięć
edytujPrezentacja danych w pamięci
edytujW 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:
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:
D4 | 83 | A5 | 1B |
Zatem tak wygląda nasza wartość (0xD4 0x83 0xA5 0x1B) gdy zrobimy zrzut pamięci.
Tryby adresowania
edytujTryb 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
edytujProgramy 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.