Tutorial Limbaj de Asamblare (Assembler) Intel 8086 – Partea 2 – Prima aplicatie

Pentru a înțelege într-un mod practic programarea în limbaj de asamblare, în aceasta parte a tutorialului assembler se analizează 2 exemple simple. Se descriu:

  • Etapele de realizare a aplicației in limbaj de asamblare
  • Structura unui program assembler
  • Tipuri de instrucțiuni assembler
  • Interfața aplicației Turbo Debugger

Etape în realizarea unui program în Limbaj de Asamblare (Assembler)

  1. Editare fișier sursa numit fisier.ASM cu un editor de text simplu; NOTEPAD in Windows sau editorul de DOS apelat din linia de comanda: C:\>edit fisier.asm
  2. construirea fișierului obiect si verificarea codului din sursa: C:\> tasm fisier.asm
  3. daca nu exista erori de asamblare se construiește fisier.OBJ
  4. obținerea executabilului: C:\> tlink fisier.OBJ
  5. rularea executabilului in Debugger: C:\> td fisier

Utilitarele, Turbo Assembler, Turbo Linker si Turbo Debugger, utilizate in acest tutorial sunt disponibile in arhiva din partea 1 a tutorialului – Tutorial Limbaj de Asamblare (Assembler) Intel 8086 – Partea 1 – Elemente de baza.

Pentru a eficientiza partea de editare și testare a programului assembler se poate construi un fișier batch pentru a executa automat pașii descriși.

Fișier batch pentru editare și realizare programe assembler:

REM Fisier pentru dezvoltarea unui program in limbaj de asamblare
@ECHO off
ECHO Lansare editor pentru fisier sursa
:AAA
EDIT %1.asm
PAUSE
ECHO Lansare asamblor pentru celelalte fisiere: .obj,.lst,.crf
TASM %1
PAUSE
ECHO Daca in urma asamblarii apar erori, se reia editarea
IF errorlevel 1 GOTO AAA
PAUSE
ECHO Obtin executabilele
TLINK %1.obj;
ECHO Lansare in executie a programului prin TD
td %1.exe

Se salvează fișierul cu numele executa.bat . Din linia de comanda se tastează executa nume_program si automat se urmăresc pașii de realizare a unui program assembler.

Descriere interfață TURBO DEBUGGER

Turbo Debugger Interface
Turbo Debugger Interface

Fereastra A

  • descrie segmentul de cod al programului;
  • in partea dreapta adresele CS:0005 indica adresa in segmentul de cod a instructiunii respective;
  • simbolul ► (sageata) indica instructiunea curenta;
  • instructiunile sunt executate pas cu pas utilizând tasta F8;
  • valoarea hexa alaturata adresei CS:0005 A00000 este reprezentarea interna a instructiunii mov al, [0000].

Fereastra B

  • descrie registrele si valorile pe care acestea le au la momentul respectiv;
  • valorile din registre sunt in hexa.

Fereastra C

  • descrie registrul FLAG.

Fereastra D

  • descrie segmentul de date;
  • imediat dupa incarcarea adresei segmentului de date in DS, pentru a vizualiza aceasta zona din memorie se utilizeaza secventa de instructiuni Ctrl+G si apoi valoarea DS:0000 in casuta de dialog (atentie fereastra activa trebuie sa fie D);
  • utilizata pentru verificarea rezultatelor;
  • pe coloana din dreapta sunt descrise adresele din segmentul de date (de ex. DS:0008)

Fereastra E

  • descrie segmentul de stiva;
  • valorile SS:0100 descriu adresele din segmentul de stiva;
  • simbolul ► (sageata) indica locatia curenta in stiva;

Forma Assembler a unui program .EXE (un singur segment de date, unul de cod si stiva)

Programarea in limbaj de asamblare este dificila deoarece sintaxa utilizata si modul in care este văzută aplicația prin prisma codului sunt foarte apropriate de limbajul masina si de modul in care aplicatia va fi executata de catre procesor. Compilatorul de Assembler, reprezentat in aceste tutoriale de Turbo Assembler si Turbo Linker realizeaza operatii destul de simple in comparatie cu compilatorul de C (cl.exe in MS Visual Studio). In cazul unor exemple simple, de genul celor prezentate in acest tutorial, cele 2 instrumente vor face, in principal, o translatare a codului din limbaj de asamblare in limbaj masina binar. Din acest motiv, se acorda o atentie deosebita structurii si a modului in care este scris programul.

