Podstawy Asemblera, Definicje (zielony)


ASSEMBLER

Definicje

Mnemonik - nazwa odpowiadająca każdej instrukcji maszynowej

Dyrektywa - nazwa odpowiadająca instrukcji dla programu asemblera

Operandy (argumenty) - liczby i inne nazwy z prawej strony mnemoników, oddzielamy je przecinkiem `,'

Instrukcja - mnemonik + operand, może być poprzedzona etykietą (łańcuch alfanumeryczny od tego momentu kojarzony z tą

instrukcją programu) oraz może po niej następować komentarz

Instrukcja maszynowa - binarny odpowiednik instrukcji

Komentarz - zaczyna się średnikiem `;`a kończy znakiem końca wiersza

Liczba - szesnastkowa: zapisujemy kończąc ją znakiem `H' , jeśli zaczyna się od litery poprzedzamy ją `0'

1202H, 0A010H

- dziesiętna: zwyczajnie

- ósemkowa: kończymy ją znakiem `Q' lub `O'.

- binarna: kończymy ją znakiem `B'

EOL = Carrier Return (13) + Line Feed (10) - znak końca wiersza

Wielkości pamięci :

NAZWA

w Asemblerze

Ilość bajtów

słowo

WORD

2

podwójne słowo

DWORD

4

poczwórne słowo

QWORD

8

10 bajtów

TBYTE

10

paragraf

PARA

16

strona

PAGE

256

segment

SEGMENT

65536

( ! ) bity zlicza się od prawej (najmniej znaczący) do lewej i numeruje od 0

Architektura mikroprocesora

W pracy procesora wyodrębnia się cykle. Podstawowym cyklem jest cykl rozkazowy. Związany jest on z wykonaniem pojedynczej instrukcji. Na jeden cykl rozkazowy przypada kilka cykli maszynowych, z których każdy może składać się z kilku cykli zegarowych. Jeden cykl zegarowy wykonywany jest w jednym takcie zegara. Poszczególne cykle maszynowe realizują osobne układy, a ich realizacja jest równoległa. Układy te to:

BU (układ magistrali systemu) - pas transmisyjny, pobiera instrukcję z pamięci i umieszcza ją w kolejce kodów, jest odpowiedzialny za przesyłanie danych do świata zewnętrznego

IU (układ instrukcji) - pobiera instrukcje z kolejki kodów, dekoduje i umieszcza w swojej kolejce instrukcji (potok).

EU (układ wykonawczy) - wykonuje instrukcje pod kontrolą mikroprogramu zapisanego w ROM (opisane np. operacje mnożenia, dzielenia itp. składające się z kilku elementarnych operacji bitowych)

AU (układ adresowy) - zarządza pamięcią, przekształca adresy wirtualne na fizyczne, pełni funkcje ochronne.

Równoległość cykli maszynowym sprowadza się do równoczesnej pracy ww. układów, podczas gdy EU wykonuje instrukcję, IU dekoduje już następną instrukcję.

Adresy

Każdy adres jest 20-bitowy. Aby go zapamiętać, potrzeba 2 rejestrów. Pierwszy zawiera adres segmentu (okienko 65536-bajtowe, przez które patrzymy na pamięć, do każdego bajtu z tego okienka mamy równoczesny dostęp), drugi - adres przesunięcia (offset) - określa konkretną komórkę pamięci w segmencie. Adres komórki liczony jest jako przesunięcie względem początku segmentu. Zwiększenie offsetu o 1 powoduje zwiększenie adresu bezwzględnego (20 - bitowego) o jeden bajt, natomiast ( ! ) każde zwiększenie segmentu o `1' powoduje zwiększenie bezwzględnego adresu początku segmentu o 16 bajtów (adres segmentowy - adres paragrafu).

( ! ) - adres nie jest jednoznaczny segmenty mogą na siebie zachodzić. Jeśli jeden segment przesunięty jest względem drugiego o 16 bajtów, to wystarczy przesunąć offset drugiego o 16 bajtów i na 2 sposoby wskazujemy się wówczas tą samą komórkę.

Zapis adresu - w postaci: <segment> : <offset> 0100H:0001H.

Dekodowanie adresu - słowo zawierające adres segmentu przemnażane jest przez 16 (otrzymujemy 20-bitową liczbę, której najmłodsze 4 bity są wyzerowane). Do takiej liczby dodawana jest 16-bitowa liczba - adres offsetu. Wynik stanowi adres 20-bitowy.

Bufor obrazu - zapisując w nim odpowiednie znaki ASCII możemy wyświetlić je na ekranie. Adres segmentu pamięci wideo wynosi: 0B800H (kolor) lub 0B000H (mono). Na każdy wyświetlany znak przypadają 2 bajty: 1 - kod ASCII wyświetlanego znaku, 2 - atrybut (pierwsze 4 bity - kolor tła, drugie - kolor tekstu). Atrybut normalnego tekstu wynosi 07H. Pierwsze dwa bajty odnoszą się do lewego górnego rogu, kolejne to wszystkie znaki z najwyższego wiersza, potem niższe wiersze. Jeden wiersz - 160 bajtów. ( ! ) Można operować bezpośrednio na pamięci obrazu, zmieniając odpowiednie bajty (np. write() )

Data BIOS - znajduje się zawsze pod adresem 0FFFF:0005. (jest to ostatni możliwy segment, jego offset jest od 0 do 15). Data zapisana jest w postaci wartości znaków ASCII odpowiadających zapisowi: 25/04/97

Restart (zimny) - w BIOS zapisana pod adresem 0FFFF:0000 instrukcja skoku do procedury startowej.

Rejestry

Rejestry segmentowe - tylko one mogą zawierać adresy segmentów, każdy ma 16 bitów.

CS (code segment) - każda instrukcja programu znajduje się w segmencie kodu. Adres segmenu, w którym znajduje się aktualnie wykonywana część programu znajduje się w CS

DS (data segment) - zmienne i stale znajdują się w segmencie danych. Może istnieć kilka segmentów danych, ale w danej

chwili można korzystać tylko z jednego

SS (stack segment) - zawiera adres początku segmentu stosu

ES (extra segment) - zawiera adres dodatkowego segmentu danych

Dodatkowe rejestry segmentowe (386 / 486)

FS i GS - (nazwa od kolejnych liter alfabetu) - zawierają adresy dodatkowych segmentów danych

( ! ) w trybie rzeczywistym rejestry segmentowe mogą być używane (z pewnymi ograniczeniami) jak rejestry ogólnego

przeznaczenia, w trybie wirtualnym mogą przechowywać tylko adresy.

Rejestry ogólnego przeznaczenia - mogą spełniać różne funkcje

Rejestry 32-bitowe (386 / 486)

Wszystkie rejestry ogólnego przeznaczenia (także BP, SP, SI, DI) poszerzono do 32 bitów nazwa 32-bitowego odpowiednika tworzona jest zgodnie ze schematem: AX EAX.

( ! ) Nie ma sposobu oddzielenia i manipulowania tylko starszym słowem rejestru (bo młodsze słowo dla EAX to AX)

Wskaźnik instrukcji - rejestr IP - służy do przechowywania offsetu instrukcji maszynowej, która będzie wykonywana jako następna. Po skończeniu instrukcji CPU zwiększa IP o taką ilość bajtów, ile zajmowała aktualnie wykonywana instrukcja maszynowa.

( ! ) - adres bezwzględny następnej instrukcji jest w CS:IP

( ! ) - adresu w IP nie można bezpośrednio czytać i zmieniać

Rejestr znaczników (flag) - 16-bitowy, większość bitów wykorzystana i traktowana jako 1-bitowe rejestry, sprawdzanie jakichkolwiek warunków w programie sprawdza stan tych rejestrów. Większość instrukcji w zależności od wyniku swojego działania odpowiednio ustawia kolejne bity (każda na swój sposób). W 8086 wykorzystuje się jedynie 9 bitów z tego rejestru. Każdy taki bit (flaga) ma swoją osobną nazwę:

OF (overflow flag) - znacznik przepełnienia ustawiany, gdy wynik operacji jest za duży i nie mieści się w operandzie przeznaczenia

DF (direction flag) - znacznik kierunku nie wskazuje niczego, ( ! ) należy go samemu ustawić, ustalając jaki kierunek zmian mają przyjąć instrukcje łańcuchowe. Gdy DF jest ustawiony, instrukcje te wykonują się „od góry do dołu” - w kierunku mniejszych adresów, gdy 0 - w kierunku większych adresów. Ustawianie na 1za pomocą STD, 0 - CLD.

( ! ) znacznik ten jest inicjalizowany wartością „0”

IF (interrupt enable flag) - znacznik zezwolenia przerwań może być ustawiany przez CPU a także przez użytkownika, za pomocą instrukcji STI (na 1) oraz CLI (na 0). Jeśli jego wartość jest 0 - ignorowane występowanie przerwań.

TF (trap flag) - znacznik pułapki umożliwia wykonywanie programu krok po kroku

SF (sign flag) - znacznik znaku ustawiany, gdy wynik operacji jest ujemny (1 na najstarszym bicie), zerowany - wynik dodatni.

