Bazele limbajului Assembly: primii pași în programarea low-level
Bazele limbajului Assembly: primii pași în programarea low-level
Bazele limbajului Assembly: primii pași în programarea low-level
Introducere
Limbajul Assembly este un limbaj de programare de nivel scăzut care corespunde aproape direct instrucțiunilor mașină pe care le execută procesorul computerului. Înțelegerea Assembly-ului oferă o perspectivă profundă asupra modului în care funcționează software-ul la cel mai de bază nivel și asupra arhitecturii hardware.
Acest curs se va concentra pe conceptele fundamentale ale Assembly-ului, folosind convențiile sintactice Intel (comune pe platformele Windows) și arhitectura x86 pe 32 de biți ca exemplu principal.
1. Ce este Limbajul Assembly?
- Nivel Scăzut: Este un pas deasupra codului binar (0 și 1) și un pas sub limbaje de nivel înalt (C, Python, Java).
- Specific Hardware: Fiecare familie de procesoare (x86, ARM, MIPS, etc.) are propriul set de instrucțiuni Assembly.
- Traducere: Codul Assembly este tradus în cod mașină de către un assembler (ex. NASM, MASM).
- Utilizări: Optimizări critice de performanță, drivere de dispozitive, sisteme de operare, reverse engineering, programare de sisteme embedded.
2. Arhitectura Procesorului (Noțiuni de Bază)
Pentru a înțelege Assembly, trebuie să cunoști câteva componente cheie ale procesorului:
- Unitatea Aritmetică și Logică (UAL / ALU): Execută operații matematice și logice.
- Unitatea de Control (UC): Dirijează fluxul de instrucțiuni.
- Registre: Locații de stocare foarte rapide în interiorul procesorului.
- Registre Generale: Folosite pentru stocarea datelor temporare (ex. EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP pe x86 32-bit).
- Registru Pointer de Instrucțiune (EIP): Conține adresa instrucțiunii următoare de executat.
- Registru de Flag-uri (EFLAGS): Conține bituri (flag-uri) care indică starea UAL după o operație (ex. Zero Flag, Carry Flag, Sign Flag).
- Memoria (RAM): Locație de stocare mai mare, dar mai lentă decât registrele. Datele trebuie mutate între memorie și registre pentru a fi procesate.
3. Sintaxa Assembly (Sintaxa Intel)
O linie tipică de Assembly arată astfel:
[label:] mnemonic [operand1] [, operand2] [; comentariu]
- Label: O etichetă care marchează o locație în cod (pentru salturi sau referințe).
- Mnemonic: Numele instrucțiunii (ex.
MOV
,ADD
,JMP
). - Operanzi: Datele sau locațiile pe care operează instrucțiunea.
- Comentariu: Text ignorat de assembler.
Exemplu:
start: ; Inceputul programului
mov eax, 10 ; Mută valoarea 10 în registrul EAX
add eax, 5 ; Adaugă 5 la valoarea din EAX
4. Tipuri de Operanzi
Instrucțiunile pot opera pe:
- Imediate: Valori constante (ex.
10
,0x5A
). - Registre: Conținutul unui registru (ex.
EAX
,EBX
). - Memorii: Conținutul unei locații din memorie. Se specifică adresa (ex.
[variabila]
,[ebx + 4]
).
Exemplu:
mov eax, 10 ; Imediat -> Registru
mov ebx, eax ; Registru -> Registru
mov [variabila], ebx ; Registru -> Memorie
mov ecx, [variabila] ; Memorie -> Registru
5. Instrucțiuni de Bază (x86 32-bit)
5.1. Instrucțiuni de Transfer de Date
MOV
(Move): Copiază date dintr-o locație în alta.- Sintaxa:
MOV destinație, sursă
- Ex:
mov eax, ebx
,mov ecx, 100
,mov [adresa], edx
- Sintaxa:
PUSH
(Push onto stack): Pune o valoare pe stivă.- Ex:
push eax
,push 20
- Ex:
POP
(Pop from stack): Extrage o valoare de pe stivă.- Ex:
pop ebx
- Ex:
XCHG
(Exchange): Schimbă conținutul a doi operanzi.- Ex:
xchg eax, ebx
- Ex:
5.2. Instrucțiuni Aritmetice
ADD
(Add): Adunare.- Ex:
add eax, ebx
,add ecx, 10
- Ex:
SUB
(Subtract): Scădere.- Ex:
sub edx, ecx
,sub [adresa], 5
- Ex:
MUL
(Multiply): Înmulțire (fără semn).- Ex:
mul ebx
(EAX * EBX -> EDX:EAX)
- Ex:
IMUL
(Integer Multiply): Înmulțire (cu semn).- Ex:
imul ebx
- Ex:
DIV
(Divide): Împărțire (fără semn).- Ex:
div ebx
(EDX:EAX / EBX -> EAX = cât, EDX = rest)
- Ex:
IDIV
(Integer Divide): Împărțire (cu semn).- Ex:
idiv ebx
- Ex:
5.3. Instrucțiuni Logice și pe Biți
AND
,OR
,XOR
: Operații logice pe biți.- Ex:
and eax, 0xFF
(mascare)
- Ex:
NOT
: Negare pe biți.SHL
(Shift Left),SHR
(Shift Right): Shiftare logică.SAL
(Shift Arithmetic Left),SAR
(Shift Arithmetic Right): Shiftare aritmetică.
5.4. Instrucțiuni de Control al Fluxului
JMP
(Jump): Salt necondiționat la o etichetă.- Ex:
jmp final
- Ex:
- Instrucțiuni de Salt Condiționat: Verifică flag-urile din EFLAGS.
JZ
/JE
(Jump if Zero / Equal)JNZ
/JNE
(Jump if Not Zero / Not Equal)JG
(Jump if Greater)JGE
(Jump if Greater or Equal)JL
(Jump if Less)JLE
(Jump if Less or Equal)- ... și altele (folosind flag-uri precum Carry Flag, Sign Flag)
- Ex:
cmp eax, ebx
,jg eticheta_mai_mare
CALL
: Salt la o subrutină (funcție), punând adresa de retur pe stivă.- Ex:
call subrutina_mea
- Ex:
RET
: Retur dintr-o subrutină, extrăgând adresa de retur de pe stivă.
5.5. Instrucțiuni de Comparație
CMP
(Compare): Compară doi operanzi prin scădere (fără a modifica operanzii) și setează flag-urile corespunzătoare.- Ex:
cmp eax, 10
- Ex:
6. Lucrul cu Memoria
- Adresare Directă:
mov eax, [adresa_variabila]
- Adresare Indirectă prin Registru:
mov ebx, [eax]
(folosind valoarea din EAX ca adresă) - Adresare Bază + Index:
mov ecx, [ebx + esi]
(folosind suma a două registre ca adresă) - Adresare Bază + Index * Scală:
mov edx, [ebx + esi*4]
(util pentru array-uri, undeesi
este indexul și4
este dimensiunea elementului) - Adresare Bază + Deplasament:
mov eax, [ebx + 8]
- Combinată:
mov ecx, [ebx + esi*4 + 10]
7. Stiva (Stack)
- O zonă de memorie folosită pentru stocarea temporară a datelor, adreselor de retur ale funcțiilor și variabilelor locale.
- Funcționează pe principiul LIFO (Last-In, First-Out).
- Controlată de registrele ESP (Stack Pointer) și EBP (Base Pointer).
PUSH
adaugă pe stivă (ESP scade).POP
extrage de pe stivă (ESP crește).
Exemplu:
push eax ; Pune EAX pe stivă
mov eax, 5 ; Modifică EAX
pop ebx ; Extrage valoarea originală a lui EAX în EBX
8. Subrutine (Funcții)
- Blocuri de cod care pot fi apelate și executate, apoi programul revine la punctul de apel.
- Folosesc
CALL
pentru a sări la subrutină șiRET
pentru a reveni. - Argumentele pot fi pasate prin registre sau prin stivă.
- Valorile de retur sunt adesea plasate în registrul EAX.
Exemplu Simplu de Subrutină:
; Functie care aduna 5 la valoarea din EAX
aduna_cinci:
push ebp ; Salveaza EBP (pentru managementul stivei)
mov ebp, esp ; Seteaza noul EBP
add eax, 5 ; Operatia principala
mov esp, ebp ; Restaureaza ESP
pop ebp ; Restaureaza EBP
ret ; Returneaza din subrutina
9. Lucrul cu Date (Definirea Variabilelor)
Assembler-ele permit definirea zonelor de memorie pentru variabile:
section .data ; Sectiune pentru date initializate
numar_intreg dd 123 ; Define Doubleword (4 bytes)
mesaj db "Salut!", 0 ; Define Byte (string, terminat cu 0)
section .bss ; Sectiune pentru date neinitializate
buffer resb 100 ; Rezerva 100 bytes
db
: Define Byte (1 octet)dw
: Define Word (2 octeți)dd
: Define Doubleword (4 octeți)dq
: Define Quadword (8 octeți)resb
,resw
,resd
,resq
: Rezervă spațiu neinițializat.
10. Interacțiunea cu Sistemul de Operare (Apeluri de Sistem)
Programele Assembly interacționează cu sistemul de operare (SO) pentru a face lucruri precum afișarea textului, citirea input-ului, accesarea fișierelor etc. Acest lucru se face prin apeluri de sistem.
Modul exact de a face apeluri de sistem depinde de SO (Windows, Linux) și de assembler.
Exemplu (Linux x86 - NASM):
section .data
mesaj db 'Salut, Lume!', 10 ; 10 este codul ASCII pentru linie nouă
lungime equ $ - mesaj ; Calculeaza lungimea mesajului
section .text
global _start
_start:
; Apel de sistem write (sys_write)
; sys_write are numarul 4 in Linux x86
mov eax, 4 ; Numarul apelului de sistem (sys_write)
mov ebx, 1 ; Descriptor de fisier (1 pentru stdout)
mov ecx, mesaj ; Adresa buffer-ului (mesajul)
mov edx, lungime ; Numarul de octeti de scris
int 0x80 ; Apelul de sistem
; Apel de sistem exit (sys_exit)
; sys_exit are numarul 1 in Linux x86
mov eax, 1 ; Numarul apelului de sistem (sys_exit)
mov ebx, 0 ; Cod de iesire (0 pentru succes)
int 0x80 ; Apelul de sistem
int 0x80
este mecanismul de a iniția un apel de sistem pe Linux x86.- Numărul apelului de sistem și argumentele sunt puse în registre specifice (EAX, EBX, ECX, EDX etc.).
11. Assembler-e și Linker-e
- Assembler: Transformă codul Assembly (
.asm
) în cod obiect (.o
,.obj
). Exemple: NASM, MASM, GAS. - Linker: Combină unul sau mai multe fișiere obiect și biblioteci pentru a crea un fișier executabil. Exemple: LD (Linux), LINK (Windows).
Procesul tipic:
- Scrie cod Assembly într-un fișier
.asm
. - Assemblează:
nasm -f elf fisier.asm -o fisier.o
(pentru Linux ELF) - Linkează:
ld -m elf_i386 fisier.o -o executabil
- Rulează:
./executabil
12. Debugging (Depanare)
Debugging-ul codului Assembly necesită un debugger de nivel scăzut (ex. GDB, WinDbg). Poți urmări execuția instrucțiune cu instrucțiune, inspecta valorile registrelor și conținutul memoriei.
13. Concepte Avansate (Le vom parcurge în alte tutoriale)
- Macro-uri: Secțiuni de cod predefinite care pot fi incluse de mai multe ori.
- Directives: Comenzi pentru assembler (ex.
section
,global
,extern
). - Moduri de Adresare Avansate: Segmentare (pe arhitecturi mai vechi sau mod real), paginare.
- Seturi de Instrucțiuni Specifice: SSE, AVX (pentru operații vectoriale), instrucțiuni pentru virtualizare etc.