Sunteți pe pagina 1din 25

Asm 1

Adrese
Orice adresa este formata din doua componente: segment si offset (deplasament),
notatia uzuala fiind segment:offset. Pentru partea de offset exista mai multe
variante:
Constanta numerica. Exemplu: [100]
Valoarea unui registru general pe 32 biti. Exemplu: [EAX] (se poate scrie si
cu litere mici)
Suma dintre valoarea unui registru general pe 32 biti si o constanta.
Exemplu: [EBX+5]
Suma a doi registri generali pe 32 biti. Exemplu: [ECX+ESI]
Combinatia celor 2 variante anterioare: suma a 2 registri si a unei constante.
Exemplu: [EDX+EBP+14]
Suma a 2 registri, dintre care unul inmultit cu 2, 4, sau 8, la care se poate
aduna o constanta. Exemple: [EAX+EDI*2], [ECX+EDX*4+5]
Instructiunea de atribuire: mov
Sintaxa: mov destinatie, sursa
Efect: pune in destinatie valoarea din sursa.
Destinatia, respectiv sursa, pot fi:
registru, registru. Exemple: mov eax, ebx;, mov al, bh;
registru, adresa de memorie. Exemplu: mov bl, [eax];
adresa de memorie, registru. Exemplu: mov [esi], esx;
registru, constanta numerica. Exemplu: mov ah, 0;
memorie, constanta numerica. Exemplu: mov [eax], 3;
Ex. 1:
#include <stdio.h>
void main(){
_asm{
mov eax, 0;
mov ah, 1;
}
}
Ex. 2:
#include <stdio.h>
void main(){
int i = 0;
_asm{
mov ax, i;

}
}
Dimensiunea operanzilor:
mov byte ptr [eax], 5; //afecteaza 1 octet
mov word ptr [eax], 5; //afecteaza 2 octeti
mov dword ptr [eax], 5; //afecteaza 4 octeti (double word)
Instruciunea add
Sintaxa: add op1, op2
Efect: op1 = op1 + op2
Ex. 1:
#include <stdio.h>
void main(){
int a=10;
_asm {
add a,5
}
printf("%d\n",a);
}
Ex. 2:
#include <stdio.h>
void main(){
_asm {
mov eax,0xFFFFFFFF;
add eax,2; // rezultatul este 0x100000001; necesita 33 biti.
// setare carry
mov eax,0;
mov ax, 0xFFFF;
add ax, 2; // doar ax se modifica!
// desi rezultatul este 0x10001,
// al 17-lea bit din eax nu se modifica.
// se seteaza carry
}
printf("%d\n",c);
}
Instruciunea sub
Sintaxa: sub op1, op2
Efect: op1 = op1 - op2

Ex. 1:
#include <stdio.h>
void main()
{
int a=10,b=14;
_asm {
mov eax,b
sub a,eax
}
printf("%d\n",a);
}
Instruciuni booleene: AND, OR, XOR, NOT
Sintaxa:
and destinatie, sursa
or destinatie, sursa
xor destinatie, sursa
not destinatie
Instruciunile and, or, xor modific indicatorul ZERO
Utilitatea principal a acestor instruciuni este in lucrul cu mti. De exemplu, dac
ne intereseaz valoarea bitului al 5-lea din registrul ax, este suficient s se
execute and intre ax i valoarea (scrisa binar) 0000000000010000 (aceasta se
numete masc). Rezultatul operaiei va fi 0 (iar indicatorul ZERO va deveni 1)
dac bitul al 5-lea din ax are valoarea 0, respectiv va fi diferit de 0 (iar indicatorul
ZERO va deveni 0) dac bitul al 5-lea din ax are valoarea 1.
Dezavantajul abordrii de mai sus este acela c instruciunea and modific
valoarea primului operand, plasind acolo rezultatul operaiei.
Instruciunea test are acelai efect ca i and (execut AND intre biii celor doi
operanzi, modific la fel indicatorul ZERO), dar nu altereaz valoarea primului
operand. De exemplu:
test ax, 0x0010 // binar: 0000000000010000
modific indicatorul ZERO ca i
and ax, 0x0010
fr a altera valoarea din ax.
Asm 2
Instruciunea mul nmulire de numere fr semn
Sintaxa: mul op

Efect: destinatie_implicita = operand_implicit * op


