Sunteți pe pagina 1din 8

Laborator 4

/////////////////////////////////////// Primul program in limbaj de asamblare ///////////////////////////////////////

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 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: 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. Ce este important de subliniat este faptul ca orice program scris intr-un limbaj de programare, altul decit limbajul masina, trebuie translat (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 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 mai apropiata de limbajul uman. Ele au fost dezvoltate

pentru a-i permite programatorului sa-si concentreze atentia in special asupra problemei de rezolvat si mai putin asupra detaliilor legate de masina pe care lucreaza. Translarea unui program din cod sursa - limbaj de nivel inalt in limbaj masina este realizata de un program special, numit compilator. Apare acum urmatoarea intrebare: de ce sa programam in limbaj de asamblare? Intrebare de bun simt 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 si 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 optimala 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 executa (in general) operatii asupra unor operanzi. In momentul lansarii unui program (prin tiparirea numelui sau 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 relationate cu o problema sau cu mai multe, pe care le vom rezolva impreuna, urmind ca intr-un laborator viitor sa adunam intr-un sumar informatii despre tot setul de instructiuni (in scopul consultarii si aducerii aminte). Aceleasi comentarii sunt valabile si pentru setul de intreruperi, apelurile sistem, modurile de adresare ale operanzilor instructiunilor. 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 mov ds, ax mov ah,9 ; incarca registrul DS cu adresa de inceput ; a segmentului my_data pregatesc registrul ah pentru apelul functiei sistem de tiparire a unui sir de caractere dx va contine deplasamentul la locatia de memorie la care este stocat sirul "Hello, world" afiseaza "Hello, world"

; ; ; mov dx,OFFSET HelloMessage ; ; ; int 21h ; 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 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 programului se numeste "my_data" si el contine o singura variabila, numita "HelloMessage" si ea este initializata cu un sir de caractere ASCII.

In general, variabilele se definesc folosind directivele DB, DW sau DD, conform sintaxei: [nume_variabila] { DB | DW | DD } expresie_de_initializare Inainte de a da citeva exemple, sa specificam care este 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); Desigur, vine intrebarea: ce se intimpla in cazul sirurilor de caractere (mai ales ca programul nostru foloseste asa ceva). Cu ajutorul directivei DB putem declara si initializa siruri de caractere de orice lungime ("orice" insemnind de fapt maximum lungimii segmentului de date, deci 64 ko), scrise intre ''. 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 65h 6Ch 6Ch 6Fh 2Ch 20h 77h 6Fh 72h 6Ch

H e l l o ,

w o r l

64h 21h 0Dh 0Ah 24h ...

d ! CR LF $

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 orice ; variabila pe un cuvint, initializat cu 0 ; 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). In continuare vom descrie pe scurt instructiunile folosite in programul nostru: Instructiunea MOV dw 0 dw ?

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). 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: * 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 deoarece my_data identifica un segment de date si deci adresa de inceput a acestui segment va fi o valoare (constanta) cunoscuta de asamblor in momentul asamblarii). *Adresarea la registri* Operandul este un registru (codificat in codul instructiunii-adica ceea ce rezulta dupa asamblare- intr-un cimp numit MOD R/M). Exemple: mov ax, 0b800h (operandul destinatie adica ax) mov ds, ax (ambii operanzi) inc bx ( bx) cmp ah,'A' (operandul destinatie adica ah) 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. *Adresare indirecta prin registri* Adresa-efectiva a operandului (adica deplasamentul fata de iceputul 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) 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 /ml,/mx,/mu Genereaza fisier listing (extensia .lst) "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". Optiuniea 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. Definiti in segmentul de date my_data inca un sir de caractere pe care si afisati-l. 2. Definiti in segmentul de date o variabila octet cifra DB ? apoi initializati continutul ei cu o cifra oarecare si afisati-o pe ecran (folosind tot functia sistem 09h).

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