6. Elementy asemblera dla procesorw X86

Transkrypt

6. Elementy asemblera dla procesorw X86
5.
Elementy asemblera
dla procesorów x86
269
5.1. Charakterystyka języka asemblera
________________________________________________________
Każdy procesor wykonuje rozkazy w postaci binarnej, zapisane w pamięci operacyjnej
komputera. Programista zapisuje program w formie wyższej niż poziom binarny poczynając
od symboli mnemonicznych (w języku asemblera) kończąc na obiektach języków wysokiego
poziomu programowania.
Zapis w formie znaków i wyrazów umownych nazwany jest postacią źródłową programu. Natomiast format źródłowy programu zostaje zamieniony (przetłumaczony) na kody
maszyny, za pomocą translatora. Niniejszy rozdział przedstawia najniższy poziom języka
programowania zwany językiem asemblera, lub językiem symboli mnemonicznych. Zapis
źródłowy tego programu zostaje zamieniony na kod maszynowy za pomocą translatora zwanego asemblerem.
Język asemblera umożliwia:
- zastąpienie kodów dwójkowych rozkazów procesora znakami symbolicznymi,
- zapisanie argumentów rozkazów w formie symbolicznej,
- komunikowanie się z systemem operacyjnym komputera.
Nieco wyższą formą tego języka jest Makroasembler. Daje on możliwość symbolicznej reprezentacji ciągów rozkazów. Podobnie jak dla języków wysokiego poziomu. Poniżej
omówiono elementy MAMS, jednego z podstawowych asemblerów dla komputerów osobi-
270
stych z procesorami firmy Inlet. Na tym przykładzie opisane zostały formaty podstawowych
rozkazów języka asemblera.
Alfabet języka
Programy źródłowe pisane w języku asemblera zawierają znaki z następującego zbioru (alfabetu):
- litery małe (do a do z) i duże (od A do Z),
- cyfry od 0 do 9,
- znaki specjalnych, jak: + - * / = ( ) [ ] < > . , : ? @ $ & %,
- niewidoczne znaki ASCII, jak:
- Space (20H) - odstęp = spacja,
- CR (0DH) - powrót kursora,
- NL (0AH) - nowa linia.
Umieszczenie w programie źródłowym znaku spoza tej listy będzie sygnalizowane
przez asembler jako błąd syntaktyczny. Rozróżnianie liter (duże/małe) odbywa się wyłącznie
przy operacjach tekstowych na tzw. łańcuchach. Słowa kluczowe języka asemblera, mogą być
zapisywane literami małymi i dużymi.
Stałe programu
W programach asemblera wyróżnione zostały dwa rodzaje stałych:
- numeryczne, będące liczbami całkowitymi,
- alfanumeryczne; inaczej tekstowe lub łańcuchowe.
Stałe numeryczne mogą być zapisywane w jednym z czterech systemów: dwójkowo,
ósemkowo, dziesiętnie lub szesnastkowo.
Liczba dwójkowa jest zapisywana cyframi 0 i 1 na szesnastu bitach. Zapis wartości
liczby szesnastkowej musi być zakończony literą B (lub b), na przykład: 1011011b, 0B,
01100B.
Liczba ósemkowa jest zapisywana cyframi od 0 do 7 zastępującymi trzy pozycje zapisu binarnego od 000 do 111. Maksymalna liczba ósemkowa jest reprezentowana zapisem
177777. Liczba ósemkowa musi być zakończona znakiem O lub Q (o lub q), na przykład 0Q,
5o, 452Q.
Liczba dziesiętna jest zapisywana cyframi z przydziału od 0 do 9, zastępującymi pozycje binarne z przedziału od 0000 do 1001. Liczba dziesiętna może być zakończona literą D
(lub d), choć nie jest to warunek konieczny.
271
Liczba szesnastkowa jest zapisywana cyframi od 0 do 9 i literami od A do F, dla
liczb, odpowiednio, od 10 do 16. Maksymalna liczba szesnastkowa ma zapis FFFF. Zapis
szesnastkowy kończy litera H (lub h), na przykład: 1FC3H lub 02DFh.
Stałe alfanumeryczne to znaki kodu ASCII ujęte w apostrofy lub cudzysłowy, na
przykład: 'zapis tekstu', "test", itp. Stałe alfanumeryczne zawierające jeden bajt lub jedno słowo mogą być przedstawione w formie numerycznej - odpowiednikiem heksadecymalnym z
tablicy kodów ASCII, na przykład: MOV CL, 'B' jest równoważne zapisowi MOV CL, 42H.
Słowa kluczowe i nazwy symboliczne
Słowo kluczowe ma w języku asemblera specjalne znaczenie. Jego nazwa pozostaje
zastrzeżona i może być użyta tylko zgodnie z jego definicją. Nie jest możliwe użycie nazwy
takiej samej jak słowo kluczowe w innym kontekście. Przykłady słów kluczowych to MOV,
LDA, STA, SI, itp.
W języku asemblera można również wprowadzić nazwy własne o szczególnym znaczeniu zwane nazwami symbolicznymi. Służą one do identyfikacji zmiennych, stałych, adresów symbolicznych (etykiet), oraz specjalnych konstrukcji programu. Do nazw specjalnych
zaliczamy: segment, rekord, grupa oraz parametry formalne tych konstrukcji. Wszystkie
nazwy symboliczne muszą odpowiadać pewnym regułom, jak:
- pierwszy znak nazwy jest literą (a ..z, A..Z) lub jednym ze znaków @, ?. nazwa symboliczna
może być również pojedynczym znakiem z wyjątkiem pytajnika),
- każdy znak może być cyfrą dziesiętną 0 .. 9,
- nazwa może być dowolnie długa, ale tylko 31 znaków jest rozróżnialnych.
Składania języka
Program źródłowy napisany w języku asemblera zawiera określone formy tekstowe,
jak:
- rozkazy (zwane również instrukcjami), będące symbolicznym zapisem kodu maszynowego procesora,
- dyrektywy, odnoszące się do działań asemblera,
- makrorozkazy (makroinstrukcje), reprezentujące ciąg rozkazów podstawowych.
272
Każdy wiersz programu kończy znak końca linii (LF) i znak powrotu kursora (CR).
Jeśli wiersz jest zbyt krótki, aby w nim zapisać całą treść rozkazu, można go kontynuować w
linii następnej po umieszczeniu na jej początku znaku &.
W zapisie wiersza w języku asemblera można wyróżnić pięć podstawowych części:
[etykieta:][przedrostek][rozkaz][argumenty][;komentarze]
Nawiasy kwadratowe oznaczają dowolność zapisu pola, tzn. że pole nie musi wystąpić.
• Etykieta jest nazwą symboliczną adresu komórki pamięci, do której ma nastąpić skok.
• Przedrostek jest rozszerzeniem rozkazu stosowanym w niektórych złożonych konstrukcjach programowych (np. REP - repetycja).
• Rozkaz/Instrukcja jest symbolem mnemonicznym (symbolicznym) kodu rozkazu dla
procesora.
Może
być
również
nazwą
wcześniej
zdefiniowanego
makrorozka-
zu/makroinstrukcji. Pole może pozostać puste, ale wtedy musi wystąpić etykieta lub komentarz.
• Argumenty reprezentują operandy rozkazów/instrukcji. Rozkaz może być bezargumentowy, z jednym lub z dwoma argumentami.
• Komentarz jest tekstem poprzedzonym średnikiem i zakończonym znakami nowej linii i
powrotu kursora. Komentarz nie jest interpretowany przez asembler, jest on jedynie opisem umożliwiającym objaśnienie kolejnych fragmentów programu.
273
5.2. Dyrektywy i pseudoinstrukcje
___________________________________________________________________________
W języku makroasemblera możliwe jest zastosowanie złożonych form programowych
zwanych dyrektywami. Spełniają one funkcje sterujące w programie i funkcje generacji danych. Dyrektywa może mieć nazwę, którą umieszcza się przed słowem kluczowym opisującym jej działanie lub może występować bez nazwy. Dyrektywy mogą być bezargumentowe
lub zawierać od jednego do kilku argumentów. Wiersz programu z dyrektywą może być zakończony komentarzem, tak jak w przypadkach opisanych w poprzednim rozdziale.
Dyrektywy
Dyrektywa jest elementem sterowania asemblera, który nie pozostawia śladu w kodach wynikowych programu (po asemblacji), podobnie jak pseudoinstrukcja generująca stałe
lub deklarująca zmienne (dane) w programie. Język MASM (Makro ASeMbler) dopuszcza
około 50 różnych dyrektyw sterujących pracą asemblera oraz instrukcji generujących dane.
Wiersz programu z dyrektywą lub (pseudo)instrukcją zawiera następujące pola:
[nazwa] dyrektywa/instrukcja [argumenty] [;komentarze]
• Nazwa występuje w niektórych dyrektywach. Nazwa dyrektywy nie może być używana w
programie tak jak etykieta.
• Argument dyrektywy może być pusty (dyrektywa bezargumentowa), pojedynczy lub wieloelementowy (jeden lub więcej argumentów). Parametry dyrektywy oddzielane są przecinkiem lub spacją.
274
• Komentarz rozpoczyna średnik po czym następuje część opisowa., która nie jest analizowana przez asembler. Jest to jedynie element redakcyjny źródłowej formy programu.
Kody programu napisanego w języku asemblera dla procesorów 8086 można ulokować w dowolnym miejscu przestrzeni pamięciowej w komórkach od 00000H do FFFFFH, tj.
w obszarze 1 MB definiowanym przez 20-bitowy adres.
Segmenty logiczne
Każdy rozkaz lub zmienna w programie muszą być umieszczone wewnątrz bloku logicznego, zwanego segmentem. Z tworzeniem segmentów logicznych asemblera związane są
dyrektywy: SEGMENT/ENDS, GROUP, ORG, EVEN, ASSUME, LABEL, PROC/ENDP.
Dyrektywy SEGMENT i ENDS występują w następującej konstrukcji programowej:
nazwa SEGMENT [typ] [połączenie]['nazwa_klasy']
; początek konstrukcji
...
nazwa ENDS
; koniec segmentu
Argumenty występujące po dyrektywie SEGMENT określają jej atrybuty. Wszystkie
rozkazy/instrukcje i dane występujące po dyrektywie SEGMENT odnoszą się do identyfikatora nazwa - aż do dyrektywy ENDS zamykającej segment. Obie dyrektywy (SEGMENT i
ENDS) musi poprzedzać ta sama nazwa.
Typ określa lokację w pamięci początku nowego (właśnie zdefiniowanego) segmentu,
zdefiniowanego za pomocą jednej z czterech form typu:
• PARA oznacza, że początek segmentu będzie zlokalizowany pod najbliższym wolnym adresem podzielnym bez reszty przez 16 (16 = długość słowa adresu efektywnego). Jeśli po
dyrektywie segment nie ma deklaracji typu, asembler traktuje przypadek jako równoważny
deklaracji PARA. W szczególnym przypadku może pozostać niewykorzystanych do 15
bajtów pamięci w segmencie.
• BYTE oznacza deklarację początku nowego segmentu bez odstępu od segmentu poprzedniego (z dokładnością do jednego bajtu). To znaczy z zapisaniem niewykorzystanych komórek poprzedniego segmentu.
• WORD oznacza deklarację początku nowego segmentu z dokładnością do dwóch bajtów
adresowych. Jest to deklaracja bliźniacza do dyrektywy bajt określająca początek segmentu z odstępem nie większym od jednego bajtu w stosunku do segmentu poprzedniego.
• PAGE określa początek segmentu od najbliższej strony adresowej - od adresu podzielnego
bez reszty przez 256.
275
Połączenie jest argumentem określającym sposób i kolejność łączenia segmentu z
innymi segmentami. Brak tego argumentu oznacza, że segment ma charakter lokalny i nie jest
łączony z żadnym innym segmentem. Argument połączenia może być deklarowany za pomocą pięciu rodzajów połączeń:
• PUBLIC jest połączenie z segmentami o tej samej nazwie występującymi w różnych modułach tworząc w ten sposób jeden wspólny segment. Wszystkie łączone segmenty mają
ten sam adres początkowy. Różnią się jedynie ofsetem poczynając od początku pierwszego
aż do końca ostatniego.
• COMMON jest połączeniem oznaczającym ładowanie do tego samego obszaru pamięci
segmentów o tej samej nazwie przynależnych do różnych modułów programowych. Rozmiar rezerwowanej pamięci odpowiada rozmiarowi segmentu największego.
• STACK jest połączeniem segmentów o tej samej nazwie. Rezerwują one jeden wspólny
obszar dla stosu pamięci o wspólnym adresie początkowym i rozmiarze równym sumie
rozmiarów wszystkich połączonych segmentów.
• ‘AT'adres' jest połączeniem ładującym określone segmenty do komórek pamięci o lokacji
wskazywanej adresem 16 bitowym = adres.
• Bez deklaracji argumentu jest połączeniem segmentów o tych samych nazwach w tym samym module w jeden segment.
‘nazwa_klasy’ jest argumentem według którego program łączący grupuje segmenty o tej samej nazwie klasy, należące do różnych modułów. Nazwa może zawierać do 40 znaków
umieszczonych w apostrofach. Argument nie ma charakteru porządkującego.
Dyrektywa GROUP występuje w następującej konstrukcji programowej:
nazwa GROUP lista_argumentów
Jest to konstrukcja umożliwiająca łączenie w jeden 64 kB blok pamięci, segmentów
wymienionych w liście argumentów.
Dyrektywa ORG deklaruje tzw. wskaźnik pozycji asemblera, będący ofsetem dla adresu rozkazu, który w następnej kolejności jest pobierany do asemblacji. Asembler po napotkaniu dyrektywy SEGMENT ustawia wskaźnik asemblacji na zero. Asemblacja kolejnych
bajtów segmentu powoduje zwiększanie zawartości wskaźnika asemblacji o jeden, dla każdego bajtu. ENDS kończy zliczanie bajtów. Dyrektywa ORG ustawia nowy offset adresowy, dla
bieżącego segmentu, na wskazanej (parametrem dyrektywy) komórce pamięci.
Konstrukcja programowa: ORG wyrażenie.
276
Przykład:
PROGRAM
SEGMENT
ORG 255H
- oznacza, że segment ma początek w komórce
255 H.
Wartość wyrażenia jest liczbą całkowitą z przedziału [0 - 65535]. Wyrażenie może
być nazwą symboliczną, wcześniej zdefiniowaną, ale nie może to być etykieta.
Dyrektywa EVEN jest bezparametrowa tzn. że ustawia wskaźnik pozycji asemblacji
na najbliższy parzysty adres, o ile aktualny adres jest nieparzysty.
Dyrektywa ASSUME służy do deklaracji zawartości czterech rejestrów segmentowych dla bieżącego programu. Większość rozkazów komunikacji z pamięcią korzysta z 16
bitowego słowa adresowego, odnosząc adres fizyczny do zawartości odpowiedniego rejestru
segmentowego. Każde działanie na danych w programie lub na stosie wymaga wcześniejszego określenia zawartości DS, SS i ES, czyli przed rozkazami związanymi z dostępem do pamięci musimy umieścić dyrektywę ASSUME. Podobnie postępujemy z rejestrem CS, którego
zawartość musi odpowiadać lokacji kodów. Deklaracja zawartości CS musi nastąpić przed
pierwszą instrukcją poprzedzoną etykietą.
Konstrukcja programowa: ASSUME rej_seg: nazwa_seg [, rej_seg: nazwa_seg, ...]
rej_seg - jeden z rejestrów CS, DS, SS, ES.
nazwa_seg. - nazwa określonego segmentu lub grupy segmentów zdefiniowanych
dyrektywą GROUP.
Dyrektywa LABEL - umożliwia zdefiniowanie typu nazwy symbolicznej reprezentującej określoną zmienną lub etykietę.
Konstrukcja programowa: nazwa LABEL typ
Dyrektywa LABEL przyporządkowuje adresowi pamięci nazwę oraz określony typ,
gdzie typ może być zdefiniowany jako: BYTE, WORD lub DWORD.
Dyrektywy PROC i ENDP - służą do definiowania podprogramów wywoływanych za
pomocą rozkazu maszynowego CALL. Powrót do programu głównego odbywa się za pomocą
rozkazu RET.
Konstrukcja programowa:
nazwa PROC typ
........
; podprogram
RET ; rozkaz powrotu z podprogramu
........
nazwa ENDP
277
gdzie typ może przyjmować wartość:
NEAR - dla skoków wewnątrz segmentu,
FAR - dla skoków między segmentami. Podprogramy mogą znajdować się
wewnątrz innych podprogramów, ale muszą w całości mieścić się
wewnątrz jednego segmentu.
Nazwy symboliczne
Do tej grupy zaliczamy dyrektywy powoływania nazw symbolicznych (EQU) i ich
usuwania (PURGE).
EQU jest dyrektywą umożliwiającą definiowanie nazw symbolicznych przypisywanych określonym wartościom numerycznym adresu.
Konstrukcja programowa: nazwa EQU wartość
Dyrektywa PURGE pozwala usunąć nazwę symboliczną z tablicy nazw symbolicznych, podczas asemblacji programu. Następnie ta sama nazwa może zostać wykorzystana po
ponownej definicji dyrektywą EQU.
Konstrukcja programowa: PURGE nazwa_1 [, nazwa_2, .... , nazwa_n]
Połączenia międzymodułowe
Do tej grupy zaliczamy dyrektywy pozwalające realizować programy w postaci oddzielnych modułów. Taka technika wykonywania oprogramowania ma istotne znaczenie w
przypadku realizacji programów bardzo dużych. Moduły są uruchamiane i kodowane niezależnie - często przez różnych programistów. Związki występujące między modułami oraz ich
cechy są definiowane dyrektywami: NAME, PUBLIC, EXTRN, END i INCLUDE.
Dyrektywa NAME przypisuje nazwę modułowi (do sześciu znaków) wykorzystywaną
dalej przez program łączący.
Konstrukcja programowa: NAME nazwa_modułu
Dyrektywa PUBLIC definiuje wewnętrzne symboliczne nazwy globalne, określone
wewnątrz modułu; dostępne dla innych modułów łączonych w jedną całość.
Konstrukcja programowa: PUBLIC nazwa_1 [, nazwa_2, nazwa_3, ...]
Przedstawione w konstrukcji programowej nazwy mogą być etykietami, nazwami
zmiennych lub zdefiniowane dyrektywą EQU.
Dyrektywa EXTRN uzupełnia dyrektywę PUBLIC o zewnętrzne symbole globalne
używane w bieżącym module, ale zdefiniowane w innym module.
Konstrukcja programowa: EXTRN nazwa_1: typ_1 [, nazwa_2: typ_2, ...]
278
Typ nazwy może przyjmować wartości: WORD, DWORD, NEAR, FAR lub ABS.
Dyrektywa END wskazuje zakończenie programu źródłowego i zatrzymanie procesu
asemblacji.
Konstrukcja programowa: END [etykieta]
Dyrektywa INCLUDE umożliwia dołączenie do bieżącego programu innego pliku
źródłowego w punkcie oznaczonym dyrektywą INCLUDE.
Konstrukcja programowa: INCLUDE nazwa_pliku
Deklaracja danych
Procesor 8086 może przetwarzać dane o długości bajtu (DB), słowa 16-bitowego
(DW) i podwójnego słowa 32-bitowego (DD). Dla koprocesora 8087 istnieje możliwość deklaracji czterech bajtów (DQ) lub dziesięciu bajtów (DT).
Dyrektywy DB, DW, DD, DQ, DT stosuje się do rezerwacji pamięci dla danych, które mogą przyjmować wartości wymagające użycia słowa o odpowiedniej długości.
Konstrukcja programowa: [nazwa] Dx wyrażenie [,wyrażenie, ...]
gdzie: Dx = DB, DW, ... , DT. Wyrażenie może być wartością numeryczną, łańcuchem, adresem lub ? - wartością nie zdefiniowaną.
Dyrektywa RECORD umożliwia przydzielenie pamięci operacyjnej na poziomie bitów dla rekordu o określonej nazwie.
Konstrukcja programowa:
nazwa_rekordu RECORD nazwa_pola_1: wymiar [=wyrażenie]
wymiar - oznacza długość pola w bitach,
wyrażenie - określa wartość w polu rekordu w chwili jego inicjacji.
Raport asemblacji
W czasie asemblacji (tłumaczenia) zapisu źródłowego programu sporządzana jest jego
dokumentacja zwana raportem asemblacji. W raporcie zawarte są, oprócz zapisu źródła, kody
maszynowe rozkazów, adresy, rozwinięcia makroinstrukcji i komunikaty o błędach. Opracowanie określonej postaci raportu zapewniają specjalne dyrektywy zapisu jego formatu:
• PAGE jest deklaracją formatu strony raportu,
• TITLE jest deklaracją nagłówka każdej strony raportu,
• COMMENT jest deklaracją komentarza (bez użycia znaku średnika), i inne.
279
280
5.3. Wyrażenia i operatory
___________________________________________________________________________
W języku asemblera rozróżnia się wyrażenia numeryczne oraz wyrażenia adresowe,
konstruowane za pomocą operatorów. Operatory języka asemblera można podzielić na pięć
podstawowych grup:
- operatory dla wyrażeń adresowych, określające przedrostek dla rejestru segmentowego, jak:
OFFSET, SEG, TYPE, LENGHT, SIZE,
- operatory dla wyrażeń numerycznych to: *, /, MOD, SHL, SHR, OR, XOR, AND, NOT,
SHORT,
- operatory dla obu poprzednio wymienionych grup wyrażeń: EQ, LT, LE, GT, GE, NE, +, -,
PTR,
- operatory dla dyrektywy RECORD: SHIFT, WIDTH, MASK
- operatory dla wskaźnika pozycji: THIS, $.
Operatory dla wyrażeń adresowych
Przedrostek dotyczy rejestru segmentowego. Adres fizyczny jest obliczany na podstawie offsetu i zawartości jednego z rejestrów segmentowych (CS, SS, DS lub ES). Rejestry
segmentowe są standardowo przypisane odpowiednim rozkazom. Użycie niestandardowe rejestrów segmentowych musi zostać poprzedzone dyrektywą ASSUME lub przedrostkiem argumentu operacji, np. DS: wyrażenie_adresowe (definiujące adres lokacji argumentu).
Operator OFFSET wyodrębnia z bieżącego adresu odstęp adresowy od początku segmentu, który w trakcie asemblacji nie ulega zmianie.
Operator SEG wydziela z wyrażenia adresowego 16-bitowy adres początku segmentu.
Operator TYPE określa liczbę całkowitą charakterystyczną dla zmiennej lub etykiety:
1 - dla typu bajt,
2 - dla słowa dwubajtowego,
281
4 - dla czterech bajtów (podwójne słowo), itp.
Operator SIZE określa wymiar zmiennej w bajtach.
Operatory dla wyrażeń arytmetycznych
Operatory *, /, MOD należą do grupy operatorów multiplikatywnych, realizujących
odpowiednio: mnożenie, dzielenie całkowitoliczbowe i resztę z dzielenia całkowitoliczbowego.
Operatory a SHR b, a SHL b dokonują przesunięć argumentu (a) o zadaną liczbę bitów (b) odpowiednio w kierunku pozycji młodszych lub starszych.
Operatory OR, XOR, AND, NOT należą do grupy operatorów logicznych, wykonujących operacje, odpowiednio: sumy, alternatywy wyłączającej (sumy modulo 2), iloczynu i
negacji.
Operator SHORT - zapisuje wynik operacji w jednym bajcie. Jeśli zapis w jednym
bajcie nie jest możliwy (nie mieści się) sygnalizowany jest błąd deklaracji a kod maszynowy
nie zostaje wygenerowany.
Operatory porównań EQ, LT, LE, GT, GE, NE umożliwiają porównanie dwóch wyrażeń według następujących reguł:
EQ - równość wyrażeń,
LT - bada stan mniejszy niż,
LE - bada stan mniejszy lub równy,
GT - stan większy niż,
GE - stan większy lub równy,
NE - stan nierówny.
Jeśli wynik porównania jest prawdziwy to wyrażenie zawierające odpowiedni operator
przyjmuje wartość 1, w przeciwnym przypadku wartość 0.
Operatory +/- (plus/minus) - realizują sumę i różnicę arytmetyczną liczb całkowitych,
zmiennych lub etykiet.
Operator PRT - umożliwia przypisanie typu wyrażeniu adresowemu: BYTE, WORD
lub DWORD dla zmiennych oraz FAR i NEAR dla etykiet.
Operator $ określa aktualną wartość wskaźnika pozycji asemblera.
Operatory manipulacji polami bitów są związane z dyrektywą RECORD:
• WIDTH umożliwia obliczenie długości pól rekordu lub poszczególnych jego pól,
282
• MASK pozwala na wyodrębnienie określonego pola rekordu przez zamaskowanie pól nieważnych.
283
5.4. Podstawowe rozkazy mikroprocesora 8086
___________________________________________________________________________
Temat rozkazów poziomu maszynowego dla procesorów 16-bitowych poruszano w
tym podręczniku już wielokrotnie. Głownie przy okazji dyskusji metod wyznaczania adresów
logicznych i fizycznych, przy okazji pokazano grupy rozkazów oraz zasady ich działania. Na
przykładach języka wysokiego poziomu omówiono również zasady organizacji programów w
językach maszynowych.
Mikroprocesory firmy Intel charakteryzuje dziedziczność kodów maszynowych dla
kolejnych ich generacji. Poznanie zasad kodowania programów dla systemów z mikroprocesorami 8-bitowymi znacznie ułatwia zrozumienie zasad programowania w języku asemblera
procesorów wyższej generacji.
Lista rozkazów dla procesorów 8080/8085 została uzupełniona o rozkazy nowe z których na uwagę zasługują:
- mnożenie i dzielenie dwójkowe oraz dwójkowo-dziesiętne, ze znakiem i bez znaku,
- badanie określonych bitów,
- przetwarzanie tablic (łańcuchów),
- sterowanie skokami, wywołanie podprogramów, organizacja pętli programowych,
- programowa obsługa przerwań.
Rozkazy transmisji danych
A. Rejestry/Pamięć
MOV
jest to rozkaz przesunięcie bajtu lub słowa, z rejestru do rejestru
Zapis:
MOV argument1, argument2
Działanie:
r1: r1 ← (r2) – oznacza kopiowanie zawartości rejestru r2 do rejestru r1
P r z y k ł a d 1: MOV WYNIK_1, AX - oznacza skopiowanie zawartości
akumulatora do komórki pamięci wskazywanej adresem symbolicznym
WYNIK_1
284
P r z y k ł a d 2: MOV WYNIK_2[BX][DI+5], AX - zawartość rejestru AX
zostaje skopiowana do komórki pamięci lokalizowanej adresem względnym indeksowanym.
Format:
1010 001w adrL adrH
- kopiowanie zawartości akumulatora do
komórki pamięci.
gdzie: w = 0 dla części AL, w = 1 dla AX - całego akumulatora.
W tabl. 5.1 przedstawiono kombinacje argumentów uwzględnianych w liście rozkazów dla operacji przesłań rozkazem MOV.
Tablica 5.1
Przykłady możliwych operacji dla rozkazu MOV
Przeznaczenie
Źródło
rejestr
rejestr segmentowy
pamięć
+
+
+
+
+
x
+
x
+
+
x
+
rejestr
rejestr segmentowy
pamięć
wartość bezpośrednia
Objaśnienia:
+
x
oznacza operacja istnieje,
oznacza brak możliwości przesłania dla rejestrów segmentowych z wyłączeniem CS
Dla innych operacji przesunięć zawartości rejestrów mamy do czynienia z identycznym zapisem formalnym, zmieniają się jedynie kody maszynowe, których programista i tak
nie zauważa. Na przykład transmisja w drugą stronę, zapisana w przestawnym szyku mnemonicznym:
MOV AX, WYNIK_1
zostaje zakodowana inną wartością jednego bitu:
1010 000w
adrL adrH
PUSH
jest to rozkaz przesunięcia zawartości wskazanego rejestru na szczyt stosu,
Zapis:
PUSH rejestr_źródłowy
Działanie:
[SP] ← (RP) - jest to przesunięcie zawartości wskazanego rejestru do komórki
pamięci wskazywanej zawartością wskaźnika stosu [SP]. po czym jest modyfikowany wskaźnik stosu: SP ← (SP) - 2
P r z y k ł a d 1: PUSH ES - oznacza przesunięcie zawartości rejestru segmentowego ES na szczyt stosu.
P r z y k ł a d 2: PUSH AX - oznacza przesunięcie zawartości AX do komórki pamięci lokalizowanej adresem (SP).
285
Format:
0101 0 reg
- jest to kod rozkazu przesunięcia zawartości rejestrów na szczyt
stosu (z wyłączeniem rejestrów segmentowych).
Dla innych operacji PUSH mamy do czynienia z identycznym zapisem formalnym,
zmieniają się jedynie kody maszynowe. Na przykład przesunięcie zawartości rejestrów segmentowych jest kodowane na sześciu bitach z wyborem jednego z czterech rejestrów segmentowych:
000 reg 110
gdzie reg koduje jeden z czterech rejestrów, odpowiednio: ES, DS, CS
i SS, na przykład: PUSH ES, czy PUSH DS
POP
jest to rozkaz pobrania słowa ze szczytu stosu do wskazanego rejestru
Zapis:
POP rejestr_przeznaczenia
Działanie:
RP ← [SP] - jest to kopiowanie zawartości wskazanej przez SP i SP+1 komórek pamięci do pary rejestrów, po czym jest modyfikowany wskaźnik stosu:
SP ← (SP) + 2.
P r z y k ł a d 1: POP ES - oznacza skopiowanie dwóch bajtów zapisanych na
szczycie stosu do rejestru segmentowego ES.
P r z y k ł a d 2: POP AX - oznacza skopiowanie do AX zawartości dwóch
komórek pamięci lokalizowanych adresem: (SP) i (SP) +1.
Format:
0101 1reg
- jest to kod rozkazu przeniesienia zawartości szczytu stosu do
wskazanych rejestrów (z wyłączeniem rejestrów segmentowych).
Dla innych operacji związanych z rozkazem POP mamy do czynienia z identycznym
zapisem formalnym. Zmiany dotyczą jedynie kodów maszynowych rozkazu. Na przykład
kopiowanie do rejestrów segmentowych zawartości szczytu stosu jest kodowane na sześciu
bitach z wyborem jednego z czterech rejestrów segmentowych:
000 reg 111
gdzie reg koduje, odpowiednio wartości ES, DS, CS i SS, na przykład:
POP ES czy POP DS.
XCHG
jest rozkazem dla operacji zamiany miejscami zawartości wskazanych bajtów
lub słów danych,
Zapis:
XCHG rejestr1, rejestr2
Działanie:
rozkaz powoduje wzajemną zamianę miejscami obu argumentów.
P r z y k ł a d 1: XCHG CX, BX - oznacza zamianę wzajemną zawartości 16bitowych rejestrów CX i BX,
P r z y k ł a d 2: XCHG SI,DI
286
Format:
1000 011w 11 reg reg - jest to dwubajtowy kod rozkazu wymiany zawartości
szczytu wskazanych rejestrów (reg reg). Wartość w ma znaczenie takie samo jak dla MOV; para X (w =
1) lub rejestr pojedynczy (w = 0).
Operacja wymiany zawartości wskazanego rejestru z zawartością akumulatora jest
kodowana jednobajtowo:
1001 0 reg - np. XCHG AX, BX
XLAT
jest rozkazem dla pobrania bajtu danych z tablicy i przesłanie do rejestru AL.
Zapis:
XLAT
Działanie:
rozkaz korzysta z tablicy, dla której offset adresowy rezyduje w rejestrze BX,
natomiast indeks adresowy jest zapisany w rejestrze AL. Rozkaz wykonuje
operację: AL ← ((BX + AL)) adres logiczny określa zawartość BX i AL. Zawartość tej komórki zostaje wpisana do rejestru AL.
Format:
1101 0111 - rozkaz ma stały kod.
B. Porty WE/WY
IN
jest rozkazem pobrania bajtu lub słowa z (portu) rejestru wejściowego,
Zapis:
IN akumulator, adres
Działanie:
rozkaz powoduje wpisanie zawartości portu wejściowego do rejestru AL lub
pary AX. Rozkazy czytające 16-bitowe dane adresują port zawartością rejestru
DX. Adres może następować w drugim bajcie rozkazu.
P r z y k ł a d 1: MOV DX, ADRES
IN AX, DX
Format:
1110 110w
Dla adresu występującego bezpośrednio w drugim bajcie rozkazu IN ma inny
kod:
1110 010w adr_portu
OUT
jest rozkazem przepisania bajtu lub słowa do (portu) rejestru wyjściowego.
Zapis:
OUT adres, akumulator
Działanie:
rozkaz powoduje wyprowadzenie do portu wyjściowego zawartości rejestru
AL lub pary AX. Adres jest pobierany z rejestru DX lub podawany jest w
drugim bajcie rozkazu.
P r z y k ł a d 1: MOV DX, ADRES
OUT DX, AX
287
Format:
1110 111w
Dla adresu występującego bezpośrednio w drugim bajcie rozkazu OUT kod
rozkazu:
1110 011w adr_portu
C. Rejestr znaczników
LAHF
jest rozkazem przepisanie młodszego bajtu znaczników do rejestru AH
Zapis:
LAHF
Działanie:
rozkaz powoduje przesłanie do rejestru AH, mniej znaczącego bajtu rejestru
flagowego. Ta część rejestru odpowiada wskaźnikom flagowym procesorów
8-bitowych 8080/85.
P r z y k ł a d: LAHF
; przepisanie wskaźników do akumulatora
NOT AH
; inwersja wskaźników
SAHF
; przepisanie wskaźników na powrót do rejestru
flagowego
Format:
SAHF
1001 1111 - rozkaz o stałym kodzie.
-
jest rozkazem przepisania zawartości rejestru AH do rejestru znaczników,
w kierunku przeciwnym do LAFH
Format:
1001 1110 - rozkaz o stałym kodzie.
PUSHF
jest rozkazem przepisania zawartości rejestru wskaźników na stos,
Działanie:
Przekazuje na stos słowo flagowe, SP ← (SP) - 2
Format:
1001 1100 - rozkaz o stałym kodzie.
POPF
rozkaz przepisania zawartości szczytu stosu do rejestru flagowego
Działanie:
dwa bajty ze szczytu stosu zostają przepisane do rejestru wskaźników.
SP ← (SP) +2
Format:
1001 1101 - rozkaz o stałym kodzie.
Rozkazy arytmetyczne i logiczne
A. Grupa arytmetyczna
Dodawanie
ADD
jest rozkazem dodawania binarnego dwóch bajtów lub dwóch słów 16bitowych (zestawienie możliwości pobierania argumentów dla rozkazu przedstawiono w tabl. 5.2).
288
Zapis:
ADD argument1, argument2
Działanie:
jest to dodawanie dwóch argumentów, wynik jest wpisywany w miejsce
argumentu lewego (argument1).
argument1 ← argument1 + argument2
Operacja dodawania może modyfikować następujące wskaźniki: O, S, Z, AC,
P, C
P r z y k ł a d: ADD DI, CX - dodawanie zawartości rejestrów
Format:
0000 001w 11reg reg
Tablica 5.2
Zestaw możliwości dla rozkazu dodawania (ADD)
Argument1 przeznaczenie
Argument2 - źródło
rejestr
rejestr
rejestr segmentowy
pamięć
+
x
+
rejestr segmentowy pamięć wartość bezpośr.
x
x
x
+
x
x
+
x
+
+ oznacza, że taka operacja istnieje, x oznacza brak możliwości,
przesłania dla rejestrów segmentowych z wyłączeniem CS
ADC
jest dodawaniem bajtów lub słów 16-bitowych z uwzględnieniem przeniesienia,
Zapis:
ADC argument1,argument2
Działanie:
jest to rozkaz bliźniaczy do ADD, z tym że w dodawaniu uwzględniamy wartość bitu flagowego C
argument1 ← argument1 + argument2 + (C)
Format:
0001 001w 11reg reg
INC
jest rozkazem dodawania wartości 1 do bajtu lub słowa,
Działanie:
argument ← argument + 1
Format:
0100 0 reg
DAA
jest rozkazem uwzględnienia poprawki dziesiętnej dla dodawania w kodzie
BCD,
Odejmowanie
SUB
jest rozkazem binarnego odejmowania dwóch bajtów lub dwóch słów 16bitowych,
289
Zapis:
SUB argument1,argument2
Działanie:
jest to odejmowanie dwóch argumentów, wynik zostaje wpisywany w miejsce
argumentu lewego (argument1).
argument1 ← argument1 - argument2
Operacja dodawania może modyfikować następujące wskaźniki: O, S, Z, AC,
P, C
P r z y k ł a d: SBB DI, CX
- dodawanie zawartości rejestrów
Format:
0010 101w
11reg reg
SBB
jest rozkazem odejmowanie bajtów lub słów 16-bitowych z uwzględnieniem
pożyczki, rozkaz analogiczny do SBB
Działanie:
argument1 ← argument1 - argument2 - (C)
Format:
0001 101w 11reg reg
DEC
rozkaz odejmowania 1 od bajtu lub słowa,
NEG
jest rozkazem negacji, inaczej - dopełnienia do 2,
bajtu
lub słowa 16-
bitowego,
Zapis:
NEG argument
Działanie:
rozkaz powoduje odjęcie argumentu od 0 ze zmianą znaku liczby całkowitej.
CMP
oznacza operację porównania dwóch argumentów - dwóch bajtów lub dwóch
słów,
Zapis:
CMP argument1, argument2
Działanie:
rozkaz powoduje odjęcie rgumentu2 od argumentu1, bez zmiany ich wartości,
a wynik porównania jest oceniany na podstawie wartości znaczników flagowych.
Dobór argumentów operacji porównania jest zdefiniowany tak samo jak dla
operacji ADD zgodnie z tabl. 5.2.
Format:
DAS
0011 101w 11reg reg
jest rozkazem wywołania poprawki dziesiętnej dla odejmowania w kodzie
BCD.
Mnożenie
MUL
jest rozkazem mnożenia zawartości dwóch bajtów lub słów 16-bitowych bez
uwzględnienia znaku,
290
Zapis:
MUL CL;
mnożenie zawartości AL przez zawartość CL (mnożenie bajtów)
MUL BX; mnożenie zawartości AX przez zawartość BX (słów)
Działanie:
Jeśli jeden z operandów jest bajtem, drugi musi być również bajtem. Jeśli
jeden z argumentów jest słowem drugi również musi być słowem. W przypadku operacji na bajtach wynik wpisywany jest do rejestru 16-bitowego - AX.
Dla operacji na słowach, wynik mnożenia wpisywany jest do dwóch rejestrów
16-bitowych AX i DX.
Format:
1111 011w 1110 0 reg
IMUL
mnożenie bajtów lub słów 16-bitowych z uwzględnieniem znaku,
Zapis:
IMUL BL
;
IMUL BX
; obie operacje działają tak samo jak MUL z uwzględnieniem
dodatkowo bitu znaku.
AAM
jest rozkazem wprowadzającym poprawkę po mnożeniu w rozpakowanym
kodzie BCD.
Dzielenie
DIV
jest rozkazem dzielenia bajtów lub słów 16-bitowych bez znaku,
Zapis:
DIV CL
; dzielenie AX przez CL
DIV BX
; dzielenie DX i AX przez CX
Działanie:
dla bajtów dzielna jest zawarta w rejestrze 16-bitowym AX, dzielnik jest argumentem 8-bitowym o wartości maksymalnej 0FFH. Wynik przekazany zostaje do AL. Jeśli wynik dzielenia przekracza wymiar bajtu (0FFH) generowane jest przerwanie INT 0 - przypadek dzielenia przez 0. Podobnie dla słów 16bitowych. Dzielna rezyduje w rejestrach DX i AX, dzielnik jest argumentem
16-bitowym. Wynik operacji jest przekazywany do AX. Gdy wartość przekracza 0FFFFH, generowany jest również INT 0.
Format:
1111 011w 1111 0 reg
IDIV
jest rozkazem dzielenia bajtów lub słów z uwzględnieniem znaku,
Zapis:
IDIV BL
;
IDIV BX
; obie operacje działają identycznie jak MUL z uwzględnieniem
dodatkowo bitu znaku,
AAD
jest rozkazem poprawki przed dzieleniem w rozpakowanym kodzie BCD,
CBW
jest rozkazem zamiany formatu danych z bajtu na słowo,
291
CWD
jest rozkazem zmiany formatu słowa 16-bitowego na słowo 32-bitowe
(słowo podwójne),
B. Grupa logiczna
NOT
jest to operator negacji (dopełnienia) zawartości bajtu lub słowa danych,
Zapis:
NOT rejestr
; dla zawartości rejestru,
lub NOT adres ; dla komórki pamięci.
Działanie:
rozkaz neguje zawartość rejestru (lub komórki pamięci) podanego po słowie
kluczowym
AND
jest operatorem iloczynu logicznego dwóch bajtów lub dwóch słów
16-bitowych,
Zapis:
AND argument1,argument2
Działanie:
argument1 ← argument1 ∧ argument2
Rozkaz realizuje logiczne mnożenie odpowiadających sobie bitów dwóch
argumentów, które mogą być bajtami lub słowami 16-bitowymi. Bit przyjmuje
wartość 1 wtedy, gdy te same bity w obu słowach przyjmują wartość 1.
P r z y k ł a d: AND DI,CX - iloczyn logiczny zawartości dwóch rejestrów
Format:
0010 001w 11reg reg
OR
jest operatorem sumy logicznej zawartości dwóch bajtów lub dwóch słów 16bitowych,
Zapis:
OR argument1, argument2
Działanie:
argument1 ← argument1∨ argument2
Rozkaz realizuje sumę logiczną odpowiadających sobie bitów dwóch
argumentów, które mogą być bajtami lub słowami 16-bitowymi. Bit wyniku
operacji przyjmuje wartość 1 wtedy, gdy na tych samych bitach w obu słowach
występują wartości 1.
P r z y k ł a d: OR DI,CX - suma logiczna zawartości dwóch rejestrów
Format:
0000 101w 11reg reg
XOR
jest rozkazem dla operacji exclusive-or bajtów lub słów 16-bitowych,
Zapis:
XOR argument1,argument2
Działanie:
argument1 ← argument1∨ argument2
Rozkaz realizuje funkcję logiczną nierównoważności odpowiadających sobie
bitów dwóch argumentów, które mogą być bajtami lub słowami 16-bitowymi.
292
Bit wyniku operacji przyjmuje wartość 1 wtedy, gdy na tych samych bitach w
obu słowach występują wartości przeciwne.
P r z y k ł a d: XOR DI,CX - „suma modulo 2” zawartości dwóch rejestrów
XOR AX, 00EFH - jest operacją wykonywaną na zawartości
akumulatora i argumencie bezpośrednim.
Format:
0011 001w 11reg reg
TEST
jest porównaniem wartości logicznych dwóch bajtów lub słów.
Zapis:
TEST argument1, argument2
Działanie:
argument1 ∧ argument2, oraz flagi: O ← 0 i Z ← 0
P r z y k ł a d y:
TEST BP, DI - porównaniem zawartości dwóch rejestrów,
TEST BH, AL - porównaniem zawartości dwóch rejestrów,
TEST BP, ADRES - porównanie zawartości rejestru i argumentu umieszczonego w pamięci,
TEST AX, 00EFH - jest porównaniem zawartości akumulatora
i wartości bezpośredniej,
TEST ADRES, 00EFH - porównanie zawartości komórki pamięci i wartości bezpośredniej.
C. Przesunięcia:
SHL/SAL
jest rozkazem dla przesunięcia logicznego/arytmetycznego w lewo bajtu lub
słowa (tzn. w kierunku bitów starszych).
Zapis:
SHL argument1,argument2
Działanie:
Rozkaz przesuwa argument1 o liczbę bitów zadaną argumentem2
P r z y k ł a d: SHL AL,1
SHL BL,CL
- przesunięcie o 1 zawartości AL
- przesunięcie BL o wartość CL
SHR/SAR
przesunięcie logiczne/arytmetyczne w prawo bajtu lub słowa (jak wyżej).
ROL
przesunięcie cykliczne w lewo bajtu lub słowa, pozycja najstarsza przechodzi
na pozycję bitu najmłodszego.
ROR
przesunięcie cykliczne w prawo bajtu lub słowa, pozycja najmłodsza wchodzi
na pozycję najstarszą.
RCL
przesunięcie cykliczne w lewo (jak wyżej) z bitem przeniesienia bajtu lub słowa.
293
RCR
przesunięcie cykliczne w prawo z bitem przeniesienia, bajtu lub słowa.
Rozkazy przetwarzania tablic
Mikroprocesory pracujące w trybie 8086 posiadają rozkazy umożliwiające operowanie na łańcuchach (tablicach i blokach) danych umieszczonych w pamięci operacyjnej. W tej
grupie rozkazów wykorzystuje się rejestry specjalne:
- SI i DI do adresacji źródła danych,
- ES i DI do adresowania miejsca umieszczenia wyników operacji,
- CX jako licznik operacji.
Rozkazy dla pojedynczych operacji
MOVS -
przepisz w pamięci wskazany element łańcucha (tablicy lub bloku),
LODS -
załaduj wskazany element łańcucha do akumulatora,
STOS -
zapamiętaj element tablicy lub bloku znajdujący się w akumulatorze,
SCAS -
porównaj element łańcucha z zawartością akumulatora,
CMPS -
porównaj elementy dwóch łańcuchów.
Rozkazy powtórzeń (przedrostki)
REP
- powtórz operację pojedynczą aż do wyzerowania rejestru CX,
REPZ/REPE
- powtórz operację pojedynczą aż do wyzerowania rejestru CX lub
znacznika Z, rejestru flagowego (Z=0),
REPNZ/REPNE - powtórz operację pojedynczą aż do wyzerowania rejestru CX lub
ustawienia znacznika zera; Z=1.
Realizacja powtórzeń jest związana z badaniem stanu wyzerowania licznika CX lub
innych warunków związanych z ustawianiem znacznika zera (Z).
Znacznik kierunku:
• D = 0 oznacza zwiększenie zawartości rejestrów SI oraz DI po jednokrotnym wykonaniu
rozkazu.
• D = 1 oznacza, że po każdorazowym wykonaniu rozkazów zawartość rejestrów SI oraz DI
zmniejsza się o 1 dla bajtów lub o 2 dla słów 16- bitowych.
Oznacza to automatyczne zaadresowanie kolejnego argumentu operacji przetwarzania.
Rozkazy skoków
294
Bezwarunkowe:
JMP - zwykły,
CALL - do podprogramu,
RET - powrót z podprogramu.
Warunkowe - badające stan znacznika F:
JF dla F=1 lub JNF dla F=0,
gdzie F reprezentuje flagi (znaczniki) procesora, jak: C, O, S, P i Z.
Rodzaje rozkazów skoków i zasady ich wykorzystania zostały omówione wcześniej.
Rozkazy przerwań
Rozkazy przerwań uruchamiają mechanizmy wywołania podprogramów w taki sam
sposób jak sygnały wysyłane przez urządzenia zewnętrzne.
INT
- skok do podprogramu obsługi przerwania,
INTO
- skok do podprogramu obsługi przerwania, gdy występuje przepełnienie,
IRET
- powrót z podprogramu obsługi przerwania do miejsca wywołania
przerwania,
Rozkazy sterujące procesora
Sterowanie znacznikami:
STC
- ustaw znacznik przeniesienia,
CLC
- zeruj znacznik przeniesienia,
CMC
- zaneguj znacznik przeniesienia,
STD
- ustaw znacznik kierunku,
CLD
- zeruj znacznik kierunku,
STI
- ustaw znacznik przerwania,
CLI
- zeruj znacznik przerwania.
Rozkazy synchronizacji:
HLT
- zatrzymanie pracy procesora,
WAIT
- stan oczekiwania procesora na zmianę poziomu logicznego wejścia
TEST z wartości niskiej na wysoką (nieaktywną),
295
ESC
- deklaracja wyłączenia rozkazów sterujących procesora; przełączenie
przełączenie na procesor zewnętrzny,
LOCK
- deklaracja dostępu do magistrali na czas wykonywania następnego
rozkazu.
NOP
- nie rób nic (no operation).
Organizacja pętli programowych
Rozkazy organizacji pętli wyliczanych badają stan rejestru CX zwanego licznikiem
obiegu pętli. Korzystają z tego stanu następujące rozkazy:
LOOP
- powtórz sekwencję rozkazów, jeśli zawartość CX≠0
LOOPE/LOOPZ
- powtórz sekwencję rozkazów, jeśli zawartość CX≠0 lub jest
równość argumentów/wartość równa zeru,
LOOPNE/LOOPNZ - powtórz sekwencję rozkazów, jeśli zawartość (CX≠0) lub jest
nierówność/ wartość różna od zera,
JCXZ
296
- skok względny, gdy CX = 0.

Podobne dokumenty