Operaia de nmulire este o operaie binar. Din moment ce la instruciunea mul se
precizeaz un singur operand, este evident c celalalt operand va fi implicit.
Operandul implicit depinde de dimensiunea operandului explicit op (dup cum
tim i de la celelalte instruciuni studiate, operanzii trebuie s aib aceeai
dimensiune). n tabelul de mai jos sunt precizai operanzii implicii pentru fiecare
din dimensiunile posibile ale operandului explicit.
n plus, trebuie observat faptul c reprezentarea rezultatului operaiei de nmulire
poate avea lungime dubl fa de lungimea operanzilor. De exemplu, nmulind
urmtoarele 2 numere reprezentate pe cte 8 bii, obinem un rezultat reprezentat
pe 16 bii:
10110111 *
11010010
---------------0
10110111
10110111
10110111
10110111
---------------1001011000011110
Deci dimensiunea destinaiei implicite trebuie s fie dublul dimensiunii
operanzilor.
Tabelul de mai jos prezint operanzii implicii i destinaiile implicite pentru
diversele dimensiuni ale operandului implicit:
Dimensiune operand explicit Operand implicit Destinaie implicit
1 octet
al
ax
2 octei
ax
(dx, ax)
4 octei
eax
(edx, eax)
Operandul explicit nu poate fi constant numeric:
mul 10;
//EROARE
mul byte ptr 2; //EROARE
El trebuie s fie ori un registru de date (al, ebx, ...), ori o adres de memorie. Dac
adresa de memorie nu este dat prin numele simbolic (de exemplu, numele unei
variabile declarate n programul C ce ncapsuleaz codul asm), ci prin modurile de
adresare discutate anterior, trebuie precizat dimensiunea n octei a zonei de

memorie ce conine respectivul operand explicit, pentru a se putea stabili


