Sunteți pe pagina 1din 9

Laborator nr.

4
�������������������

Cuvinte cheie
�������������������

asamblor,
compilator,
mnemonica,
directiva SEGMENT,
directiva ASSUME,
directivele DB, DW, DD,
instructiunea MOV,
instructiunea INT,
operatorul OFFSET

1. Introducere
��������������

Un limbaj de programare este format dintr-un set de simboli,


un set de reguli de sintaxa care guverneaza modul in care acesti simboli
pot fi combinati, si o semantica ce asociaza diferitelor combinari de
simboli un inteles. Exista foarte multe limbaje de programare; totusi
ele pot fi clasificate dupa criteriul "nivelului" unui limbaj de
programare. Astfel, nivelul "cel mai de jos" se refera la acele limbaje
de programare care sunt cel mai aproape de o masina concreta. Mergind
de la nivelul cel mai de jos spre cel inalt, avem urmatoarea clasificare:
Limbaje masina,
Limbaje asamblare,
Limbaje de nivel inalt

Limbajele masina sunt cele care pot fi intelese direct de un


calculator. Un program in limbaj masina este un sir de numere binare care
reprezinta instructiunile programului, locatiile de memorie si datele
necesare rezolvarii unei probleme specifice. Orice program scris intr-un
limbaj de programare, altul decit limbajul masina, trebuie tradus (pentru a
putea fi executat) in limbajul pe care calculatorul il intelege, deci in limbaj
masina.
Limbajele de asamblare se afla la un nivel mai ridicat decit cele
masina, dar sunt inca de nivel "low". Un limbaj de asamblare permite
descrierea instructiunilor microprocesorului sub forma de mnemonice (simboli
abreviati)
ca ADD, MOV, SUB, MUL, etc., permite folosirea unor date numerice cu valori
zecimale, octale, hexazecimale, manevrarea variabilelor. Desigur, un
program scris in asamblare trebuie tradus in limbaj masina inainte de
a fi executat. Acest lucru este realizat de un program special, numit
asamblor.
Limbajele de nivel inalt sunt cele care pun la dispozitia
programatorului un set de simboli si posibilitati de combinare a acestora
intr-o forma apropiata de limbajul uman. Ele au fost dezvoltate
pentru a-i permite programatorului sa-si concentreze atentia nu asupra
detaliilor legate de masina pe care lucreaza ci asupra problemei de rezolvat.
Traducerea unui program din cod sursa al unui limbaj de nivel inalt in limbaj
masina este realizata de un program special, numit compilator.
Apare firesc intrebarea: de ce sa programam in limbaj de
asamblare? Intrebare justificata daca ne referim doar la citeva din
avantajele limbajelor de nivel inalt: timpul necesar implementarii unei
solutii la o problema este incomparabil mai mic fata de implementarea in
asamblare; independenta fata de masina (limbajele de nivel inalt cele
mai raspindite sunt portabile, deci un program sursa scris pe o masina
poate fi compilat si executat pe alta); programele sunt usor de
depanat si corectat. Cu toate acestea, foarte multe compilatoare genereaza
cod ineficient si folosesc ineficient memoria. In plus, uneori poate
fi deranjant pentru programator sa nu poata accesa direct componentele
microprocesorului: registri, indicatori, set de instructiuni speciale.
Solutia optima in scrierea unui program este de a dezvolta unele rutine
intr-un limbaj de nivel inalt, iar rutinele al caror timp de rulare este
mare sau care sunt critice din punct de vedere al spatiului folosit
sa fie scrise in asamblare.

Orice microprocesor are un set de instructiuni pe care le poate


executa,instructiuni care realizeaza (in general) operatii asupra unor operanzi.
In momentul lansarii unui program (prin tiparirea numelui lui la linia de
comanda), sunt incarcate in memorie instructiunile pe care masina trebuie
sa le execute. Microprocesorul le citeste din memorie, le decodifica si apoi
le executa.
Instructiunile microprocesorului 8086 sunt grupate in urmatoarele
categorii: instructiuni pentru transfer de date, instructiuni aritmetice,
instructiuni logice, instructiuni pentru siruri de caractere, instructiuni
de intrerupere, instructiuni pentru indicatori si instructiuni de
sincronizare.
Incepind cu laboratorul de astazi si cu cele ce vor urma, vom
prezenta pe rind instructiunile necesare implementarii unei probleme ori
a mai multora, pe care le vom rezolva impreuna.

