background image

Programowanie w asemblerze x86 - podstawy 

1

. Narzędzia programistyczne 

Język asembler jest językiem niskiego poziomu. Można wyróżnić trzy główne programy, 

jakie są niezbędne do napisania i przetworzenia programu w asemblerze. Są to: 
1.  tasm.exe - (asemblacja) program ten służy do analizy programu źródłowego pod kątem 

poprawności ze składnią języka. Użycie tego programu jest następujące: 

tasm nazwa_pliku.asm 

Jeżeli program jest napisany poprawnie, pojawi się stosowny komunikat informujący nas 
o tym fakcie oraz powstanie plik o takiej samej nazwie, jaką posiada plik źródłowy i roz-
szerzeniu *.obj. W przeciwnym wypadku pojawią się komunikaty o błędach wyświetlają-
ce numer linii, w której popełniono błąd i zdawkową informację na czym ten błąd polega. 
Aby błąd poprawić należy edytować plik źródłowy, odszukać błędną linię, poprawić błąd 
i powtórzyć operację asemblacji. 

2.  tlink.exe  -  (konsolidacja)  program  ten  służy  do  utworzenia  wersji  uruchamialnej  pro-

gramu na podstawie pliku obiektowego (*.obj). Na ogół w prostych zastosowaniach wy-
wołanie tego programu zawsze zakończy się sukcesem i utworzeniem pliku o takiej samej 
nazwie, jak plik obiektowy i rozszerzeniu (najczęściej) *.exe o ile proces asemblacji prze-
biegł bezbłędnie. Użycie tego programu jest następujące: 

tlink nazwa_pliku.obj 

3.  td.exe 

- (debugger) programu tego używa się w sytuacji, gdy udało się utworzyć pro-

gram uruchamialny, ale nie działa on poprawnie, tzn. jest w nim jakiś błąd nie składnio-
wy,  lecz  funkcjonalny.  Wówczas  wykorzystujemy  narzędzie  td.exe  do  pracy  krokowej, 
sprawdzając krok po kroku jak działa program, jakie wartości przyjmują wybrane zmien-
ne, itp. Użycie tego programu jest następujące: 

td nazwa_pliku.exe 

Aby  jednak  plik  *.exe  mógł  być  uruchomiony  z  programem  td.exe,  należy  dołączyć  do 
niego pewne dodatkowe informacje. Aby tak się stało, należy dokonać procesu asemblacji 
i konsolidacji w sposób następujący: 
 

tasm /zi nazwa_pliku.asm 

 

tlink /v nazwa_pliku.obj 

Odpowiednie  opcje  (/zi  oraz  /v)  programów  tasm.exe  i  tlink.exe  pozwolą  na  dodanie  do 
pliku wynikowego *.exe tych informacji, których potrzebuje program td.exe.  
 

2

. Elementy składni (program nierealokowalny) 

Każdy język programowania wymaga odpowiedniej organizacji pliku źródłowego, tj. od-

powiedniego rozmieszczenia elementów języka, zachowania kolejności pewnych sekcji, uży-
cia odpowiednich słów kluczowych. 
W pliku źródłowym powinny się znaleźć następujące elementy: 
a) .model nazwa_modelu 

-  określenie  modelu  pamięci.  W  prostych  zasto-
sowaniach możliwe modele pamięci to small i ti-
ny

b) .stack rozmiar_stasu_w_bajtach 

 - określenie rozmiaru stosu dla programu. W pro-
stych  zastosowaniach  wystarczy  512-bajtowy 
stos. 

c) .data 

-  ta  dyrektywa  rozpoczyna  część  programu,  w 
której deklaruje się zmienne. 

d) .code 

 -  ta  dyrektywa  rozpoczyna  część  programu,  w 
której wpisuje się kod. 

e) end 

- kończy program w sensie struktury. 

 

background image

a) 

Przyjęcie modelu pamięci determinuje, ile miejsca w pamięci pozostaje na program, dane 

i stos. Dla modelu tiny wszystkie segmenty są łączne, to znaczy, że program, stos i dane mu-
szą się zmieścić w jednym segmencie, przy czym na program przypada 32kB pamięci, a łącz-
nie na dane i stos także 32kB. Tak więc ten typ pamięci służy do pisania małych programów 
operujących na niewielkiej liczbie danych. 
b) 