ZF (zero flag) - znacznik zera ustawiony, gdy wynikiem operacji jest `0' ( ! ) , zerowany, gdy cokolwiek innego.

AF (auxiliary carry flag) - znacznik przeniesienia pomocniczego wykorzystywany w arytmetyce liczb dziesiętnych kodowanych binarnie - BCD - bajt traktowany jako para 4-bitowych nybli - w każdym z nich zapisana jest cyfra 0-9.

PF (parity flag) - znacznik parzystości ustawiony, gdy liczba ustawionych bitów w najmniej znaczącym bajcie wyniku jest parzysta, zerowany - gdy nieparzysta.

CF (carry flag) - znacznik przeniesienia ustawiony, gdy w wyniku działań arytmetycznych lub przesuwania z rejestru zostanie wyniesiony jeden ustawiony bit. Flagę tą można modyfikować instrukcjami STC (CF = 1), CLC (CF = 0), CMC (CF = -CF)

AC (alignment check) (486) - wykrywa błędy wyrównania ustawiany, gdy zaszło odwołanie do pamięci pod adres niepodzielny przez 4. W 486 jednocześnie wczytuje się 32-bity najszybciej dokonuje się to gdy adres pamięci wyrównany jest do granicy podwójnych słów.

Kopiowanie rejestru flag - nie można za pomocą MOV nale¿y u¿yæ stosu - naspierw PUSHF a potem POP

Rodzaje danych i adresacje

Operandy źródła i przeznaczenia

Instrukcje wymagaj --> [Author:brak] --> [Author:brak] --> [Author:brak] ą często podania adresu źródła danych potrzebnych do ich działania (operand źródła ) i adresu przeznaczenia, gdzie ma być zapisany efekt ich działania (operand przeznaczenia). Operand przeznaczenia stoi za zwyczaj bliżej instrukcji np. MOV DX, AX DX to przeznaczenie, AX - źródło.

Rodzaje danych

Istnieją 3 rodzaje danych, które mogą być użyte jako operandy:

Dane natychmiastowe - osiągalne dzięki adresowaniu natychmiastowemu - dana zawarta jest w segmencie kodu zaraz po kodzie instrukcji (dana należy do wczytywanej instrukcji).

( ! ) - Może nią być tylko operand źródła, musi on być wielkości odpowiedniej do operandu docelowego (jak 8 bitów celu, to 8-bitowa liczba w źródle).

Dane natychmiastowe to stałe, zapisuje się je jako liczby np. MOV AX, 1

Dane te można wykorzystywać także przy adresowaniu bezpośrednim, w którym offset argumentu pamięciowego zawarty jest bezpośrednio w rozkazie jako 16-bitowa (lub 8-bitowa ze znakiem adresowanie względne) stała.

Dane rejestrowe - przechowywane w rejestrach CPU. Dostępne dzięki adresowaniu rejestrowemu (ukrytemu). Sprowadza się ono do podania nazwy rejestru: MOV AX, BX - w BX jest dana rejestrowa, która zostanie zapisana jako dana rejestrowa w AX.

( ! ) - nie można mieć w źródle 8 bitów, a celu 16-to bitowego i odwrotnie.

Dane pamięciowe - znajdują się gdzieś w 1MB obszarze pamięci. ( ! ) W większości instrukcji tylko jeden z operandów może być adresem pamięci. Dane takie podlegają adresacji pośredniej . Najpierw należy załadować do odpowiednich rejestrów wartość adresu segmentu i przesunięcia. Następnie adresuje się przy użyciu [ ], którymi wskazujemy, w jakich rejestrach jest adres komórki pamięci.

Adresacja pośrednia

Sposoby adresacji pośredniej:

( ! ) W każdym przypadku można użyć przedrostka zmiany segmentu.

( ! ) można wykorzystać przy adresowaniu pól tablicy rejestr bazy wskazuje na początek tablicy, rejestr indeksu

modyfikujemy wskazując na kolejne elementy w tablicy (przesunięcie względem początku tablicy)

( ! ) można wykorzystać do odwoływania się do kolejnych pól rekordu rejestr wskazuje na początek danego obiektu

(rekordu), przesunięcie wskazuje na konkretne pole rekordu (w każdym obiekcie danego typu rekordowego dane pole

jest przesunięte o stałą liczbę bajtów względem adresu obiektu)

( ! ) można wykorzystywać do odwoływania się do konkretnych pól w tablicach rekordów <rejestr_bazowy>

wskazuje adres tablicy, <rejestr_indeksu> - element tablicy (rekord), <dana_natychmiastowa> - konkretne pole w danym

rekordzie (znajduje się ono w każdym obiekcie tego typu rekordowego o taką samą ilość bajtów od początku obiektu)

Adresowanie wielowymiarowe - dla tablic o wielu wymiarach korzystać z ostatniego typu adresowania, <rejestr_bazowy> - początek tablicy, <dana_natychmiastowa> - pole w rekordzie, <rejestr_indeksu> - konkretny element tablicy, który wcześniej należy obliczyć (i załadować do tego rejestru) ze wzoru (indeksacja od „0”) :

nr_wiersza * ilość_bajtów_wiersza + nr_kolumny

Domyślny adres segmentowy - rzadko musi się podawać pełny adres komórki. Wystarczy jedynie adres offsetu, a adres segmentu zostanie przypisany domyślnie. Wartości tego adresu zależą od instrukcji i rejestru, w którym przechowywany jest offset. Najczęściej domyślnie przyjmuje się segment:

Jeśli chcemy zmienić domyślne założenia, musimy przy adresowaniu użyć przedrostka zmiany segmentu w postaci <rejestr_segmentu> : przed nazwą rejestru offsetu np. ES:[BX], CS:[SI], ES:[BX + DI] itd.

Tworzenie programu „exe”

Każdy program musi zawierać co najmniej 3 segmenty logiczne: stosu, danych i kodu. Możne być więcej segmentów.

Segment definiuje się za pomocą dyrektywy SEGMENT, zgodnie ze składnią:

<nazwa> SEGMENT [typ] [połączenie] [`nazwa_klasy'] np. MojeDane SEGMENT.

gdzie:

BYTE - od pierwszego wolnego bajtu

WORD - od pierwszego wolnego słowa

PARA - od pierwszego wolnego adresu podzielnego przez 16

PAGE - od pierwszego wolnego adresu podzielnego przez 256

( ! ) jeśli nie podamy typu domyślnie przyjęte zostanie PARA

PUBLIC - tworzony będzie jeden wspólny segment z segmentów o tej samej nazwie, będą one miały jeden adres

początkowy, a rozmiar segmentu będzie sumą rozmiarów wszystkich segmentów o tej nazwie

COMMON - segmenty o tej samej nazwie będą miały wspólny adres początkowy, wielkość segmentu =

wielkości największego z łączonych segmentów

MEMORY - segment zostanie umieszczony na końcu ładowanego programu, może istnieć tylko 1 taki segment

AT <wyrażenie_numeryczne> - segment zostanie umieszczony pod adresem segmentowym znanym w czasie

asemblacji określanym przez <wyrażenie_numeryczne>

STACK - wszystkie segmenty o tej nazwie i połączeniu STACK stworzą wspólny segment stosu, o wielkości

równej ich sumie

( ! ) Jeśli połączenie nie jest podane domyślnie przyjęte jest PRIVATE - segment nie będzie z żadnym innym łączony.

Koniec definicji segmentu jest określany dyrektywą ENDS, przed którą stoi <nazwa>.

Każdy kod programu (definicje wszystkich segmentów, nie tylko kodu) kończymy przez wpisanie dyrektywy END poza każdym z segmentów. Asembler nie będzie czytał żadnych instrukcji ani dyrektyw, znajdujących się po END.

Wyrażenia matematyczne

Asembler umożliwia stosowanie wyrażeń matematycznych (używających operatorów matematycznych np. 20 * 15 - stała) w kodzie programu. Obliczenia ich dokonuje się w czasie asemblacji - zapis wyrażenia zostaje zastąpiony w kodzie programu przez jego wartość w wyrażeniach mogą występować tylko stałe (liczby lub etykiety zdefiniowane za pomocą EQU). Można również użyć operatora NOT <op> , traktując go wraz z operandem jak wyrażenie po obliczeniu zostanie wstawiona na jego miejsce odpowiednia wartość. NOT neguje wszystkie bity <op>.

Przełączanie w tryb 286 / 386 / 486

Należy podać asemblerowi specjalne dyrektywy (w segmencie kodu): .486 (MASM/TASM) lub P486N (TASM).

Jak rozróżnić procesory

Segment stosu

Adres segmentu stosu przechowuje SS. Jest on ładowany przez DOS przy uruchamianiu programu. Rozmiar stosu rzadko musi przekraczać 1KB, jednak z tego stosu nie korzysta tylko nasz program, ale również funkcje przerwań DOS i BIOS. Segment stosu zaczyna się od SS:0, ale wszelkie na nim operacje odbywają się początkowo na jego przeciwległym końcu. Na początku SP wskazuje na koniec stosu (SS + rozmiar stosu).

Między SS + rozmiar a SS:SP znajdują się dane, przy czym SS:SP wskazuje na aktualnie ostatnio zapisaną daną (Szczyt stosu). Między SS:0 a SS:SP znajduje się pusty obszar gotowej do wykorzystania pamięci.

0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic

0x08 graphic

Odkładanie na stos

Przy odkładaniu najpierw zmniejsza się SP, a potem zapisuje odpowiednią daną pod adresem SS:SP. Odbywa się to za pomocą PUSH (odkłada na stos zawartość dowolnego rejestru ,tego na co rejestr wskazuje lub (286) danej natychmiastowej) lub PUSHF (odkłada rejestr flag) np.

PUSHF

PUSH AX

PUSH [BX]

PUSH 21H

( ! ) Obie instrukcje odkładają na stos po 1 słowie, nie można odkładać na stos rejestrów 8-bitowych. Bajty słowa znajdują się pod adresami: mniej znaczący - SS:SP, bardziej znaczący SS:SP+1

( ! ) nie można odkładać na stos danych bezpośrednich (8086)

Zdejmowanie ze stosu

Zdejmując najpierw odczytuje się szczyt stosu, a potem zwiększa odpowiednio wartość SP. Realizuje się to za pomocą POP (ogólne) lub POPF (dla flag). ( ! ) Obie czytają dane o wielkości 1 słowa (nie 8-bit).

POPF

POP AX

POP [BX]

Inicjalizacja stosu

Definiuje się go jako jedną ogromną zmienną bez nazwy, przy użyciu DUP (patrz: Segment danych) np.