2. Hello, world!
����������������

In cele ce urmeaza, este descris un program foarte simplu, care nu


face altceva decit sa afiseze un sir de caractere, anume "Hello, world!".

;������������������������������������������������������������������������
; Cod sursa asamblare pentru afisarea sirului "Hello, world!"

my_data segment ; definesc segmentul in care tin


; datele necesare programului
HelloMessage DB 'Hello, world!',13,10,'$'
; definesc o variabila care identifica sirul
; pe care vreau sa-l afisez si o initializez.
my_data ends ; marchez sfirsitul definitiei segmentului
; de date

my_text segment ; definesc segmentul de cod


assume cs:my_text, ds:my_data
start:

mov ax, my_data ; incarca registrul DS cu adresa de inceput


mov ds, ax ; a segmentului my_data

mov ah,9 ; pregatesc registrul ah pentru


; apelul functiei sistem de tiparire
; a unui sir de caractere
mov dx,OFFSET HelloMessage ; dx va contine deplasamentul la locatia
; de memorie la care este stocat
; sirul "Hello, world"
int 21h ; afiseaza "Hello, world"

mov ah,4ch ; pregatesc registrul ah pentru


; apelul functiei sistem care termina
int 21h ; programul
my_text ends ; sfirsitul definitiei segmentului
; de cod
end start ; sfirsitul programului
;������������������������������������������������������������������������

Orice program are unul sau mai multe segmente de date care contin
datele necesare prelucrarii in executie, precum si unul sau mai multe
segmente de cod. Segmentele se definesc folosind directiva SEGMENT, conform
sintaxei:

nume SEGMENT [tip_aliniere][tip_de_combinare][clasa]


; declaratii ale datelor, descrieri de mnemonice, etc.
nume ENDS

Pentru moment, nu vom insista asupra a ce inseamna tip_aliniere,


tip_de_combinare si clasa. Deocamdata sa remarcam faptul ca orice definitie
de segment se sfirseste (obligatoriu) cu "nume ENDS" si sa subliniem citeva
aspecte: in cadrul unui segment de date se definesc (eventual se
initializeaza) de obicei constante, variable, etichete, articole, structuri,
etc. iar in cadrul unui segment de cod se descriu, sub forma de mnemonice,
instructiunile pe care microprocesorul trebuie sa le execute la momentul
rularii programului.
Observatie: in interiorul definitiei unui segment poate fi inclusa
definitia altuia, ca in exemplul urmator:

code1 segment
assume cs:code1, ds:data1
...
data1 segment
...
data1 ends
...
code1 ends

Este important de subliniat ca cele doua segmente nu se vor


suprapune in nici un caz fizic (practic, asamblorul va concatena prima
portiune a segmentului "code1" cu cea de dupa definitia lui "data1", iar
"data1" va rezida in memorie intr-o zona distincta).
Revenind asupra programului mai sus prezentat, segmentul de date
necesar lui se numeste "my_data" si el contine o singura variabila,
numita "HelloMessage" si ea este initializata cu un sir de caractere
ASCII.

3. Definirea si initializarea variabilelor


���������������������������������������

In general, variabilele se definesc folosind directivele DB, DW sau


DD, conform sintaxei:
[nume_variabila] { DB | DW | DD } expresie_de_initializare

Iata semnificatia meta-simbolilor folositi mai sus:


simbolul care apare intre [] este optional, iar intre {} apar simboli separati prin
|,
cu semnificatia ca unul dintre ei trebuie obligatoriu specificat (deci in definirea
unei
variabile, trebuie sa apara sau DB, sau DW, sau DD).
Directivele DB, DW si DD au urmatoarea semnificatie:
- DB (Define Byte): oricarei variabile definita cu DB (mai putin un
sir de caractere) i se aloca in memorie un singur octet (byte);
- DW (Define Word): oricarei variabile definita cu DW (mai putin un
sir de caractere) i se aloca in memorie un cuvint (2 octeti);
- DD (Define Double-word): oricarei variabile definita cu DD (mai
putin un sir de caractere) i se aloca in memorie doua cuvinte
(4 octeti);

Ce se intimpla in cazul sirurilor de caractere? Cu ajutorul directivei