Stos powinien być w programie zadeklarowany, ponieważ nawet jeżeli programista świa-

domie go nie używa, to stos jest wykorzystywany podczas wywołań podprogramów lub pod-
czas realizacji przerwań programowych. Jeżeli nie ma stosu, program konsolidujący wyświe-
tli ostrzeżenie. 
c) 

W asemblerze generalnie można mówić o trzech typach danych: 

-  typ całkowity 
-  typ łańcuchowy 
-  typ tablicowy 
Pierwszy  z  typów  jest  traktowany  jako  ciąg  bitów  w  pamięci,  które  to  bity  mogą  opisywać 
liczby  różnych  typów:  liczby  binarne  proste,  liczby  binarne  U2  i  liczby  rzeczywiste.  Z 
tym,  że  to  programista  powinien  wiedzieć,  jak  interpretować  zawartość  zmiennej  (jakiego 
typu jest zawartość zmiennej). 
Deklaracje tego typu zmiennej odbywa się w następujący sposób: 
nazwa_zmiennej  

rozmiar 

wartosc_początkowa 

przy czym w miejscu rozmiar pojawić się może: 
- DB 

- wartości zmiennej zapisana na jednym bajcie, 

- DW 

- wartości zmiennej zapisana na dwóch bajtach, 

- DD 

- wartości zmiennej zapisana na czterech bajtach, 

- DQ 

- wartości zmiennej zapisana na ośmiu bajtach, 

- DT 

- wartości zmiennej zapisana na dziesięciu bajtach, 

 
Przykład

y: 

deklaracja w asemblerze: 

odpowiednik w C 

liczba DB 0 

char liczba=0; 

liczba DW 2 

int liczba=2; 

liczba DW 30h 

int liczba=0x30; // wartość heksadecymalna 

liczba DD 23 

long liczba=23; 

liczba DD 2.34 

float liczba=2.34; 

liczba DQ 1.23456 

double liczba=1.23456; 

 
Drugi z typów służy tylko i wyłącznie do deklaracji ciągu znaków, które w następstwie będą 
wyświetlane na ekranie (znaki zapisane są na jednym bajcie, co oznacza typ danych DB). 
Deklaracja tego typu zmiennej odbywa się w sposób następujący: 
nazwa_zmiennej  

DB 

‘Tu wpisz wyświetlany tekst$’

 

Pojawiający  się  znak  ‘$’  na  końcu  typu  łańcuchowego  związany  jest  z  wykorzystywaniem 
funkcja wyświetlającej łańcuch znaków (funkcja 9. przerwania 21h), która rozpoznaje koniec 
ciągu znaków, gdy napotka znak ‘$’. W ciąg znaków można wprowadzać również bezpośred-
nio kody ASCII.  
Przykład.

 

 

tekst  db ‘Ala ma kota$’ 

 

tekst  db ‘Ala ma kota’,13,10,’$’ 

;13,10 to przejście do następnej linii 

 

background image

 
Typ  tablicowy  umożliwia  deklarację  w  programie  zmiennych,  które  będą  zajmowały  w  pa-
mięci  pewną  liczbę  kolejnych  bajtów  (słów,  podwójnych  słów  itd.).  Sposób  deklaracji  tego 
typu zmiennych jest następujący: 
nazwa_zmiennej  

rozmiar 

liczba_elementów dup(‘znak wypełniający tablicę’)

 

Przykład.

 

 

tab 

db 100 dup(‘0’) 

d) 

Pomiędzy  dyrektywami  .code  i  end  wpisujemy  program.  To ta  część  naszego  pliku  źró-

dłowego będzie skojarzona z rejestrem segmentowym CS. 
 
3. Elementy składni programu realokowalnego 

W punkcie 2 zaprezentowana została składnia programu assemblerowego w wersji niere-

alokowalnej,  to  znaczy  w  takiej  wersji,  w  której  model  pamięci  determinuje  z  góry  maksy-
malny  rozmiar  pamięci  na  dane  program  i  stos. Gdyby  się  okazało,  że  pamięci  tej  potrzeba 
więcej, program należałoby assemblować i linkować ponownie w innym modelu pamięci.  