Structura standard, chiar daca nu este strict obligatorie, a unui program assembler este

.model

.stack

.data

…

.code

…

end

Directivele din aceasta structura sunt descrise prin prisma exemplului 1.

Exemplu 1 – program in limbaj de asamblare ce calculeaza valoarea expresiei e = a + b in care variabilele au valori fixe:

; programul determina expresia e=a+b
;----------------------------------------------------------------------
.model small
.286
.stack 100h
.data
        a db 10
        b db 35

        e db ?
.code
        mov AX,@data
        mov DS,AX

        mov AL,a
        add AL,b
        mov e,AL

        mov AX,4c00h
        int 21h
end

Descriere program:

.model small : Liniile care incep cu “.“reprezinta instructiuni speciale care indica programului assembler anumite informatii, descrise de cuvintele cheie ce urmeaza, cu privire la programul de construit. in aceasta situatie model indica faptul ca urmeaza a se indica modelul de memorie (si cantitatea de memorie) utilizat de program. Acest program necesita un spatiu redus de memorie, fapt indicat prin small. Un model de memorie specifica modul in care codul si datele sunt adresate, ori sunt in acelasi segment fizic ori in mai multe segmente. Când toate datele (sau tot codul) se afla in acelasi segment atunci elementele sunt adresate prin adrese near date de deplasarea lor (offset) fata de adresa de inceput a segmentului. Daca se utilizeaza mai multe segmente atunci elementele sunt adresate prin adrese far date de adresa segmentului si offset. Tipuri standard de modele sunt: SMALL, MEDIUM, COMPACT si LARGE.

Tip adrese

SMALL

MEDIUM

COMPACT

LARGE

Adrese COD

near

near

far

far

Adrese DATE

near

far

near

far

Modele de memorie

.stack : Alta linie care descrie programul. Instructiunea indica locul in care incepe segmentul de stiva. Acesta este utilizat ca zona temporara de stocare a rezultatelor intermediare sau pentru a conserva starea sistemului (descrisa de valorile din registre) inainte de a efectua operatii care sa altereze datele existente. De asemenea stiva este utilizata si in transferul parametrilor catre si din proceduri. Indiferent daca este utilizata sau nu este obligatorie definirea segmentului de stiva deoarece un program .EXE trebuie sa aiba o stiva. Optional se poate defini si dimensiunea stivei ca numar de octeti. Daca nu se specifica dimensiunea se iau implicit 1024 de bytes.

.data : indica faptul ca incepe segmentul de date. Implicit, reprezinta terminarea segmentului de stiva. In segmentul de date sunt definite variabilele cu care se lucreaza in program.

a db 10: se defineste o variabila de tip byte cu valoarea zecimala 10.

.code : indica inceperea segmentului de cod si implicit, terminarea segmentului de date. Acest segment contine instructiunile programului.

mov AX, @data : Instructiunea incarca in registrul AX adresa segmentului de date. Alte simboluri predefinite @code – adresa segmentului de cod.

mov DS,AX : Instructiunea incarca in registrul segment DS adresa segmentului de date din registrul AX. Operatia este necesara deoarece nu este permisa incarcarea registrului DS cu o valoare constanta (adresa).

mov AL,a: se pune in registrul AL valoarea lui a.

add AL,b: se aduna la valoarea din registrul AL, valoarea lui b.

mov e,AL: variabila e este initializata cu valoarea din registrul AL

mov AX, 4c00h : incarca in registrul AX valoarea hexazecimala 4c00h. Acest lucru este necesar apelarii ulterioare a rutinei 21h.

int 21h : Apelul intreruperii 21h. In registrul AH se gaseste valoarea 4ch deoarece AX are valoarea 4c00h. Pentru rutina DOS acest lucru semnifica iesire din program. Valoarea din AL, adica 00h, reprezinta codul de iesire din program ce indica terminare executie fara eroare.

.end : marcheaza sfârsitul fisierului sursa. ATENTIE: Daca se specifica numele unei etichete definita anterior (de obicei inainte de prima instructiune) atunci aceasta reprezinta adresa din CS la care porneste executia programului. De exemplu programul

...
.code
        mov AX,@data
        mov DS,AX
Start:
        mov AL,a
        add AL,b
        mov e,AL
...
end Start