DB putem declara si initializa siruri de caractere de orice lungime
("orice" insemnind maximum lungimii segmentului de date, deci 64
ko), scrise intre '' (doua caractere apostrof).
Iata citeva exemple:

litere db 'ABCDEFGHIJKLMNOPRSTUVWXYZ' ; variabila "litere" are 26 de


; octeti alocati
cifre db '0123456789' ; "cifre" are 10 octeti alocati

Reluind variabila "HelloMesage":

HelloMessage DB 'Hello, world!',13,10,'$'

sa specificam faptul ca ea are 16 octeti alocati, astfel:

baza segmentului my_data � ...�


����Ĵ
locatia de inceput a variabilei � 48h� H
����Ĵ
� 65h� e
����Ĵ
� 6Ch� l
����Ĵ
� 6Ch� l
����Ĵ
� 6Fh� o
����Ĵ
� 2Ch� ,
����Ĵ
� 20h�
����Ĵ
� 77h� w
����Ĵ
� 6Fh� o
����Ĵ
� 72h� r
����Ĵ
� 6Ch� l
����Ĵ
� 64h� d
����Ĵ
� 21h� !
����Ĵ
� 0Dh� CR
����Ĵ
� 0Ah� LF
����Ĵ
� 24h� $
����Ĵ
� ...�

Simbolii CR si LF au urmatoarea semnificatie: CR (Carriage Return)


cu codul ASCII 0Dh (13 in zecimal) determina mutarea cursorului la
inceputul rindului curent, iar LF (Line Feed) cu codul ASCII 0Ah (10 in
zecimal) determina mutarea cursorului la linia urmatoare (astfel incit,
orice alt sir de caractere care ar trebui afisat va incepe pe linia
urmatoare celei in care "Hello, world!" a fost scris). Caracterul $
trebuie sa termine un sir de caractere care va fi afisat folosind apelul
sistem 09h.
Sa mai dam citeva exemple de definitii de variabile:

intreg dw 0 ; variabila pe un cuvint, initializat cu 0