Program  w  wersji  realokowalnej  ma  tę  cechę,  że  ilość  miejsca  potrzebnego  na  program, 

dane i stos jest obliczana w trakcie procesu assemblacji i linkowania (konsolidacji) i zawsze 
jest przydzielona w potrzebnym wymiarze. W najprostszym przypadku budowa programu w 
wersji jest następująca: 
 
sts segment stack 'stack' 

 

- segment stosu 

 

db ROZMIAR_STOSU dup(0) 

 

- zarezerwowanie pamięci na stos 

sts ends 
 
dane
 segment 

 

 

 

 

- segment danych 

.. 

 

 

 

 

 

 

- tutaj kolejne deklaracje zmiennych 

dane ends 
 
program segment   

 

 

 

- segment kodu 

assume cs: program, ds: dane, ss: sts   

- powiązanie rejestrów segmentowych z  
  odpowiednimi segmentami 

start
 

.. 

 

 

 

 

 

- tutaj program 

program ends 
end start 
 
W  powyższym  przykładzie  pokazano  jeden  z  kilku  sposobów  konstrukcji  pliku  źródłowego 
dla realokowalnej wersji programu. Pogrubioną czcionką  wyróżnione  zostały  niezbędne ele-
menty  programu  należące  do  słów  kluczowych  assemblera,  natomiast  kursywa  stwarza  pole 
do popisu dla inwencji twórczej programistów, bowiem są to nazwy części składowych pro-
gramu, które mogą być zupełnie dowolne. 
 

3

. Wywołania systemowe 

W asemblerze  można wykorzystać z fakt, że wiele rzeczy można zrealizować wywołując 

pewne usługi BIOS-u lub DOS-u. Numery przerwań do 20h są to przerwania BIOS-u, nato-
miast  21h  i wyżej, to usługi  DOS-u. Na ogół  jest tak, że usługi DOS-u są dublowane przez 
usługi BIOS-u i odwrotnie. W skrócie można to przedstawić następująco: 
Podczas startu systemu tworzona jest w pamięci tzw. tablica wektorów przerwań zawierająca 
adresy  w  pamięci,  gdzie  znajdują  się  wcześniej  napisane  procedury.  Tworzenie  tej  tablicy 

background image

odbywa się dwuetapowo, tzn. część adresów programów wpisuje do niej BIOS, a reszta jest 
dopisywana po fakcie startu systemu operacyjnego DOS. Sięgnięcie do odpowiedniej proce-
dury jest możliwe jedynie w przypadku wywołania przerwania. Przerwania można podzielić 
na sprzętowe (generowanych przez urządzenia wchodzące w skład systemu komputerowego) 
oraz programowe, które może wywołać programista za pomocą instrukcji INT). W momen-
cie  wystąpienia  przerwania  następuje  odwołanie  do  tablicy  wektorów  przerwań  i  pobrane 
cztery  bajty  adresu procedury obsługi danego przerwania (segment oraz offset adresu). Nie-
które przerwania mają wiele procedur i wtedy należy dodatkowo oprócz wywołania przerwa-
nia podać numer procedury w ramach tego przerwania (np. przerwanie 10h to jest zbiór pro-
cedur obsługi ekranu).  Rejestrem, który jest  zawsze domyślnie sprawdzany w celu okre-
ślenia  numeru 

procedury  jest  rejestr  AH.  Chcąc  zatem  wywołać  usługę  numer  10h  prze-

rwania 10h należy to zrobić w sposób następujący: 
 
mov 

ah,10h 

int 

10h 

 
Czasem do wywołania określonej procedury należy podać dodatkowe parametry w innych 
rejestrach procesora
 (np.  aby ustawić pozycję kursora na ekranie  należy tą podać tą  pozy-
cję).  W wyniku wykonania określonej procedury mogą być również zwracane pewne para-
metry do wybranych rejestrów procesora
 (np. wywołując usługę odczytującą pozycję kur-
sora na ekranie, procedura ta do odpowiednich  rejestrów wpisze odczytaną pozycję).  Infor-
mację o tym do jakich rejestrów należy wprowadzić dodatkowe parametry przed wyw

