Differences

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

Link to this comparison view

iocla:laboratoare:laborator-09 [2019/11/28 20:02]
bogdan.purcareata [Tutoriale și exerciții analiză statică]
iocla:laboratoare:laborator-09 [2022/05/06 15:02] (current)
radu.nichita
Line 1: Line 1:
-====== Laborator 09: Analiza statică și dinamică a programelor. GDB ======+====== Laborator 09: Apeluri de funcții ​======
  
-În acest laborator vom studia modalitățile de analiză a programelor ​în scopul de a înțelege mai amănunțit modul lor de funcționare ​și pentru a găsi ușor cauzele unor probleme (debugging). Vom studia câteva utilitare ​de analiză, vom rezolva bug-uri, iar în final vom ști ce unelte ​și cum să le folosim pentru a dezvolta programe corecte și sigure. +Î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.
-Conținutul laboratorului alternează între secțiuni de tip tutorial, cu parcurgere pas cu pas și prezentarea soluției, și exerciții care trebuie să fie rezolvate.+
  
-===== Analiza statică =====+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.
  
-Analiza statică a unui program constă în inspectarea diferitelor aspecte din fișierul obiect sau executabil. Analiza statică +===== Cunoștințe și abilități ce vor fi dobândite =====
-unui program nu implică si rularea acestuia. Aceasta presupune analiza codului imediat dupa ce s-a terminat partea de codare și +
-înainte de rularea testelor.+
  
-Analiza statică poate fi efectuată de un program, în mod automat, prin parcurgerea codului ​și verificarea faptului că sursa +  * Traducerea apelului ​și implementării unei funcții din limbajul C în limbaj de asamblare 
-a fost scrisă în conformitate cu regulile specificeUn exemplu clasic în acest sens este compilatorul care identifică +  * Transmiterea parametrilor in diferite arhitecturi
-erorile lexicale, sintactice ​și, uneori, semantice dintr-un program. Remarcați faptul că programul nu este rulat atunci cand +  * Folosirea instrucțiunilor ''​%%call%%'' ​și ''​%%ret%%''​ pentru a realiza ​un apel de funcție 
-compilatorul inspectează sursa.+  * 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
  
-Analiza statică poate fi efectuată și manual atunci cand codul este revizuit ("code review"​) pentru a se asigura calitatea +===== Transmiterea Parametrilor =====
-si lizibilitatea codului.+
  
