Documente Academic
Documente Profesional
Documente Cultură
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
}
}
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;
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
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;
}
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
_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{
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
mov ecx, 0
for_j:
cmp ecx, [ebp+12]
jae exit2
mov eax, [ebp+12]
mul ebx
add eax, ecx
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");
}
}