Laborator 12 - Suport teoretic
Programare multi-modul (asm+C)
Programare multi-modul (asm+C)
Motivație:
- viteză mare de execuție în rezolvarea task-urilor cu consum minim de resurse;
Codul de apel
Codul de intrare
Codul de ieșire
- Restaurarea resurselor nevolatile alterate;
- Eliberarea variabilelor locale ale funcției;
- Dezafectarea cadrului de stivă;
- Revenirea din funcție și eliberarea argumentelor.
Declararea simbolurilor externe:
- Pentru a accesa din cadrul unui program C o funcție definită în asamblare, funcția trebuie declarată global în programul asm și trebuie să conțină caracterul '_' în fața numelui ei
- Dacă funcția va fi apelată în programul C ca
fun()
, atunci programul asm va conține următoarele:global _fun segment code public code use32 _fun:
Nealterarea valorilor unor regiștri
Limbajele de nivel înalt impun ca anumiți regiștri să își păstreze la ieșire valoarea cu care intră într-o procedură. În acest scop, dacă subprogramul definit în limbaj de asamblare modifică unii din aceștia, atunci valorile lor de intrare trebuie salvate (eventual pe stivă). Ele vor fi restaurate înainte de revenirea din procedură.- PUSHAD și POPAD pot fi utilizați pentru a salva și restaura valorile celor 8 regiștri generali.
Transmiterea parametrilor unei funcții
- parametrii sunt transmiși prin intermediul stivei, ceea ce oferă o flexibilitate mai mare decât transmiterea parametrilor prin regiștri (referitor la numărul de parametri);
Crearea cadrului de stivă
- La intrarea în funcție se va seta registrul EBP←ESP, valoare care va fi restaurantă la ieșirea din funcție;
Deoarece ESP se modifică odată cu introducerea paramtrilor în stivă, cel mai indicat mod este de a accesa valorile parametrilor prin intermediul unui registru de bază sau index.
Dintre aceste variante, registrul EBP este cel mai indicat scopului nostru, deoarece orice referire la EBP se face relativ la segmentul de stivă.
Secvența care pregătește accesul la stivă este:
push ebp mov ebp, esp
Alocarea de spațiu de memorie pentru datele locale
Uneori este necesar ca procedura să aibă date locale. Dacă valoarea lor nu trebuie să se păstreze într două apeluri consecutive ale procedurii atunci acestea vor fi alocate în segmentul de stivă și le vom numi date volatile. În caz contrar spunem că este vorba despre date statice și spațiul pentru ele va fi alocat într-un segment diferit de segmentul de stivă, de exemplu în segmentul de date. Alocarea unui număr n de octeți (n fiind multiplu de 4) pentru datele locale se poate face relativ la EBP.sub esp,n
Așadar:
- registrul EBP va fi folosit pentru a accesa parametrii (de exemplu [EBP+8] accesează primul parametru reprezentat pe 32 de biți);
- primul parametru accesibil din stivă este ultimul parametru adăugat pe stivă de către programul apelant;
- se rezervă spațiu pe stivă pentru variabilele locale, de exemplu:
sub esp,4*1
- această metodă simplifică modalitatea de accesare a parametrilor în special în cadrul funcțiilor cu număr variabil de parametri;
- este responsabilitatea programatorului să scoată parametrii de pe stivă.
Returnarea valorilor de către o funcție
- dacă funcția returnează un întreg, atunci acesta este returnat în EAX;
- dacă funcția returnează un șir, atunci adresa acestuia este returnată în EAX;
- prin folosirea convenției CDECL se presupune că regiștrii EBX, ESI, EDI, EBP și ESP nu își modifică valoarea în timpul apelului de funcții;
Revenirea din procedură
La revenirea din procedură trebuie să se execute operațiile:- refacerea valorilor regiștrilor (vezi secțiunea Nealterarea valorilor unor regiștri);
- refacerea stivei astfel încât în vârf să conțină adresa de revenire:
mov esp, ebp pop ebp
Scheletul unei funcții:
global _fun segment code public code use32 _fun: push ebp mov ebp, esp pushad ;... codul funcției ... popad mov eax, returned_value mov esp, ebp pop ebp end
Utilizarea procedurilor definite în asamblare în cadrul unui program C
Exemplul 1
Se definește în cadrul unui program asm o procedură numită hello_world care nu primește nici un parametru și nu returnează nimic. Aceasta afisează pe ecran textul "Hello World!".
hello_world.asm |
hello_world.c |
bits 32 extern _printf global _hello_world segment data public data use32 mesaj db 'Hello world!', 0 segment code public code use32 _hello_world: push ebp mov ebp,esp push dword mesaj call _printf add esp, 4*1 pop ebp ret |
#include <stdio.h> void hello_world(); int main() { hello_world(); printf("Programul care nu face nimic e gata!"); return 0; } |
---|
Observați utilizarea cuvântului cheie extern. Acesta transmite compilatorului că funcția / variabila este definită într-un alt fișier (și nu în fișierul curent). Linker-ul are datoria creării unei conexiuni între această declarație a funcției / variabilei și definiția acesteia.
Exemplul 2
Se definește în cadrul unui program asm o procedură numită return_10 care nu primește nici un parametru și returnează un întreg.
return_10.asm |
return_10.c |
---|---|
bits 32 global _return_10 segment data public data use32 segment code public code use32 _return_10: mov eax, 10 ret |
#include <stdio.h> int return_10(); int main() { printf("Programul returneaza %d!",return_10()); return 0; } |
Exemplul 3
Se definește în cadrul unui program asm o procedură numită sum care primește doi parametri întregi și returnează suma lor (un întreg).
sum.asm |
sum.c |
---|---|
bits 32 global _sum segment data public data use32 segment code public code use32 _sum: push ebp mov ebp, esp mov eax, [ebp+8] add eax, [ebp+12] mov esp, ebp pop ebp ret |
#include <stdio.h> int sum(int, int); int main() { printf("%d\n", sum(2, 3)); return 0; } |
Exemplul 4
Se definește în cadrul unui program asm o procedură numită factorial care primește un parametru întreg pozitiv și returnează factorialul lui (un întreg pozitiv).
factorial.asm |
factorial.c |
---|---|
bits 32 global _factorial segment data public data use32 segment code public code use32 _factorial: push ebp mov ebp,esp sub esp, 4 mov eax, [ebp+8] cmp eax,2 jbe .trivial .recursiv: dec eax push eax call _factorial add esp, 4 mov [ebp-4], eax ; m = (n-1)! mov eax, [ebp+8] ; n mul dword [ebp-4] ; edx:eax ← n * m jmp .final .trivial: xor edx, edx .final: add esp, 4 mov esp, ebp pop ebp ret |
#include <stdio.h> int factorial(int); int main() { int n, f; printf("n = "); scanf("%d", &n); f = factorial(n); printf("factorial(%d) = %d\n", n, f); return 0; } |
Programare multi-modul (asm+C) în Visual Studio
Următorul tutorial se bazează pe Visual Studio 2015, se presupune că aveți instalat pe calculator o versiune de Visual Studio. Pentru mai multe detalii accesati in TEAMS, in canalul General, sectiunea Files, fisierul procedura_instalare.doc.
Exemplul urmator arata cum putem compila, rula si depana programul din sectiunea Exemple.
Se utilizează linia de comandă pentru a compila/asambla modulele
Pașii urmați pentru a compila programul main.c sunt:
- se deschide linia de comandă Visual Studio, pentru aceasta se navighează în meniul Windows Start la Visual Studio și se alege opțiunea VS2015 x86 Native Tools Command Prompt, ca în figura de mai jos.
- În fereastra terminal se navighează către directorul unde se găsesc sursele programului. În exemplul din figura de mai jos sursele se găsesc în directorul tmp, comanda dir listează conținutul directorului curent. Pe lângă fișierele sursă ale programului, în directorul tmp se găsește și programul executabil nasm.exe folosit pentru asamblarea lui modulAsm.asm.
- În primul pas se asamblează modulAsm.asm folosind comanda:
nasm modulAsm.asm -fwin32 -o modulAsm.obj(a se vedea figura de mai jos). Rezultatul este fisierul modulAsm.obj.
- Folosind compilatorul Visual Studio (cl.exe) se compileaza main.c, se dorește editarea legăturilor în acest pas -> se specifică ca și parametru /linker fișierul modulAsm.obj. Rezultatul este programul main.exe.
- programul poate fi executat apelând din linia de comandă main.exe:
Se poate depana programul folosind Ollydbg, pentru aceasta la asamblare/compilare trebuie specificat acest lucru, comenzile sunt:
> nasm modulAsm.asm -fwin32 -g -o modulAsm.objDin Ollydbg, File -> Open și se deschide main.exe.
> cl /Z7 main.c /link modulAsm.obj
În Visual C dacă se dorește includerea de informații de depanare, opțiunile sunt /Z{7|i|I} (a se vedea https://msdn.microsoft.com/en-us/library/958x11bc.aspx).