DB 64 DUP(`STACK!!!');

( ! ) - opłaca się rezerwować stos inicjalizując go jakimś identyfikującym napisem (np. `stack'), bo dzięki temu łatwo można go odnaleźć przez DEBUG. Tyczy się to każdego segmentu lub dużej zmiennej

Segment danych

Typ danych - w asemblerze oznacza rozmiar danych.

Definiowanie zmiennych

Definiując zmienną określa się ją etykietą (nazwa zmiennej) oraz dyrektywą definicji danych:

DB (define byte) - definiuj bajt

DW (define word) - definiuj słowo

DD (define double) - definiuj podwójne słowo

DQ - definiuje poczwórne słowo

DT - definiuje 10 bajtów (5 słów)

np. zmienna DW ; „zmienna” będzie miała rozmiar 2 bajtów.

( ! ) W przypadku gdy zmienna „zm” jest 16-bitowa, nieprawidłowe będzie: MOV AL, zm, bo niezgodność typów do 8-bitów „pakuje się” 16 bitów. Należy wówczas użyć specyfikatora zmiany typu.

Zmienne można od razu inicjalizować - po dyrektywie podaje się wartość zmiennej np. zmienna DW 0FFFFH.

Zmienne łańcuchowe

Zmienne takie to ciągi znaków (string) umieszczone po sobie w pamięci. Miejsce dla nich rezerwuje się inicjalizując je w odpowiedni sposób. Po nazwie zmiennej podaje się dyrektywę DB ( ! ) po której umieszcza się ciąg znaków. Ciąg znaków umieszcza się w „ ” lub ` `, albo podając liczbowe wartości odpowiednich znaków ASCII oddzielanych przecinkami. Tak więc DB rezerwuje tylko 1 bajt, będący początkowym znakiem łańcucha. To na niego wskazuje etykieta stojąca przed DB. Liczba znaków w apostrofach decyduje ile ma zostać zarezerwowanych bajtów. Można łączyć szeregi oddzielnych ciągów znaków inicjalizujących oddzielając je przecinkiem `,' np.

lancuch DB „Ala”, „ma kota”, `$'

Eol DB 0DH, OAH, `$'

Długość łańcucha - jeśli zainicjalizowano łańcuch i chcemy znać jego długość (bez liczenia literek, przy każdej zmianie łańcucha), w następnej linijce po jego definicji można zapisać:

<stała> EQU $ - <nazwa_łańcucha>

gdzie: <stała> - będzie zawierać długość łańcucha <nazwa_łańcucha>, $ - operator oznaczający obecne położenie - zwraca adres miejsca w kodzie, w którym on wystąpił. W tej sytuacji <nazwa_łańcucha> wskazuje na pierwszy bajt stringu, $ - na pierwszy bajt zaraz po stringu, więc <stałą> będzie ilością bajtów stringu np.

Napis DB `Ala ma kota, ale kot jest zielony'

Dl EQU $ - Napis ; „Dl” ma wartość ilości znaków „Napis”

Zmienne większych rozmiarów

Jeśli chcemy zarezerwować miejsce dla zmiennej większe niż dzięki podstawowym dyrektywom, a nie chcemy korzystać ze stringów, należy użyć dyrektywy DUP. Zmienne definiuje się zgodnie ze schematem:

<nazwa_zmiennej> DB <ile> DUP( <czym_wypelnić> )

DB - rezerwuje tylko początkowy bajt

<ile> - liczba powtórzeń argumentu w nawiasach

<czym_wypelnić> - wzorzec do inicjalizacji string, liczba lub ? - nie inicjalizuje, wypełnia dowolnym znakiem już znajdującym się w pamięci.

np. Nazwa DB 10 DUP(`Luke') ; ”Nazwa” zajmie 40B

Temp DB 10 DUP(?) ; „Temp” zajmie 10B, nie inicjalizowana

( ! ) - zmienna będzie zajmować: ile * sizeof ( <czym_wypelnić>)

Zmienne anonimowe

Tworzy się je nie podając etykiety przed dyrektywą rezerwującą miejsce np. DB 100 DUP(?). Dostęp do takiej zmiennej może odbywać się przy pomocy adresu przesunięcia tej zmiennej w segmencie danych.

( ! ) - offset zmiennej w DS = ilość bajtów, które zajęły zmienne zdefiniowane wcześniej w segmencie danych

Dyrektywa LABEL

Dyrektywa ta służy do określania wielkości pamięci kojarzonej z etykietą zmiennej. Ma ona następującą składnię:

<nazwa_zmiennej> LABEL <rozmiar_pamięci>

gdzie: <rozmiar_pamięci> jest jedną z nazw asemblera np. WORD, DWORD

Nazwa zmiennej będzie się odnosić do pierwszej komórki pamięci, która zostanie zdefiniowana w kodzie za pomocą jednej z dyrektyw definicji zmiennych (DB, DW). Z <nazwa_zmiennej> będzie odtąd kojarzony rozmiar nie trzeba będzie stosować specyfikatora zmiany typu (patrz niżej).

Tablice

Definiuje się przy użyciu dyrektywy DB, po której można inicjalizować pamięć jak w przypadku zwykłego stringu lub używając dyrektywy DUP (), w której w nawiasach można podać wzorzec o długości jednego pola, a przed DUP może stać liczba = ilości potrzebnych pól tablicy np.

Tab DB '1234567890' - tablica 10-cio bajtowa, można ją traktować jak zbiór 10-ciu elementów 1-bajtowych.

Tab DB 100 DUP(`12') - tablica, którą można używać do przechowania 100 elementów 2-bajtowych.

Można także użyć innych dyrektyw np. DW, wówczas tworzymy zmienną odpowiednią dyrektywą, a po niej tworzy się ciąg anonimowych zmiennych tymi samymi dyrektywami (w końcu chodzi tylko o zarezerwowanie miejsca, a nazwa zmiennej potrzebna jest tylko do zaadresowania początku tablicy). Przykładowo:

Tab DW 0B800H

DW 0A000H

DW 0000H . . .

( ! ) Przy odwoływaniu się do kolejnych elementów tablicy, używa się adresowania indeksowego względem bazy, wówczas rejestr bazy wskazuje początek tablicy, a rejestr indeksu - kolejny element tablicy. Jeśli elementy tablicy mają rozmiar

n-bajtów, rejestr indeksu musi być wielokrotnością n.

( ! ) Opłaca się wielkość elem. tablicy (n) definiować jako wielokrotność „2”, ponieważ szukając przesunięcia i-tego elementu dokonuje się mnożenia i*n, co można wówczas szybko wykonać za pomocą SHL

( ! ) Przy odwoływaniu się do el. tablicy można wykorzystać adresowanie pośrednie z przesunięciem wówczas rejestr bazy wskazuje na kolejny element, a daną natychmiastową stanowi (konkretna wartość) etykieta, będąca nazwą tablicy.

( ! ) odwołania do elementów tablicy mogą wyglądać następująco:

<nazwa_tablicy>[ <indeks> ] np. tab[2]

gdzie <indeks> oznacza numer komórki tej tablicy. Rodzaj (wielkość) komórki zależny jest od dyrektywy użytej do definiowania tablicy (np. DB, DW ), ewentualnie od zadeklarowanego dla <nazwa_tablicy> rozmiaru pamięci za pomocą dyrektywy LABEL.

Szybkie odwzorowania - tworzenie tablic przeglądowych tablica, obrazująca odwzorowanie, w której w i-tym elemencie znajduje się wartość odwzorowania na argumencie i. ( f ( i ) ). Taką tablicę najlepiej od razu inicjalizować (na etapie pisania kodu). Dzięki temu nie trzeba dla danego elementu mozolnie obliczać f( i ) (np. wykonywać serii sprawdzeń typu: jeśli i = 0 zwróć 7, jeśli i = 1 zwróć 2, ...

Stałe

Definicji stałych dokonuje się przy użyciu dyrektywy EQU, przed którą stoi nazwa stałej, a po której występuje wyrażenie, którym ta nazwa ma być zastąpiona (np. wyrażenie arytmetyczne, tekst w apostrofach, stała liczbowa itp.) np.

Stala EQU 2-7*4

Imie EQU `Cindy'

Data EQU 0FFFF:0005

( ! ) EQU dziala jak #define, na etapie preprocesingu zamienia każde wystąpienie nazwy stałej na odpowienią wartość.

Zmiana typu

W większości przypadków, gdy instrukcja jednoargumentowa odwołuje się do pamięci przez adres w rejestrze (adresowanie pośrednie) - np. NEG, INC, DEC, NOT - należy podać, do jakiej wielkości danych ma się odwoływać (np. czy NEG ma zanegować bajt czy całe słowo spod podanego adresu). Osiąga się to przez podanie specyfikatorów zmiany typu - BYTE PTR, WORD PTR itp. np. NEG BYTE PTR [BX]

( ! ) nie istnieje domyślny typ danych dla tych instrukcji.

( ! ) W sytuacji, gdy chcemy do 8-bitów zapisać 16-bitową daną, dodajemy przed nazwą 16-bitowej danej specyfikator zmiany BYTE PTR i zostanie tam zapisana starsza połówka tego słowa np.

MOV AL, BYTE PTR Zmienna

Segment kodu

Definiowanie Etykiet

Etykieta - nazwa symboliczna służąca do określenia miejsca w pamięci (etykieta = nazwa zmiennej, adres danej instrukcji w kodzie, adres segmentu itp. ).

W kodzie programu można zdefiniować etykiety, którym przypisany zostanie adres instrukcji stojącej po etykiecie. Umożliwia to łatwiejsze wykorzystanie instrukcji skoku skok do etykiety = skok do instrukcji w programie, mającej adres przesunięcia względem CS przypisany etykiecie. Etykietę definiuje się następująco:

<etykieta> : gdzie <etykieta> - dowolny string np. Tutaj :

( ! ) Odwołanie do etykiety odbywa się już bez pisania dwukropka np. MOV SI, Tutaj.

Definiowanie początku programu

Przed instrukcją od której chcemy rozpocząć program tworzymy etykietę np. Start. Następnie po END wypisujemy nazwę tej etykiety.

Określenie kodu dla odpowiednich segmentów

Do skojarzenia etykiet segmentów z odpowiednimi rodzajami segmentów służy dyrektywa ASSUME. Używa się jej zgodnie z następującym wzorcem: ASSUME CS:Kod, DS:Dane, SS:Stos , gdzie Kod, Dane i Stos to etykiety - nazwy segmentów. Odtąd asembler wie, że kod między Dane SEGMENT a Dane ENDS należy traktować jako kod segmentu danych - dzięki temu wszystkie zmienne definiowane w kodzie między Dane SEGMENT a Dane ENDS znajdą się w segmencie danych i będą do nich odnosić się inne instrukcje niż do zmiennych definiowanych w segmencie kodu. Na tej podstawie asembler będzie generował także wartości domyślnych adresów segmentowych.

( ! ) - Należy załadować adres segmentu danych do rejestru DS. Nie można tego dokonać jednak bezpośrednio z danej natychmiastowej czy pamięciowej. Robi to się w następujący sposób:

mov AX, Dane

mov DS, AX

Procedury

Definiowanie

Procedury wydzielamy za pomocą dyrektywy PROC, przed którą stoi etykieta = nazwa procedury. Następnie definiuje się instrukcje tej procedury i kończy się za pomocą ENDP, przed którą stoi nazwa procedury np.

Moja PROC

mov AX, Dane

mov DS, AX

dec CX

....

ret

Moja ENDP

Wywołanie procedury - za pomocą CALL <nazwa_procedury> np. call Moja. CALL odkłada na stos adres powrotu (offset = 2B = 16b) i skacze do wskazanego miejsca. ( ! ) - Procedura musi zawierać instrukcję RET umożliwiającą powrót w przypadku gdy wywołujemy ją przez CALL, RET pobiera adres ze stosu i skacze do niego.

( ! ) - wewnątrz procedury można robić wszystko co w każdej części programu także wywoływać inne proc.

Bezpieczeństwo

Poprawnie napisana procedura powinna dbać o to, aby wszystkie rejestry (też znaczniki) miały taką samą wartość po wywołaniu procedury jak przed jej wywołaniem. Innymi słowy - patrząc na procedurę z „zewnątrz” nie powinno się stwierdzić, że modyfikuje ona jakiekolwiek rejestry. Aby to zapewnić należy posłużyć się stosem. Na początku odkładamy na stos wszystkie rejestry , które w dalszej części będziemy modyfikować, a następnie tuż przed końcem procedury zdejmujemy je w odwrotnej kolejności np.

Moja PROC

; zabezpieczenie wart. rejestrow

push AX

pushf

...

XOR AX, AX ;modyfikowany rejestr AX

CMP AX, zmienna ;modyfikowane flagi...

...

; przywrocenie wartosci z momentu wywolania

popf

pop AX

ret

Moje ENDP

Przekazywanie parametrów do procedur

Przekazywanie argumentów dla procedur przy użyciu stosu

W przypadku przekazywania przez stos należy korzystać z adresowania pośredniego z przesunięciem w celu odwołania się do komórek pamięci znajdujących się „pod” szczytem stosu. Szczyt stosu wskazywany jest przez rejestr SP. Nie może on jednak być wykorzystany jako rejestr bazowy w adresowaniu pośrednim z przesunięciem. Funkcję taką może jednak spełnić rejestr BP. W celu odczytu argumentów przekazanych do procedury należy najpierw przyrównać jego wartość do wartości SP, a następnie można odwoływać się do słów pamięci poniżej stosu o adresach odpowiednio BP+4, BP+6 ...

Poniżej znajduje się przykład procedury do której przekazuje się parametry za pomocą stosu:

Moja PROC

;zabezpieczenie rejestrów

push BP

push AX

push BX

;odczytanie argumentów wywołania

;UWAGA - pod adresem SP+2 znajduje się BX, pod SP+4 AX, pod SP+6 BP,

; pod SP+8 - adres powrotu z procedury dla RET

mov BP, SP

mov BX, [BP+10] ;do BX trafia drugi arg. wywołania

mov AX, [BP+12] ;do AX pierwszy

...

;przywrócenie wartości z momentu wywołania

pop BX

pop AX

pop BP

ret

Moja ENDP

Poprawne wywołanie takiej procedury ma postać:

...

push arg1

push arg2

call Moja

pop arg2 ;jeśli zachodzi konieczność odczytania zmodyfikowanych wartości argumentów

pop arg2 ;można je teraz odczytać...

...

( ! ) - W przypadku, gdy po wywołaniu procedury nie interesują nas wartości argumentów które odłożyliśmy na stos, nie ma potrzeby używania POP do ściągnięcia ich ze stosu. Wystarczy w następnej linii kodu napisać

ADD SP, N

gdzie N jest ilością bajtów, jaką zajmowały argumenty na stosie.

Zmienne lokalne

Jeśli zachodzi konieczność wykorzystywania zmiennych lokalnych wewnątrz procedur, zmienne takie należy odłożyć na stosie. W tym celu rezerwuje się odpowiednią ilość N - bajtów miejsca (SUB SP, N ). W tym momencie mamy do dyspozycji N bajtów stosu do wykorzystania jako zmienne lokalne. Do kolejnych zmiennych odwołujemy się także przy użyciu rejestru BP, który najpierw przyrównujemy do SP.

( ! ) Jeśli po zarezerwowaniu miejsca na zmienne lokalne chcemy odwołać się np. do argumentów wywołania procedury odłożonych na stosie, należy pamiętać, że adres BP przesunął się o N bajtów i adres tych parametrów względem nowej wartości BP jest większy o N np.

mov BP, SP

; pod BP+4 - pierwszy argument wywołania

sub SP, 4 ;zarezerwowano dwa słowa na zmienne lokalne

mov BP, SP ;SP wskazuje na ostatnią zmienną lokalną

; pod BP + 8 - pierwszy arg. wywołania

( ! ) W momencie, gdy zmienne lokalne nie są już potrzebne należy zwolnić zajmowane przez nie miejsce na stosie za pomocą ADD SP, N.

( ! ) Przy okazji rezerwowania zmiennych na stosie dobrze jest obarczyć każde rezerwowane słowo komentarzem np.

sub SP, 4; rezerwacja miejsca na 2 słowa

mov BP, SP

;OPIS ZMIENNYCH LOKALNYCH:

;BP - zmienna używana do ...

;BP+2 - zmienna używana do ...

...

Punkty wejściowe

W ramach procedury można zdefiniować etykiety, które później można traktować jak punkty wejściowe tej procedury. Dzięki temu można korzystając z 1 kodu procedury realizować różne funkcje ( omijając za pomocą punktu wejściowego kilka pierwszych instrukcji). Dzięki temu można np. zdefiniować wartości domyślne procedury, które będą inicjalizowane instrukcjami zaraz po dyrektywie PROC, a następnie zdefiniować punkt wejściowy po tych instrukcjach, pozwalający na zewnętrzne ustawienie tych parametrów np.

DodajDomysl PROC

mov AX, 2

mov BX, 2

Dodaj: add AX, BX

ret

DodajDomysl ENDP

Wywołania procedury przez jej punkt wejściowy dokonuje się używając: CALL <punkt_wejściowy>.

( ! ) Do każdej procedury dodawać komentarz:

Makroinstrukcje

Makra traktowane są jak procedury „inline” - wstawiane w miejsce wywołania w czasie preprocesingu asemblacji. Odbywa się to na podobnej zasadzie, jak obliczanie wyrażeń matematycznych.

Definiowanie

Makra definiuje się ( ! ) poza wszelkimi segmentami, definicję makra rozpoczyna się następująco:

<nazwa_makra> MACRO <parametr1> , <parametr2>, ...

gdzie <parametr i> - etykiety oznaczające kolejne parametry makra, pod które w trakcie wywołania (preprocesingu) podstawiane są odpowiednie argumenty.

Parametr makra - etykiety (nazwy) wpisane bezpośrednio za dyrektywą MACRO przy definicji makra

Argument makra - konkretne wartości podane przy wywołaniu makra.

Odwołanie do parametrów (a w praktyce argumentów) odbywa się w kodzie makra za pomocą etykiet <parametr i>

Definicję makra kończy dyrektywa ENDM.

( ! ) - etykiety w makrach muszą być lokalne (każde wstawienie makra powoduje powielenie definicji etykiety, a ta musi być unikatowa). W tym celu należy w 2 linii kodu makra (po linii zawierającej MACRO) zadeklarować jako lokalne nazwy wszystkich używanych wewnątrz makra etykiet:

LOCAL <etykieta1> , <etykieta2>, ...

Etykiety takie widoczne są tylko w obrębie definicji makra.

Przykład (treść makra nie ma wiele wspólnego z nazwą)

Gotoxy MACRO WspX, WspY

LOCAL Petla, Koniec

mov DH, WspX

mov DL, WspY

Petla: dec CX

...

Koniec: mov AX,[BX]

ENDM

( ! ) - na końcu makra nie wstawiać RET (nie ma tu powrotu, bo nie ma skoku do makra)

( ! ) - przed ENDM nie stoi nazwa makra

( ! ) - jeśli wykorzystuje się w makrze instrukcje, które operują na parametrach makra, a mają ograniczenia co do tych parametrów (wykorzystuje się instrukcję która nie może używać danych natychmiastowych, instrukcja ta działa na parametrze jeśli za parametr zostanie podstawiony argument w postaci rejestru - makro zadziała poprawnie, gdy argumentem będzie dana natychmiastowa, asembler wykaże błąd) należy w opisie makra podać ograniczenia dotyczące parametrów lub spróbować ominąć niedogodności (przed użyciem instrukcji nie operującej na danych natychmiastowych, przenieść jej parametr do rejestru, następnie wywołać tą instrukcję nad tym rejestrem)

Wywoływanie

Zlecenia wstawienia makra w odpowiednie miejsce programu (wywołania) dokonuje się zgodnie ze schematem:

<nazwa_makra> <argument1> , <argument2>, ... np. Gotoxy 10, 20

( ! ) - można podać więcej argumentów niż parametrów (ostatnie zostaną zignorowane). Za mało arg. błąd.

( ! ) - nie używać CALL

Przekazywanie dalekiego adresu - przydatne, gdy musimy oprócz offsetu jakiejś danej przekazać adres jej segmentu (np. dla pracy z pamięcią karty graficznej), do tego używa się instrukcji LES wg. schematu:

Clrscr MACRO AdresBuf, Czymczysc

LES DI, DWORD PTR AdresBuf ;AdresBuf zostanie załadowany do ES:DI

Wywołanie takiego makro może mieć postać:

Clrscr 0B800H:0000H,` `

Wzorzec programu „exe”

;---------------------------SEGMENT STOSU - START

Stos SEGMENT STACK

DB 64 DUP (`STACK!!!')

Stos ENDS

;---------------------------SEGMENT STOSU - KONIEC

;---------------------------SEGMENT DANYCH - START

Dane SEGMENT

Zmienna DB

Dane ENDS

;---------------------------SEGMENT DANYCH - KONIEC

;---------------------------SEGMENT KODU - START

Kod SEGMENT

assume CS:Kod, DS:Dane

Main PROC

Start: ;tu zaczyna się program

mov AX, Dane

mov DS, AX ;pośrednie załadowanie rejestru segmentu danych

...

mov AH, 4CH ;wybór procedury DOS kończącej program

mov AL, 0 ;przekazanie wartości do zmiennej ERRORLEVEL

int 21H

Main ENDP

Kod ENDS

;---------------------------SEGMENT KODU - KONIEC

END Start

Program typu „com”

Właściwości

Wzorzec programu

Kod SEGMENT

assume CS:Kod, DS:Kod, SS:Kod

ORG 100H

Start: jmp Main

;tu definiuje się zmienne w segmencie:

Zmienna DB

...

Main PROC NEAR

...

ret

Main ENDP

Kod ENDS

END Start

Tworzenie programu wynikowego

Tworzenie bibliotek zewnętrznych

Tworzenie modułów z procedurami i danymi

Procedury można grupować w osobnych plikach. Plik taki po zasemblowaniu będzie traktowany jako moduł. Plik modułu ma następujące cechy:

PUBLIC <etykieta1> , <etykieta2> , ... <etykieta i> - nazwa udostępnianej procedury lub punktu wejściowego

W dalszej części segmentu kodu definiujemy treść tych procedur zwyczajnie przy użyciu PROC i ENDP

PUBLIC <zmienna1> , <zmienna2> , ... w dalszej części segmentu należy zdefiniować w normalny sposób te zmienne

(np. przy użyciu dyrektyw DB, DW czy DUP( ) )

Przykład:

;------------------moduł z procedurami dodawania------------------

Dane SEGMENT PUBLIC

PUBLIC Eol, Ekran

Eol DB 0DH, 0AH, `$' ; przykładowe zmienne nie związane z modułem

Ekran DW 184FH ; y = 18H = 24D, x = 4FH = 79D

Dane ENDS

Kod SEGMENT PUBLIC

PUBLIC DodajDomysl, Dodaj

ASSUME CS:Kod, DS:Dane

DodajDomysl PROC

mov AX, 2

mov BX, 2

Dodaj: add AX, BX

ret

DodajDomysl ENDP

Kod ENDS

END

Wykorzystanie procedur i danych z modułów

EXTERN <nazwa_procedury> : PROC np. EXTERN ClrScr: PROC

Dzięki temu „definiujemy” etykietę i odtąd asembler dba o jej jednoznaczność. Asembler wie, że etykieta ta oznacza

procedurę znajdującą się w zewnętrznym module.

( ! ) Deklaracji dokonuje się w segmencie kodu, poza ciałem wszelkich procedur

( ! ) Jeśli chcemy korzystać z punktów wejściowych, należy je także zadeklarować.

EXTERN <nazwa_zmiennej> : <specyfikator_typu> , ...

gdzie specyfikator typu określa rozmiar zmiennej (asembler musi wiedzieć jak taką zmienną traktować, w zależności

od rozmiaru zmiennych różne są wersje odpowiednich instrukcji maszynowych). Może on mieć następujące wartości:

BYTE, WORD, DWORD gdy zewnętrzne zmienne są zdefiniowane odpowiednio jako DB, DW, DD.

( ! ) Gdy zewnętrzna zmienna jest typu „string” lub deklarowana za pomocą DUP( ) jest ona definiowana przy pomocy

dyrektywy DB należy ją zadeklarować jako BYTE

Przykład deklaracji zmiennej łańcuchowej i słowa: EXTERN Eol:BYTE, Ekran:WORD

( ! ) Przy deklarowaniu procedury zewnętrznej rolę specyfikatora typu pełni PROC

( ! ) Deklaracja ta jest w segmencie danych

Łączenie modułów

Łączenia dokonuje linker, w tym celu należy najpierw stworzyć pliki .obj dla wszystkich modułów, następnie należy podać w wywołaniu linkera nazwę programu głównego i wszystkich modułów.

Biblioteki makr

Bibliotekę stanowi niezasemblowany plik zawierający tylko definicje kolejnych makr (bez segmentów danych ani segmentu kodu). Plik taki ma rozszerzenie .mac . Włączenie tego pliku (jako 1 „instrukcja” w kodzie programu głównego) następuje przy pomocy dyrektywy INCLUDE , po której występuje nazwa pliku.

Przerwania

Przerwania sprzętowe

Urządzenia zewnętrzne komunikują się z procesorem za pomocą przerwań sprzętowych. W odpowiedniej chwili oprócz wysłania sygnału przerwania wysyłany jest także sygnał identyfikujący dane urządzenie - kod typu. Mikroprocesor może rozróżnić 256 różnych kodów. Na podstawie kodu procesor uruchamia odpowiedni program obsługi - oblicza adres jego wektora przerwania mnożąc kod typu przez 4. Dzięki temu, w wyniku przerwania pochodzącego od różnych urządzeń uruchamiane są różne programy. Oprócz przerwań pochodzących od urządzeń, sam procesor jest w stanie wygenerować sygnał przerwania (np. przy dzieleniu przez 0). Programy obsługi przerwań mogą być także wywoływane jawnie w kodzie programów...

Przerwania programowe

W segmencie „0” znajduje się tabela o 256 pozycjach, zawierająca adresy 4 bajtowe (2 starsze bajty - segment + 2 młodsze - przesunięcie) adresy kodów różnych procedur (przerwań programowych) zajmuje ona pierwszy 1KB RAM. Każdy adres - wektor przerwania . Tabela ta zwana jest tabelą wektorów przerwań. Pamięć tą uzupełniają BIOS i DOS, każdy może zająć określone pozycje. W każdej wersji DOS w danym wektorze (numer wektora w tabeli = numer przerwania) znajduje się adres procedury (grupy procedur) wykonującej to samo, ale ta procedura znajduje się w różnych miejscach.

Wywołanie przerwania następuje za pomocą INT <numer_przerwania> np. INT 21H. Przed skokiem do odpowiedniej procedury (przerwania) INT wykonuje:

Aby umożliwić powrót z przerwania, w kodzie przerwania ostatnią instrukcją jest IRET, która wykonuje działania przeciwne do INT.

( ! ) - istnieje warunkowa wersja INT - INTO, przerwanie wykonane zostaje jedynie, gdy nastąpiło przepełnienie (overflow)

Przerwania zarezerwowane

Przerwania od 0 do 1FH są zarezerwowane przez Intel'a. Mają min. następujące funkcje:

0 - błąd dzielenia - wynik nie mieści się w rejestrze lub dzielenie przez 0

1 - pojedynczy krok - występuje po każdej instrukcji, umożliwia debugowanie krok po kroku

2 - niemaskowanle przerwanie - nie może być zablokowane z poziomu żadnego programu

3 - punkt zatrzymania - pozwala wykonywać program aż do osiągnięcia odpowiedniego adresu

4 - nadmiar - obsługuje sytuację wystąpienia nadmiaru.

...

Przerwania od 20H do 3FH są zarezerwowane przez DOS.

20h - zakończenie wykonywania programu

21h - dyspozytor usług DOS

22h - adres powrotu po wykonaniu programu

23h - adres wyjścia dla Ctrl-Break

24h - obsługa poważnego błędu

25h - odczyt sektorów z dysku

26h - zapis sektorów na dysk

27h - zakończenie programu z pozostawieniem w pamięci

Definiowanie własnych procedur obsługi przerwań

Procedury obsługi przerwań muszą być procedurami dalekimi. Definiuje się je zgodnie ze wzorcem:

<nazwa> PROC FAR

...

IRET

<nazwa> ENDP

Wewnątrz procedury nie trzeba się martwić o ustawianie IF, odkładanie na stos zawartości flag itd. ponieważ dba o to INT.

Podmiana istniejących procedur obsługi przerwań

  1. przed podmianą należy zapamiętać dotychczasową zawartość wektora podmienianego przerwania

  2. należy wstawić odpowiedni bezwzględny adres kodu procedury (własny wektor przerwania)

  3. przed przekazaniem sterowania z programu do systemu operacyjnego należy przywrócić pierwotny wektor przerwania

Przy realizacji tych czynności korzysta się z następujących funkcji przerwania 21H DOS:

Przykład przechwycenia przerwania obsługi dzielenia przez zero:

; w segmencie danych istnieje linia: wektor DW

; zdefiniowano daleką procedurę MojePrzerw

; w segmencie danych istnieje linia: adres DD MojePrzerw

...

;odczytanie dotychczasowej wartości

mov AH, 35H

mov AL. 0

int 21H

;zapamiętanie starej wartości

mov wektor[0], BX

mov AX, ES

mov wektor[1], AX

;zapisanie nowego adresu

lds DX, adres ;do DS:DX trafia adres nowej proc. obsługi

mov AH, 25H

mov AL. 0

int 21H

...

;przywrócenie pierwotnych wart.

lds DX, DWORT PTR wektor

mov AH, 25H

mov AL. 0

int 21H

...

Dyspozytor usług DOS

DOS udostępnia ponad 50 procedur, znajdują się one pod przerwaniem 21H. Wybór procedury za pośrednictwem AH oraz odpowiednio ustawionych innych rejestrów, w zależności od wymagań danej usługi.

( ! ) - Funkcje obsługi dysku twardego modyfikują CF. Gdy CF = 0 - operacja zakończyła się pomyślnie, CF=1 gdy nastąpił błąd, którego kod trafia do AX.

( ! ) - wszystkie łańcuchy zawierające nazwy plików muszą kończyć się znakiem `0'.

AH

Operacja

Dane wejściowe

Wyniki

Obsługa ekranu

2

Wyświetlanie znaku (z kontrolą Ctrl-Break)

DL = znak

kursor za znakiem

5

Wydruk znaku

DL = znak

6

Wyświetlenie znaku (bez kontroli Ctrl-Break)

DL = znak

kursor za znakiem

9

Wyświetlenie łańcucha zakończonego `$'

(nie można w ten sposób wyświetlić znaku dolara)

DS:DX = adres łańcucha

( ! ) - zakończony `$'.

kursor za łańcuchem

40

patrz Rozszerzone funkcje obsługi plików

BX = 1, ...

Obsługa klawiatury

1

Czekanie na znak z klawiatury, następnie wyświetlenie go (z kontrolą Ctrl-Break)

AL = znak

6

Odczyt znaku z klawiatury (bez kontroli Ctrl-Break)

DL = 0FFH

AL = znak, jeśli on jest

= 0, gdy brak znaku

7

Czekanie na znak z klawiatury, ale bez wyświetlania (bez kontroli Ctrl-Break)

AL = znak

8

Czekanie na znak z klawiatury, ale bez wyświetlania

(z kontrolą Ctrl-Break)

AL = znak

A

Wczytanie łańcucha z klawiatury do bufora

(rozmiar bufora powinien być o 2 większy niż ilość bajtów, jakie maksymalnie chcemy tam wczytać,

koniec wczytywania następuje w momencie naciśnięcia ENTER)

DS:DX = adres bufora,

( ! ) - pierwszy bajt bufora określa jego rozmiar w bajtach nie licząc tego bajtu, a wliczając 2-gi bajt.

Drugi bajt bufora określa ilość przeczytanych znaków

B

Odczyt stanu klawiatury

AL = FFH - brak znaku,

= 0 - znak dostępny

C

Wyczyszczenie bufora klawiatury i wywołanie funkcji obsługi klawiatury

AL = numer funkcji obsługi

(1, 6, 7, 8, A)

W zależności od wywoływanej funkcji

Czas i data

2A

Pobranie daty

CX = rok

DH = miesiąc

DL = dzień

2B

Ustawienie daty

CX = rok (1980 - 2099)

DH = miesiąc

DL = dzień

AL = 0 - data poprawna

= FF - data błędna

2C

Pobranie czasu

CH = godzina

CL = minuty

DH = sekundy

DL = setne sekundy

2D

Ustawienie czasu

CH = godzina

CL = minuty

DH = sekundy

DL = setne sekundy

AL = 0 - czas poprawny

= FF - czas błędny

Komunikacja asynchroniczna

3

odczytanie znaku z wejścia asynchronicznego

AL = znak

4

wysłanie znaku na wyjście asynchroniczne

DL = znak

Obsługa plików

D

Zapis zmienionych buforów dyskowych z powrotem na dyskietkę

E

Wybranie domyślnego napędu dysku

DL = numer dysku

(0 = A, 1 = B, 2 = C ...)

AL = ilość dysków

(2 dla jednego napędu)

19

Pobierz numer domyślnego dysku

AL = nr dysku (j. w.)

2E

Zmień stan wskaźnika weryfikacji

DL=0

AL = 0 - wyłączenie

= 1 - włączenie

30

Pobranie numeru wersji MS-DOS

AL = numer wersji

AH = numer zmiany

BX, CX = 0

Obsługa wektora przerwań

25

Ustawienie wektora przerwań

DS:DX = adres wektora

AL = numer przerwania

35

Pobranie wektora przerwań

AL = numer przerwania

ES:BX = adres wektora

Rozszerzone funkcje obsługi plików i katalogów

39

Utworzenie katalogu

DS:DX = adres łańcucha z nazwą katalogu

3A

Usunięcie katalogu

DS:DX = j. w.

3B

Zmiana katalogu roboczego

DS:DX = adres katalogu do którego chcemy przejść

47

Pobranie nazwy katalogu roboczego - bieżącego

DS = nr napędu

(0 = domyślny,

1 = A, 2 = B ...)

DS:DX = adres 64-bajtowego bufora

DS:SI = adres łańcucha docelowego

36

Pobranie wolnego obszaru na dysku

DL = nr napędu (j. w.)

AX = ilość sektorów na klaster, = 0FFFFH w razie błędu

BX = ilość wolnych klastrów

CX = ilość bajtów na sektor

DX = całkowita ilość klastrów

3C

Utworzenie pliku

DS:DX = adres łańcucha z nazwą

CX = atrybuty pliku (patrz pod tabelką)

AH = uchwyt do pliku

(patrz pod tabelką)

3D

Otwarcie pliku

DS:DX = adres łańcucha z nazwą

AL =0 - otwarcie do odczytu

=1 - zapisu

=2 - odczyt & zapis

AH = uchwyt do pliku

(patrz pod tabelką)

3E

Zamknięcie pliku

BX = uchwyt pliku

(patrz pod tabelką)

3F

Odczyt z pliku (urządzenia)

BX = uchwyt pliku

(patrz pod tabelką)

CX = ilość czytanych bajtów

DS:DX = adres bufora

AX = ilość odczytanych bajtów

= 0 odczyt poza plik

40

Zapis do pliku (urządzenia)

BX = uchwyt pliku

(patrz pod tabelką)

CX = ilość bajtów zapisu

DS:DX = adres źródła czyt.

AX = ilość zapisanych bajtów

41

Usunięcie pliku

DS:DX = adres łańcucha z nazwą

43

Nadawanie atrybutów plikowi

AL = 1

DS:DX = adres łańcucha z nazwą

CX = atrybuty (patrz pod tabelką)

54

Pobranie znacznika sprawdzania

AL = 0 - brak zapisu

sprawdzania

= 1 - zapis ze

sprawdzaniem

56

Zmiana nazwy pliku

DS:DX = adres łańcucha ze starą nazwą

ES:DI = adres łańcucha z nową nazwą

Zarządzanie procesami

31

Zakończenie programu z pozostawieniem w pamięci

AL = kod powrotu

DX = rozmiar pamięci w paragrafach

4B

Załadowanie i wykonanie programu

AL = 0

DS:DX = adres łańcucha nazwy programu

ES:BX = adres bloku parametrów

4C

Zakończenie programu

AL = kod powrotu dla ErrorLevel

4D

Pobranie kodu błędu dla wywołanego programu

AL = kod powrotu dla procesu wołającego

62

Pobranie adresu bloku PSP

BX = adres segmentu PSP programu

Zarządzanie pamięcią

48

Zarezerwowanie pamięci systemowej (RAM)

BX = ilość rezerwowanych paragrafów

AX = adres segmentu z zarezerwowaną pamięcią

49

Zwolnienie zarezerwowanej pamięci

ES = adres segmentu ze zwalnianą pamięcią

4A

Zmiana wielkości zarezerwowanego bloku pamięci

ES = adres segmentu obszaru pamięci

Pobranie rozszerzonego kodu błędu

59

Pobranie rozszerzonego kodu błędu

BX = 0

AX = rozszerz. kod

BH = klasa błędu

BL = zalecana reakcja

CH = położenie

Atrybuty pliku określa bajt atrybutów. Znaczenie odpowiednich bitów:

0 - read only, 1 - hidden, 2 - system, 3 - czy etykieta dysku, 4 - czy nazwa podkatalogu, 5 - czy zwykły plik (nie backup)

Uchwyt pliku - numer identyfikujący otwarty plik lub urządzenie I/O. Istnieją następujące przypisania uchwytów:

0 - standardowe wejście (DOS przypisuje to klawiaturze, ale można zmienić)

1 - standardowe wyjście (domyślnie - ekran)

2 - standardowe wyjście strumienia błędów (niezmienialne)

3 - standardowe urządzenie dodatkowe (plik)

4 - drukarka

Standardowo do dyspozycji użytkownika pozostają dalsze 4 uchwyty, bo DOS rezerwuje 8 uchwytów. Można to jednak zwiększyć.

Przykładowe przerwania oferowane przez DOS / BIOS

( ! ) Często w ramach przerwania istnieje kilka osobnych procedur (usług), ich wybór dla przerwań BIOS ( i chyba wszystkich DOS) dokonuje się na podstawie zawartości rejestru AH

VIDEO (BIOS) - przerwanie 10H.

Konfiguracja (BIOS) - przerwanie 11H, zapisuje do AX słowo, w którym ustawienie poszczególnych bitów oznacza:

0 - istnieje FDD 1 - istnieje koprocesor 2 - zainstalowana mysz 3 - ilość RAM 4,5 - tryb video 6,7 - ilość FDD

9,10,11 - ilość portów szeregowych 12 - istnieje GamePort 13 - zainstalowany modem (wew.) 14,15 - il. druk.

Programy rezydentne

Program rezydentny różni się od zwykłego programu tylko funkcją, przekazując sterowanie do systemu operacyjnego. Program nie zostaje skasowany z pamięci, jego dane także tam pozostają i inne programy nie mogą z tej pamięci korzystać. Wykorzystywane to jest głównie, jeśli podmieniania się procedury obsługi przerwań. Wówczas, jeśli podmienimy przerwanie i opuścimy program, kod procedury podmieniającej musi zostać w pamięci w postaci programu TSR. Programy rezydentne mogą również wykonywać inne funkcje oprócz zmiany obsługi przerwań. Ich praca polega wówczas na oczekiwaniu na zdarzenie (przerwanie), które budzi taki program do pracy. W związku z tym program taki musi podmienić np. procedurę obsługi przerwania klawiatury, żeby zareagować na odpowiednią kombinację klawiszy uaktywniającą jego działanie itp.

Program można uczynić rezydentnym używając jednej z następujących funkcji:

KEEP - funkcja 31H przerwania 21H. Przed wywołaniem przerwania należy ustawić:

AH - 31H

AL - kod wyjścia, jaki program przekaże do procesu rodzica (np. do zmiennej ERRORLEVEL)

DX - rozmiar pamięci zatrzymywanej dla programu rezydentnego w 16-bajtowych paragrafach

Funkcja ta powoduje wyjście do procesu rodzica (np. shell'u DOS) zostawiając kod i dane procesu w pamięci. Gdy program otrzymuje kontrolê, funkcja EXEC (4bH) alokuje blok pamiêci zaczynajacy siê od PSP i zawieraj¹cy ca³¹ dostêpn¹ pamiêæ. Pamiêæ ta przydzielana jest procesowi. Wykorzystuj¹c funkcjê 31H DOS ogranicza pamięć przeznaczoną dla procesu rezydentnego do rozmiaru zapisanego w DX (wartość DX * 16 Bajtów). Funkcja ta nie zwalnia żadnych obszarów pamięci, które proces w między czasie alokował przy użyciu funkcji 48H.

TSR - przerwanie 27H. Należy ustawić:

DX - adres ostatniej komórki pamięci do zatrzymania jako część rezydentna, podany jako offset względem DS,

powiększony o 1.

Funkcja powoduje przekazanie sterowania do DOS, przy czym w pamięci pozostaje obszar pamięci o początku na początku PSP i długości zawartej w DX. Pamięć ta nie jest udostępniana innym procesom.

( ! ) Przerwanie 27H w momencie przekazywania sterowania do DOS przywraca wartości wektorów przerwań 22H-24H użycie tej funkcji do instalowania programów przechwytujących Critical Error lub Ctrl - Break jest niemożliwe.

( ! ) Przerwanie 27H nadaje się tylko do instalowania jako rezydentne programów o rozmiarze <= 64KB (co wynika z faktu, że rozmiar podawany jest w Bajtach w 16-bitowym rejestrze DX)

Dyrektywy zaawansowane

EVEN - wymusza parzysty adres wskaźnika asemblera, dane spod parzystych adresów odczytywane są szybciej program może wykonywać się szybciej.

<nazwa> GROUP <nazwa_segmentu1> [,<nazwa_segmentu2>, ...] - grupuje segmenty pod jedną nazwą <nazwa> - segmenty te zajmują jeden 64 kilobajtowy obszar.

IF?? <wyrażenie> ... [ ELSE ] ... ENDIF - dyrektywa pozwalająca na warunkowe poddawanie asemblacji pewnych części programu w zależności od <wyrażenie>. Asemblacja instrukcji przed ELSE zachodzi w zależności od rodzaju dyrektywy IF:

IFE - wyrażenie jest 0

IF - wyrażenie nie jest 0

IFDEF - gdy wyrażeniem jest zdefiniowany symbol

IFNDEF - gdy wyrażeniem nie jest zdefiniowany symbol

Operatory

Operatorów można używać w polu argumentu asemblera lub dyrektywy.

+, -, *, /, MOD - o składni <wartość1> <operator> <wartość2>,

SHL, SHR o składni <wartość1> <operator> <wyrażenie>

AND, OR, XOR - o składni <wartość1> <operator> <wartość2>,

NOT - o składni NOT <wartość>

wszystkie posiadają składnię <argument1> <operator> <argument2>, zwracają prawdę gdy:

EQ - a1 = a2, NE - a1 != a2, LT - a1 < a2, GT - a1 > a2, LE - a1 <= a2, GE - a1 >= a2

$ - zwraca bieżącą wartość wskaźnika asemblera - wartość wskaźnika, gdzie stoi $

SEG - zwraca wartość segmentu zmiennej lub etykiety stojącej za tym operatorem

OFFSET - zwraca wartość offsetu zmiennej lub etykiety

LENGTH - zwraca rozmiar (w bajtach lub słowach - w zależności od tego jak zmienna została zdefiniowana)

zmiennej zdefiniowanej przy użyciu DUP

TYPE - dla zmiennych stojących za TYPE zwraca: 1 - BYTE, 2 - WORD, 4 - DWORD . Dla etykiet zwraca: -1 -

NEAR, -2 - FAR.

SIZE - zwraca iloczyn LENGTH i TYPE

PTR - występuje ze składnią: <typ> PTR <wyrażenie>, gdzie <typ> (BYTE, WORD, ... , NEAR, FAR,) oznacza

jak ma być traktowane wyrażenie, jakiego ma być typu.

DS:, ES:, SS:, CS: - atrybuty zmiany segmentu

SHORT - składnia: JMP SHORT <etykieta> - informuje asembler, że <etykieta> leży nie dalej niż 127 bajtów od

instrukcji aktualnej.

THIS - zwraca adres bieżącego wskaźnika asemblera

HIGH - zwraca starszy bajt 16-bitowej liczby

LOW - zwraca młodszy bajt - // -

Koprocesor

( ! ) - Aby korzystać z koprocesora, należy podać dyrektywę .287 lub podobną.

Budowa

Koprocesor posiada 8, 80-bitowych rejestrów danych, 1 słowo stanu, 1 słowo kontrolne. Słowo stanu spełnia funkcje analogiczne do rejestru flag. Słowo kontrolne określa, w jaki sposób koprocesor ma wykonywać zaokrąglenie, jak ma traktować nieskończoność i ustala dokładność obliczeń. Rejestrów koprocesora używa się jako stosu koprocesora. Stos ten wykorzystywany jest w obliczeniach (dane do obliczeń pobierane ze stosu, wynik ląduje na stosie).

Typy danych

Liczby w rejestrach zapisywane są w postaci: 1 - bit znaku, 15 - bitów wykładnika, 64 - bity mantysy. Korzystając z takiej reprezentacji koprocesor może operować na liczbach całkowitych oraz zmiennopozycyjnych. Dostępne są następujące typy danych (z uwzględnieniem ilości dokładnych cyfr):

Instrukcje koprocesora

Instrukcja

Opis

Przesyłanie danych

FBLD <źródło>

FBSTP <przeznaczenie>

FILD <źródło>

FIST <przeznaczenie>

FISTP <przeznaczenie>

FLD <źródło>

FST <przeznaczenie>

FSTP <przeznaczenie>

FXCH <przeznaczenie>

przenoszą liczby na i ze szczytu stosu rejestrów danych koprocesora

ładuje spakowaną dziesiętnie

zapamiętuje w <przeznaczeniu> spakowaną dziesiętnie i zdejmuje ze stosu

ładuje całkowitą

zapamiętuje w <przeznaczeniu> całkowitą ( ! - nie zdejmuje ze stosu)

zapamiętuje w <przeznaczeniu> całkowitą i zdejmuje ze stosu

ładuje rzeczywistą

zapamiętaj rzeczywistą

zapamiętaj rzeczywistą i usuń ze stosu

wymień rejestr przeznaczenia z wierzchołkiem stosu

Arytmetyczne

FADD [<przeznaczenie>, <źródło>]

FADDP <przeznaczenie>, <źródło>

FIADD źródło

FSUB [<przeznaczenie>, <źródło>]

FSUBP <przeznaczenie>, <źródło>

FISUB <źródło>

FSUBR [<przeznaczenie>, <źródło>]

FSUBRP <przeznaczenie>, <źródło>

FISUBR <źródło>

FMUL [<przeznaczenie>, <źródło>]

FMULP <przeznaczenie>, <źródło>

FIMUL <źródło>

FDIV [<przeznaczenie>, <źródło>]

FDIVP <przeznaczenie>, <źródło>

FIDIV <źródło>

FDIVR [<przeznaczenie>, <źródło>]

FDIVRP <przeznaczenie>, <źródło>

FIDIVR <źródło>

FSQRT

FSCALE

FPREM

FRNDINT

XTRACT

FABS

FCHS

( ! ) - odejmowanie i dzielenie występuje w 2 wariantach, normalnym (odejmuje się źródło od przeznaczenia, dzieli przeznaczenie przez źródło) i odwróconym (odejmuje się przeznaczenie od źródła, dzieli źródło przez przeznaczenie)

dodaje rzeczywiste

dodaje rzeczywiste i zdejmuje ze stosu

dodaje całkowite

odejmuje rzeczywiste

odejmuje rzeczywiste i zdejmuje ze stosu

odejmuje całkowite

odejmuje rzeczywiste (odwrócona)

odejmuje rzeczywiste i zdejmuje ze stosu (odwrócona)

odejmuje całkowite (odwrócona)

mnoży rzeczywiste

mnoży rzeczywiste i zdejmuje ze stosu

mnoży całkowite

dzieli rzeczywiste

dzieli rzeczywiste i zdejmuje ze stosu

dzieli całkowite

dzieli rzeczywiste (odwrócona)

dzieli rzeczywiste i zdejmuje ze stosu (odwrócona)

dzieli całkowite (odwrócona)

pierwiastek kwadratowy

skalowanie przez potęgę 2

częściowa reszta

zaokrąglenie do całkowitej

wydzielenie cechy i mantysy

Wartość bezwzględna

Zmiana znaku

Porównania

FCOM [<źródło>]

FCOMP [<źródło>]

FCOMPP

FICOM <źródło>

FICOMP <źródło>

FTST

FXAM

porównują liczby ze szczytu stosu z inną liczbą przechowywaną na stosie lub w pamięci

porównuje rzeczywiste

porównuje rzeczywiste i zdejmuje ze stosu

porównuje rzeczywiste i 2 razy zdejmuje ze stosu

porównuje całkowite

porównuje całkowite i zdejmuje ze stosu

sprawdza, czy szczyt stosu jest 0

bada szczyt stosu

Przestępne

F2XM1

FYL2X

FYL2XP1

FPTAN

FPATAN

obliczają funkcje log i trygonometryczne.

oblicza 2^X - 1

oblicza Y * log_2 (X)

oblicza Y * log_2 (X+ 1)

częściowy tangens

częściowy arcus tangens

Stałych

FLDZ

FLD1

FLDPI

FLDL2T

FLDL2E

FLDLG2

FLDLN2

umieszczają jedną ze stałych na szczycie stosu, liczby te mają pełną dokładność (Temporary Real)

załaduj 0.0

załaduj 1.0

załaduj PI

załaduj log_2 (10)

załaduj log_2 (e)

załaduj log_10 (2)

załaduj log_e (2)

Sterujące

FLDCW <źródło>

FSTCW/ FNSTCW <przeznaczenie>

FSTSW / FNSTSW <przeznaczenie>

FSTSW/ FNSTSW AX

FSAVE/ FNSAVE <przeznaczenie>

FRSTOR <źródło>

FLDENV <źródło>

FSETPM

FSTENV / FNSTENV <przeznaczenie>

FWAIT

FINIT / FNINIT

FENI / FNENI

FDISI / FNDISI

DCLEX / FNCLEX

FINCSTP

FDECSTP

FFREE

FNOP

udostępniają informacje o stanie, możliwości zmiany spos. zaokrągleń wyników, umożliwiają i zabraniają przerwania itp.

załaduj słowo sterujące

zapamiętaj słowo sterujące

zapamiętaj słowo stanu

zapamiętaj słowo stanu AX

zachowaj stan

odtwórz stan

załaduj środowisko

ustaw wirtualny tryb pracy

zapamiętaj środowisko

wait (zatrzymuje proc., zabezp. przed korzyst. z tych samych obszarów RAM)

inicjuje (resetuje) koprocesor

zezwala na przerwania

blokuje reakcje przerwania

zeruje wyjątki

zwiększa wskaźnik stosu

zmniejsza wskaźnik stosu

zwalnia (zeruje) rejestry

nic nie rób

Niebieskie karty

Instrukcje asemblera opisuje się często za pomocą niebieskich kart. Zawierają one opis działania instrukcji, ich ograniczenia, uwagi, listę modyfikowanych znaczników oraz poprawne formy ich użycia. Aby uogólnić dostępne formy użycia stosuje się następujące symbole operandów:

Instrukcje

Rozkazy transmisji danych

( ! ) - Instrukcje transmisji nie modyfikują znaczników.

MOV <op1>, <op2> przenosi zawartość operandy 2 (lub tego, na co ona wskazuje) do operandy 1. Operandy mogą być dowolne z wyjątkiem pewnych ograniczeń:

( ! ) - nie może przenosić wartości z jednej kom. pamięci do drugiej

( ! ) - nie może przenosić wart. jednego rejestru segmentowego do drugiego

( ! ) - nie może przenosić danych natychmiastowych do rejestrów segmentowych

( ! ) - nie może działać na mieszanych operandach różnych wielkości (MOV 16bitów 8bitów)

( ! ) - nie potrafi operować na rejestrze znaczników

W przypadku, gdy operand 2 jest etykietą zmiennej, do operandu 1 przeniesiona zostanie wartość tej zmiennej.

LEA <op1>, <op2> przenosi efektywny adres operandu 2 do operandu 1 np. LEA DX, Zmienna

LDS <op1>, <op2> przenosi daleki adres <op2> do pary rejestrów DS:<op1>

LES <op1>, <op2> przenosi daleki adres <op2> do pary rejestrów ES:<op1>

LAHF przenosi do AH młodszy bajt rejestru znaczników

SAHF przenosi do młodszego bajtu rejestru znaczników zawartość rejestru AH

IN <op1> , <op2> przenosi do <op1> zawartość port[ <op2> ]

( ! ) operandy mogą stanowić tylko pary: AL i wartość 8-bitowa albo AX i DX

OUT <op1>, <op2> przenosi do port[ <op1> ] zawartość <op2>

( ! ) operandy mogą stanowić tylko pary: wartość 8-bitowa i AL albo DX i AX

XCHG <op1>, <op2> zamienia między sobą wartości wskazywane przez operandy.

XLAT zapisuje w AL bajt pamięci spod adresu DS:[BX+AL] , stosuje siê, gdy pod DS:BX jest tablica bajtów, a do AL załadowano indeks interesuj¹cego elementu tablicy

PUSHA (286) - odkłada na stos rejestry w kolejności: AX, CX, DX, BX, SP, BP, SI, DI.

( ! ) Wartość SP odłożona na stos - wartość przed wywołaniem PUSHA jeśli chce się zdejmować przez POP kolejne rejestry, ignorować zdjęcie SP, bo po zdjęciu SP będziemy nim wskazywać poniżej (w sensie stosu a nie adresów) rejestrów AX, CX, DX i BX i stracimy do nich dostęp

POPA (286) - ściąga ze stosu rejestry w kolejności odwrotnej do PUSHA, przy czym ignoruje zapis SP

PUSHAD i POPAD (386) - jak PUSHA i POPA, tylko dla rejestrów 32-bitowych

IN <op1>, <op2> - przenosi dane z portu do akumulatora. <op1> = AL - przenoszony jest bajt, = AX - przenoszone jest słowo, <op2> - numer portu - dana natychmiastowa lub zawartość rejestru DX.

OUT <op1>, <op2> - przenosi dane z akumulatora na port, <op1> - numer portu - dana natychmiastowa lub zawartość DX, <op2> - dana do przesyłu w AL lub AX.

Operacje arytmetyczne

( ! ) O ile nie jest to opisane, instrukcje arytmetyczne mogą modyfikować: OF, SF, ZF, AF, PF, CF

INC <op> zwiększa o 1 wartość operandu, ( ! ) nie zmienia CF.

DEC <op> zmniejsza o 1 wartość operandu, ( ! ) nie zmienia CF

NEG <op> neguje wartość operandu 5 -5

ADD <op1> <op2> dodaje <op2> do <op1>, dla bez i ze znakiem

SUB <op1> <op2> odejmuje <op2> od <op1>, dla bez i ze znakiem

MUL <op> mnożenie bez znaku, wynik umieszczany w AX lub jeśli jest większy od 65535 w parze DX i AX (młodsze bity w AX). <op> może być r8, wówczas drugi mnożnik ładuje się do AL (mnożenie 8-bitowe). <op> może być <r16> lub <m16>, wówczas drugi mnożnik do AX (dwa słowa).

( ! ) MUL modyfikuje AX i DX

( ! ) <op> NIE może być daną natychmiastową

IMUL <op> tak samo jak MUL, ale dla liczb ze znakiem, wynik trafia do AX jeśli nie jest większy od 32767

DIV <op> dzieli liczbę bez znaku znajdującą się w AX przez <op>, wynik trafia do AL, a do AH trafia modulo. Możliwe jest także dzielenie 16-bitowe dzielna znajduje się w DX - AX, wynik trafia do AX, modulo do DX

IDIV <op> jak DIV, tylko dla liczb ze znakiem

ADC <op1> <op2> dodaje <op2> do <op1> wykorzystując także CF,

SBB <op1> <op2> odejmuje <op2> od <op1> korzystając z pożyczki w CF

CBW jeśli bit znaku jest w AL ustawiony wypełnia jedynkami AH, w przeciwnym wypadku wypełnia go zerami

CWD jeśli bit znaku w AX ustawiony wypełnia jedynkami DX, w przeciwnym wypadku wypełnia go zerami

AAA wprowadza poprawkę korygującą wynik dodawania dla BCD (działa na AX). W BCD w ramach bajtu zajęte są tylko 4 najmłodsze bity (4 starsze są ignorowane), przy czym dozwolone jest zapisanie na nich cyfr 0-9. AAA działa następująco: koryguje zawartość 4 młodszych bitów AL, - jeśli zawierają liczbę >9 dodaje 6 do AL., zeruje 4 starsze bity, ustawia znaczniki CF i AF, ponieważ zawartość AH została zwiększona. Jeśli w AL. była liczba <= 9 CF i AF są zerowane.

AAD poprawia wynik odejmowania dla BCD, działa na AX

AAM poprawia wynik mnożenia dla BCD, działa na AX

AAS poprawia wynik dzielenia dla BCD, działa na AX

( ! ) Znaki ASCII odpowiadające cyfrom są tak zdefiniowane, że jeśli bajt odpowiadający kodowi ASCII danego znaku potraktować jak liczbę BCD wówczas ma ona wartość symbolizowaną przez ten znak.

DAA korygowanie wyniku dodawania dla liczb traktowanych jako upakowane BCD, gdzie każdy bajt reprezentuje 2-cyfrową liczbę dziesiętną, wykorzystuje się po 4 bity na każdą cyfrę

DAS korygowanie wyniku odejmowania dla liczb traktowanych jako upakowane BCD

Operacje logiczne

( ! ) Nie można ich wykonywać na rejestrach segmentowych najpierw skopiować zawartość rejestru seg. do innego

( ! ) Działają na bajtach i na słowach

AND <op1>, <op2> dokonuje logicznego and na każdej z pary bitów pochodzących po jednym z <op1> i <op2>, wynik umieszcza w <op1>.

<opi> - może być 8 lub 16-bitowe.

OR <op1>, <op2> jak w AND, tylko dla logicznego or.

XOR <op1>, <op2> jak w AND, tylko dla logicznego xor.

NOT <op> neguje wszystkie bity <op>, wynik w <op>

( ! ) nie modyfikuje ¿adnych znaczników (nawet ZF)

Wydzielanie bitu - za pomocą maski (w której badany bit ustawiony jest na 1) i instrukcji AND między maską a badanym bajtem (słowem)

Ustawianie bitu - tworzy się maskę, w której bity do ustawienia mają wart. „1”, a następnie OR między maską a bajtem / słowem w którym chcemy ustawić bity

Zerowanie bitu - stworzyć maskę, w której bity do zerowania ustawiamy na „1”, robimy NOT na bajcie (słowie) w którym chcemy zerować bity, OR między maską i zanegowanym bajtem (słowem), wynik negujemy (NOT)

Szybkie zerowanie danej - użycie instrukcji XOR na tej samej danej np. XOR AX, AX zeruje AX.

Instrukcje przesuwania

SHL <op>, <licznik> przesuwa bity <op> w lewo, uzupe³nia 0-wym bitem, <op> - pamiêæ lub rejestr, <licznik> - liczba „1” lub rejestr CL (nie CX), w CL jest iloœæ, o jaki¹ nale¿y przesun¹æ bity, ewentualnie przesuniêcie o „1”.

( ! ) wysuwane bity trafiaj¹ do CF (a z niego donik¹d)

( ! ) przesuniêcie 16-krotne = zerowanie s³owa

(286) - <licznik> może być dowolną wartością natychmiastową np. SHL AX, 7

(286) - wartość przesunięcia <licznik> ograniczono do 31 (bo np. 32 daje to samo co 0)

SAL <op>, <licznik> działa tak samo jak SHL

SHR <op>, <licznik> jak SHL, tylko w prawo i także uzupełnia zerami (też 286).

SAR <op>, <licznik> jak SHR, ale uzupe³nia najstarszym bitem (bitem znaku)

ROL <op>, <licznik> rotuje bity <op> w lewo, wysuniêty bit trafia na koniec oraz do CF (też 286)

ROR <op>, <licznik> jak ROL, tylko w prawo, wysuniêty bit trafia na pocz¹tek oraz do CF (też 286)

RCL <op>, <licznik> rotuje bity <op> w lewo, wysuniêty bit trafia do CF a bit z CF na koniec(też 286)

RCR <op>, <licznik> jak ROL, tylko w prawo, wysuniêty bit trafia do CF, a bit z CF na pocz¹tek (też 286)

Szybciej jest zapisaæ kilka razy SH? <op>, 1 , niż raz przez załadowanie do CL liczby przesunięć (do 4 razy włącznie)

Szybkie mnożenie i dzielenie - w przypadkach mnożenia i dzielenia przez potęgę dwójki lepiej jest używać przesuwania niż instrukcji mnożenia i dzielenia. (zwykłe MUL trwa 77 cykli)

( ! ) - lepiej rezerwowaæ trochę miejsca „na wyrost” przy definiowaniu wielkości elementu tablicy ( n ), aby była ona wielokrotnością 2, ponieważ szukając przesunięcia i-tego elementu musimy mnożyć i * n (co szybko wykonamy za pomocą SHL)

Instrukcje skoku i pętli

JMP <etykieta> --> [Author:brak] skok do <etykieta> (<etykieta> - wczeœniej zdefiniowana etykieta lub konkretna wartość liczbowa - segment:offset), skok jest bezwarunkowy.

( ! ) możliwy jest skok do dalekiej etykiety (do pełnego 32-bitowego adresu)

J? <etykieta> skok do <etykiety> w zależności od `?' i stanu znaczników (skok warunkowy)

( ! ) Instrukcje skoku warunkowego umożliwiają skoki bliskie - do adresu położonego o [-128, 127] od aktualnego

( ! ) jeżeli zachodzi potrzeba wykonania skoku warunkowego dalekiego, wykonuje się wartunkowy skok bliski do instrukcji

dokonującej bezwarunkowego skoku dalekiego

( ! ) Dla każdej instrukcji skoku warunkowego istnieje instrukcja wykonująca skok, gdy jest spełniony przeciwny warunek

( ! ) każda instrukcja ma synonim (np. większy = mniejszy lub równy)

( ! ) Istnieją osobne wersje instrukcji dla liczb signed i unsigned.

Liczby ze znakiem - najstarszy bit = 1, gdy liczba ujemna

Relacje między liczbami:

Relacje po wykonaniu CMP:

Relacja

dla:

bez znaku

Skocz jeśli

(co testuje)

dla:

ze znakiem

Skocz jeśli

(co testuje)

=

JE

ZF = 1

JE

ZF = 1

< >

JNE

ZF = 0

JNE

ZF = 0

>

JA

JNBE

CF = 0 i ZF = 0

JG

JNLE

ZF = 0 lub SF = OF

<

JB

JNAE

CF = 1

JL

JNGE

SF < > OF

>=

JAE

JNB

CF = 0

JGE

JNL

SF = OF

<=

JBE

JNA

CF = 1 lub ZF = 1

JLE

JNG

ZF = 1 i SF < > OF

Instrukcje skoku warunkowego testujące pojedyncze flagi:

mnemonik

działanie

co testuje

JNS

skok, gdy brak znaku

SF = 0

JS

skok, gdy jest znak

SF = 1

JNO

skok, gdy nie ma przepełnienia

OF = 0

JO

skok, gdy jest przepełnienie

OF = 1

JP/JPE

skok, gdy jest parzystość

PF = 1

JNP/JPO

skok, gdy nie ma parzystości

PF = 0

JCXZ <etykieta> skok do <etykieta>, skacze gdy CX = 0 (nie sprawdza żadnego znacznika)

( ! ) tylko skoki bliskie [-128 - 127]

( ! ) nie istnieje instrukcja przeciwna JCXNZ - skacząca dopóty, dopóki CX <>0.

( ! ) nie pomylić się JCXZ działa jakby odwrotnie niż LOOP, tzn. NIE skacze, gdy CX <> 0 nadaje się jako instrukcja wyjścia z pętli nieskończonej - do skoków poza pętlę, gdy licznik CX osiąga 0.

CMP <op1>, <op2> - porównuje operandy i ustawia odpowiednie znaczniki, w zależności od relacji między operandami (<, <=, =, >=, > ). Działa jakby wykonywała: wynik = <op1> - <op2>, zapomina wynik a tylko ustawia znaczniki.

( ! ) gdy chcemy użyć instrukcji skoku np. J?, w zależności od relacji między dwoma argumentami, należy najpierw wykonać CMP na tych argumentach, a dopiero potem użyć J?, bo J? skaczą (lub nie) tylko na podstawie zawartości flag.

TEST <op>, <maska> - testuje wyszczególnione maską bity <op>, dokonując operacji AND, wynik „gubi”, jedynie ustawia znaczniki.

( ! ) TEST modyfikuje ZF potrafi jedynie sprawdzić, czy dany bit jest ustawiony, gdy chodzi o sprawdzenie, czy są zerami, należy najpierw użyć NOT <op>

BT <op>, <nr> (386) - sprawdza wartość bitu o numerze <nr> (od 0) w operandzie <op>, bit ten jest kopiowany do CF.

( ! ) TEST jest szybsze, ale tylko dla sprawdzania, czy bit jest `1' .

LOOP <etykieta> - instrukcja pętli, działa następująco:

LOOPNZ / LOOPNE <etykieta> - instrukcja pętli podobna do LOOP, działa następująco:

LOOPZ / LOOPE <etykieta> - instrukcja pętli podobna do LOOP, działa następująco:

( ! ) Instrukcje LOOP? stosuje się, gdy oprócz wyjścia z pętli po wykonaniu zadanej ilości razy istnieje potrzeba wyjścia, gdy jakaś zmienna (rejestr) będzie równa / różna wzorcowi (stała, zmienna, rejestr). Wówczas ostatnią linią przed wywołaniem LOOP? jest instrukcja CMP porównująca odpowiednią zmienną ze wzorcem.

Instrukcje łańcuchowe

Instrukcje łańcuchowe korzystają z następujących założeń:

( ! ) łańcuch źródłowy wskazywany jest przez DS:SI (suorce index)

( ! ) łańcuch docelowy wskazywany jest przez ES:DI (destination index)

( ! ) długość obu łańcuchów określona jest w CX

( ! ) dane przechodzą przez rejestr AX (będzie on modyfikowany)

( ! ) kierunek kopiowania (modyfikowania SI i DI) zależny jest od flagi DF, gdy DF=0 adresy te są zwiększane, gdy DF=1 adresy są zmniejszane.

REP STOS{W/B} - tworzy łańcuch docelowy pod ES:DI (o długości 2*CX bajtów) wypełnia go CX razy słowem zawartym w AX (bajtem z AL). ( ! ) Przy każdym odczycie słowa z pamięci CX jest zmniejszany o 1, aż zostanie wyzerowany. Wówczas instrukcja kończy działanie z wskaźnikiem ES:DI ustawionym na pierwszym słowie (bajcie) za stworzonym łańcuchem.

( ! ) Gdy DF = 1 - kopiowanie słów (bajtów) zaczyna się od adresu ES:DI, przy czym za każdym razem adres DI zostaje zmniejszony o 2 (1) bajty, i tam kopiowane zostaje następne słowo („w tył”). Na końcu DI wskazuje początek łańcucha.

STOS{W/B} - kopiuje słowo z AX (bajt z AL) pod ES:DI, DI zwiększane (zmniejszane dla DF = 1) jest o 2, nie modyfikuje CX

REP MOVS{W/B} - odpowiednik instrukcji REP STOSW (bez REP jak STOSW), różnica polega na tym, że dane pobierane są spod adresu DS:SI i kopiowane słowo po słowie lub bajt po bajcie (przy udziale AX / AL) pod adres ES:DI

LODS{W/B} kopiuje spod DS:[SI] słowo / bajt i zapisuje je w AX / AL. Na koniec zwiększa SI o słowo / bajt.

SCAS{W/B} porównuje (odejmuje ustawiając tylko znaczniki) słowo / bajt spod ES:[DI] z zawartością AX / AL. Następnie zwiększa DI.

CMPS{W/B} porównuje słowa / bajty spod DS:[SI] oraz ES:[DI], w zależności od DF zwiększa lub zmniejsza SI i DI.

REP przedrostek powtarzający dla MOVS, STOS, jeśli CX <>0 wykonuje odpowiednią instrukcję łańcuchową, dekrementuje CX oraz zmniejsza o 1 zawartość IP (w ten sposób następną instrukcją będzie znów REP)

REPE / REPZ przedrostek dla SCAS, CMPS, gdy CX<>0 wykonuje odpowiednią instrukcję łańcuchową, dekrementuje CX, jeśli ZF = 1 wówczas powtarza pętlę (zmniejsza IP o 1)

REPNE / REPNZ przedrostek dla SCAS, CMPS, gdy CX<>0 wykonuje odpowiednią instrukcję łańcuchową, dekrementuje CX, jeśli ZF = 0 wówczas powtarza pętlę (zmniejsza IP o 1)

INS{W/B} w DX znajduje się numer portu, przesyła zawartość portu do łańcucha przeznaczenia

OUTS{B/W} w DX numer portu, przesyła na port dane z łańcucha źródłowego

Instrukcje synchronizacji zewnętrznej

NOP - nie robi nic, zapycha miejsce w kodzie programu, wprowadza drobne opóźnienia

HLT - zatrzymuje wykonywanie instrukcji, może być tylko odblokowane przez przerwanie niemaskowalne

WAIT - zatrzymanie, odblokowane na skutek spełnienia warunku na linii wejściowej procesora TEST - musi zajść zdarzenie związane z urządzeniem zewnętrznym

ASSEMBLER strona 1

SS + rozmiar

SS:SP

SS:0



Wyszukiwarka