o-

łaniem procedury, lub  z j

akich je odczytać po  wykonaniu procedury można  zdobyć na 

podstawie opisu przerwań, który znajduje się w dodatkach do większości książek o pr

o-

gramowaniu w asemblerze. 
 
Inaczej rzecz ujmując wywołanie przerwania (INT) powinno przebiegać wg następującej ko-
lejności: 
1.  Odnaleźć  numer  odpowiedniej  procedury  oraz  numer  przerwania  realizującej  wymagane 

zadanie (np. ustawienie kursora na danej pozycji) 

2.  Jeżeli w wyniku wykonania procedury do rejestrów zwracane są wyniki jej działania (lub 

do rejestrów konieczne  jest wpisanie parametrów dodatkowych dla wywoływanej proce-
dury), a przechowują one dane istotne dla działania programu to konieczne jest zachowa-
nie zawartości tych rejestrów np. na stosie. 

3.  Należy wprowadzić do odpowiednich rejestrów parametry dodatkowe (np. pozycję kurso-

ra). 

4.  Dopiero wówczas można wywołać przerwanie 
5.  Z rejestrów można odczytać i wykorzystać w programie informacje zwrócone przez pro-

cedurę 

 
Przykład:

 

Ustawienie kursora na pozycji 3 wiersz, 2 kolumna: 
1.  Funkcję  ustawienia  kursora  realizuje  funkcja  02h  przerwania  10h  BIOS-u.  W  wyniku 

działania dodatkowe parametry (nr wierszakolumny i strony) należy umieścić w odpo-
wiednich rejestrach zgodnie z opisem przerwania (odpowiedni w rejestrze dhdl i bh). 

2.  Funkcja  nie zwraca  informacji zwrotnej, ale  należy  wpisać parametry oraz  nr  funkcji do 

rejestrów: ah, bh, dl, dh. Przyjmijmy że w programie przed wywołanie przerwania rejestry 
te przechowywały istotne dla programu wartości i należy je przechować na stosie. 

3.  Po zabezpieczeniu wartości rejestrów można do nich wprowadzić odpowiednie parametry 

dla przerwania 

background image

4.  Wywołanie przerwanie 
5.  Procedura nie zwraca informacji zwrotnej, ale skoro zachowaliśmy wartości rejestrów na 

stosie to można po wywołaniu procedury je przywrócić pobierające je ze stosu 

 
W wyniku przeprowadzenia 5 kroków program realizujący to zadanie będzie wyglądał nastę-
pująco: 
---; 

 

; krok nr 2 – zabezpieczenie rejestrów 

push   ah 
push   bh 
push   dh 
push   dl 
 

 

;krok nr 3 – wprowadzenie parametrów 

 

mov  dh,3   

;wiersz 

 

mov  dl,2 

 

;kolumna 

 

mov  bh,0   

;numer strony 

 

mov  ah,02h 

;numer funkcji realizującej ustawienie kursora 

 

 

 

;krok nr 4 – 

wywołanie przerwania 

 

int 

10h  

 

 

 

;krok nr 5 – 

przywrócenie poprzednich wartości w rejestrach 

 

pop 

dl 

 

;pobranie wartości ze stosu w odwrotnej kolejności niż ich 

pop 

dh 

 

;położenie na stos 

 

pop 

bh 

 

pop 

ah 

---; 
 
4. Pierwszy program 
 

Pierwszy program w asemblerze może wyglądać następująco: 

 
.model small 
.stack 512 
.code 
 

mov  ah,4ch 

 

int 

21h 

end 
 
Powyższy program należy zasemblować: 
 
tasm nazwa_programu.asm
 
tlink nazwa_programu.obj 
 
Powstały plik nazwa_programu.exe można uruchomić. Powyższy program nic nie robi, jed-
nak  ma  zasadniczą  zaletę  –  nie  zawiesza  komputera,  będąc  przy  tym  w  pełni  zgodnym  ze 
składnią języka.  

background image

W wersji relokowalnej program będzie wyglądał następująco: 
 