ruleaza incepând cu instructiunea mov AL,a.

Exemplu 2 – afiseaza un mesaj la consola:

; programul afiseaza un mesaj
;----------------------------------------------------------------------
.model small
.stack
.data
mesaj   db "Afisare mesaj !!!","$"

.code

main   proc
   mov   AX,seg mesaj
   mov   DS,AX

   mov   AH,09
   lea   DX,mesaj
   int   21h

   mov   AX,4c00h
   int   21h
main   endp
end main

Descriere program:

main proc : codul poate fi structurat prin intermediul procedurilor. Pentru a face analogie cu programul C care trebuie sa contina obligatoriu o procedura numita main, se defineste procedura si in programul assembler. Instructiunea indica inceputul subprogramului numit main. Sfarsitul subprogramului este indicat de instructiunea end main.

mesaj db “
Afisare mesaj !!!
: defineste o variabila sir de caractere numita message ce contine textul Afisare mesaj !!!.

mov AX, seg mesaj : Instructiunea incarca in registrul AX adresa segmentului de date. Reprezinta o alternativa la instructiunea mov AX, @data pentru ca instructiunea seg intoarce adresa segmentului in care se afla definita variabila respectiva. Situatia este posibila pentru ca fiecare variabila este identificata printr-o adresa de forma segment:offset. Fara a incarca registrul DS cu adresa segmentului activ de date nu se incarca corect adresa variabilei mesaj.

mov AH, 09 : incarca in registrul AH valoarea constanta 09. Este necesar pentru a afisa un mesaj pe ecran utilizând intreruperea DOS 21h.

lea DX, message : Instructiunea LEA (Load Efective Address) incarca in registrul DX offset-ul din cadrul segmentului de date la care se gaseste variabila mesaj . Acest lucru este necesar pentru a afisa mesajul pe ecran utilizând intreruperea 21h.

int 21h : Instructiunea genereaza o intrerupere DOS. Procesorul apeleaza rutina indicata de numarul intreruperii, in acest caz o rutina DOS. Procedura verifica registrul AH pentru a vedea ce trebuie sa faca. In acest caz, valoarea 09 din AH indica faptul ca trebuie sa scrie pe ecran un sir de biti.

Instructiuni utilizate in cele 2 exemple:

Instructiunea MOV

  • realizeaza principalele operatii de transfer a valorilor;
  • este atât de utilizata incât este imposibil sa scrii un program assembler fara aceasta instructiune;
  • forma analitica a instructiunii este:

MOV destinatie, sursa

si realizeaza copierea valorii de la sursa la destinatie fara a modifica sursa.

  • permite multiple combinatii de tipuri ale sursei si destinatiei (registru, valoare imediata, locatie de memorie) insa exclude situatiile:
    • destinatia nu poate fi segmentul CS; cum segmentul CS contine intotdeauna adresa codului de executat nu este indicat a se modifica aceasta valoarea in timpul prelucrarii.; singurul mod de a modifica CS este prin intermediul instructiunilor int, jmp sau call;
    • destinatia si sursa nu pot fi in acelasi timp operanzi din memorie (variabile definite in segmentul de date); aceasta restrictie se aplica tuturor instructiunilor procesoarelor 80×86 care necesita doi operanzi; exemplu:
.data
	vb1 DW 300
	vb2 DW ?
….
mov vb2,vb1	;EROARE

pentru a evita situatia se utilizeaza un registru asemenea unei zone temporare; de exemplu:

.data
	vb1 DW 300
	vb2 DW ?
….
mov AX, vb1
mov vb2,AX
  • daca sursa este o valoare imediata (constanta valorica), destinatia nu poate fi unul dintre registrele de segment (CS, DS, ES, SS); pentru a evita situatia se utilizeaza un registru;
  • destinatia nu poate fi un operand imediat (constanta valorica); exemplu:
mov 5, AX	;EROARE
  • cea mai rapida combinatie este MOV registru, registru;

Instructiunea XCHG

  • interschimba valorile din sursa si destinatie;
  • forma analitica:

XCHG destinatie, sursa

  • nu afecteaza nici un Flag bit;