operandul implicit i destinaia implicit.
De exemplu:
mul byte ptr [ebp - 4]: operandul explicit se afl n memorie la adresa [ebp 4] i are dimensiunea de 1 octet (valoarea efectiv este cea din octetul de la
aceasta adresa)
mul word ptr [ebp - 4]: operandul explicit se afl la adresa [ebp - 4] i are
dimensiunea de 2 octei (valoarea efectiv este cea compus din primii 2
octei de la aceasta adresa)
mul dword ptr [ebp - 4]: operandul explicit se afl la adresa [ebp - 4] i are
dimensiunea de 4 octei (valoarea efectiv este cea compus din primii 4
octei de la aceasta adresa)
Cteva exemple:
linia de cod mul bl va avea urmatorul efect:
se calculeaz rezultatul nmulirii dintre al i bl (bl are dimensiunea de
1 octet, deci operandul implicit la nmulire este al)
acest rezultat se pune n ax(care este destinaia implicit pentru
nmuliri cu operandul explicit op de 1 octet
Mai concret, mul bl <=> ax = al * bl
linia de cod mul bx va avea urmatorul efect:
se calculeaz rezultatul nmulirii dintre ax i bx (bx are dimensiunea
de 2 octei, deci operandul implicit la nmulire este ax)
acest rezultat se pune n (dx,ax) astfel: primii 2 octei (cei mai
semnificativi) din rezultat vor fi plasai n dx, iar ultimii 2 octei (cei
mai puin semnificativi) n ax
16
rezultatul nmulirii este, de fapt, dx*2 + ax
Desigur, acest rezultat pe 4 octei ar fi ncput n eax. Se folosesc ns
regitrii (dx,ax) pentru compatibilitatea cu mainile mai vechi, pe 16 bii.
Exemplu de cod:
#include <stdio.h>
void main(){
_asm {
mov ax, 60000; //in baza 16: EA60
mov bx, 60000; //in baza 16: EA60
mul bx;
//rezultatul inmultirii este 3600000000;
//in baza 16: D693A400, plasat astfel:
//in registrul dx - partea cea mai semnificativa: D693
//in registrul ax - partea cea mai putin semnificativa: A400

}
}
linia de cod mul ebx va avea urmatorul efect:
se calculeaz rezultatul nmulirii dintre eax i ebx (ebx are
dimensiunea de 4 octei, deci operandul implicit la nmulire este eax)
acest rezultat se pune n (edx,eax) astfel: primii 4 octei (cei mai
semnificativi) din rezultat vor fi plasai n edx, iar ultimii 4 octei (cei
mai puin semnificativi) n eax
32
rezultatul nmulirii este, de fapt, edx*2 + eax
Exemplu de cod:
#include <stdio.h>

void main(){
_asm {
mov eax, 60000; //in baza 16: 0000EA60
mov ebx, 60000; //in baza 16: 0000EA60
mul ebx;
//rezultatul inmultirii este 3600000000;
//in baza 16: D693A400, plasat astfel:
//in edx - partea cea mai semnificativa: 00000000
//in eax - partea cea mai putin semnificativa: D693A400
}
}
Instruciunea imul nmulire de numere cu semn (Integer MULtiply)
Sintaxa: imul op
Dup cum am precizat mai sus, la instruciunea mul operanzii sunt considerai
numere fr semn. Aceasta nseamn c se lucreaz cu numere pozitive, iar bitul
cel mai semnificativ din reprezentare este prima cifr a reprezentrii binare, nu
bitul de semn.
Pentru operaii de nmulire care implic numere negative exist
instruciunea imul (este nevoie de dou instruciuni distincte deoarece, spre
deosebire de adunare sau scdere, agoritmul de nmulire este diferit la numerele cu
semn). Ceea s-a prezentat la mul este valabil i pentru imul. Diferena este aceea c
numerele care au bitul cel mai semnificativ 1 sunt considerate numere negative,
reprezentate n complement fa de 2.
Exemplu:
void main(){
_asm {
mov ax, 0xFFFF;
mov bx, 0xFFFE;

mul bx;

//rezultatul inmultirii numerelor FARA SEMN:


//65535 * 65534 = 4294770690;
//in baza 16: FFFD0002, plasat astfel:
//in dx - partea cea mai semnificativa: FFFD
//in ax - partea cea mai putin semnificativa: 0002

mov ax, 0xFFFF;


mov bx, 0xFFFE;
imul bx;
//rezultatul inmultirii numerelor CU SEMN:
//-1 * -2 = 2;
//in baza 16: 00000002, plasat astfel:
//in dx - partea cea mai semnificativa: 0000
//in ax - partea cea mai putin semnificativa: 0002
}
}
Exerciiu:
Fie urmtorul program care calculeaz factorialul unui numr. S se nlocuiasc
linia de cod din interiorul buclei for (f = f * i) cu un bloc de cod asm, cu obinerea
aceluiai efect. Pentru simplificare, vom considera c rezultatul nu depete 4
octei.
#include <stdio.h>
void main(){
unsigned int n = 10, i, f = 1;
for(i=1;i<=n;i++){
f = f * i;
}
printf("%u\n",f);
}
mprirea
Instruciunea div mprire de numere fr semn
Sintaxa: div op
Efect: cat_implicit, rest_implicit = deimpartit_implicit : op
Instruciunea div corespunde operaiei de mprire cu rest.
Ca i la nmulire, operandul implicit (dempritul) i destinaia implicit (ctul i
restul) depind de dimensiunea operandului explicit op (mpritorul):
Dimensiune operand explicit Demprit Ct Rest
(mpritor)

1 octet
ax
al ah
2 octei
(dx, ax) ax dx
4 octei
(edx, eax) eax edx
(A se observa similaritatea cu instruciunea de nmulire.)
n toate cazurile, ctul este depus n jumtatea cea mai puin semnificativ a
dempritului, iar restul n cea mai semnificativ. Acest mod de plasare a
rezultatelor permite reluarea operaiei de nmprire nn bucl, dac este cazul, fr a
mai fi nevoie de operaii de transfer suplimentare.
Analog cu nmulirea, operandul explicit (mpritorul) poate fi un registru sau o
locaie de memorie, dar nu o constant:
div ebx
div cx
div dh
div byte ptr [...]
div word ptr [...]
div dword ptr [...]
div byte ptr 10 // eroare
Operaia de mprire ridic o problem care nu se ntlnete n alte pri:
mprirea la 0:
#include <stdio.h>
void main(){
_asm {
mov eax, 1
mov edx, 1
mov ebx, 0
div ebx
}
}
Programul va semnala o eroare la execuie (integer divide by zero) i va fi terminat
forat.
Efectund urmtoarea modificare:
#include <stdio.h>
void main(){
_asm {
mov eax, 1
mov edx, 1
mov ebx, 1 //1 in loc de 0

div ebx
}
}
se obine o alt eroare la execuie: integer overflow.
Motivul este acela c se ncearc mprirea numrului 0x100000001 la 1, ctul
fiind 0x100000001. Acest ct trebuie depus n registrul eax, ns valoarea lui
depete valoarea maxim ce poate fi pus n acest registru, adic 0xFFFFFFFF.
Mai concret, n cazul n care ctul nu ncape n registrul corespunztor, se obine
eroare:
(edx*232 + eax) / ebx 232 <=>
edx*232 + eax ebx*232 <=>
eax (ebx - edx) * 232 <=>
ebx edx
Cu alte cuvinte, vom obine cu siguran eroare dac mpritorul este mai mic sau
egal cu partea cea mai semnificativ a dempritului. Pentru a evita terminarea
forat a programului, trebuie verificat aceast situaie nainte de efectuarea
mpririi.
Instruciunea idiv mprire de numere cu semn
Sintaxa: idiv op
idiv funcioneaz ca i div, cu diferena c numerele care au bitul cel mai
semnificativ 1 sunt considerate numere negative, reprezentate n complement fa
de 2.
Exemple de cod
#include <stdio.h>
void main(){
_asm {
mov ax, 35;
mov dx, 0; //nu trebuie uitata initializarea lui (e)dx!
//(in general, initializarea partii celei mai
// semnificative a deimpartitului)
mov bx, 7;
div bx; //rezultat: ax devine 5, adica 0x0005 (catul)
//
dx devine 0 (restul)
mov ax, 35;
mov dx, 0;
mov bx,7
idiv bx // acelasi efect, deoarece numerele sunt pozitive

mov ax, -35; //in hexa (complement fata de 2): FFDD


mov dx, 0;
mov bx,7
div bx
//deimpartitul este (dx, ax), adica 0000FFDD
//in baza 10: 65501
//rezultat: ax devine 0x332C, adica 13100 (catul)
//
dx devine 0x0001 (restul)
mov ax, -35; //in hexa (complement fata de 2): FFDD
mov dx, 0;
mov bx,7
idiv bx //deimpartitul este (dx, ax), adica 0000FFDD
//este un mumar pozitiv, adica, in baza 10, 65501
//rezultat: ax devine 0x332C, adica 13100 (catul)
//
dx devine 0x0001 (restul)
//(efectul este acelasi ca la secventa de mai sus)
mov ax, -35; //in hexa (complement fata de 2): FFDD
mov dx, -1; //in hexa (complement fata de 2): FFFF
mov bx,7
idiv bx //deimpartitul este (dx, ax), adica FFFFFFDD
// - numar negativ, reprezentat in complement fata de 2
//in baza 10: -35
//rezultat: ax devine 0xFFF9, adica -5 (catul)
//
dx devine 0 (restul)
mov ax, -35; //in hexa (complement fata de 2): FFDD
mov dx, -1; //in hexa (complement fata de 2): FFFF
mov bx,7
div bx
//deimpartitul este (dx, ax), adica FFFFFFDD
// - numar pozitiv (deoarece folosim div)
// in baza 10: 4294967261
//rezultat: EROARE, deoarece FFFF > 0007,
// catul (613566751, adica 2492491F) nu incape in ax
}
}
Exerciiu

Fie urmtorul program. S se nlocuiasc liniile 4 i 5 cu un bloc de cod asm, cu


obinerea aceluiai efect.
1. #include <stdio.h>
2. void main(){
3. unsigned a=500007,b=10,c,d;
4. c=a/b;
5. d=a%b;
6. printf("%u %u\n",c,d);
7.}
Instruciuni de deplasare
Sunt instruciuni care permit deplasarea biilor n cadrul operanzilor cu un numr
precizat de poziii.
Deplasrile pot fi aritmetice sau logice. Deplasrile aritmetice pot fi utilizate
pentru a nmuli sau mpri numere prin puteri ale lui 2. Deplasrile logice pot fi
utilizate pentru a izola bii n octei sau cuvinte.
Dintre modificrile pe care deplasrile le fac asupra indicatorilor:
Carry Flag (CF) = ultimul bit deplasat n afara operandului destinaie;
Sign Flag (SF) = bitul cel mai semnificativ din operandul destinaie;
Zero Flag (ZF) = 1 dac operandul destinaie devine 0, 0 altfel.
O ilustrare a deplasrii logice la stnga cu o poziie:
Instruciunile de deplasare sunt:
shr dest, count
shl dest, count
sar dest, count
sal dest, count
unde:
dest semnific destinaia a crei valoare va fi modificat; poate
fi registru sau locaie de memorie:
shl eax, 1
shl dx, 3
shl byte ptr [...], 2
count precizeaz cu cte poziii se face deplasarea; poate fi constant
numeric sau registrul cl:
shl ebx, cl
Instruciunea shr (SHift Right)
Sintaxa: shr dest, count

Efect: deplasarea la dreapta a biilor din dest cu numrul de poziii precizat


de count; completarea la stnga cu 0; plasarea n Carry a ultimului bit ieit.
Exemplu:
mov bl, 33; //binar: 00100001
shr bl, 3; //bl devine 00000100
//Carry devine 0
shr bl, 3 //bl devine 00000000
//Carry devine 1
Instruciunea shl (SHift Left)
Sintaxa: shl dest, count
Efect: deplasarea la stnga a biilor din dest cu numrul de poziii precizat
de count; completarea la dreapta cu 0; plasarea n Carry a ultimului bit ieit.
Exemplu:
mov bl, 33; //binar: 00100001
shl bl, 3; //bl devine 00001000
//Carry devine 1
shl bl, 1 //bl devine 00010000
//Carry devine 0
Instruciunea sar (Shift Arithmetic Right)
Sintaxa: sar dest, count
Efect: deplasarea la dreapta a biilor din dest cu numrul de poziii precizat
de count; bitul cel mai semnificativ i pstreaz vechea valoare, dar este i
deplasat spre dreapta (extensie de semn); plasarea n Carry a ultimului bit ieit.
Exemplu:
mov bl, -36; //binar: 11011100
sar bl, 2; //bl devine 11110111
//Carry devine 0
Trebuie menionat c sar nu furnizeaz aceeai valoare ca i idiv pentru operanzi
echivaleni, deoarece idiv trunchiaz toate cturile ctre 0, n timp
ce sar trunchiaz cturile pozitive ctre 0 iar pe cele negative ctre infinit negativ.
Exemplu
mov ah, -7; //binar: 11111001
sar ah, 1; //teoretic, echivalent cu impartirea la 2
//rezultat: 11111100, adica -4
//idiv obtine catul -3
Instruciunea sal (Shift Arithmetic Left)
Sintaxa: sal dest, count
Efect: identic cu shl.

Asm 3
EFLAGS
Este un registru de 32 de biti care indica "starea" procesorului la un moment dat.
Doar o parte din cei 32 de biti sunt folositi pentru a furniza informatii despre
rezultatul ultimei operatii executate de procesor. Bitii din EFLAGS se mai numesc
si indicatori. Dintre acestia, amintim:
CF - carry flag (transport) - are valoarea 1 (este setat) daca dupa ultima operatie a
aparut transport, 0 (nu este setat) altfel.
PF - parity flag (paritate) - are valoarea 1, daca numarul de biti de 1 din rezultatul
ultimei operatii este par.
ZF - zero flag - are valoarea 1, daca rezultatul ultimei operatii a fost 0.
SF - sign flag (semn) - are valoarea 1, daca rezultatul ultimei operatii a fost negativ
(bitul cel mai semnificativ este 1).
OF - overflow flag (depasire) - are valoarea 1, daca ultima operatie a produs
depasire aritmetica.
Instruciuni de salt
Instruciunile de salt modific valoarea registrului contor program (EIP), astfel
nct urmtoarea instruciune care se execut s nu fie neaprat cea care urmeaz n
memorie. Sunt utile pentru implementarea, la nivel de limbaj de asamblare, a
structurilor de control (testri sau bucle).
Salturile pot fi:
necondiionate: instruciunea jmp
condiionate: instruciuni de forma j<condiie>
Sintaxa: instructiune_salt adresa
Vom considera n continuare doar cazul n care adresa este o constant referit de o
etichet.
Exemplul de mai jos ilustraz modul de definire i utilizare a etichetelor:
#include <stdio.h>
void main(){
int i;
_asm{
mov i, 11;
jmp eticheta;

sub i, 3; // aceasta instructiune nu se executa


eticheta:
add i, 4;
}
printf ("%d\n", i);
}
Saltul necondiionat (instruciunea jmp)
Nu introduce o ramificaie n program, neexistnd variante de execuie. Este util,
folosit mpreun cu salturi condiionate, pentru reluarea unei secvene de de cod
ntr-o bucl, aa cum se va vedea ntr-un exemplu ulterior.
Salturi condiionate
Introduc o ramificaie n program, deoarece avem dou variante:
condiia de salt este adevrat se face saltul la adresa indicat
condiia de salt este fals se continu cu instruciunea urmtoare din
memorie ca i cum nu ar fi existat instruciune de salt.
Instruciuni care testeaz indicatorii individuali
Cele mai utile la acest nivel sunt cele care testeaz indicatorii: Carry, Overflow,
Sign, Zero. Pentru fiecare indicator exist dou instruciuni de salt condiionat: una
care face saltul cnd indicatorul testat are valoarea 1 i una care face saltul cnd are
valoarea 0.
indicator testat salt pe valoarea 1 salt pe valoarea 0
Carry
jc
jnc
Overflow
jo
jno
Zero
jz
jnz
Sign
js
jns
Exemplu:
#include <stdio.h>
void main(){
int a, b, s=0;
printf("a=");
scanf("%x", &a);
printf("b=");
scanf("%x", &b);
_asm{
mov eax, a;
add eax, b;
jc semnaleaza_depasire; //in Visual C++,

// putem sari la o eticheta din codul C


mov s, eax;
jmp afiseaza_suma;

//sau din alt bloc asm

}
semnaleaza_depasire:
printf ("S-a produs depasire!\n");
return;
_asm{
afiseaza_suma:
}
printf ("%x + %x = %x\n", a, b, s);
}
Instruciuni corespunztoare operatorilor relaionali
n practic, utilizm mai des ramificri dictate de operatori relaionali: <, <=, !=,
etc. n acest sens este util instruciunea de comparare cmp:
cmp funcioneaz ca i sub (aceleai restricii pentru operanzi, aceiai indicatori
modificai), ns nu modific primul operand (destinaia). Prin verificarea
indicatorilor se poate stabili n urma aceste operaii relaia dintre operanzi.
Instruciunile care fac aceste verificri sunt:
relaie
instruciune Comentariu
op1 < op2 jb
"Jump if Below"
op1 <= op2 jbe
"Jump if Below or Equal"
op1 > op2 ja
"Jump if Above"
op1 >= op2 jae
"Jump if Above or Equal"
pentru numere fr semn, respectiv
relaie
instruciune Comentariu
op1 < op2 jl
"Jump if Less than"
op1 <= op2 jle
"Jump if Less than or Equal"
op1 > op2 jg
"Jump if Greater than"
op1 >= op2 jge
"Jump if Greater than or Equal"
pentru numere cu semn.
Sunt necesare instruciuni diferite pentru numere fr semn, respectiv cu semn,
deoarece indicatorii ce trebuie verificai difer. De exemplu, comparnd 00100110
i 11001101, ar trebui s obinem relaia 00100110 < 11001101 dac sunt numere
fr semn, i 00100110 > 11001101 dac sunt numere cu semn.
Independent de statutul bitului celui mai semnificativ (semn sau cifr) funcioneaz
instruciunile:

relaie
instruciune Comentariu
op1 == op2 je
"Jump if Equal" (identic cu jz)
op1 != op2 jne
"Jump if Not Equal" (identic cu jnz)

Asm 4
Lucrul cu stiva. Apelul funcilor
Lucrul cu stiva
Procesorul folosete o parte din memoria RAM pentru a o accesa dup o disciplin
de tip LIFO (ca n cazul unei structuri de stiv). Dup cum se tie, singura
informaie fundamental pentru gestiunea stivei este vrful acesteia. n cazul
procesorului, adresa la care se afl vrful stivei este memorat n perechea de
regitri SS i ESP; deoarece regitrii segment au, pe mainile pe 32 de bii, doar
scop de "validare" a adresei, vom lucra n continuare numai cu ESP.
Instruciunile care permit lucrul cu stiva sunt push i pop.
Instruciunea push
Realizeaz introducerea unei valori n stiv.
Sintaxa: push operand;
Operandul poate fi registru, locaie de memorie sau constant numeric. Stiva
lucreaz doar cu valori de 2 sau 4 octei, pentru uniformitate preferndu-se numai
operanzi de 4 octei (varianta cu 2 se pstraz pentru compatibilitate cu
procesoarele mai vechi).
Exemple:
push eax
push dx
push dword ptr [...]
push word ptr [...]
push dword ptr 5
push word ptr 14
Introducerea valorii n stiv se face astfel: se scade din ESP dimensiunea, n octei,
a valorii care se vrea depusa n stiv, dupa care procesorul scrie valoarea
operandului la adresa indicat de registrul ESP (vrful stivei); dimensiunea poate fi
2 sau 4 (se observ c se avanseaz "n jos", de la adresele mai mari la adresele mai
mici); n acest mod, vrful stivei este pregtit pentru urmtoarea operaie de
scriere.
De exemplu, instruciunea
push eax;
ar fi echivalent cu:

sub esp, 4;
mov [esp], eax;
Prin folosirea lui push n locul secvenei echivalente se reduce, ns, riscul erorilor.
Instruciunea pop
Extrage vrful stivei ntr-un operand destinaie.
Sintaxa: pop operand;
Operandul poate fi registru sau locaie de memorie, de 2 sau 4 octei.
Exemple:
pop eax
pop cx
pop dword ptr [...]
pop word ptr [...]
Extragerea valorii din stiv se face prin depunerea n destinaie a valorii aflate n
vrful stivei (la adresa [ESP]) i adunarea, la ESP, a numrului de octei ai
operandului (acesta indic, practic, numrul de octei scoi din stiv).
Rolul stivei
Rolul stivei procesorului este acela de a stoca informaii cu caracter temporar. De
exemplu, dac avem nevoie s folosim un registru pentru nite operaii, dar nu
avem la dispoziie nici un registru a crui valoare curent s ne permitem s o
pierdem, putem proceda ca mai jos:
push eax; //se salveaza temporar valoarea lui eax pe stiva
... // utilizare eax
pop eax //restaurare
Variabilele locale (cu excepia celor statice) sunt plasate de asemenea n stiv
(deoarece au caracter temporar: sunt create la intrarea n funcie i distruse la
ieire).
n lucrul cu stiva, instruciunile de introducere n stiv trebuie riguros compensate
de cele de scoatere, din punctul de vedere al numrului de instruciuni i al
dimensiunii operanzilor. Orice eroare poate afecta mai multe date, din cauza
decalajelor.
push edx;
push eax;
... //utilizare registri
pop ax //se recupereaza doar 2 octeti din valoarea anterioara a lui eax
pop edx //nu se recupereaza edx, ci 2 octeti din eax, 2 din edx
//decalajul se poate propaga astfel pana la capatul stivei
O alt eroare poate aprea atunci cnd registrul ESP este manipulat direct. De
exemplu, pentru a aloca spaiu unei variabile locale (neiniializat), e suficient a
scdea din ESP dimensiunea variabilei respective. Similar, la distrugerea
variabilei, valoarea ESP este crescut. Aici nu se folosesc n general instruciuni

push, repectiv pop, deoarece nu intereseaz valorile implicate, ci doar ocuparea i


eliberarea de spaiu. Se prefer adunarea i scderea direct cu registrul ESP;
evident c o eroare n aceste operaii are consecine de aceeai natur ca i cele de
mai sus.
Apelul funciilor
Un apel de funcie arat la prima vedere ca o instruciune de salt, n sensul c se
ntrerupe execuia liniar a programului i se sare la o alt adres. Diferena
fundamental const n faptul c la terminarea funciei se revine la adresa de unde
s-a fcut apelul i se continu cu instruciunea urmtoare. Din moment ce ntr-un
program se poate apela o funcie de mai multe ori, din mai multe locuri, i
ntotdeauna se revine unde trebuie, este clar c adresa la care trebuie revenit este
memorat i folosit atunci cnd este cazul. Cum adresa de revenire este n mod
evident o informaie temporar, locul su este tot pe stiv.
Instruciunea call
Apelul unei funcii se realizeaz prin instruciunea call.
Sintaxa: call adresa
n Visual C++ vom folosi nume simbolice pentru a preciza adresa, cu meniunea c
de data asta nu este vorba de etichete, ca la salturi, ci chiar de numele funciilor
apelate.
Efectul instruciunii call: se introduce n stiv adresa instruciunii urmtoare
(adresa de revenire) i se face salt la adresa indicat. Aceste aciuni puteau fi
realizate i cu instruciuni push i jmp, dar din nou se prefer call pentru evitarea
erorilor.
Instruciunea ret
Revenirea dintr-o funcie se face prin instruciunea ret, care poate fi folosit fr
operand. n acest caz, se preia adresa de revenire din vrful stivei (similar unei
instruciuni pop) i se face saltul la adresa respectiv. Din motive de conlucrare cu
Visual Studio, nu vom folosi aceast instruciune.
Transmiterea parametrilor
Parametrii sunt tot nite variabile locale, deci se gsesc pe stiv. Cel care face
apelul are responsabilitatea de a-i pune pe stiv la apel i de a-i scoate de pe stiv
le revenirea din funcia apelat. Avem la dispoziie instruciunea push pentru
plasarea n stiv. Evident, aceast operaie trebuie realizat imediat nainte de
apelul propriu-zis. n plus, n limbajul C/C++ (nu n toate), parametrii trebuie pui
n stiv n ordine invers celei n care se gsesc n lista de parametri. La revenire,
parametrii trebuie scoi din stiv, nemaifiind necesari. Cum nu ne intereseaz
preluarea valorilor lor, nu se folosete instruciunea pop, care ar putea altera inutil
un registru, de exemplu, ci se adun la ESP numrul total de octei ocupat de
parametri (atenie, pe stiv se lucreaz n general cu 4 octei, chiar dac operanzii
au dimensiuni mai mici).

S lum ca exemplu funcia urmtoare:


void show_dif(int a,int b){
int c;
c=a-b;
printf("%d\n",c);
}
Apelul dif(5,9) se traduce prin secvena care se poate vedea mai jos:
void main(){
_asm{
push dword ptr 9
push dword ptr 5
call show_dif
add esp,8
}
}
Returnarea unei valori
Convenia n Visual C++ (i la majoritatea compilatoarelor) este c rezultatul se
depune ntr-un anumit registru, n funcie de dimensiunea sa:
pentru tipurile de date de dimensiune 1 octet - n registrul AL
pentru tipurile de date de dimensiune 2 octei - n registrul AX
pentru tipurile de date de dimensiune 4 octei - n registrul EAX
pentru tipurile de date de dimensiune 8 octei - n regitii EDX i EAX
Evident, la revenirea din funcie, cel care a fcut apelul trebuie s preia rezultatul
din registrul corespunztor.
Vom modifica exemplul de mai sus astfel nct funcia s returneze diferena pe
care o calculeaz ntr-o secven de instruciuni n limbaj de asamblare:
#include <stdio.h>
int compute_dif(int a,int b){
_asm{
mov eax, a;
sub eax, b;
//in eax ramane rezultatul, care
// va fi preluat la termiarea functiei
};
}
void main(){
int c;

_asm{
push dword ptr 9
push dword ptr 5
call compute_dif //se salveaza adresa de revenire pe stiva
mov c, eax;
add esp,8
//"stergerea" parametrilor din stiva
}
printf("Diferenta este %d.\n", c);
}
Parametri
Exista o alta modalitate de accesa in cadrul unei functii parametrii acesteia in
cadrul unui bloc limbaj de asamblare. Ei se gasesc pe stiva incepand cu
adresa [ebp+8] si ocupa numarul de octeti al tipului de date respectiv.
Exemplu, o functie cu 3 parametri de tip int (o variabila de tip int are 4 octeti):
void functie(int a, int b, int c){
_asm{
mov eax, [ebp+8] // muta in eax valoarea lui a
mov ebx, [ebp+12] // muta in ebx valoarea lui b
mov ecx, [ebp+16] // muta in ecx valoarea lui c
};
}
Scris in aceasta maniera, exemplul de mai sus ar arata in felul urmator:
#include <stdio.h>
int compute_dif(int ,int ){ // nu mai este nevoie sa punem nume variabilelor,
deoarece vom lucra direct cu stiva
_asm{
mov eax, [ebp+8];
sub eax, [ebp+12];
//in eax ramane rezultatul, care
// va fi preluat la termiarea functiei
};
}
void main(){
int c;
_asm{

push dword ptr 9


push dword ptr 5
call compute_dif //se salveaza adresa de revenire pe stiva
mov c, eax;
add esp,8
//"stergerea" parametrilor din stiva
}
printf("Diferenta este %d.\n", c);
}
Asm 5
Pointeri.
Pentru a intelege cum se folosesc tablourile in ASM, trebuie inteles mai intai
conceptul de pointer. Pointer-ul reprezinta o variabila ce pastreaza o adresa de
memorie a unei date ("pointeaza" spre o adresa de memorie). Un pointer poate fi
utilizat pentru referirea a diferite tipuri de date (tablouri de tip int, siruri de
caracetere, matrici etc.) sau structuri de date. Schimband adresa memorata in
pointer, pot fi manipulate informatii situate la diferite locatii de memorie.
Legatura dintre tablouri si pointeri
Numele unui tablou este un pointer constant spre primul sau element. Expresiile de
mai jos sunt deci echivalente:
nume_tablou
&nume_tablou
&nume_tablou[0]
*nume_tablou
nume_tablou[0]
// Ex.1 Interschimbarea a 2 valori
#include <stdio.h>
void swap (int *a, int *b)
{
_asm{
mov eax, a;
// punem in eax adresa data de pointerul *a
mov ecx, [eax]; // punem in ecx valoarea efectiva a lui *a (valoarea
de la adresa pointerului)
mov ebx, b;
// analog pt b

mov edx, [ebx];


mov [eax], edx; // mutam la adresa lui a valoarea lui *b
mov [ebx], ecx; // analog invers
}
}
void main()
{
int a=2, b=3;
swap(&a,&b);
printf("%d %d", a, b);
}
// Ex.2 Suma elementelor dintr-un vector
#include <stdio.h>
int suma_vector (int *, int )
{
_asm
{
mov eax, 0
// suma
mov ebx, [ebp+8] // primul parametru, pointer la vectorul de
elemente
mov ecx, 0
// contor
bucla:
cmp ecx, [ebp+12] // al 2-lea parametru, lungimea vectorului de
elemente
jae stop
add eax, [ebx+ecx*4]
// elementul vectorului de pe pozitia ecx
inc ecx
jmp bucla
stop:
}
}
void main()
{
int v[5]={5,1,2,3,6};
int *p=v;

int s;
_asm{
push 5
push p
call suma_vector
add esp, 8
mov s, eax
}
printf("Suma: %d", s);
}
// Ex.3 Lungimea unui sir de caractere (un sir de numere se termina cu valoarea 0)
#include <stdio.h>
int lungime(char *)
{
_asm{
mov eax, 0
mov ebx, [ebp+8] // adresa de inceput a sirului de caractere
bucla:
cmp [ebx+eax], 0 // comparam caracterul curent cu 0
je stop
inc eax
jmp bucla
stop:
}
}
void main()
{
char *sir="zigyzagy";
int l;
_asm{
push sir
call lungime
add esp, 4
mov l, eax

}
printf("Lungime: %d %d\n", l, strlen(sir));
}
Pentru matrice de a[n][m] (n x m elemente), pentru avea acces la elementul de pe
pozitia [i][j] (linia i, coloana j), va trebui sa aflam adresa acestuia. "a[i][j]" este
echivalent cu: "&a + (i*m+j)*4" (adresa primului element la care adaugam i x
nr_coloane + j, totul inmultit cu dimensiunea elementelor, in cazul nostru 4 octeti
pentru int)
// Ex. 4 - Construirea matricii unitate (1 pe diagonala, 0 in rest)
#include <stdio.h>
void matrice_unitate(int *, int )
{
_asm{
mov edi, [ebp+8]
mov ebx, 0
for_i:
cmp ebx, [ebp+12]
jae exit1

// adresa la primul element din matrice


// dimensiunea matricii

mov ecx, 0
for_j:
cmp ecx, [ebp+12]
jae exit2
mov eax, [ebp+12]
mul ebx
add eax, ecx

// construim adresa de pe pozitia [i][j]

cmp ebx, ecx


jne not_eq
mov dword ptr [edi+eax*4], 1 // i == j, deci vom pune 1
jmp inside
not_eq:
mov dword ptr [edi+eax*4], 0 // altfel, 0
inside:

inc ecx
jmp for_j
exit2:
inc ebx
jmp for_i
exit1:
}
}
void main()
{
int n=5;
int mat[5][5];
int *p = mat[0];
_asm
{
push n
push p
call matrice_unitate
add esp, 8
}
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
printf("%d ", mat[i][j]);
printf ("\n");
}
}

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