;--- 

segment stosu 

sts segment 'stack' 
 

db 512 dup(0) 

sts ends 
;--- 

segment danych 

dane segment 
 
dane ends 
;--- 

segment kodu 

program segment  
 

assume cs:program, ds: dane, ss: sts 

start: 
 
 

mov   ah,4ch 

 

int  

21h 

 
program ends 
end start 
 
 
5. Makra 

Przy  wielokrotnym  powtarzaniu  się  różniących  się  nieznacznie  fragmentów  programu 

można sobie ułatwić życie stosując makra, to jest rzeczywisty ciąg operacji jakiemu podlegają 
abstrakcyjne zmienne przeciążane wirtualnie w momencie wywołania poprzez wykorzystanie 
mechanizmów inline. Najlepiej to wyjaśnić na przykładzie: 
 
;Program 1 
.model small 
.stack 512 
.data 
 

txt1 

db 

'Tekst 1',10,13,'$' 

 

txt2 

db 

'Tekst 2',10,13,'$' 

 

txt3 

db 

'Tekst 3',10,13,'$' 

.code 
 

mov  ax,@data 

;ustalenie segmentu danych  

 

mov  ds,ax 

;wyswietlenie pierwszego tekstu (przerwanie 21h, funkcja 09h, w dx offset tekstu) 
 

lea 

dx,txt1 

 

mov  ah,09h 

 

int 

21h 

;wyswietlenie drugiego tekstu (przerwanie 21h, funkcja 09h, w dx offset tekstu) 
 

lea 

dx,txt2 

 

mov  ah,09h 

 

int 

21h 

;wyswietlenie trzeciego tekstu (przerwanie 21h, funkcja 09h, w dx offset tekstu)   

lea 

dx,txt3 

 

mov  ah,09h 

 

int 

21h 

background image

