Differences

This shows you the differences between two versions of the page.

Link to this comparison view

iocla:laboratoare:laborator-07 [2021/11/11 21:51]
andrei.albisoru [Apelatul în cadrul apelului unei funcții]
— (current)
Line 1: Line 1:
-====== Laborator 07: Apeluri de funcții ====== 
  
-În acest laborator vom prezenta modul în care se realizează apeluri de funcții. Vom vedea cum putem folosi instrucțiunile ''​call''​ și ''​ret''​ pentru a realiza apeluri de funcții și cum folosim stiva pentru a transmite parametrii unei funcții. 
- 
-Laboratorul este de forma //learn by doing//, partea practică alternând între secțiuni de tip tutorial, cu parcurgere pas cu pas și prezentarea soluției, și exerciții care trebuie să fie rezolvate. 
- 
- 
- 
-===== Cunoștințe și abilități ce vor fi dobândite ===== 
- 
-  * Traducerea apelului și implementării unei funcții din limbajul C în limbaj de asamblare 
-  * Transmiterea parametrilor in diferite arhitecturi. 
-  * Folosirea instrucțiunilor ''​call''​ și ''​ret''​ pentru a realiza un apel de funcție 
-  * Implementarea unei funcții în limbaj de asamblare 
-  * Folosirea stivei pentru a transmite parametrii unei funcții 
-  * Apelarea unei funcții externe (aflată în biblioteca standard C) din limbaj de asamblare 
- 
-===== Transmiterea Parametrilor ===== 
-Cand vine vorba de a chema o functie cu parametri exista doua mari optiuni de plasare a acestora: 
- 
-1. **Plasarea in registre** - aceasta metoda, in mod intuitiv, presupune transmiterea parametrilor cu ajutorul registrelor. ​ 
-              
-**Avantaje** 
-  * Este foarte usor de folosit atunci cand numarul parametrilor este mic. 
-  * Este foarte rapida, intrucat parametrii sunt imediat accesibili din registre. 
-        ​ 
-**Dezavantaje** 
-  * Din cauza faptului ca exista un numar limitat de registre, numarul de parametri ai unei functii ajunge sa fie limitat. 
-  * O alta problema o reprezinta faptul ca e foarte probabil ca unele registre sa fie folosite in interiorul functiei apelate. Prin urmare, e necesara salvarea temporara a registrelor inainte de a face apelul de functie. Ei bine, cel de-al doilea avantaj enumerat dispare, deoarece accesul la stiva presupune lucru cu memoria, adica latenta crescuta. 
- 
-2.**Plasarea pe stiva** - aceasta metoda presupune push-uirea pe stiva a tuturor parametrilor. ​ 
- 
-**Avantaje** 
-  * Poate fi transmis un numar mare de parametri. 
-**Dezavantaje** 
-  * Este lenta intrucat se face acces la memorie. 
-  * Mai complicata din punct de vedere al accesului la parametri. ​ 
- 
-<note tip>​Pentru arhitecturiile **32-bit** se foloseste metoda plasarii pe stiva, iar pentru cele **64-bit** se foloseste metoda plasarii in registre. Noi vom folosi conventia de la 32-bit architecture.</​note>​ 
- 
-    ​ 
-        ​ 
-===== Apelul unei funcții ===== 
- 
-Atunci când apelăm o funcție, pașii sunt următorii: 
-  * Punem argumentele pe stivă, apelul de tip push fiind în ordinea inversă în care sunt trimiși ca argumente funcției. 
-  * Apelăm call. 
-  * Restaurăm stiva la sfârșitul apelului. 
- 
-=== Funcționarea stivei === 
- 
-După cum știm, operațiile pe stivă sunt de două tipuri: 
-  * ''​push val''​ în care valoarea ''​val''​ este plasată pe stivă 
-  * ''​pop reg/​mem''​ în care ce se găsește în vârful stivei se plasează în registru sau într-o zonă de memorie 
- 
-În momentul în care se face ''​push''​ spunem că stiva **crește** (se adaugă elemente). În mod oarecum paradoxal însă, pointerul de stivă (indicat de registrul ''​esp''​ pe 32 de biți) scade. Acest lucru se întâmplă întrucât stiva crește în jos, de la adrese mari către adrese mici. 
- 
-La fel, în momentul în care facem ''​pop''​ spunem că stiva **scade** (se scot elemente). Acum pointer-ul de stivă (indicat de registrul ''​esp''​ pe 32 de biți) crește. 
- 
-Un sumar al acestui lucru este explicat foarte bine la acest link: https://​en.wikibooks.org/​wiki/​X86_Disassembly/​The_Stack 
- 
-Spre exemplu, daca avem functia **foo** cu urmatoarea semnatura (in limbaj C): 
- 
-<​code>​ 
-int foo(int a, int b, int c); 
-</​code>​ 
- 
-Apelul acestei functii va arata astfel: 
- 
-<code asm> 
- 
-mov ecx, [c]     ; luam valoarea parametrului c dintr-o zona de memorie 
-mov ebx, [b] 
-mov eax, [a] 
- 
-push ecx         ; punem parametrii in ordine inversa, incepand cu c 
-push ebx         ; apoi b 
-push eax         ; apoi a 
-call foo         ; apelam functia 
-add esp, 12 
- 
-</​code>​ 
-====== Laborator 07: Date Structurate. Structuri, vectori. Operatii pe siruri ====== 
- 
-În acest laborator vom introduce noțiunea de structură din limbajul assembly și vom lucra cu operații specializate pe șiruri. 
- 
-===== Structuri ===== 
- 
-Structurile sunt folosite pentru a grupa date care au tipuri diferite, dar care pot fi folosite împreună pentru a crea un tip compus. 
- 
-În continuare vom trece prin pașii necesari pentru a folosi o structură: declararea, instanțierea și accesarea câmpurilor unei structuri. 
- 
-==== Declararea unei structuri ==== 
- 
-În NASM, o structură se declară folosind construcția ''​%%struc <nume structura>​%%'',​ urmată de o listă de câmpuri și încheiată cu ''​%%endstruc%%''​. 
- 
-Fiecare câmp al structurii este definit prin următoarele:​ o etichetă (folosită pentru a putea accesa membrii), specificatorul de tip și numărul de elemente. 
- 
-Exemplu: 
- 
-<​code>​ 
-struc mystruct 
-    a:    resw 1    ; a va referi un singur element de dimensiune un cuvânt 
-    b:    resd 1    ; b va referi un singur element de dimensiune un dublu cuvânt 
-    c:    resb 1    ; c va referi un singur element de dimensiune un octet 
-    d:    resd 1    ; d va referi un singur element de dimensiune un dublu cuvânt 
-    e:    resb 6    ; e va referi 6 elemente de dimensiune un octet 
-endstruc 
- 
-</​code>​ 
-<​note>​ Aici sunt folosite pseudo-instrucțiunile NASM din familia ''​%%res%%''​ pentru a defini tipul de date și numărul de elemente pentru fiecare dintre câmpurile structurii. Pentru mai multe detalii despre sintaxa ''​%%res%%''​ urmați acest link: [[https://​www.nasm.us/​doc/​nasmdoc3.html#​section-3.2.2|https://​www.nasm.us/​doc/​nasmdoc3.html#​section-3.2.2]] </​note>​ 
- 
-Fiecare etichetă ce definește un câmp reprezintă offset-ul câmpului în cadrul structurii. De exemplu, ''​%%b%%''​ va avea valoarea 2, deoarece sunt 2 octeți de la începutul structurii până la câmpul ''​%%b%%''​ (primii 2 octeți sunt ocupați de cuvântul ''​%%a%%''​). 
- 
-<note warning> 
-Dacă doriți să folosiți același nume de câmp în două structuri diferite, trebuie să prefixați numele etichetei cu ''​%%.%%''​ (dot) astfel: 
- 
-<​code>​ 
-struc mystruct1 
-   ​.a: ​   resw 1 
-   ​.b: ​   resd 1 
-endstruc 
- 
-struc mystruct2 
-   ​.a: ​   resd 16 
-   ​.b: ​   resw 1 
-endstruc 
- 
-</​code>​ 
-Folosiți contrucția ''​%%mystruct2.b%%''​ pentru aflarea valorii offset-ului lui '​b'​ din cadrul structurii mystruct2. 
-</​note>​ 
-==== Instanțierea unei structuri ==== 
- 
-O primă variantă pentru a avea o structură în memorie este de a declara-o static în secțiunea ''​%%.data%%''​. Sintaxa folosește macro-urile NASM ''​%%istruc%%''​ și ''​%%iend%%''​ și keyword-ul ''​%%at%%''​. 
- 
-În exemplul următor este prezentată instanțierea statică a structurii declarate mai sus, unde ''​%%struct_var%%''​ este adresa din memorie de unde încep datele. 
- 
-<​code>​ 
-struct_var: 
-    istruc mystruct 
-        at a, dw        -1 
-        at b, dd        0x12345678 
-        at c, db        ' ' 
-        at d, dd        23 
-        at e, db        '​Gary',​ 0 
-    iend 
- 
-</​code>​ 
-În cazul în care definiți câmpurile structurii folosind . (dot), instanțierea structurii se face în felul următor: 
- 
-<​code>​ 
-struct_var: 
-    istruc mystruct 
-        at mystruct.a, dw        -1 
-        at mystruct.b, dd        0x12345678 
-        at mystruct.c, db        ' ' 
-        at mystruct.d, dd        23 
-        at mystruct.e, db        '​Gary',​ 0 
-    iend 
- 
-</​code>​ 
-<note warning> Pentru a nu inițializa valorile membrilor greșit, va trebui să aveți grijă ca pentru fiecare câmp, tipul de date din instanțiere să corespundă tipului din declarare. </​note>​ 
- 
-==== Accesarea valorilor dintr-o structură ==== 
- 
-Pentru a accesa și/sau modifica un anumit membru al structurii instanțiate trebuie să îi cunoaștem adresa. Această adresă se poate obține calculând suma dintre adresa de început a structurii și offset-ul din cadrul structurii al membrului dorit. 
- 
-Următoarea secvență de cod prezintă punerea unei valori în câmpul b al structurii și, ulterior, afișarea valorii acestui câmp. 
- 
-<​code>​ 
-mov eax, 12345 
-mov dword [struct + b], eax ; adresa câmpului b este adresa de bază a structurii instanțiate static + offset-ul câmpului (dat de eticheta '​b'​) 
- 
-mov ebx, dword [struct + b] ; punerea valorii din câmpul b în registrul ebx pentru afișare 
-PRINTF32 `%d\n\x0`, ebx 
- 
-</​code>​ 
-===== Vectori ===== 
- 
-Putem considera un vector ca o înșiruire de elemente de același tip, plasate contiguu în memorie. Ați observat ceva similar în laboratoarele trecute când declaram static șiruri de caractere în secțiunea .data. 
- 
-==== Declararea unui vector ==== 
- 
-În general, datele statice declarate pot fi inițializate sau neinițializate. Diferențierea se face atât prin faptul că la datele inițializate oferim o valoare inițială, dar și prin sintaxa NASM folosită. 
-De exemplu, pentru a declara un vector de 100 de cuvinte inițializate cu valoarea 42, vom folosi construcția:​ 
- 
-<​code>​ 
-section .data 
-    myVect: ​   times 100    dw 42 
- 
-</​code>​ 
-Pe de altă parte, dacă dorim declararea unui vector de 20 de elemente dublu cuvinte neinițializate,​ folosim instrucțiuni din familia ''​%%res%%''​ astfel: 
- 
-<​code>​ 
-section .bss 
-    myVect: ​   resd 20 
- 
-</​code>​ 
-===== Vectori de structuri ===== 
- 
-Adesea vom avea nevoie de vectori care să conțină elemente de dimensiuni mai mari decât cea a unui cuvânt dublu. Pentru a obține acest lucru vom combina cele două concepte prezentate anterior și vom folosi vectori de structuri. Bineînțeles,​ instrucțiunile de operare pe șiruri nu vor funcționa, deci vom fi nevoiți să ne întoarcem la metoda clasică de accesare a elementelor:​ cea prin adresarea explicită a memoriei. 
- 
-Pentru exemplul din această secțiune, creăm o structură ce reprezintă un punct într-un spațiu 2D. 
- 
-<​code>​ 
-struc point 
-    .x:    resd 1 
-    .y:    resd 1 
-endstruc 
- 
-</​code>​ 
-==== Declararea unui vector de structuri ==== 
- 
-Deoarece NASM nu suportă niciun mecanism pentru a declara explicit un vector de structuri, va trebui să declarăm efectiv o zonă de date în care să încapă vectorul nostru. 
- 
-Considerând că ne dorim un vector zeroizat de 100 de elemente de tipul structurii point (care este de dimensiune 8 octeți), trebuie să alocăm 100 * 8 (= 800) octeți. 
- 
-Obținem: 
- 
-<​code>​ 
-section .data 
-    pointArray: ​   times 800    db 0 
- 
-</​code>​ 
-În plus, NASM oferă o alternativă la calculul “de mână” al dimensiunii unei structuri, generând automat macro-ul ''​%%<​nume structura>​_size%%''​. Astfel, exemplul anterior poate deveni: 
- 
-<​code>​ 
-section .data 
-    pointArray: ​   times point_size * 100    db 0 
- 
-</​code>​ 
-Dacă ne dorim să declarăm un vector de structuri neinițializat putem folosi: 
- 
-<​code>​ 
-section .bss 
-    pointArray: ​   resb point_size * 100 
- 
-</​code>​ 
-==== Parcurgerea unui vector de structuri ==== 
- 
-Cum am mai spus, pentru accesarea câmpului unui element dintr-un vector trebuie să folosim adresarea normală (în particular adresarea “based-indexed with scale”). Formula pentru aflarea adresei elementului este ''​%%baza_vector + i * dimensiune_struct%%''​. 
- 
-Presupunând că avem în registrul ''​%%ebx%%''​ adresa de început a vectorului și în ''​%%eax%%''​ indicele elementului pe care dorim să îl accesăm, exemplul următor prezintă afișarea valorii câmpului y a acestui element. 
- 
-<​code>​ 
-mov ebx, pointArray ​                        ; mutăm în ebx adresa de început a șirului 
-mov eax, 13                                 ; să zicem că vrem al 13-lea element 
-mov edx, [ebx + point_size * eax + point.y] ; se calculează adresa câmpului dorit între [] 
-                                            ; și apoi se transferă valoarea de la acea adresă 
-                                            ; în registrul edx 
- 
-PRINTF32 `%u\n\x0`, edx 
- 
-</​code>​ 
-Parcurgem vectorul, având la fiecare iterație indicele curent în registrul eax. Putem să afișăm valorile din ambele câmpuri ale fiecărui element din vector cu următorul program: 
- 
-<​code>​ 
-struc   point 
-    .x: resd 1 
-    .y: resd 1 
-endstruc 
- 
-section .data 
-    pointArray: times point_size * 100 db 0 
- 
-section .text 
-    global CMAIN 
- 
-CMAIN: 
-    push ebp 
-    mov ebp, esp 
- 
-    xor edx, edx 
-    xor eax, eax 
-label: 
-    mov edx, [pointArray + point_size * eax + point.x] ; accesăm membrul x 
-    PRINTF32 `%u\n\x0`, edx 
- 
-    mov edx, [pointArray + point_size * eax + point.y] ; accesăm membrul y 
-    PRINTF32 `%u\n\x0`, edx 
- 
-    inc eax ; incrementarea indicelui de iterare 
-    cmp eax, 100 
-    jl label 
- 
-    leave 
-    ret 
- 
-</​code>​ 
-===== Exerciții ===== 
- 
-<note important>​ În cadrul laboratoarelor vom folosi repository-ul de git al materiei IOCLA - [[https://​github.com/​systems-cs-pub-ro/​iocla|https://​github.com/​systems-cs-pub-ro/​iocla]]. Repository-ul este clonat pe desktop-ul mașinii virtuale. Pentru a îl actualiza, folosiți comanda ''​%%git pull origin master%%''​ din interiorul directorului în care se află repository-ul (''​%%~/​Desktop/​iocla%%''​). Recomandarea este să îl actualizați cât mai frecvent, înainte să începeți lucrul, pentru a vă asigura că aveți versiunea cea mai recentă. Dacă doriți să descărcați repository-ul în altă locație, folosiți comanda ''​%%git clone https://​github.com/​systems-cs-pub-ro/​iocla ${target}%%''​. 
-Pentru mai multe informații despre folosirea utilitarului ''​%%git%%'',​ urmați ghidul de la [[https://​gitimmersion.com/​|Git Immersion]]. </​note>​ 
- 
-==== 0. Recapitulare:​ Fibonacci sum ==== 
- 
-Pornind de la fișierul ''​%%fibo_sum.asm%%'',​ implementați un program care calculează suma primelor N numere din șirul fibonacci utilizând instrucțiunea ''​%%loop%%''​. Suma primelor 9 este 54. 
- 
-<​note>​ Puteți să investigați secțiunea [[https://​ocw.cs.pub.ro/​courses/​iocla/​laboratoare/​laborator-04#​instructiuni_de_transfer_de_date|Instrucțiuni de transfer de date]] din laboratorul 4. </​note>​ 
- 
-==== 1. Tutorial: Afișarea conținutului unei structuri ==== 
- 
-În programul ''​%%print_structure.asm%%''​ sunt afișate câmpurile unei structuri. 
- 
-Urmăriți codul, observați construcțiile și modurile de adresare a memoriei. Rulați codul. 
- 
-<note important>​ O explicație utilă pentru instrucțiunea lea o găsiți: [[https://​www.aldeid.com/​wiki/​X86-assembly/​Instructions/​lea|aici]] </​note>​ 
- 
-<note important>​ Treceți la următorul pas doar după ce ați înțeles foarte bine ce face codul. Vă va fi greu să faceți următorul exercițiu dacă aveți dificultăți în înțelegerea exercițiului curent. </​note>​ 
- 
-==== 2. Modificarea unei structuri ==== 
- 
-Scrieți cod în cadrul funcției ''​%%main%%''​ astfel încât să modificați câmpurile structurii sample_student pentru ca: 
- 
-  * anul nașterii să fie ''​%%1993%%''​ 
-  * vârsta să fie ''​%%22%%''​ 
-  * grupa să fie ''​%%323CA%%''​ 
- 
-<note warning> Nu modificați ce se afișează, modificați codul structurii. Nu vă atingeți de codul de afișare, acel cod trebuie să rămână același. Trebuie să adăugați la începutul funcției main, în locul marcat cu ''​%%TODO%%''​ codul pentru modificarea structurii. </​note>​ 
- 
-<note warning> Trebuie să modificați conținutul structurii din cod, adică trebuie să scrieți în zona de memorie aferentă câmpului din structură. Nu modificați structura din secțiunea ''​%%.data%%'',​ este vorba să folosiți cod pentru a modifca structura. </​note>​ 
- 
-<note tip> Pentru modificarea grupei, va trebui să schimbați al treilea octet/​caracter al câmpului ''​%%group%%''​ (adică octetul/​caracterul cu indexul 2). </​note>​ 
- 
-==== 3. Getter ==== 
- 
-În fișierul ''​%%getter_setter_printf.asm%%''​ implementați funcțiile ''​%%get_int%%'',​ ''​%%get_char%%'',​ respectiv ''​%%get_string%%'',​ ce vor returna valorile câmpurilor ''​%%int_x%%'',​ ''​%%char_y%%'',​ respectiv ''​%%string_s%%''​ din structura ''​%%my_struc%%''​. Valorile vor fi returnate prin registrul ''​%%eax%%''​. 
- 
-<note tip> Funcțiile primesc ca argument un pointer la începutul structurii. Parametrul se află la adresa ''​%%esp + 8%%''​ și pentru a fi folosit ca pointer, trebuie citită valoarea sa într-un registru (ex. registrul ''​%%ebx%%''​). </​note>​ 
- 
-Output-ul programului după o rezolvare corectă este: 
- 
-<​code>​ 
-1000 
-a 
-My string is better than yours 
- 
-</​code>​ 
-Urmăriți comentariile marcate cu **TODO**. 
- 
-==== 4. Setter ==== 
- 
-Mai departe, implementați funcțiile ''​%%set_int%%'',​ ''​%%set_char%%'',​ respectiv ''​%%set_string%%'',​ ce vor suprascrie valorile câmpurilor ''​%%int_x%%'',​ ''​%%char_y%%'',​ respectiv ''​%%string_s%%''​ din ''​%%structura my_struc%%''​ cu noile valori date. 
- 
-<note tip> Funcțiile primesc doi parametri - un pointer la începutul structurii, ca la exercițiul anterior, și o valoare care trebuie să fie folosită ca sursă pentru atribuire. Cei doi parametri sunt în ordine la adresele ''​%%esp + 8%%''​ (primul parametru) și ''​%%esp + 12%%''​ (al doilea parametru). </​note>​ 
- 
-Output-ul programului după o rezolvare corectă este: 
- 
-<​code>​ 
-2000 
-b 
-Are you sure? 
- 
-</​code>​ 
-Urmăriți comentariile marcate cu ''​%%TODO%%''​. 
- 
-==== 5. Printf ==== 
- 
-În funcția ''​%%main%%'',​ afișați câmpurile structurii utilizând apeluri ale funcției ''​%%printf%%''​. Verificați că programul afișază valorile corespunzătoare cu, respectiv fără, folosirea funcțiilor ''​%%set_*%%''​. Puteți folosi formaturile de la liniile 10-12 pentru a printa câmpurile. 
- 
-==== 6. Bonus: Căutarea unui subșir într-un șir ==== 
- 
-Găsiți toate aparițiile subșirului ''​%%substring%%''​ în șirul ''​%%source_text%%''​ din fișierul ''​%%find_substring.asm%%''​. 
- 
-Afișați rezultatele sub forma: 
- 
-<​code>​ 
-Substring found at index: <N> 
- 
-</​code>​ 
-<note important>​ Nu puteți folosi funcția de bibliotecă ''​%%strstr%%''​ (sau similar) pentru acest subpunct. </​note>​ 
- 
-<note tip> 
-Pentru afișare puteți folosi atât macro-ul ''​%%PRINTF32%%'',​ cât și funcția ''​%%printf%%'',​ ca la exercițiile anterioare. Pașii pentru afișare folosind ''​%%printf%%''​ sunt următorii: 
- 
-  * puneți pe stivă valoarea pe care vreți să o afișați (poziția unde a fost găsit subșirul) 
-  * puneți pe stivă adresa șirului ''​%%print_format%%''​ 
-  * apelați funcția ''​%%printf%%''​ 
-  * curățați parametrii adăugați anterior de pe stivă prin adăugarea valorii 8 la registrul ''​%%esp%%''​ (fiecare dintre parametri are 4 octeți). 
-</​note>​ 
- 
- 
-===== Soluții ===== 
- 
-Soluțiile pentru exerciții sunt disponibile:​ [[https://​elf.cs.pub.ro/​asm/​res/​laboratoare/​lab-07-sol.zip|aici]] 
-===== Exerciții ===== 
- 
-<note important>​ 
-În cadrul laboratoarelor vom folosi repository-ul de git al materiei IOCLA - https://​github.com/​systems-cs-pub-ro/​iocla. Repository-ul este clonat pe desktop-ul mașinii virtuale. Pentru a îl actualiza, folosiți comanda ''​git pull origin master''​ din interiorul directorului în care se află repository-ul (''​~/​Desktop/​iocla''​). Recomandarea este să îl actualizați cât mai frecvent, înainte să începeți lucrul, pentru a vă asigura că aveți versiunea cea mai recentă.Dacă doriți să descărcați repository-ul în altă locație, folosiți comanda ''​git clone https://​github.com/​systems-cs-pub-ro/​iocla ${target}''​.Pentru mai multe informații despre folosirea utilitarului ''​git'',​ urmați ghidul de la [[https://​gitimmersion.com/​ | Git Immersion]]. 
-</​note>​ 
-==== 0. Recapitulare:​ Șirul lui Fibonacci ==== 
- 
-Completați fișierul ''​fibo.asm''​ din arhivă pentru a realiza un program care afișează primele N numere din șirul lui Fibonacci. 
- 
-Aveți voie să folosiți doar memorie alocată pe stivă. 
- 
-==== 1. Hello, world! ==== 
- 
-Deschideți fișierul ''​hello-world.asm'',​ asamblați-l și rulați-l. Observați afișarea mesajului //Hello, world!// 
- 
-Remarcați că: 
-  * Programul ''​hello-world.asm''​ folosește apelul funcției ''​puts''​ (funcție externă modulului curent) pentru a efectua afișarea. Pentru aceasta pune argumentul pe stivă și apelează funcția. 
-  * Variabila ''​msg''​ din programul ''​hello-world.asm''​ conține octetul ''​10''​. Acesta simbolizează caracterul //​line-feed//,​ mai cunoscut și sub forma ''​\n'',​ folosit pentru a adăuga o linie nouă pe Linux. 
- 
-Încheierea cu ''​\n''​ este, în general, utilă pentru afișarea șirurilor. Funcția ''​puts''​ pune automat o linie nouă după șirul afișat, însă aceasta trebuie adăugată explicit în cazul folosirii funcției ''​printf''​. 
- 
-==== 2. Dezasamblarea unui program scris în C ==== 
- 
-După cum spuneam, în final, totul ajunge în limbaj de asamblare (ca să fim 100% corecți, totul ajunge cod mașină care are o corespondență destul de bună cu codul asamblare). Adesea ajungem să avem acces doar la codul obiect al unor programe și vrem să inspectăm modul în care arată. 
- 
-Pentru a observa acest lucru, haideți să compilăm până la codul obiect un program scris în C și apoi să-l dezasamblăm. Este vorba de programul ''​test.c''​ din arhiva de laborator. 
- 
-<note tip> 
-Pentru a compila un fișier cod sursă C/C++ în linia de comandă, urmați pașii: 
-  - Deschideți un terminal. (shortcut ''​Ctrl+Alt+T''​) 
-  - Accesați directorul în care aveți codul sursă. 
-  - Folosiți comanda<​code>​ 
-gcc -m32 -o <​executabil>​ <​nume-fisier>​ 
-</​code>​ unde ''<​nume-fisier>''​ este numele fișierului iar ''<​executabil>''​ este executabilul rezultat. 
- 
-  - Dacă doriți **doar** să compilați fișierul (**fără** să-l link-ați), atunci folosiți comanda<​code>​ 
-gcc -m32 -c -o <​fisier-obiect>​ <​nume-fisier>​ 
-</​code>​ unde ''<​nume-fisier>''​ este numele fișierului iar ''<​fisier-obiect>''​ este fișierul obiect rezultat. 
-</​note>​ 
- 
-În cazul nostru, întrucât dorim doar să compilăm fișierul ''​test.c''​ la modulul obiect, vom accesa din terminal directorul în care se găsește fișierul și apoi vom rula comanda<​code>​ 
-gcc -m32 -c -o test.o test.c 
-</​code>​ 
-În urma rulării comenzii de mai sus în directorul curent vom avea fișierul obiect ''​test.o''​. 
- 
-Putem obține și forma în limbaj de asamblare a acestuia folosind comanda<​code>​ 
-gcc -m32 -masm=intel -S -o test.asm test.c 
-</​code>​ 
-În urma rulării comenzii de mai sus obținem fișierul ''​test.asm''​ pe care îl putem vizualiza folosind comanda<​code>​ 
-cat test.asm 
-</​code>​ 
- 
-Pentru a dezasambla codul unui modul obiect vom folosi un utilitar frecvent întâlnit în lumea Unix: ''​objdump''​. Pentru dezasamblare,​ vom rula comanda<​code>​ 
-objdump -M intel -d <​path-to-obj-file>​ 
-</​code>​ 
-unde ''<​path-to-obj-file>''​ este calea către fișierul obiect ''​test.o''​. 
- 
-Veți obține un output similar celui de mai jos 
- 
-<​code>​ 
- 
-2-test] $ objdump -M intel -d test.o ​                                                                                                                                     ​ 
- 
-test.o: ​    file format elf32-i386 
- 
-Disassembly of section .text: 
- 
-0000054d <​second_func>:​ 
- ​54d: ​  ​55 ​                     push   ebp 
- ​54e: ​  89 e5                   ​mov ​   ebp,esp 
- ​550: ​  e8 a6 00 00 00          call   5fb <​__x86.get_pc_thunk.ax>​ 
- ​555: ​  05 ab 1a 00 00          add    eax,0x1aab 
- ​55a: ​  8b 45 08                mov    eax,DWORD PTR [ebp+0x8] 
- ​55d: ​  8b 10                   ​mov ​   edx,DWORD PTR [eax] 
- ​55f: ​  8b 45 0c                mov    eax,DWORD PTR [ebp+0xc] 
- ​562: ​  01 c2                   ​add ​   edx,eax 
- ​564: ​  8b 45 08                mov    eax,DWORD PTR [ebp+0x8] 
- ​567: ​  89 10                   ​mov ​   DWORD PTR [eax],edx 
- ​569: ​  ​90 ​                     nop 
- ​56a: ​  ​5d ​                     pop    ebp 
- ​56b: ​  ​c3 ​                     ret    ​ 
- 
-0000056c <​first_func>:​ 
- ​56c: ​  ​55 ​                     push   ebp 
- ​56d: ​  89 e5                   ​mov ​   ebp,esp 
- ​56f: ​  ​53 ​                     push   ebx 
- ​570: ​  83 ec 14                sub    esp,0x14 
- ​573: ​  e8 83 00 00 00          call   5fb <​__x86.get_pc_thunk.ax>​ 
- ​578: ​  05 88 1a 00 00          add    eax,0x1a88 
- ​57d: ​  c7 45 f4 03 00 00 00    mov    DWORD PTR [ebp-0xc],​0x3 
- ​584: ​  83 ec 0c                sub    esp,0xc 
- ​587: ​  8d 90 80 e6 ff ff       ​lea ​   edx,​[eax-0x1980] 
- ​58d: ​  ​52 ​                     push   edx 
- ​58e: ​  89 c3                   ​mov ​   ebx,eax 
- ​590: ​  e8 4b fe ff ff          call   3e0 <​puts@plt>​ 
- ​595: ​  83 c4 10                add    esp,0x10 
- ​598: ​  83 ec 08                sub    esp,0x8 
- ​59b: ​  ff 75 f4                push   DWORD PTR [ebp-0xc] 
- ​59e: ​  8d 45 08                lea    eax,​[ebp+0x8] 
- ​5a1: ​  ​50 ​                     push   eax 
- ​5a2: ​  e8 a6 ff ff ff          call   54d <​second_func>​ 
- ​5a7: ​  83 c4 10                add    esp,0x10 
- ​5aa: ​  8b 45 08                mov    eax,DWORD PTR [ebp+0x8] 
- ​5ad: ​  8b 5d fc                mov    ebx,DWORD PTR [ebp-0x4] 
- ​5b0: ​  ​c9 ​                     leave  ​ 
- ​5b1: ​  ​c3 ​                     ret    
- 
-000005b2 <​main>:​ 
- ​5b2: ​  8d 4c 24 04             ​lea ​   ecx,​[esp+0x4] 
- ​5b6: ​  83 e4 f0                and    esp,​0xfffffff0 
- ​5b9: ​  ff 71 fc                push   DWORD PTR [ecx-0x4] 
- ​5bc: ​  ​55 ​                     push   ebp 
- ​5bd: ​  89 e5                   ​mov ​   ebp,esp 
- ​5bf: ​  ​53 ​                     push   ebx 
- ​5c0: ​  ​51 ​                     push   ecx 
- ​5c1: ​  e8 8a fe ff ff          call   450 <​__x86.get_pc_thunk.bx>​ 
- ​5c6: ​  81 c3 3a 1a 00 00       ​add ​   ebx,0x1a3a 
- ​5cc: ​  83 ec 0c                sub    esp,0xc 
- ​5cf: ​  6a 0f                   ​push ​  0xf 
- ​5d1: ​  e8 96 ff ff ff          call   56c <​first_func>​ 
- ​5d6: ​  83 c4 10                add    esp,0x10 
- ​5d9: ​  83 ec 08                sub    esp,0x8 
- ​5dc: ​  ​50 ​                     push   eax 
- ​5dd: ​  8d 83 8e e6 ff ff       ​lea ​   eax,​[ebx-0x1972] 
- ​5e3: ​  ​50 ​                     push   eax 
- ​5e4: ​  e8 e7 fd ff ff          call   3d0 <​printf@plt>​ 
- ​5e9: ​  83 c4 10                add    esp,0x10 
- ​5ec: ​  b8 00 00 00 00          mov    eax,0x0 
- ​5f1: ​  8d 65 f8                lea    esp,​[ebp-0x8] 
- ​5f4: ​  ​59 ​                     pop    ecx 
- ​5f5: ​  ​5b ​                     pop    ebx 
- ​5f6: ​  ​5d ​                     pop    ebp 
- ​5f7: ​  8d 61 fc                lea    esp,​[ecx-0x4] 
- ​5fa: ​  ​c3 ​                     ret    ​ 
- 
- 
-</​code>​ 
- 
-Există multe alte utilitare care permit dezasamblare de module obiect, majoritatea cu interfața grafică și oferind și suport pentru debugging. ''​objdump''​ este un utilitar simplu care poate fi rapid folosit în linia de comandă. 
- 
-Este interesant de urmărit, atât în fișierul ''​test.asm''​ cât și în dezasamblarea sa, modul în care se face un apel de funcție, lucru despre care vom discuta în continuare. 
- 
- 
-==== 3. Afișarea unui șir ==== 
- 
-Pentru afișarea unui string putem folosi macro-ul intern ''​PRINTF32''​. Sau putem folosi o funcție precum ''​puts''​. În fișierul ''​print-string.asm''​ este implementată afișarea unui string folosind macro-ul ''​PRINTF32''​. 
- 
-Urmărind fișierul ''​hello-world.asm''​ ca exemplu, implementați afișarea șirului folosind și ''​puts''​. 
- 
-<note tip> 
-Urmăriți și indicațiile din secțiunea [[http://​ocw.cs.pub.ro/​courses/​iocla/​laboratoare/​laborator-07?&#​apelul_unei_functii|Apelul unei funcții]]. 
-</​note>​ 
- 
-==== 4. Afișarea lungimii unui șir ==== 
- 
-Programul ''​print-string-len.asm''​ afișează lungimea unui șir folosind macro-ul ''​PRINTF32''​. Calculul lungimii șirului ''​mystring''​ are loc în cadrul programului (este deja implementat). 
- 
-Implementați programul pentru a face afișarea lungimii șirului folosind funcția ''​printf''​. 
- 
-La sfârșit veți avea afișată de două ori lungimea șirului: inițial cu apelul macro-ului ''​PRINTF32''​ și apoi cu apelul funcției externe ''​printf''​. 
- 
-<note tip> 
-Gândiți-vă că apelul ''​printf''​ este de forma ''​%%printf("​String length is %u\n", len);​%%''​. Trebuie să construiți stiva pentru acest apel. 
- 
-Pașii de urmat sunt: 
-  - Marcarea simbolului ''​printf''​ ca simbol extern. 
-  - Definirea șirului de formatare ''​%%"​String length is %u", 10, 0%%''​. 
-  - Realizarea apelului funcției ''​printf'',​ adică: 
-    - Punerea celor două argumente pe stivă: șirul de formatarea și lungimea. 
-    - Apelul ''​printf''​ folosind ''​call''​. 
-    - Restaurarea stivei. 
- 
-Lungimea șirului se găsește în registrul ''​ecx''​. 
-</​note>​ 
- 
-==== 5. Afișarea șirului inversat ==== 
- 
-În soluția de mai sus adăugați funcția ''​reverse_string''​ astfel încât să aveți un listing similar celui de mai jos:<​code>​ 
-[...] 
-section .text 
-global main 
- 
-reverse_string:​ 
-    push ebp 
-    mov ebp, esp 
- 
-    mov eax, dword [ebp+8] 
-    mov ecx, dword [ebp+12] 
-    add eax, ecx 
-    dec eax 
-    mov edx, dword [ebp+16] 
- 
-copy_one_byte:​ 
-    mov bl, byte [eax] 
-    mov byte [edx], bl 
-    dec eax 
-    inc edx 
-    loopnz copy_one_byte 
- 
-    inc edx 
-    mov byte [edx], 0 
- 
-    leave 
-    ret 
- 
-main: 
-    push ebp 
-    mov ebp, esp 
-[...] 
-</​code>​ 
- 
-<note important>​ 
-Când copiați funcția ''​reverse_string''​ în programul vostru, rețineți că fucția începe la eticheta ''​reverse_string''​ și se oprește la eticheta ''​main''​. Eticheta ''​copy_one_byte''​ este parte a funcției ''​reverse_string''​. 
-</​note>​ 
- 
-Funcția ''​reverse_string''​ inversează un șir și are următoarea signatură: ''​void reverse_string(const char *src, size_t len, char *dst);''​. Astfel ca primele ''​len''​ caractere și șirul ''​src''​ sunt inversate în șirul ''​dst''​. 
- 
-Realizați inversarea șirului ''​mystring''​ într-un nou șir și afișați acel nou șir. 
- 
-<note tip> 
-Pentru a defini un nou șir, recomandăm ca, în secțiunea de date să folosiți construcția<​code>​ 
-    store_string times 64 db 0 
-</​code>​ 
-Construcția creează un șir de 64 de octeți de zero, suficient pentru a stoca inversul șirului. 
- 
-Apelul echivalent în C al funcției este ''​reverse_string(mystring,​ ecx, store_string);''​. În registrul ''​ecx''​ am presupus că este calculată lungimea șirului. 
- 
-Nu puteți folosi direct valoarea ''​ecx''​ în forma ei curentă. După apelul funcției ''​printf''​ pentru afișare numărului valoarea ''​ecx''​ se pierde. Ca să o păstrați, aveți două opțiuni: 
-  - Stocați valoarea registrului ''​ecx''​ în prealabil pe stivă (folosind ''​push ecx''​ înaintea apelului ''​printf''​) și apoi să o restaurați după apelul ''​printf''​ (folosind ''​pop ecx''​). 
-  - Stocați valoarea registrului ''​ecx''​ într-o variabilă globală, pe care o definiți în secțiunea ''​.data''​. 
- 
-Nu puteți folosi un alt registru pentru că sunt șanse foarte mari ca și acel registru să fie modificat de apelul ''​printf''​ pentru afișarea lungimii șirului. 
-</​note>​ 
- 
- 
-==== 6. Implementarea funcției toupper ==== 
- 
-Ne propunem implementarea funcției ''​toupper''​ care traduce literele mici în litere mari. Pentru aceasta, porniți de la fișierul ''​toupper.asm''​ din arhiva de exerciții a laboratorului și completați corpul funcției ''​toupper''​. 
- 
-Șirul folosit este ''​mystring''​ și presupunem că este un șir valid. Acest șir este transmis ca argument funcției ''​toupper''​ în momentul apelului. 
- 
-Faceți înlocuirea //in place//, nu este nevoie de un alt șir. 
- 
-<note tip> 
-Ca să traduceți o litera mică în literă mare, trebuie să **scădeți** ''​0x20''​ din valoare. Aceasta este diferența între litere mici și mari; de exemplu ''​a''​ este ''​0x61''​ iar ''​A''​ este ''​0x41''​. Puteți vedea în [[http://​man7.org/​linux/​man-pages/​man7/​ascii.7.html|pagina de manual ascii]]. 
- 
-Ca să citiți sau să scrieți octet cu octet folosiți construcția ''​byte [reg]''​ așa cum apare și în implementarea determinării lungimii unui șir  în fișierul ''​print-string-len.asm'',​ unde ''​[reg]''​ este registrul de tip pointer în care este stocată adresa șirului în acel punct. 
- 
-Vă opriți atunci când ați ajuns la valoarea ''​0''​ (''​NULL''​ byte). Pentru verificare puteți folosi ''​test''​ așa cum se întâmplă și în implementarea determinării lungimii unui șir în fișierul ''​print-string-len.asm''​. 
-</​note>​ 
- 
-==== Bonus: toupper doar pentru litere mici ==== 
- 
-Implementați funcția ''​toupper''​ astfel încât translatarea să aibă loc doar pentru caractare reprezentând litere mici, nu litere mari sau alte tipuri de caractere. 
-==== Bonus: rot13 ==== 
- 
-Realizați și folosiți o funcție care face translatarea [[http://​www.decode.org/​|rot13]] a unui șir. 
- 
-==== Bonus: rot13++ ==== 
- 
-Implementați ''​rot13''​ pe un array de șiruri: șirurile sunt continue în memorie separate prin terminatorul de șir (''​NULL''​-byte,​ ''​0''​). De exemplu: ''​ana\0are\0mere\0''​ este un array de trei șiruri. 
- 
-Aplicați ''​rot13''​ pe caracterele alfabetice și înlocuiți terminatorul de șir cu spațiu (''​%%'​ '​%%'',​ blank, caracterul ''​32''​ sau ''​0x20''​). Astfel, șirul inițial ''​ana\0are\0mere\0''​ se va traduce în ''​nan ner zrer''​. 
- 
-<note tip> 
-Pentru a defini array-ul de șiruri care să conțină terminatorul de șir, folosiți o construcție de forma: 
-<​code>​ 
-    mystring db "​ana",​ 0, "​are",​ 0, "​mere",​ 0 
-</​code>​ 
-</​note>​ 
- 
-<note tip> 
-Va trebui să știți când sa vă opriți din parcurgerea array-ului de șiruri. Cel mai simplu este să definiți o variabilă de lungime în secțiunea ''​.data'',​ de forma<​code>​ 
-    len dd 10 
-</​code>​ 
-în care să rețineți fie lungimea totală a șirului (de la începutul până la ultimul ''​NULL''​-byte),​ fie numărul de șiruri din array. 
-</​note>​ 
-===== Alte resurse ===== 
- 
-  * [[http://​www.nasm.us/​|nasm]] 
- 
-===== Soluții ===== 
-Soluțiile pentru exerciții sunt disponibile [[https://​elf.cs.pub.ro/​asm/​res/​laboratoare/​lab-07-sol.zip|aici]]. 
iocla/laboratoare/laborator-07.1636660287.txt.gz · Last modified: 2021/11/11 21:51 by andrei.albisoru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0