orice dw ? ; variabila pe un cuvint, cu continut nedeterminat
; (de fapt, neinitializata cu o valoare concreta)
apostrof '''' ; caracterul apostrof trebuie dublat pentru a
; putea fi afisat
ceva dw 0FFFFh ; 16 biti de 1
crlf db 13,10 ; lista de octeti continind simbolii CR si LF
multime db 1000 dup(?); sir de 1000 octeti cu continut nedeterminat
array dw 100 dup(0) ; sir de cuvinte, toate continind valoarea 0

Sa ne concentram atentia asupra definitiei segmentului de cod, numit


in program "my_text". Prima linie,
assume cs:my_text, ds:my_data
contine directiva ASSUME.
Practic, directiva ASSUME este doar o "promisiune" facuta asamblorului
ca instructiunile si datele sunt adresabile la momentul executiei programului
prin intermediul anumitor registri segment (astfel incit el sa poata verifica
daca variabilele folosite in program sunt adresate corect). Dar incarcarea
in fapt a registrilor segment cu valorile necesare si manipularea acestor
registri este exclusiv responsabilitatea programatorului. Astfel,
registrul DS este incarcat explicit (prin instructiuni MOV) cu adresa la care
incepe "my_data", astfel incit, orice referire la variabilele care rezida in
my_data sa se poata face prin intermediul registrului DS (care trebuie sa
contina adresa de inceput, deci adresa de segment, a segmentului de date
definit).
Sa observam ca pentru segmentul de cod n-am avut nevoie sa
facem o incarcare explicita (de altfel, o incercare de a incarca registrul CS
cu ceva va determina aparitia unui mesaj de eroare). In schimb, este
obligatoriu sa marcam punctul in care incep instructiunile care trebuiesc
executate. Aceasta se face prin marcarea cu o eticheta (in cazul nostru
"start:") iar sfirsitul programului trebuie marcat (dupa terminarea definitiei
segmentului cu ENDS) prin "END eticheta" (in cazul nostru: end start).

4. Instructiuni
������������
In continuare vom descrie pe scurt instructiunile folosite in
programul nostru:

Instructiunea MOV

MOV destinatie,sursa

Aceasta instructiune transfera un octet sau un cuvint de memorie din


operandul sursa in operandul destinatie. Exemple din program:

mov ax, my_data ; adresa de inceput a lui MY_DATA va fi transferata in AX


mov ds, ax ; continutul registrului AX va fi transferat in registrul DS
mov ah, 9h ; AH va contine octetul cu valoarea 9h

Exista mai multe restrictii asupra folosirii registrilor segment ca operanzi


destinatie: un registru segment nu poate fi incarcat cu o valoare imediata;
el poate fi incarcat insa cu continutul unui alt registru.

Operatorul OFFSET folosit in istructiunea

mov dx, OFFSET HelloMessage

permite obtinerea deplasamentului zonei de memorie ce contine mesajul


'Hello, world!' fata de adresa de inceput a segmentului unde aceasta se afla.
Deci, in urma acestei instructiuni, in registrul dx se va gasi valoarea acestui
deplasament.
O alta instructiune folosita este

INT tip-intrerupere

Aceasta instructiune (INTerrupt) activeaza rutina de tratare a


intreruperii specificata de tip-intrerupere. Asocierea intre tipul de
intrerupere si rutina de tratare a intreruperii este facuta printr-o tabela
numita tabela vectorilor de intrerupere. Aceasta ocupa prima zona de memorie,
pornind de la adresa fizica 0 pina la 1 KB. Tabela poate avea pina la 256 de
intrari, cite una pentru fiecare intrerupere. Fiecare intrare din tabela este
un pointer pe dublu cuvint (o adresa reprezentata pe 4 octeti) reprezentind
adresa rutinei de tratare a intreruperii de tipul respectiv. Cuvintul de la
adresa mai mare contine adresa de baza a segmentului in care se gaseste
rutina, iar in cuvintul de adresa mai mica se gaseste deplasamentul rutinei
fata de inceputul segmentului.
In exemplul nostru a fost utilizata intreruperea 21h care ofera
majoritatea functiilor interne ale sistemului DOS. Una din functiile folosite
de noi este functia 09h. Aceasta functie trimite dispozitivului de iesire
standard (ecranul) un sir de caractere terminat prin '$'(acest caracter nefiind
tiparit),al carui deplasament fata de adresa de segment din registrul DS se
afla in registrul DX. Inainte de apelul functiei, trebuiesc pregatiti
registrii:
AH incarcat cu 09h (care identifica functia)
DS incarcat cu adresa de segment in care se afla sirul
DX incarcat cu deplasamentul zonei de memorie in care se afla sirul
fata de inceputul segmentului.

Observatie: Afisarea caracterului "linie noua" se face prin ASCII 0dh urmat de
ASCII 0ah.

O alta functie sistem utilizata de noi este functia 4ch care termina
un program si preda controlul procesului parinte (cel din care s-a apelat
programul curent). Pentru apelul functiei se folosesc registrii:
AH incarcat cu 4ch si
AL incarcat cu codul de retur (valoare pozitionata de aceasta functie
care poate fi folosita de procesul parinte la revenire).

5. Adresarea operanzilor
���������������������

Vom discuta acum citeva lucruri legate de adresarea operanzilor


cu referire directa la programul-exemplu. Exista mai multe moduri de
a adresa datele (operanzii instructiunilor). Operanzii pot fi continuti in
registri, in memorie, in instructiune sau in porturile de intrare/iesire.
Instructiunile care specifica numai operanzi registri (de ex. MOV dx,ax)
sint compacte si rapide, operatiile se executa numai in unitatea centrala
nemaifiind necesara aducerea datelor din memorie. De asemenea cind
instructiunile folosesc operanzi imediati (un operand imediat ar fi spre
exemplu valoarea 0b800h din instructiunea MOV ax, 0b800h) executia lor se
face deasemenea foarte rapid deoarece acesti operanzi imediati pot fi accesati
fara a mai fi transferati din memorie, ei gasindu-se in corpul
instructiunii deja incarcata in coada de instructiuni.
Dintre modurile de adresare amintim aici:

a) Adresarea imediata

Operanzii imediati sint date constante continute in instructiune de lungime


8 sau 16 biti. Ei pot servi doar ca operanzi sursa (operanzii cei mai din
dreapta). De exemplu:

mov ax, 5
mov ax, 41h
in care operanzii sursa sunt valori imediate.

In programul nostru apare:


mov dx, OFFSET HelloMessage
(aici operandul imediat este OFFSET HelloMessage deoarece acesta
reprezinta deplasamentul variabilei HelloMessage fata de inceputul segmentului
de date fiind astfel o valoare (constanta) cunoscuta de asamblor in momentul
asamblarii).
mov ax, my_data
(aici operandul imediat este my_data, care identifica adresa de
inceput a unui segment).

b) Adresarea la registri

Operandul este un registru.

Exemple:
mov ax, 0b800h ;operandul destinatie este un registru
mov ds, ax ;ambii operanzi sunt operanzi registri
inc bx ;unicul oerand al instructiunii este un registru
cmp ah,'A' ;operandul destinatie este registru

In modurile de adresare precedente nu era nevoie ca operanzii sa fie transferati


din memorie, ei se gaseau fie in instructiune (operanzi imediati) fie in
registri. Sa descriem in continuare citeva moduri de adresare ce presupun
operanzi din memorie. Evident vor trebui puse in evidenta adresele in memorie
ale acestor operanzi.
c) Adresare indirecta prin registri

Adresa-efectiva a operandului (adica deplasamentul fata de inceputul


zonei segment in care se gaseste acesta ) poate fi luata din unul din
registrii de baza sau de index prezenti in instructiune( bx, bp, si sau di).
Exemple:
mov ah,ds:[bx]
(operandul sursa este adresat indirect prin registrul bx: continutul
acestui registru va contine deplasamentul zonei de memorie de un octet
fata de inceputul segmentului)

6. Cod asamblare -> executabil


���������������������������

Este momentul acum sa vedem cum anume transformam un fisier care


contine codul sursa al unui program in asamblare in fisier executabil
(cu extensia .exe). Primul lucru este apelul comenzii TASM care lanseaza
in executie un program ce verifica daca sursa este corecta sintactic si
genereaza un fisier intermediar cu extensia .obj . Asadar, daca fisierul
care contine codul sursa are numele "hello.asm", apelul comenzii va fi

tasm hello.asm

iar fisierul generat (in cazul in care nu apar erori) este "hello.obj".
Comanda TASM pune la dispozitie mai multe optiuni dintre care amintim
citeva:

/l Genereaza fisier listing (extensia .lst)


/ml,/mx,/mu "Case sensitivity" pe simboli: ml=toti simbolii,
mx=simboli globali,
mu=nici un simbol (implicit)
/z Afiseaza numerele liniilor sursa in care au aparut erori, cu
mesajele de eroare
/zi,/zd,/zn Informatie de debug: zi=informatie completa,
zd=doar numerele de linie,
zn=nici o informatie.

Deoarece in etapa de testare a programului ne intereseaza sa avem


informatie de debug completa (pentru depanare, adica urmarire pas cu pas)
si, in cazul in care sunt erori, ne intereseaza sa le localizam intocmai,
comanda pe care o vom folosi este:

tasm /l /z /zi hello.asm

In cazul in care fisierul sursa contine erori, puteti consulta


fisierul listing "hello.lst". Altfel, puteti apela comanda TLINK, care
lucreaza asupra fisierului "hello.obj". Optiunea comenzii TLINK pe care
o vom folosi este:

/v Informatie de debug completa

Asadar:

tlink /v hello.obj

va genera fisierul hello.exe .


Ca sa puteti urmari executia programului pas cu pas, apelati
Turbo Debugger - ul cu comanda

td hello.exe

EXERCITII

1. (2p) Modificati segmentul de date din programul prezentat incluzind


variabilele:

var1 DW ?
var2 DD ?
HelloMessage DB 'Hello world!',13,10,'$'
cifra DB ?

Specificati care este valoarea operatorului OFFSET pentru fiecare din


aceste variabile.

2. (2p) Definiti in segmentul de date inca un sir de caractere


si afisati-l.

3. (3p) Definiti in segmentul de date o variabila octet

cifra DB ?

apoi initializati continutul ei cu o cifra oarecare si afisati-o pe ecran.


Ce constatati?

4. (3p) Realizati un program care va transfera (copia) 1024 de octeti de la


adresa 0000:0000 intr-o zona definita in segmentul de date al programului.

5. (3p) Realizati un program care va copia 512 octeti de la adresa fizica


10AC0 intr-o zona definita in segmentul de date al programului.

S-ar putea să vă placă și