;czekanie na wcisniecie klawisza (przerwanie 21h, funkcja 01h, kod ASCII 
;wciśniętego klawisza zwrócony zostaje w rejestrze AL
 

mov  ah,01h 

 

int 

21h 

;zakonczenie programu (przerwanie 21h, funkcja 4ch) 
 

mov  ah,4ch 

 

int 

21h 

end 
 
Powyższy program zajmuje 29 linii wraz z komentarzami. Gdyby nie te właśnie komentarze, 
to byłby on mało nieczytelny. A teraz taki sam program, ale wykorzystujący makra... 
 
Program 2 
.model small 
.stack 512 
.data 
 

txt1 

db 

'Tekst 1',10,13,'$' 

 

txt2 

db 

'Tekst 2',10,13,'$' 

 

txt3 

db 

'Tekst 3',10,13,'$' 

 
write  macro txt 
;wyswietlenie tekstu (przerwanie 21h, funkcja 09h w dx offset tekstu) 
 

lea 

dx,txt 

 

mov  ah,09h 

 

int 

21h 

endm 
 
readkey 

macro 

;czekanie na wcisniecie klawisza (przerwanie 21h, funkcja 01h, kod ASCII 
;wciśniętego klawisza zwrócony zostaje w rejestrze AL
 

mov  ah,01h 

 

int 

21h 

endm 
 
exit 

macro 

;zakonczenie programu (przerwanie 21h, funkcja 4ch) 
 

mov  ah,4ch 

 

int 

21h 

endm 
 
.code 
 

mov  ax,@data 

;ustalenie segmentu danych 

 

mov  ds,ax 

 

write  txt1 

;wyswietlenie pierwszego tekstu 

 

write  txt2 

;wyswietlenie drugiego tekstu 

 

write  txt3 

;wyswietlenie trzeciego tekstu 

 

readkey 

;czekanie na wcisniecie klawisza  

 

exit 

 

;zakonczenie programu 

end 
 

background image

Istotne jest to, że w miejscu wywołania makra wstawiane są fizycznie linie, które makro 
opisuje, toteż nikogo nie powinno dziwić, że program 1 i 2 w wersji uruchamialnej mają 
dokładnie 

taki sam rozmiar

 
6. Procedury 

Procedury są modułem programowym, który może, w przeciwieństwie do makr, być udo-

stępniany  na  zewnątrz.  Udostępniać  na  zewnątrz  można  także  zmienne,  ale  tylko  globalne. 
Sytuacja  ta  dotyczy  przypadku,  gdy  jeden  program  składa  się  z  wielu  plików  składowych. 
Program działający identycznie, jak przedstawione w rozdziale poprzednim, ale zrealizowany 
przy użyciu procedur może wyglądać następująco: 
 
.model small 
.stack 512 
.data 
 

txt1 

db 

'Tekst 1',10,13,'$' 

 

txt2 

db 

'Tekst 2',10,13,'$' 

 

txt3 

db 

'Tekst 3',10,13,'$' 

.code 
;segmentu danych (dobrze, żeby to było zaraz na poczatku programu) 
 

mov  ax,@data 

 

mov  ds,ax 

 

lea 

dx,txt1  

;pobranie adresu pierwszego tekstu 

 

call 

write   

;wyswietlenie pierwszego tekstu 

 

lea 

dx,txt2  

;pobranie adresu drugiego tekstu 

 

call 

write   

;wyswietlenie pierwszego tekstu 

 

lea 

dx,txt3  

;pobranie adresu pierwszego tekstu 

 

call 

write   

;wyswietlenie pierwszego tekstu 

 

call   readkey 

;czekanie na wcisniecie klawisza 

;zakonczenie programu (przerwanie 21h, funkcja 4ch) 

mov  ah,4ch 

 

int 

21h 

 
;czesc programu, do ktorej nie ma mozliwosci wejscia normalnym trybem  
write  proc 
;wyswietlenie tekstu (przerwanie 21h, funkcja 09h, w dx offset tekstu) 

 

 

mov  ah,09h 

 

int 

21h 

 

ret 

endp 
 
readkey 

proc 

 

mov  ah,01h 

 

int 

21h 

 

ret 

endp 
end 
 
Istotne  przy  użyciu  procedur  jest  to,  że  w  miejscu  wywołania  procedur  nie  jest  wpisy-
wane ciało procedury jak przy użyciu makr

.  

 

background image

7. Program typu com 

Program  typu  com  ma  inną  budowę,  niż  program  typu  exe.  Aby  uzyskać  plik  com  ko-

nieczne jest spełnienie trzech warunków: 

  Program nie może zawierać linii sygnalizującej segment stosu ani segment danych i musi 

się zaczynać od dyrektywy org 100h

  W  programie  musi  istnieć  dyrektywa  (etykieta  z  dwukropkiem)  określająca  początek  i 

koniec segmentu kodu (w przykładzie nazwana Start). 

  Turbo linker (program Tlink.exe) należy koniecznie uruchomić z parametrem /t. 

Proces asemblacji i konsolidacji jest więc następujący: 

 

tasm nazwa_pliku.asm 

 

tlink /t nazwa_pliku.obj 

 

;przykład programu w wersji dla pliku com:

 

 
program segment  
 

assume cs:program, ds:program 

 

org 100h 

start: 
 

lea 

dx,txt1  

;wyswietlenie pierwszego tekstu 

 

call 

write 

 

lea 

dx,txt2  

;wyswietlenie drugiego tekstu 

 

call 

write 

 

lea 

dx,txt3  

;wyswietlenie trzeciego tekstu 

 

call 

write 

 

call   readkey 

;czekanie na wcisniecie klawisza 

;zakonczenie programu (przerwanie 21h, funkcja 4ch) 

mov  ah,4ch 

 

int 

21h 

;czesc programu, do ktorej nie ma mozliwosci wejscia normalnym trybem 
 
;wyswietlenie tekstu (przerwanie 21h, funkcja 09h, w dx offset tekstu) 

 

 

mov  ah,09h 

 

int 

21h 

 

ret 

endp 
readkey 

proc 

 

mov  ah,01h 

 

int 

21h 

 

ret 

endp 
 
;--- 

deklaracje zmiennych 

 

txt1 

db 

'Tekst 1',10,13,'$' 

 

txt2 

db 

'Tekst 2',10,13,'$' 

 

txt3 

db 

'Tekst 3',10,13,'$' 

program ends 
end start