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.