Instructiunile LDS si LES

  • sunt printre putinele instructiuni care prelucreaza un dublu cuvânt (32 de biti); transfera dublu-cuvântul din memorie catre 2 registre de 16 biti; valoarea din cei doi octeti superiori ai dublu-cuvântului este copiata intr-unul din registrele de segment (fie DS sau ES in functie de instructiune LDS sau LES); cuvântul (16 biti) mai putin semnificativ este copiat intr-un registru general indicat ca operand destinatie in instructiune; de obicei dublu-cuvântul este un pointer ce contine adresa unei variabile data de segment:offset (adresa segmentului pe 16 biti si offestul in cadrul segmentului tot pe 16 biti);
  • incarca adresa in segment;
  • forma analitica:

LDS registru, sursa

LES registru, sursa

unde sursa este reprezentata de o variabila definita pe 32 de biti;

  • registrul FLAG este neafectat;
  • restrictie: registru nu poate fi unul din registrele de segment;
  • exemplu:
mesaj db "Afisare mesaj !!!","$"

pointer_mesaj dd mesaj

…

lds DX,pointer_mesaj    ; incarca in DS adresa segmentului in care se afla mesaj si in DX offest-ul 

sau daca DS este deja incarcat cu adresa segmentului atunci

les DX, mesaj        ;incarca in ES adresa segmentului si in DX offset-ul

ultima instructiune poate fi inlocuita cu:

mov DX, OFFSET mesaj ;OFFSET returneaza offset-ul variabilei mesaj

Instructiunea ADD

  • aduna la operandul destinatie valoarea operandului sursa;
  • adunarea se realizeaza pe 16 biti;
  • forma analitica:

ADD destinatie, sursa

  • permite multiple combinatii de tipuri ale sursei si destinatiei (registru, valoare imediata, locatie de memorie) insa exclude situatiile:
    • destinatia nu poate fi segmentul CS; cum segmentul CS contine intotdeauna adresa codului de executat nu este indicat a se modifica aceasta valoarea in timpul prelucrarii.; singurul mod de a modifica CS este prin intermediul instructiunilor int, jmp sau call;
    • destinatia si sursa nu pot fi in acelasi timp operanzi din memorie (variabile definite in segmentul de date); aceasta restrictie se aplica tuturor instructiunilor procesoarelor 80×86 care necesita doi operanzi; exemplu:
.data
	vb1 DW 300
	vb2 DW ?
….
add vb2,vb1	;EROARE

pentru a evita situatia se utilizeaza un registru asemenea unei zone temporare; de exemplu:

.data
	vb1 DW 300
	vb2 DW ?
….
mov AX, vb1
add vb2,AX
  • daca sursa este o valoare imediata (constanta valorica), destinatia nu poate fi unul dintre registrele de segment (CS, DS, ES, SS); pentru a evita situatia se utilizeaza un registru;
  • destinatia nu poate fi un operand imediat (constanta valorica); exemplu:
add 5, AX    ;EROARE
  • registru FLAG modificat: OF, CF, PF, SF, AF, ZF;

Instructiunea SUB (SUBstract)

  • scade din operandul destinatie valoarea operandului sursa;
  • forma analitica:

SUB destinatie, sursa

  • restrictii identice ca la instructiunea ADD;
  • exemple:
.data
	vb1 DW 300
	vb2 DW 50
….
mov AX, vb1
mov CX, vb2
sub vb1, 50
sub AX, CX
sub AX, vb1
sub AX,10

Instructiunea NEG

  • utilizat pentru a nega operandul, scazând-ul din valoarea 0;
  • forma analitica:

NEG operand

  • registru FLAG afectat: OF,SF, ZF, AF, PF, CF;
  • operandul trebuie sa fie registru sau variabila;
  • când operandul contine valoarea minima posibila aceasta nu este afectata;

Instructiunea MUL (MULtiply)

  • multiplica o valoare unsigned din AL (când operandul este de tip byte) sau din AX (când operandul este de tip word) cu valoarea operandului specificat; rezultatul este returnat in AX când operandul este de tip byte si in DX:AX (cei 2 octeti superiori in DX) când acesta este de tip word;
  • forma analitica:

MUL operand

  • registru FLAG modificat: OF si CF sunt setati daca partea superioara a rezultatului (DX daca operandul este de tip word, sau AH este de tip byte) este diferita de 0;alte flag-uri SF, ZF, AF, PF;
  • operandul poate fi alt registru sau o variabila;
  • daca valoarea inmultita are semn atunci se utilizeaza IMUL;