-Avantajele analizei statice: +Cand vine vorba de a chema functie cu parametri exista doua mari optiuni ​de plasare a acestora:
-  * identifică zona exactă unde poate aparea ​eroare +
-  * ușurează înțelegerea codului de către alți (posibili) cititori ai codului +
-  * nu este nevoie să fie rulat codul (avantaj în cazul în care rularea codului necesită resurse foarte ​mari+
-  * identifică erori ce nu pot fi găsite printr-un alt tip de analizăcod mort ("​unreachable code"​),​ variabile nefolosite, funcții neapelate etc.+
  
-Dezavantajele analizei statice:+1. **Plasarea in registre** - aceasta metoda, in mod intuitiv, presupune transmiterea parametrilor cu ajutorul registrelor.
  
-  ​consumă foarte mult timp dacă este realizată manual +**Avantaje** 
-  * nu poate detecta vulnerabilități apărute la runtime (spre exemplu "​segmentation fault"​) +  * Este foarte usor de folosit atunci cand numarul parametrilor este mic. 
-  * ai nevoie de codul sursă pentru a putea realiza acest tip de analiză+  * Este foarte rapida, intrucat parametrii sunt imediat accesibili din registre.
  
-Câteva din programele utile pentru analiza statică ​pe care le vom folosi și în cadrul tutorialelor/​exercițiilor sunt:+**Dezavantaje** 
 +  * Din cauza faptului ca exista un numar limitat de registre, numarul de parametri ai unei functii ajunge sa fie limitat. 
 +  * E foarte probabil ca unele registre sa fie folosite in interiorul functiei apelate si devine necesara salvarea temporara a registrelor ​pe stiva inaintea apeluluide functie. Astfel, cel de-al doilea avantaj enumerat dispare, deoarece accesul la stiva presupune lucru cu memoria, adica latenta crescuta.
  
-  ​* **nm** - utilitar folosit pentru inspectarea simbolurilor și secțiunilor din executabile;​ +2. **Plasarea pe stivă** - aceasta metoda presupune push-uirea pe stiva tuturor parametrilor.
-  * **objdump** - program folosit pentru dezasamblarea (traducerea din cod-mașină în limbaj de asamblare) programelor binare; +
-  * **IDA** - o unealtă foarte puternică pentru dezasamblarea și inspectarea fișierelor obiect și executabile;​ +
-  * **coverity**,​ **clang-analyzer**, **cppcheck** - utilitare pentru identificare statică ​problemelor de tip: memory-leak,​ buffer overflow, NULL pointer dereferences.+
  
-===== Analiza dinamică =====+**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.
  
-Spre deosebire de analiza staticăanaliza dinamică constă în inspectarea unui program aflat în execuțiePractic, analiza dinamică se face la //runtime//.+<​note>​ Pentru arhitecturiile **32-bit** se foloseste metoda plasarii pe stivaiar pentru cele **64-bit** se foloseste metoda plasarii in registreNoi vom folosi conventia de la 32-bit architecture. </note>
  
-Avantajele analizei dinamice:+===== Apelul unei funcții =====
  
-  * identifică erorile apărute la runtimesegmentation fault, arithmetic exception etc. +Atunci când apelăm o funcție, pașii sunt următorii:
-  * oferă posibilitatea analizării programului chiar dacă nu avem acces la codul sursă +
-  * identifică vulnerabilități care ar fi putut fi fals negative în momentul analizei statice +
-  * permite validarea rezultatelor analizei statice +
-  * poate fi realizată pentru orice aplicație+
  
-Dezavantajele analizei dinamice:+  * 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.
  
-  * este mai greu de localizat unde exact în cod are loc eroarea +==== Funcționarea stivei ====
-  * programele ce automatizează analiza dinamică produc falsuri pozitive și falsuri negative +
-  * nu poate garanta acoperirea completă a tuturor cazurilor unui fișier sursă+
  
-Unul dintre cele mai folosite programe pentru analiză dinamică este **gdb**. Acesta oferă o gamă largă de operații ce pot fi făcute, de la inspectarea memoriei, la schimbarea control flow-ului și până la modificarea registrelor de pe procesor, în timpul rulării unui program.+După cum știm, ​operațiile pe stivă sunt de două tipuri:
  
-===== Tutoriale și exerciții ===== +  * ''​%%push val%%''​ în care valoarea ''​%%val%%''​ este plasată pe stivă 
-În cadrul exercițiilor vom folosi ​ [[http://​elf.cs.pub.ro/​asm/​res/​laboratoare/​lab-09-tasks.zip|arhiva de laborator]]. Descărcați arhiva, decomprimați-o și accesați directorul aferent.+  ​* ''​%%pop reg/mem%%''​ în care ce se găsește în vârful stivei se plasează în registru sau într-o zonă de memorie
  
-Deși folosirea unui mediu grafic pentru programare poate părea mai atractivă, de multe ori folosirea liniei ​de comandă oferă mai multă putere școntrol asupra a ceea ce vrem să facemÎn plus, folosirea utilitarelor din linia de comandă ​ în scripturi poate facilita automatizarea unor task-urilucru care ne va face viața mai ușoară în nenumărate cazuri.+În momentul în care se face ''​%%push%%''​ spunem că stiva **crește** (se adaugă elemente). Din motive ce vor fi explicate ​mai bine la SOpointerul ​de stivă (indicat ​de registrul ''​%%esp%%''​ pe 32 de biți) scade in valoare atunci cand **stiva creste** (la ''​%%push%%''​)Putem spune totusi ca aceasta contrazicere in numire vine de la faptul ca stiva este deregula reprezentata pe verticala si valorile mici se afla susiar valorile mari se afla jos.
  
-În cadrul acestui laboratorvom folosi utilitare ​în linia de comandă atât pentru asamblarea și link-editarea fișierelor sursă, cât șpentru analiza statică ​și dinamică a programelor obținute din parcurgerea tutorialelor și a exercițiilor.+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 valoric.
  
 +Un sumar al acestui lucru este explicat foarte bine la acest [[https://​en.wikibooks.org/​wiki/​X86_Disassembly/​The_Stack|link]].
  
-=== [0.5p] 1. Tutorial: Asamblarea din linia de comandă === +Spre exempludaca avem functia foo cu urmatoarea semnatura ​(in limbaj C):
- +
-Deschideți fișierul ''​hello-world.asm''​ din directorul ''​1-2-tutorial''​. Pentru a asambla fișierul ''​hello-world.asm''​vom folosi utilitarul ''​nasm'' ​(program care este folosit în spate și de către SASM).+
  
 <​code>​ <​code>​
-nasm -g -f elf32 hello-world.asm -o hello-world.o+int foo(int a, int b, int c); 
 </​code>​ </​code>​
 +Apelul acestei functii va arata astfel:
  
-<note+<code
-''​-g''​ spune asamblorului să adauge simboluri ​de debug în fișierul obiect rezultat+mov ecx, [c]     ; luam valoarea parametrului c dintr-o zona de memorie 
 +mov ebx, [b] 
 +mov eax, [a]
  
-''​-f''​ menționează formatul executabilului (în cazul nostru ''​elf32''​) +push ecx         ; punem parametrii in ordine inversa, incepand cu c 
-</​note>​+push ebx         ; apoi b 
 +push eax         ; apoi a 
 +call foo         ; apelam functia 
 +add esp, 12      ; restauram stiva
  
-Pentru a verifica "​corectitudinea"​ asamblării,​ haideți să dezasamblăm fișierul ''​hello-world.o''​ folosind utilitarul ''​objdump'',​ astfel: 
-<code asm> 
-objdump -M intel -d hello-world.o 
 </​code>​ </​code>​
 +===== Apelantul si Apelatul =====
  
-<​note>​ +Atunci când apelăm o funcție spunem că funcția care apelează (contextul care apelează) se cheamă **apelant** (sau **caller**),​ iar funcția apelată se cheamă **apelat** (sau **callee**). In paragraful anterior am discutat despre cum arată lucrurile la nivelul apelantului (cum construim stiva).
-''​-M intel''​ spune dezasamblorului să folosească sintaxa Intel și nu pe cea AT&T +
-</​note>​+
  
-Putem observa similaritatea dintre codul inițial școdul dezasamblat,​ mai puțin ​la instrucțiunea ''​call'' ​asociată ​funcției ​printf, unde adresa pare greșităAcest fapt se întâmplă din cauza faptului că fișierul obiect obținut nu "​știe"​ cine este printf. Acest lucru se va afla la pasul de link-editare,​ iar adresa va fi modificată la cea corespunzătoare. Observați faptul că apelul la funcția ​''​print_message'' ​s-a realizat în mod normal deoarece label-ul acesteia se află în zona de text a fișierului obiect, și deci este cunoscută.+Haideți să urmărim ce se întâmplă si la la nivelul apelatului. Până în momentul ​instrucțiunii ''​%%call%%'' ​stiva conține parametrii ​funcției. ​Apelul ​''​%%call%%'' ​poate fi echivalat grosier următoarei secvențe:
  
-=== [0.5p] 2. Tutorial: Link-editarea unui fișier obiect ===+<​code>​ 
 +push eip 
 +jmp function_name
  
-Link-editarea unuia sau mai multor fișiere obiect constă în rezolvarea tuturor simbolurilor externe ​și crearea unui singur fișier executabil din toate fișiere primite la intrare.+</​code>​ 
 +Adică și apelul ''​%%call%%''​ folosește ​în continuare stiva și salvează adresa următoarei instrucțiuni,​ cea de după ''​%%call%%''​ numită ​și instrucțiunea de retur sau adresa de retur (return address). Aceasta este necesară pentru a ști, în apelat, unde să revenim în apelant.
  
-Pentru link-editare vom folosi ​''​gcc''​. De asemenea ​și gcc este folosit de SASM pentru link-editarea fișierelor obiect.+În apelat, la începutul său (numit preambul, preamble) se salvează frame pointer-ul (în arhitectura i386 este vorba de registrul ​''​%%ebp%%''​) urmând ca frame pointer-ul să refere adresa curentă de pe stivă (adică tocmai fostul frame pointer). Deși nu este obligatorie,​ salvarea frame pointer-ului ajută la debugging ​și este în cele mai multe cazuri folosităDin aceste motive, orice apel de funcție va avea în general, preambulul:
  
 <​code>​ <​code>​
-gcc -g -m32 hello-world.o -o hello-world+push ebp 
 +mov ebp, esp 
 </​code>​ </​code>​
 +Aceste modificări au loc în apelat. De aceea este responsabilitatea acestuia să restaureze stiva la vechea sa valoare. De aceea este uzuală existența unui epilog care să readucă stiva la starea sa inițială; acest epilog este:
  
 +<​code>​
 +leave
  
-<note+</code
-''​-g'' ​este folosit cu același scop ca la nasm, de a introduce simboluri de debug în executabil+Dupa aceasta instructiune,​ stiva este ca la începutul funcției (adică imediat după call).
  
-''​-m32'' ​specifică arhitectura pentru ​care executabilul este generat (în cazul nostru, arhitectură ​pe 32 biți) +Pentru incheierea functiei, este necesar ca executia codului sa se intoarca (return) si sa continue sa execute de la instructiunea de dupa ''​%%call%%''​-ul care a pornit functia. Acest lucru presupune sa influentam registrul ​''​%%eip%%''​ si sa punem valoarea ​care a fost salvata ​pe stiva initial de apelul ''​%%call%%''​. Acest lucru este indeplinit folosind instructiunea:​
-</​note>​+
  
-Acum puteți rula executabilul pentru a vedea că toți pașii au funcționat. Pentru lansarea în execuție din linie de comandă folosiți construcția+<​code>​ 
 +ret 
 + 
 +</​code>​ 
 +care este grosier echivalentul instrucțiunii:
  
 <​code>​ <​code>​
-./​hello-world+pop eip 
 </​code>​ </​code>​
 +Spre exemplu, definitia si corpul functiei foo, care realizeaza suma a 3 numere, vor arata astfel:
  
-Ar trebui să vi se afișeze pe ecran ''​HelloWorld''​.+<​code>​ 
 +foo: 
 +    push ebp 
 +    mov ebpesp
  
 +    mov eax, [ebp + 8]
 +    mov ebx, [ebp + 12]
 +    mov ecx, [ebp + 16]
  
 +    add eax, ebx
 +    add eax, ecx
  
 +    leave
 +    ret
  
-==== Tutoriale și exerciții analiză statică ​====+</​code>​ 
 +==== Remarcati: ​====
  
 +1. O functie se defineste printr-un label.
 +2. Dupa preambulul functiei, stiva arata in felul urmator:
  
 +{{https://​raw.githubusercontent.com/​systems-cs-pub-ro/​iocla/​master/​laborator/​content/​apel-functii/​images/​function_stack1.jpg?​width=650|function_stack1.jpg}}
  
 +3. De observat că pe parcursul execuției funcției, ceea ce nu se schimbă este poziția frame pointer-ul. Acesta este și motivul denumirii sale: pointează la frame-ul curent al funcției. De aceea este comun ca accesarea parametrilor unei funcții să se realizeze prin intermediul frame pointer-ului. Presupunând un sistem pe 32 de biți și parametri de dimensiunea cuvântului procesorului (32 de biți, 4 octeți) vom avea:
 +        * primul argument se găsește la adresa ''​%%ebp+8%%''​
 +        * al doilea argument se găsește la adresa ''​%%ebp+12%%''​
 +        * al treilea argument se găsește la adresa ''​%%ebp+16%%''​
 +        * etc.
  
-=== [1p] 3. Tutorial objdump === +Acesta este motivul pentru ​care, pentru ​obține ​parametrii ​funcției ​''​%%foo%%'' ​în registrele ''​%%eax%%''​, ''​%%ebx%%'',​ ''​%%ecx%%''​, folosim construcțiile:
-Prin dezasamblarea unui fișier binar (executabil,​ cod de tip obiect, bibliotecă partajată...) obținem echivalentul în limbaj de asamblare al codului (eventual, scris în C) de la care a pornit totul. În funcție de formatul fișierului binar, codul în assembly va fi structurat într-un mod specific. În cadrul laboratoarelor lucrăm în general cu binare în formatul ELF (nu ne vom intersecta cu executabile de tipul Mach O sau IEEE-695dar și acestea au la bază idei asemănătoare). În urma dezasamblării ​pentru ​acest tip de format vom obține ​un ansamblu de secțiuni, fiecare corespunzând anumitor date din codul sursă; secțiunile esențiale sunt următoarele:​ +
-  * .data - date inițializate de tip read-write +
-  * .bss - date neinițializate de tip read-write +
-  * .rodata - date read-only (const) +
-  * .text - instrucțiuni executabile +
-  * .init și .fini - secțiuni pe care probabil nu le veți întâlni în fișierele obiect, dar ele sunt legate de etapele de inițializare/​terminare a proceselor pornite din executabile ELF. Cu alte cuvinte, sistemul face astfel încât codul din aceste secțiuni să fie executat înaintea ​funcției ​main, respectiv ​în urma terminării cu success a execuției. +
-Zonele de memorie pentru stivă și heap vor fidesigur, alocate la runtime și le vom putea vizualiza doar în cadrul analizei dinamice. +
-  +
-Pentru acest tutorial avem la dispoziție fișierul de tip obiect ​''​objd_tutorial.o'' ​și codul sursă corespunzător acestuiaambele aflate în directorul ​''​3-objdump-tutorial''​. Urmăriți codul din fișierul .c și dezasamblați codul obiect folosind comanda:+
  
 <​code>​ <​code>​
-objdump -D -M intel <​file_name>​+    mov eax, dword [ebp+8] ​  ; primul argument in eax 
 +    mov ebx, dword [ebp+12] ​ ; al doilea argument in ebx 
 +    mov ecx, dword [ebp+16] ​ ; al treilea argument in ecx
 </​code>​ </​code>​
  
-<​note>​ +4. Valoare de retur a unei functii se plaseaza in registre (in general in eax). 
-''​-D'' ​determină dezasamblarea conținutului tuturor secțiunilornu doar al celor ce conțin instrucțiuni+   * Daca valoarea de retur este pe **8 biti** rezultatul functiei se plaseaza in ''​%%al%%''​. 
 +   * Daca valoarea de retur este pe **16 biti** rezultatul functiei se plaseaza in ''​%%ax%%''​. 
 +   * Daca valoarea de retur este pe **32 biti** rezultatul functiei se plaseaza in ''​%%eax%%''​. 
 +   * Daca valoarea de retur este pe **64 biti** rezultatul se plaseaza in registrele ''​%%edx%%''​ si ''​%%eax%%''​. Cei mai semnificativi 32 de biti se plaseaza in ''​%%edx%%'', ​iar restul in registrul ''​%%eax%%''​. 
 +   //De asemnea, in unele cazuri, se poate returna o adresa de memorie catre stiva/heap, sau alte zone de memorie, care refera obiectul dorit in urma apelului functiei.//
  
-''​-M intel''​ spune dezasamblorului să folosească sintaxa Intel, nu pe cea AT&T +5. O functie foloseste aceleasi registre hardwareasadar, la iesirea din functie valorile registrelor ​nu mai sunt aceleasi. Pentru a evita aceasta situatie, se pot salva unele/toate registrele pe stiva.
-</note>+
  
-Se poate observa că porțiunile de cod în assembly sunt precedate de două coloane cu informații suplimentare:​ +<noteDeoarece limbajele ​de asamblare ofera mai multe oportunitatiexista necesitatea ​de a avea conventii de apelare a functiilor ​in x86Diferenta dintre acestea ​poate consta in ordinea parametrilor,​ modul cum parametrii sunt pasati functiei, ​ce registre trebuiesc conservate de apelat sau daca apelantul ori apelatul ​se ocupa de pregatirea stiveiMai multe detalii puteti gasi [[https://​en.wikipedia.org/​wiki/​X86_calling_conventions|aici]] sau [[https://​levelup.gitconnected.com/​x86-calling-conventions-a34812afe097|aici]] daca wikipedia e prea mainstream pentru voi. </​note>​
-<code> +
-   ​0:​ 55 ​                  ​ push ​  rbp +
-   1: 48 89 e5             ​ mov ​   rbp,rsp +
-   4: f3 0f 11 45 fc       ​ movss ​ DWORD PTR [rbp-0x4],​xmm0 +
-</​code>​ +
-Prima coloană reprezintă offsetul în octeți față ​de începutul secțiuniiiar cea de-doua, codul mașină corespunzător fiecărei instrucțiuni,​ afișat în baza 16.  +
-Identificați variabilele și funcțiile din fișierul .c în secțiunile corespunzătoare din outputul comenzii objdumpSe poate observa prezența unei funcții (helper_function) ​ce nu se regăsește în fișierul sursă, aceasta deoarece după etapa de preprocesare conținutul header-elor incluse în fișierul ​.c devine parte din acesta.+
  
-Având în vedere dimensiunea tipurilor de date, ce dimensiune au secțiunile .bss și .data? Declarați una sau mai multe variabile în codul sursă astfel încât dimensiunea secțiunii .bss să crească cu 4 octeți. După modificarea codului, regenerați fișierul obiect.+===== Exerciții =====
  
-<​note>​ +<​note ​importantÎn cadrul laboratoarelor vom folosi repository-ul de git al materiei [[https://​github.com/​systems-cs-pub-ro/​iocla|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>​
-Pentru ​generarea unui binar .oputeți folosi comanda ''​gcc objd_tutorial.-c''​ +
-</​note> ​+
  
-<​note>​ +==== 0Recapitulare:​ Șirul lui Fibonacci ====
-Verificați corectitudinea folosind comanda ''​size objd_tutorial.o''​ +
-</​note>​+
  
-Decomentați cele 2 linii din fișierul ​.c, regenerați și dezasamblați fișierul obiect. Observați apelul funcției f. În ce regiștri sunt puși parametrii? Dar în cazul în care ar fi fost compilat cu opțiunea -m32? În ce secțiune ar trebui să apară variabila ​''​global_var3''​? Am putea determina motivul ​pentru care nu se întâmplă ceea ce ne-am aștepta adăugând opțiunea -t comenzii objdump. Totuși, există o alternativă ce prezintă acest tip de informație într-un mod mai user-friendly - comanda ''​nm''​.+Completați fișierul ''​%%fibo.asm%%'' ​din arhivă ​pentru ​a realiza un program ​care afișează primele N numere din șirul lui Fibonacci.
  
-<​note>​ +Aveți voie să folosiți doar memorie alocată pe stivă.
-Pentru a afla mai multe despre tabela de simboluri, puteți studia [[http://​www.skyfree.org/​linux/​references/​ELF_Format.pdf|documentația formatului ELF]], secțiunea dedicată Symbol Table. +
-</​note>​+
  
-=== [1.5p] 4. nm ===+===1. Hello, world! ====
  
-În cadrul diverselor etape de compilare, dar șîn etapa de linkare, entităților dintr-un program le sunt asociate anumite metadate, fiind îndeosebi de interes adresele la care acestea - funcții, variabile - pot fi găsite. În acest context, funcțiile ​și variabilele sunt cunoscute drept simboluri, iar informațiile corespunzătoare sunt păstrate în structuri de date asociative denumite tabele de simboluri. Aici intervine comanda ​''​nm'', ​care ne va oferi tot ce avem nevoie în materie de simboluri.  +Deschideți fișierul ''​%%hello-world.asm%%'', ​asamblați-l ​și rulați-lObservați afișarea mesajului //Helloworld!//
-Outputul comenzii este alcătuit din trei coloane: valoarea simbolului, tipul și numele. Există multe tipuri de simboluri, mare parte dintre acestea corespunzând secțiunii din care datele fac parte; enumerăm aici două dintre tipurile de simboluri (puteți citi mai multe în pagina de ''​man''​ a comenzii):​ +
-  * U simbol nedefinit; cu alte cuvinte, el este utilizat în cadrul sursei curente, dar nu este declarat aiciAcest lucru este posibil în C datorită cuvântului cheie ''​extern'';​ locația exactă va fi determinată în urma etapei de linkare sau chiar la runtime +
-  * C - simbol comun; identifică variabilele neinițializate,​ nestatice; în cazul în care aceeașvariabilă este declarată ​și inițializată într-un alt fișier sursă folosit la generarea executabilului finalaceastă definiție va fi cea luată în considerare la nivelul modulului; în acest caz, variabila se va comporta ca un simbol nedefinit (valabil în C; în C++ acest tip de simbol va fi plasat în zona de date neinițializate (.BSS), iar link-editarea binarelor din acest scenariu va genera o eroare)+
  
-În directorul ''​4-nm''​ aveți la dispoziție două fișiere tip obiect. Încercați să generați un executabil plecând de la fișierul source1.o+Remarcați că:
  
-<​note>​ +  * 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. 
-Pentru link-editare (generarea unui executabil dintr-un număr de fișiere obiect) folosim: ​''​gcc file1.o [file2.o] [filen.o] -lm -o nume_executabil''​  +  * Variabila ''​%%msg%%''​ din programul ''​%%hello-world.asm%%'' ​conține ​octetul ''​%%10%%''​. Acesta simbolizează caracterul ​//​line-feed//​ (''​%%\n%%''​),​ folosit pentru a adăuga o linie nouă pe Linux.
-  * opținea ​-lm are rolul de a încărca biblioteca ce conține ​funcții matematice +
-</note>+
  
-Apare o problemă; codul sursă încearcă să folosească o funcție și o variabilă ale căror definiții nu le cunoaște. Folosiți comanda ​''​nm source1.o'' ​pentru a observa tipul simbolurilor corespunzătoare variabileirespectiv funcției ce cauzează eroriFolosiți comanda ​''​nm source2.o'' ​pentru a vedea dacă cel de-al doilea binar conține simbolurile de care avem nevoie ​șicel mai important, dacă acestea sunt ''​defined''​+Încheierea cu ''​%%\n%%'' ​esteîn general, utilă pentru afișarea șirurilorFuncția ''​%%puts%%'' ​pune automat o linie nouă după șirul afișatînsă aceasta trebuie adăugată explicit în cazul folosirii funcției ​''​%%printf%%''​.
-Reîncercați să generați un executabil, de data aceasta făcând linkarea ambelor fișiere obiect.+
  
-Descoperim că, deși variabila ''​outsider_var''​ este definită în binarul source2.o, ea nu este vizibilă ​în afara fișierului în care a fost declarată.+==== 2Dezasamblarea unui program scris în C ====
  
-<​note>​ +După cum spuneam, în final, totul ajunge în limbaj ​de asamblare (ca să fim 100% corecțitotul 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ă.
-Majuscula identifică (pentru majoritatea tipurilor ​de simboluri afișate de nm) entitățile globalece pot fi accesate ​și din alte fișiere. Minuscula (litera d în cazul nostru) identifică entitățile statice, vizibile ​doar în cadrul fișierului ​în care a fost definită. +
-</​note>​+
  
-Rezolvați erorile generate ​la link-editare creând ​un fișier sursă adițional ​în care veți defini variabila ​și funcția pe care source1.o nu le poate găsi; din acest fișier ​.c veți genera un fișier obiect și îl veți adăuga, alături ​de celelalte două, ca parametru al comenzii de link-editareConsiderați ​că funcția nu are parametri, iar valoarea ​de return este de tipul char; de asemenea, variabila outsider_var va avea tipul int.+Pentru a observa acest lucru, haideți să compilăm până ​la codul obiect ​un program scris în și apoi să-l dezasamblămEste vorba de programul ''​%%test.c%%''​ din arhiva ​de laborator.
  
-Folosind nm pentru executabilul obținut, veți observa că multe din simbolurile nedefinite în cadrul ​fișierelor obiect au căpătat noi tipuri; mai exacttoate variabilele șfuncțiile definite de noi; funcțiile din biblioteci standard (printf, sqrt) și-au păstrat însă titulatura, ceea ce este normal când avem de-a face cu biblioteci dinamice - locațiile de memorie vor putea fi stabilite doar la runtime.+<​note>​ Pentru a compila un fișier cod sursă C/C++ în linia de comandă, urmațpașii:
  
-Totuși, cât de importante sunt simbolurile?​ Folosiți comanda ​''​strip -s'' ​pentru a șterge toate simbolurile prezente în executabilul final. Rulați executabilul. Procedați în același mod pentru binarul source1.o. Refaceți linkarea celor 3 fișiere obiect și rulați scriptul. Evident, linkarea în absența simbolurilor este imposibilă.+  - 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.
  
-=== [2p] 5. Exercițiu objdump === +  ​Dacă doriți **doar** să compilați fișierul ​(**fără** să-l link-ați)atunci folosiți comanda 
-În directorul ''​5-objdump''​ găsiți un executabil care primește ca dată de input o valoare numerică n reținută pe un octet, scopul executabilului fiind acela de a calcula valoarea lui 2^(n). Așa cum veți descoperi însă în urma dezasamblării, scopul nu este atins întotdeauna,​ întrucât codul ce stă la baza binarului conține o eroare. Folosiți objdump pentru inspectarea codului în limbaj de asamblaredescoperiți eroarea și găsiți o valoare de input pentru care se obține un rezultat corect. + 
-  +<​code>​ 
-<note+gcc -m32 -c -<​fisier-obiect>​ <​nume-fisier>​ 
-Secțiunile din cadrul funcției main care se ocupă de citirea și afișarea datelor sunt irelevante în cadrul acestui exercițiu. De interes este doar codul delimitat prin flag-urile ​''​BEGINNING_AREA_OF_INTEREST:​''​ ș''​END_AREA_OF_INTEREST:​''​; de asemenea, valoarea afișată de funcție va fi preluată din registrul ''​eax''​+ 
 +</code
 +unde ''​%%<​nume-fisier>​%%'' ​este numele fișierului iar ''​%%<​fisier-obiect>​%%'' ​este fișierul obiect rezultat.
 </​note>​ </​note>​
-=== [0.5p] 6. Cppcheck === +În cazul nostru, întrucât dorim doar să compilăfișierul ​''​%%test.c%%''​ la modulul obiectvom accesa din terminal directorul în care se găsește ​fișierul și apoi vom rula comanda
-[[http://​cppcheck.sourceforge.net/​|Cppcheck]] este un exemplu de utilitar open-source de analiză statică folosit pentru a detecta potențiale probleme neraportate de compilator. Spre deosebire de //objdump// care permite analiza direct pe fișierul ​executabil//​cppcheck//​ analizează fișierele sursă. Acest utilitar nu detectează erorile de sintaxă, ci mai degrabă problemele cauzate de comportamente detefinite ​și construcții periculoase.+
  
-Tipuri de probleme detectate de //​cppcheck//:​ +<​code>​ 
-  * Dead pointers +gcc -m32 -c -o test.o test.c
-  * Division by zero +
-  * Buffer and integer overflow +
-  * Uninitialized data +
-  * Null pointers dereferences +
-  * Memory leaks+
  
-//Cppcheck// oferă șinterfață web minimală pentru realizarea unui demo, disponibila [[http://​cppcheck.sourceforge.net/​demo/​|aici]]. Urmăriți exemplele din demo și rulați //​cppcheck//​ pe ele. Identificați sursa fiecărei erori din rezultat.+</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
  
-Următoarea funcție ar trebui să detecteze dacă un procesor este //little// sau //big endian//, însă ea nu arată tot timpul adevărul. Corectați funcția pornind de la //​cppcheck//​. +<​code>​ 
-<​code ​c+gcc -m32 -masm=intel -S -o test.asm test.c
-void check_endianess(void) +
-+
-  int a; +
-  char *p (char *)&a;+
  
-  if (*p) 
-    printf("​Little endian\n"​);​ 
-  else 
-    printf("​Big endian\n"​);​ 
-} 
 </​code>​ </​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>​
  
-==== Tutoriale ​și exerciții analiză dinamică ====+</​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
  
-=== [3p] 7Tutorial: GDB ===+<​code>​ 
 +$ objdump -M intel -d test.o
  
-GDB este unealtă foarte utilă pentru analiza dinamică a programelor. Acesta este folosit foarte des pentru găsirea cauzelor care duc la erori într-un program. În continuare vă vom prezenta câteva dintre comenzile cele mai importante.+test.o:     file format elf32-i386
  
-Primul pas este să urmăriți și să înțelegeți codul din ''​7-8-gdb/​gdb-tutorial.asm''​. Pe scurt, programul primește un parametru ''​index''​ și citește de la tastatură o linie. Programul afișează doar un caracter, mai exact al ''​index''​-lea caracter din șirul dat la intrare.+Disassembly of section ​.text:
  
-<note warning+0000054d ​<second_func>: 
-Când rulați în GDB să dați acel parametru ca argument comenzii ''​run''​ sau ''​start''​ vedeți mai jos. Altfelrularea nu va fi corespunzătoare. + 54d:   ​55 ​                     push   ebp 
-</note>+ ​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
  
-După ce ați citit codul sursăasamblați și link-editați fișierulDupă ce ați obținut executabilul ''​gdb-tutorial''​ (sau ce nume i-ați dat)vom porni GDB-ul cu acel fișier:+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
  
-<code+000005b2 ​<main>: 
-gdb ./gdb-tutorial + 5b2:   8d 4c 24 04             ​lea ​   ecx,​[esp+0x4] 
-</code>+ ​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
  
-<note tip> 
-Recomandăm folosirea extensiei [[https://​github.com/​longld/​peda|PEDA (Python Exploit Development Assistance) pentru GDB]]. Pentru a putea să o folosiţi, verificați faptul că fişierul ''​~/​.gdbinit''​ conţine linia ''​source ~/​peda/​peda.py''​. Dacă fişierul nu există sau nu conţine acea linie, rulaţi în terminal comanda: 
-<​code>​ 
-echo '​source ~/​peda/​peda.py'​ >> ~/.gdbinit 
 </​code>​ </​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ă.
  
-Dacă lucrați pe alt sistem decât cele din sala de laborator, va trebui să clonați în prealabil repository-ul PEDAașa cum este indicat ​în [[https://​github.com/​longld/​peda#​installation|README-ul proiectului]]:​ +Este interesant ​de urmărit, atât în fișierul ''​%%test.asm%%''​ cât și în dezasamblarea samodul în care se face un apel de funcție, lucru despre care vom discuta în continuare.
-<​code>​ +
-git clone https://​github.com/​longld/​peda.git ~/peda +
-</​code>​ +
-</​note>​+
  
-După ce ați pornit programul gdb, toată interacțiunea cu acesta se face prin prompt-ul de gdb.+==== 3Afișarea unui șir ====
  
-== Lansarea în execuție a programului ==+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%%''​.
  
-Pentru a lansa programul urmărit în execuție există două comenzi disponibile:​ +Urmărind fișierul ''​%%hello-world.asm%%''​ ca exemplu, implementați afișarea șirului folosind și ''​%%puts%%''​.
-  * **run** ​această comandă va lansa în execuție programul +
-  * **start** - spre deosebire de ''​run''​, această comandă va începe execuția programului,​ însă se va opri imediat după intrarea în main+
  
-Aceste două comenzi mai pot fi folosite în două feluri:+<​note>​ Urmăriți și indicațiile din secțiunea //"​Apelul unei funcții"//​. </​note>​
  
-  - <​code>​start 1 2 3 4</​code>​ +==== 4. Afișarea lungimii unui șir ====
-  - <​code>​start < file.in</​code>​+
  
-<​note ​tip+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). 
-În locul comenzii ​''​start'' ​puteți folosi mai sus comanda ​''​run''​.+ 
 +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> ​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 șlungimea. 
 +    - Apelul ''​%%printf%%''​ folosind ''​%%call%%''​. 
 +    - Restaurarea stivei. 
 + 
 +Lungimea șirului se găsește în registrul ​''​%%ecx%%''​.
 </​note>​ </​note>​
 +==== 5. Afișarea șirului inversat ====
  
-Utilizarea aceasta este similară cu execuția programului direct din linia de comandă (fără GDB), prima variantă însemnând că se trimit 4 parametri (1, 2, 3 ș4) programului,​ iar a doua, că ''​file.in'' ​se redirectează ca intrare standard pentru program.+În soluția de mai sus adăugațfuncția ​''​%%reverse_string%%'' ​astfel încât să aveți un listing similar celui de mai jos:
  
-Lansați programul în execuție folosind comanda GDB ''​run''​Ce observați? Rulați din nou programul, de data aceasta dând comenzii run parametrul corespunzător.+<​code>​ 
 +[...] 
 +section .text 
 +global main
  
-GDB se blochează la citirile de la input. Haideți să corectăm asta folosind un fișier de intrare. Creați un fișier (spre exemplu text.in) în directorul cu executabilul care să conțină textul "ana are mere". Porniți din nou GDB și lansați în execuție programul cu parametrul de intrare ''​11''​ și cu fișierul ''​text.in''​redirectat.+reverse_string:​ 
 +    push ebp 
 +    mov ebpesp
  
-Ce observați? Programul își termină execuția cu succes. Deoarece nu a existat niciun breakpoint setat în programprogramul nu s-a oprit din execuție decât când a terminat treaba.+    mov eaxdword [ebp+8] 
 +    mov ecx, dword [ebp+12] 
 +    add eax, ecx 
 +    dec eax 
 +    mov edx, dword [ebp+16]
  
-În cazul pornirii programuluiputeți folosi instrucțiunea ''​start''​ care va opri execuția după intrarea în main.+copy_one_byte:​ 
 +    mov blbyte [eax] 
 +    mov byte [edx], bl 
 +    dec eax 
 +    inc edx 
 +    loopnz copy_one_byte
  
-== Breakpoints ==+    inc edx 
 +    mov byte [edx], 0
  
-Elementul esențial al GDB-ului este //​breakpoint-ul//​. Practic, un //​breakpoint//​ setat la o anumită instrucțiune face ca execuția programului să se oprească de fiecare dată când se ajunge la acest punct.+    leave 
 +    ret
  
-Adăugarea unui breakpoint se face cu construcția+main: 
 +    push ebp 
 +    mov ebp, esp 
 +[...]
  
-<​code>​break [location]</code>+</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>
  
-, unde ''​location'' ​poate fi numele unei funcții sau o adresă din zona .text. În cazul cel din urmă, adresa trebuie să fie precedată de ''​*'' ​(star)Exemplu: ​''​break *0x004013af''​.+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%%''​.
  
-Pentru continuarea programului după eventuala sa oprire într-un breakpoint, puteți folosi comanda ​''​continue''​.+Realizați inversarea șirului ​''​%%mystring%%'' ​într-un nou șir și afișați acel nou șir.
  
-Un alt lucru interesant în GDB este comanda ''​commands'',​ care poate asocia unui breakpoint ​un bloc de comenzi GDB ce vor fi executate la fiecare oprire ​în breakpoint-ul respectiv.+<​note>​ 
 +Pentru a defini ​un nou șir, recomandăm ca, în secțiunea de date să folosiți construcția
  
-Exemplu: 
 <​code>​ <​code>​
-(gdb) break *0x004013af +store_string times 64 db 0 
-Breakpoint <n> at 0x4013af +
-(gdb) commands <n> +
-Type commands for breakpoint(s) <n>, one per line. +
-End with a line saying just "​end"​ +
-> print $eax +
-> x/i $eip +
-> end+
 </​code>​ </​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.
  
-Pentru a nu rămâne blocat în breakpoint (spre exemplu dacă scrieți un script de gdb), puteți ​adăuga ​în blocul de instrucțiuni și comanda ​''​continue''​.+Nu puteți ​folosi direct valoarea ''​%%ecx%%'' ​în forma ei curentă. După apelul funcției ''​%%printf%%''​ pentru afișare numărului valoarea ​''​%%ecx%%'' ​se pierdeCa să o păstrați, aveți două opțiuni:
  
-Haideți să adăugăm un breakpoint la label-ul ​''​ok''​. ​Dacă dă''​continue''​, vom observa ​că programul ​s-a oprit în breakpoint-ul tocmai creat. +  - 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%%''​). 
-<​note>​ +  - Stocați valoarea registrului ''​%%ecx%%''​ într-o variabilă globală, pe care o definiți în secțiunea ​''​%%.data%%''​
-Variaţii:​\\  + 
-**break label** - breakpoint la labelul **label**\\  +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.
-**break *(label + <​offset>​)** - breakpoint la **label + offset**\\ ​+
 </​note>​ </​note>​
 +==== 6. Implementarea funcției toupper ====
  
-== Parcurgerea instrucțiunilor ==+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%%''​.
  
-Atunci când execuția programului ​este oprită (de exemplu la un breakpoint),​ putem da comenzi care continuă execuția "pas cu pas"Pentru a face asta, cel mai des sunt folosite două comenzi:+Șirul folosit ​este ''​%%mystring%%''​ și presupunem că este un șir valid. Acest șir este transmis ca argument funcției ''​%%toupper%%''​ în momentul apelului.
  
-  * **stepi** - care practic trimite o instrucțiune spre execuție șdupă execuția acesteia întoarce control-ul la debugger (programul se oprește) +Faceți înlocuirea //in place//nu este nevoie de un alt șir.
-  * **nexti** - comandă similară cu ''​stepi''​însă dacă instrucțiunea curentă ​este un apel de funcție, debugger-ul nu va intra în funcție (va chema funcția ​și se va opri la următoarea instrucțiune după ''​call''​)+
  
-Dacă emitem comanda ​''​stepi''​, putem observa că se afișează instruction pointer-ul instrucțiunii următoare dupa cea la care am făcut break (prima ​de la label-ul ​''​ok''​).+<​note>​ 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]].
  
-== Dezasamblarea programului ==+a 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.
  
-Pentru ​dezasambla o porțiune de executabil, se poate folosi comanda ​''​disassemble'' ​din GDBDacă aceasta nu primește niciun parametru, va afișa dezasamblarea funcției curente din cadrul execuției.+Vă opriți atunci când 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>​
  
-<​note>​ +==== Bonus: toupper doar pentru litere mici ====
-Default, sintaxa folosită de GDB la dezasamblare este cea "​AT&​T"​. Pentru a folosi sintaxa cunoscută vouă (sintaxa intel), executați în GDB comanda ''​set disassembly-flavor intel''​. +
-</​note>​+
  
-În cadrul exemplului nostru, dacă cerem dezasamblarea ​funcției curente (folosind ​''​disassemble'' ​fără parametri) putem observa că ne aflăm la label-ul ''​ok''​. Observație:​ GDB iterpretează label-ul ''​ok''​ ca o funcție din cauza codului inițialcare este scris în limbaj ​de asamblare.+Implementați ​funcția ''​%%toupper%%'' ​astfel încât translatarea să aibă loc doar pentru caractare reprezentând litere micinu litere mari sau alte tipuri ​de caractere.
  
-Pentru a vedea mai clar efectul ''​stepi''/''​nexti''​ putem rula commanda ''​disassemble''​ înainte și după stepping.+==== Bonus: rot13 ====
  
-<​note>​ +Realizați și folosiți o funcție ​care face translatarea [[https://​rot13.com/|rot13]] a unui șir.
-Dacă ați intrat într-o funcție lungă ​și nu vreți să dați de ''​nexti''​ de foarte multe ori, vă recomandăm instrucțiunea GDB ''​finish'',​ care "​termină" ​o funcție. ​**Atenție** la funcțiile recursive. +
-</note> +
-<​note>​ +
-**disassemble label, +<​length>​** - afişează <​length>​ bytes de cod dezasamblat începând de la labelul **label**. +
-</​note>​+
  
-== Inspectarea memoriei și a registrelor ​==+==== Bonus: rot13++ ====
  
-Pentru a afișa diferite valori accesibile GDB-ului se folosește comanda ​''​print''​. De exemplu, pentru a afișa  valoarea unui registru (de exemplu eax), vom folosi construcția ''​print $eax''​.+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>​ Pentru a defini array-ul de șiruri care să conțină terminatorul de șir, folosiți o construcție de forma:
  
-Pentru inspectarea memoriei se folosește comanda ''​x''​ (examine). Modul de folosire al acestei comenzi este următorul: 
 <​code>​ <​code>​
-x/nfu address +mystring db "​ana"​0"​are"​0"​mere"​0
-</​code>​ +
-unde: +
-  * ''​n''​ este numărul de elemente afișate +
-  * ''​f''​ este formatul de afișare (x pentru hexad pentru zecimals pentru șir de caractere și i pentru instrucțiuni) +
-  * ''​u''​ este dimensiunea unui element (b pentru 1 octeth pentru 2w pentru 4 și g pentru 8 octeți)+
  
-De exemplu, o funcționalitate similară cu ''​disassemble''​ o putem obține și folosind ''​x''​ unde formatul ​este instrucțiune. Astfel, putem afișa, de exemplu, 10 instrucțiuni începând ​de la instrucțiunea ​curentă cu construcția ​''​x/10i $eip''​.+</​code></​note>​ 
 +<​note>​ 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
  
-=== [1p] 8. Afișarea unor informații la fiecare trecere printr-un breakpoint ===+<​code>​ 
 +len dd 10
  
-Folosind executabilul creat la exercițiul anterior (''​gdb-tutorial.asm''​),​ trebuie ​să setați un breakpoint ​la intrare în bucla din program (când se mută în subregistrul ​''​al'' ​un caracter din șirul input). În plustrebuie să adăugați o serie de comenzi astfel încât la fiecare intrare în buclă, GDB să afișeze valoarea subregistrului ''​al''​ și valoarea counter-ului (în cazul nostru ''​ecx''​). +</​code>​ 
-<note tip> +î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.
-**Hint:** folosiți comanda ''​commands''​.+
 </​note>​ </​note>​
 +===== Alte resurse =====
  
-=== [2p] 9. BonusDepanarea unui Segfault ===+  * [[http://​www.nasm.us/​|nasm]]
  
-Pornind de la executabilul ''​segfault'',​ aflat in directorul ''​9-solve-segfault'',​ rulat sub gdb, analizați atât backtrace-ul cât și pas cu pas codul pentru a identifica cauza care duce la Segmentation Fault. +===== Soluții =====
- +
-<​note>​ +
-Codul ce se ocupă cu printarea - delimitat în mod explicit în cod - nu este relevant pentru rezolvarea exercițiului. Totuși ar putea fi de interes faptul că registrul ebx este cel din care se preiau datele pentru printare +
-</​note>​ +
-<note tip>​Puteți să vă folosiți și de [[https://​linux.die.net/​man/​1/​objdump|objdump]] pentru a vedea exact conținutul secțiunii ''​.data''​ din cadrul binarului ''​segfault''</​note>​ +
-<note hint>Cu ce este diferit ''​objdump -d''​ de ''​objdump -D''?</​note>  ​+
  
 +  * Soluțiile pentru exerciții sunt disponibile [[https://​elf.cs.pub.ro/​asm/​res/​laboratoare/​lab-09-sol.zip|aici]].
iocla/laboratoare/laborator-09.1574964156.txt.gz · Last modified: 2019/11/28 20:02 by bogdan.purcareata
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