Wysłany: Nie 16:05, 06 Lis 2005 |
|
|
Popiol |
Poison Headcrab |
|
|
Dołączył: 02 Lis 2005 |
Posty: 16 |
Przeczytał: 0 tematów
Ostrzeżeń: 0/5
|
Skąd: Wygiełzów |
|
|
 |
 |
 |
|
Witam wszystkich i zapraszam na randke z assemblerem. Na dobry początek przedstawiam najkrótszą drogę do odpalenia swojego własnego programu napisanego w assemblerze.
Po pierwsze trzeba mieć kompilator. Oto [link widoczny dla zalogowanych] do kompilatora Turbo Assebler, pod którym działają zamieszczone tu programy.
Następnie otwieramy edytor tekstowy (taki, który potrafi zapisywać czysty tekst) i wpisujemy kod:
Kod: | .model tiny
.code
org 100h
start:
;wypisanie zmiennej tekst
mov ah, 9
mov dx, offset(tekst)
int 21h
;koniec programu
mov ah, 4ch
int 21h
;definicje
tekst db 'Yo!$'
end start |
Zapisujemy plik z rozszerzeniem asm w katalogu z kompilatorem (tasm). Teraz trzeba otworzyć okienko dosowe, przejść do katalogu tasm i uruchomić kompilator poleceniem:
Kod: | tasm nazwa_pliku_asm |
Dzięki temu powstanie plik z rozszerzeniem obj. Następnie odpalamy linkera wpisując:
Kod: | tlink nazwa_pliku_obj /t |
W efekcie dostaniemy plik com, który uruchamiamy i cieszymy się widokiem wypisanego na ekran tekstu. Jak widać programowanie w assemblerze nie jest takie trudne . Teraz nieco bardziej skomplikowana część artykułu, czyli próba wyjaśnienia jak to działa.
Po pierwsze program ładowany jest do pamięci operacyjnej. W naszym prostym programie sytuacja wygląda następująco. Pamięć podzielona jest na 64 KBajtowe segmenty, a nasz program mieści się w jednym takim segmencie. Znajduje się tam zarówno kod jak i dane. Taki model pamięci ustalamy pisząc .model tiny. Co więcej tworzymy program typu com. Aby tak się stało używamy opcji /t przy wywołaniu linkera. Programy typu com to właśnie małe programy mieszczące się w jednym segmencie, w których właściwy kod zaczyna się od 256 baju względem początku segmentu. Wcześniej znajdują się jakieś informacje na temat programu, ale to nas nie interesuje. Polecenie .code oznacza, że zaczynamy segment kodu. W tym przypadku jest to zarazem segment danych. Aby przejść do 256 bajtu uzywamy polecenia org 100h. Litera 'h' oznacza zapis heksadecymalny (szesnastkowy), a więc 100h = 256. W assemblerze możemy używać oprócz heksadecymalnego również zapis dziesiętny (wówczas nie trzeba dodawać żadnej litery) oraz binarny (dodajemy literę 'b'). Możemy zatem napisać równie dobrze org 256. Teraz w końcu możemy zacząć właściwy kod. O pamięci w assemblerze będzie osobny artykuł bo to dość szerokie zagadnienie.
Jak widać kod znajduje się w bloku ograniczonym przez start: i end start, które możemy potraktować jak begin i end. w pascalu. Słowo start możemy zastąpić dowolnym innym, to jest tylko nazwa modułu. Komentarze w assemblerze umieszczamy po znaku ;.
Dalej mamy polecenie mov ah, 9. Jest ono równoznaczne z pascalowym ah := 9. Trzeba pamiętać, że nie zawsze możemy podstawić wartość pod zmienną czy rejestr bespośrednio. Czasami trzeba zrobić coś takiego:
Kod: | mov ax, wartość
mov coś, ax |
gdzie coś jest zmienną lub rejestrem. O rejestrach na razie powiem tylko tyle, że są to specjalne zmienne procesora.
Aby wytłumaczyć po co robimy to i następne podstawienie należy powiedzieć pare słów o przerwaniach. Otóż programując w assemblerze mamy do dyspozycji całą listę funkcji, które np. zmieniają ustawienia BIOSu, albo wypisują tekst na ekran, albo wczytują dane z klawiatury itp. Funkcję taką wykonuje się przez wywołanie odpowiedniego przerwania. Jest tego naprawdę sporo, dlatego w kolejnych artykułach będę opisywał różne ciekawe przerwania.
Przerwania wywołuje się poleceniem int numer_przerwania. W naszym programie użyliśmy jednego przerwania o numerze 21h. Wywołuje ono jedną z funkcji dosowych. O tym która funkcja zostanie ostatecznie wywołana decyduje zawartość rejestru ah. Dlatego właśnie wykonujemy podstawienie mov ah, 9. Funkcja dosowa o numerze 9 to funkcja 'Print String'. Wypisuje ona na standardowe wyjście string, którego adres znajduje się w rejestrach ds i dx. Rejestr ds to rejestr segmentu danych (wskazuje na segment, w którym są dane). Dzięki temu, że tworzymy plik com, rejestr ten ma od razu ustawioną właściwą wartość. Rejestr dx ma natomiast, w tym przypadku, zawierać tzw. offset odpowiadający stringowi. Offset to przesunięcie w pamięci względem początku segmentu. Zatem segment i offset stanowią razem adres konkretnej komórki pamięci. W tym przypadku rejestry ds i dx stanowią adres początku stringu, który chcemy wypisać. Offset, który potrzebujemy dostajemy pisząc offset(tekst), gdzie tekst to nazwa naszego stringu. Pozostaje pytanie, gdzie jest koniec stringu. Otóż ogranicznikiem dla stringu jest znak '$'.
Przy drugim wywołaniu przerwania 21h, wartość w rejestrze ah wynosi 4ch (przypominam, że c odpowiada liczbie 12 w zapisie szesnastkowym). Jest to funkcja, która kończy działanie programu i sprząta po nim (czyści pamięc i takie tam).
Ufff doszliśmy do deklaracji zmiennych, a właściwie jednej zmiennej tekst. Kluczowym słowem jest tutaj db - skrót od define byte. Jak łatwo się domyśleć definiuje ono jeden bajt. Składnia:
Kod: | nazwa db wartość[, wartość...] |
Jeśli napiszemy kilka wartości to zdefinujemy kilka bajtów, a nazwa będzie wskazywała na pierwszy. W ten sposób utworzymy tablicę. Natomiast zapis:
jest równoważny takiemu:
Kod: | tekst db 'Y','o','!','$' |
Znaki zaś są zamieniane na kody ASCII. To zupełnie tak jakbyśmy napisali w C++: char tekst[4] = "Yo!", tylko w C++ automatycznie dodawany jest znak końca stringu. Jeśli jesteśmy przy deklarowaniu zmiennych to napiszę od razu jak zdefiniować większą zmienną:
dw - define word = 2 bajty
dd - define double word = 4 bajty
df - define far word = 6 bajtów
dq - define quad word = 8 bajtów
dt - define temp word = 10 bajtów
I jeszcze jedna ciekawa rzecz. Pokazaliśmy jak można zdefiniować kilkuelementową tablicę. Natomiast jeśli chcemy mieć większą tablicę robimy tak:
Kod: | nazwa db rozmiar dup(wartość) |
Powstanie tabilca bajtów, w której ilość elementów = rozmiar, a początkowe wartości są równe wartość. Jeśli nie chcemy ustalać początkowej wartości możemy wpisać dup(?). Aby uciąć ewentualne spekulacje dodam, że dup to skrót od duplicate . A oto tablica dwuwymiarowa n na m elementów, inicjalizowana zerami:
Kod: | nazwa db n dup(m dup (0)) |
Do wartości zmiennej odnosimy się umieszczając jej nazwę w nawiasach kwadratowych, np:
Kod: | mov [zmienna], ax ; zmienna := ax
mov ax, [tablica+10] ; ax := tablica[10]
mov ax, [tablica+5*m+4] ; ax := tablica[5,4] z tym, że m jest konkretną wartością, a nie zmienną |
Pamiętajcie, że muszą zgadzać się typy. To co podstawiamy musi mieć tyle samo bajtów co to pod co podstawiamy. Jeśli chcemy pod zmienną podstawić wartość np 2 to robimy to za pośrednictwem rejestru ax, czyli najpierw do ax i dopiero z ax do zmiennej. Skoro już przy tym jesteśmy to przyda się podstawowa wiedza na temat rejestrów ogólnego przeznaczenia. Pierwszym takim rejestrem jest akumulator. Ma on następującą konstrukcję:
rax = 64 bity = 32 starsze bity + eax
eax = 32 bity = 16 starszych bitów + ax
ax = 16 bitów = ah (8 starszych bitów) + al (8 młodszych bitów)
Analogicznie wyglądają inne rejestry ogólnego przeznaczenia: bx (bazowy), cx (licznik), dx (danych).
Zanim zaczniemy coś ciekawego programować trzeba jeszcze wspomnieć o kilku ważnych poleceniach.
Operacje arytmetyczne:
add x, y ; x := x + y
sub x, y ; x := x - y
dec x ; x := x - 1
inc x ; x := x + 1
div x ; al := ax div x, ah := ax mod x, dla x wielkości bajta
div x ; ax := dx:ax div x, dx := dx:ax mod x, dla x wielkości 2 bajtów
div x ; eax := edx:eax div x, edx := edx:eax mod x, dla x wielkości 4 bajtów
idiv x ; to samo co div ale x jest ze znakiem
mul x ; ax := al * x, dla x wielkości bajta
mul x ; dx:ax := ax * x, dla x wielkości 2 bajtów
mul x ; edx:eax := eax * x, dla x wielkości 4 bajtów
imul x ; to samo co mult ale x jest ze znakiem
neg x ; x := -x
Operacje bitowe
and x, y ; x := x and y na każdym bicie
or x, y ; x := x or y na każdym bicie
xor x, y ; x := x xor y na każdym bicie
shl x, n ; przesuwa bity w lewo o n, uzupełniając zerami
shr x, n ; przesuwa bity w prawo o n, uzupełniając zerami
Porównanie dwóch wartości:
cmp x, y ; porównuje x i y, a wynik sprawdzamy instrukcją skoku
Skoki warunkowe odnoszą się do wartości ostatniego wyrażenia i wykonują skok jeśli zachodzi warunek:
ja etykieta ; warunek := x > y (bez znaku) dla polecenia cmp albo x > 0
jb etykieta ; warunek := x < y (bez znaku) dla polecenia cmp albo x < 0
jg etykieta ; warunek := x > y (ze znakiem) dla polecenia cmp
jl etykieta ; warunek := x < y (ze znakiem) dla polecenia cmp
je etykieta ; warunek := x == y dla polecenia cmp
jz etykieta ; warunek := x == 0
jmp etykieta ; skok bezwarunkowy
Możemy też zaprzeczać powyższe warunki dodając literę 'n' w poleceniu, np:
jna etykieta ; warunek := x <= y (bez znaku) dla polecenia cmp albo x <= 0
etykiety robi się tak samo jak np w c++ czyli
Dobra, na razie wystarczy. Na koniec jeszcze jeden program.
Kod: | .model tiny
.code
org 100h
start:
;inicjalizacja grafiki 320x200 256kol
mov ax, 0013h
int 10h
rysuj:
;obliczenie funkcji
mov dx, 0000h
mov ax, [x]
imul [x]
mov bx, 0080h;
div bx;
mov dx, 200
sub dx, ax
;putpixel al=kolor cx=x dx=y
mov ah, 0ch
mov al, 100
mov cx, 00a0h
add cx, [x]
int 10h
;x-- i sprawdzenie warunku końca
dec [x]
cmp [x], -160
jnl rysuj
;koniec programu
mov ah, 4ch
int 21h
;definicje
x dw 160;
end start |
|
|
Post został pochwalony 0 razy
Ostatnio zmieniony przez Popiol dnia Pią 23:43, 11 Lis 2005, w całości zmieniany 1 raz
|
|
|
|
|