Instructiunea DIV (DIVide)

  • imparte o valoare unsigned din AX (când operandul este de tip byte; in acest caz DX trebuie sa contina valoarea 0) sau din DX:AX (când operandul este de tip word) cu valoarea operandului specificat; câtul este returnat in AL iar restul in AH când operandul este de tip byte si in DX:AX (DX contine restul si AX câtul) când acesta este de tip word;
  • forma analitica:

DIV operand

  • registru FLAG modificat: nedefiniti;
  • operandul poate fi alt registru sau o variabila;
  • in cazul operandului de tip word memorarea deimpartitului in DX:AX se face cu instructiunea cwd (convert word to double) ce extinde valoarea din AX in zona DX:AX; exemplu:
vb1 dw 55
vb2 dw 15
cat dw ?
rest dw ?
...
mov AX,vb1
cwd
div vb2
mov cat,AX
mov rest,DX<span style="text-decoration: underline;"><span style="text-decoration: underline;">
</span></span>

Instructiunea ROR (ROtate Right)

  • utilizata pentru a roti la dreapta bitii destinatiei de atâtea ori cât este specificat; cum fiecare bit este rotit la dreapta, cel mai putin semnificativ bit al destinatiei este copiat in locul celui mai semnificativ bit si in CF (carry flag);
  • forma analitica:

ROR destinatie, contor

  • destinatia poate fi registru sau variabila; contorul poate fi constanta sau registrul CL; exemple:
vb dw 1234h
…
mov CL, 4
ror AX, CL
ror AX,4
ror vb, CL
ror vb, 4
  • registru FLAG: OF este setat doar daca contorul are valoarea 1 si bitul cel mai semnificativ al destinatiei este diferit de bitul vecin; pentru alte valori nu exista regula de modificare a lui OF; se modifica si CF;

Instructiunea ROL (ROtate Left)

  • utilizata pentru a roti la stânga bitii destinatiei de atâtea ori cât este specificat; cum fiecare bit este rotit la stânga, cel mai semnificativ bit al destinatiei este copiat in locul celui mai putin semnificativ bit si in CF (carry flag);
  • analogie cu ROR;

Instructiunea SAL sau SHL (Shift Arithmetic Left si SHift Left)

  • se utilizeaza pentru a muta la stânga bitii din destinatie cu atâtea pozitii câte sunt specificate in contor; cu fiecare pozitie se adauga la stânga in bitul cel mai putin semnificativ valoarea 0;
  • forma analitica:

SAL destinatie, contor

  • registrul FLAG: SF, ZF si PF; cel mai semnificativ bit al destinatiei este mutat in CF; OF este setat daca contorul are valoarea 1 iar bitul de semn isi pastreaza valoarea;
  • utilizat pentru a inmulti destinatia o putere a lui 2;
  • destinatia poate fi registru sau variabila; contorul poate fi constanta sau registrul CL;

Instructiunea SAR (Shift Arithmetic Right)

  • se utilizeaza pentru a muta la dreapta bitii din destinatie cu atâtea pozitii câte sunt specificate in contor; cu fiecare pozitie se adauga la dreapta in vecinul bitului cel mai semnificativ valoarea 0; bitul cel semnificativ (bitul de semn) isi pastreaza valoarea;
  • forma analitica:

SAR destinatie, contor

  • registrul FLAG: SF, ZF si PF; cel mai putin semnificativ bit al destinatiei este mutat in CF; OF este setat daca contorul are valoarea 1 iar bitul de semn si vecinul sau sunt diferiti;
  • utilizat pentru a imparti un numar negativ (destinatia) la o putere a lui 2;
  • destinatia poate fi registru sau variabila; contorul poate fi constanta sau registrul CL;

Instructiunea SHR (Shift Rigth)

  • se utilizeaza pentru a muta la dreapta bitii din destinatie cu atâtea pozitii câte sunt specificate in contor; cu fiecare pozitie se adauga in vecinul bitului cel mai semnificativ valoarea 0;
  • forma analitica:

SHR destinatie, contor

  • registrul FLAG: SF, ZF si PF; cel mai putin semnificativ bit al destinatiei este mutat in CF; OF este setat daca contorul are valoarea 1 iar bitul de semn si vecinul sau sunt diferiti;
  • utilizat pentru a imparti un numar pozitiv (destinatia) la o putere a lui 2;
  • destinatia poate fi registru sau variabila; contorul poate fi constanta sau registrul CL;