Differences

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

Link to this comparison view

cpl:labs:11 [2016/01/05 23:16]
razvan.crainea [BONUS]
cpl:labs:11 [2016/12/20 01:59] (current)
bogdan.nitulescu [Exercițiul 3]
Line 1: Line 1:
 +===== 11. LLVM - transformări de cod =====
  
-====== 11. Linkers and Loaders ====== 
  
-===== Nice to read =====+===== Introducere ​=====
  
-  * [[http://www.iecc.com/linker/|Linkers and Loaders]],  John RLevine+În LLVM optimizările sunt implementate sub formă de [[http://llvm.org/docs/Passes.html|Pass]]-uri care traversează programul pentru a-l analiza și a-l transforma. Obținerea de informații despre program prin analiza fluxului de [[cpl:​labs:​09#​tipuri_de_probleme|date]] sau de [[cpl:​labs:​08|control]] constituie un pas important în implementarea optimizărilor.
  
-<​note>​ +Pentru a aplica o anumită selecție ​de optimizări se poate folosi tool-ul [[http://​llvm.org/​docs/​CommandGuide/​opt.html|opt]] prezentat și [[cpl:​labs:​llvm-pass|aici]]. Pentru a integra o optimizare nouă în sursele LLVM (fără să mai fim nevoițsă specificăm biblioteca dinamică la runtime) se va folosi [[http://​llvm.org/​docs/​CommandGuide/​llvm-build.html|sistemul de build]] din llvm și pașii descriși în exemplul de [[cpl:labs:​llvm-pass#​integrarea_unui_pas_in_llvm|aici]]. 
-Acest laborator va abunda ​de rom-englezăîn special de cuvintele **linkăre** ș**loadăre**. :+===== Afișarea informațiilor de debug ===== 
-</​note>​ +Informațiile de debug, pot fi afișate rulând utilitarul ''​opt''​ cu parametrul ''​-debug''​. Pentru a filtra informațiile de debug doar pentru un anumit pas, se folosește parametrul ''​-debug-only=//<​nume_pass>//''​. De exemplu, pentru a filtra doar log-urile optimizării hello, se rulează comanda: 
-===== Prezentare teoretică ​=====+<code bash> 
 +opt -hello -debug-only=hello hello.bc 
 +</​code>​
  
-**Linkerele** unesc mai multe fișiere compilate (obiect) pentru a genera un singur fișier executabil. 
  
-**Loaderele** încarcă un program executabil la o anumită adresă în memorie.+===== SSA ======
  
-Acestea permit construirea programelor din module, în locul unui program ​mare și monolitic. Astfel, una din principalele sarcini ale linkerelor și loaderelor ​este să lege numele abstracte folosite de programatori (ex: numele funcțiilor din biblioteci) șsă le transforme în nume concrete (ex: locația deplasată cu 512 octeți de la începutul secvenței codului executabil din memorie sau o adresă numerică).+Un program este reprezentat în forma SSA dacă fiecărei variabile ​se atribuie o valoare doar o singură dată şfiecare folosire a variabilei este dominată ​de definiţia ​ei.
  
-==== Asambloare ====+Un program poate fi convertit în forma [[http://​en.wikipedia.org/​wiki/​Static_single_assignment_form|SSA]] prin: 
 +  * asocierea de nume unice variabilelor de fiecare dată când li se atribuie o valoare 
 +  * înlocuirea numelor de variabile cu numele unice atunci când variabilele sunt folosite.
  
-La începuturile erei calculatoarelor,​ programarea se făcea ​în totalitate în cod mașină. Programatorii scriau programele simbolice pe hârtie și le transformau ​//de mână// în cod mașinăDacă programatorul folosea nume simbolice (de exemplu un nume de procedurăacestea trebuiau transformate //de mână// în adrese și, dacă apoi se descoperea că o instrucțiune trebuie adăugată într-un anumit loc în program, acesta trebuia verificat pentru a fi modificate toate adresele afectate de acea instrucțiune.+Exemple 
 +^ Program inițial ^ Program ​în forma SSA ^ 
 +| <code c> 
 +V = 4; 
 +  = V + 5; 
 +V = 6; 
 +  = V + 7; 
 +</code> | <code c> 
 +V0 = 4; 
 +   = V0 + 5; 
 +V1 = 6; 
 +   = V1 + 7; 
 +</code> | 
 +| <code c> 
 +if (...) 
 +    X = 5; 
 +else 
 +    X = 3;
  
-Problema în aceasta abordare era că numele erau legate de o adresă fixă prea devreme în procesul de dezvoltareAceastă problemă a fost rezolvată cu ajutorul asambloarelor ce permiteau folosirea de nume simboliceDacă programul trebuia schimbatprogramatorul trebuia să îl reasambleze,​ dar această muncă de a atribui adresele fizice nu mai era făcută de el.+Y = X; 
 +</​code>​ | <​code ​c
 +if (...) 
 +    X0 = 5; 
 +else 
 +    X1 = 3; 
 +X2 = O(X0X1); 
 +Y0 = X2; 
 +</​code>​ | 
 +| <code c> 
 +j = 1; 
 +while (j < X) 
 +    ++j; 
 +N = j; 
 +</​code>​ care se mai poate scrie şi <code c> 
 +    j = 1; 
 +    if (j >= X) 
 +        goto E; 
 +S: 
 +    j = j + 1; 
 +    if (j < X) 
 +        goto S; 
 +E: 
 +    N = j; 
 +</​code>​ | <code c> 
 +    j0 = 1; 
 +    if (j0 >= X0) 
 +        goto E; 
 +S: 
 +    j1 = O(j0, j2); 
 +    j2 = j1 + 1; 
 +    if (j2 < X0) 
 +        goto S; 
 +E: 
 +    j3 = O(j0, j2); 
 +    N0 = j3;
  
-==== De ce linkere? ==== 
- 
-Bibliotecile complică această problemă (existau biblioteci de cod încă înaintea apariției asambloarelor). Aceste biblioteci ar trebui să fie încărcate în memorie și să poată fi accesate din programul care le apelează (eventual să fie încărcate în același loc cu celelalte metode ale programului). În general aceste biblioteci vin sub o formă binară (compilată) și ar trebui ca adresele lor sa poată fi atribuite în funcție de adresa efectivă la care sunt încărcate. 
- 
-Pentru a rezolva această problemă a fost introdusă noțiunea de **cod relocabil**. Programatorii și asamblorul scriau module care începeau atribuirea adreselor relative de la 0 și amânau atribuirea adreselor absolute (adrese fizice) până în **momentul linkeditării**. 
- 
-==== De ce loadere? ==== 
- 
-Odată cu apariția sistemelor de operare, a devenit necesară o **separare a linkerelor de loadere**. Înainte programele puteau fi asamblate și link-ate cu adrese fixe pentru că programele aveau toată memoria la dispoziție,​ dar în contextul sistemelor de operare, în momentul în care modulele erau legate, programele nu știau la ce adresă vor fi încărcate. 
- 
-Astfel linkerele făceau partea de agregare a modulelor și foloseau adrese relative, în timp ce loaderele le transformau în adrese absolute odată cu încărcarea programelor în memorie. 
- 
-Această evoluție a continuat și a mai parcurs o serie de stagii marcate în principal de apariția blocurilor de date partajate de mai multe programe (linkerul trebuia să asigure suportul pentru accesul la aceste date), împărțirea programelor în secțiuni (linkerul trebuie să combine toate secțiunile de fiecare tip din fiecare modul), apariția bibliotecilor dinamice etc. 
- 
-===== Linkere vs. Loadere ===== 
- 
-Atât linkerele cât și loaderele lucrează (sau modifică) cod obiect și sunt cam singurele instrumente de largă răspândire care lucrează cu acest tip de cod (în afara debuggerelor). 
- 
-Există trei mari acțiuni pe care le îndeplinesc:​ 
-  * relocarea 
-  * rezolvarea simbolurilor 
-  * încărcarea 
- 
-==== Relocarea ==== 
- 
-Este o acțiune făcută și de **linker** și de **loader**. 
- 
-Compilatoarele și asambloarele creează, în general, fișiere obiect cu adrese relative (pornind de la 0). Relocarea este procesul prin care se atribuie adrese de încărcare diferitelor părți din program, ajustând codul și datele în program pentru a reflecta adresele atribuite. Un program poate fi relocat de mai multe ori. De regulă, un linker creează un program mare cu adresele pornind de la zero din mai multe subprograme (fiecare fiind creat cu adrese pornind de la zero) care vor fi introduse în zone diferite în programul mare. La încărcare,​ loaderul va reloca executabilul produs de linker la o adresă efectivă în memorie. 
- 
-==== Rezolvarea simbolurilor ==== 
- 
-Este o acțiune făcută de **linker**. 
- 
-Într-un program compus din mai multe subprograme se pot face referințe între subprograme prin simboluri (de ex. proceduri sau variabile externe: un apel către sqrt() din biblioteca matematică). Rezolvarea unui simbol se face înlocuind toate utilizările simbolului cu adresa la care acesta este definit. 
- 
-==== Încărcarea ==== 
- 
-Este o acțiune făcută de **loader**. 
- 
-Încărcarea este procesul de aducere a unui program de pe un mediu de stocare secundar (hard-disk, SSD, flash etc.) în memoria principală astfel încât programul să fie gata de execuție. Această acțiune poate varia de la simpla copiere a datelor până la alocarea spațiului, setarea unor biți de protecție ai memoriei (de ex. paginile de cod sunt read-only) sau maparea unor zone de memorie virtuală pe spații de pe disk/​flash/​SSD/​etc. 
- 
-==== Linking loaders ==== 
- 
-Un singur program poate face toate cele trei funcții amintite mai sus. Limita dintre relocare și identificarea simbolurilor poate fi destul de confuză. Din moment ce linkerele pot rezolva referințe la simboluri, un mod de a trata relocarea este de a atribui un simbol adresei de start a fiecărei părți din program și apoi de a trata relocarea adreselor ca referințe la adresa simbolului de bază. 
- 
- 
-===== Formatul Unix ELF ===== 
- 
-Fișierele ELF pot fi de trei tipuri (puțin diferite): 
-  * **relocabile** - create de compilatoare sau asambloare, dar trebuie procesate de linker înainte de execuție 
-  * **executabile** - gata relocate și cu toate simbolurile rezolvate exceptând, poate, simbolurile din bibliotecile partajate care vor fi rezolvate la runtime 
-  * **partajate** - biblioteci comune ce conțin informații despre simboluri (folosite de linker) și cod executabil 
- 
-Compilatoarele,​ asambloarele și linkerele tratează fișierele ELF ca un set de secțiuni logice descrise de un //section header table//. Loaderul tratează fișierele ELF ca un set de segmente descrise de un //program header table//, un segment fiind de obicei compus din mai multe secțiuni. 
- 
-==== Fișiere ELF relocabile și partajate ==== 
- 
-Un fișier relocabil sau partajat este considerat o colecție de secțiuni definite în header. Fiecare secțiune conține un singur tip de informație (ex: codul programului,​ date read-only sau read-write, intrări relocabile, simboluri, etc.). Fiecare simbol definit este relativ la o secțiune (ex: punctul de intrare al unei proceduri va fi definit relativ la secțiunea care conține codul programului). ​ 
- 
-Există două pseudo-secțiuni:​ 
-  * SHN_ABS - conține simboluri ne-relocabile 
-  * SHN_COMMON - conține blocuri de date neinițializate,​ moștenire din formatul a.out 
- 
-Un executabil relocabil are în jur de 12 secțiuni. Numele secțiunilor au semnificație pentru linker, care caută anumite secțiuni pentru prelucrări specifice. Tipurile de secțiuni includ: 
-  * PROGBITS - conținut al programului:​ cod, date, informații pentru debugger 
-  * NOBITS - la fel ca și PROGBITS, dar nu alocă spațiu în fișierul propriu zis, ci folosește BSS pentru alocarea datelor la încărcarea programului 
-  * SYMTAB - tabelă ce conține toate simbolurile 
-  * DYNSYM - tabelă ce conține simbolurile folosite pentru linkarea dinamică 
-  * STRTAB - tabelă de stringuri 
-  * RELA - valorile de bază pentru relocare 
-  * REL - valorile cu care se fac relocările (valorile sunt adăugate la valoarea de bază) 
-  * DYNAMIC - informații pentru linkarea dinamică 
-  * HASH - tabelă de simboluri folosite la runtime 
- 
-Se pot defini și secțiuni proprii, însă vom enumera câteva dintre secțiunile tipice (standard): 
-  * .text - de tip PROGBITS (cu modificatori ce permit execuția instrucțiunilor),​ este echivalentul segmentului text din a.out 
-  * .data - de tip PROGBITS (cu modificatori ce permit scrierea datelor), este echivalentul segmentului de date din a.out 
-  * .rodata - date read-only ​ 
-  * .bss - de tip NOBITS (cu modificatori ce permit scrierea); nu ocupă spațiu în fișier și este alocată la runtime 
-  * .init și .fini - cod care este executat la inițializarea/​finalizarea programului;​ spre exemplu, compilatoarele de C++ folosesc aceste secțiuni pentru a inițializa datele globale alocate static 
-  * .symtab și .dynsym - tabele de simboluri 
-  * .strtab și .dynstr - tabele de stringuri 
- 
- 
-==== Fișiere ELF executabile ==== 
- 
-Fișierele executabile au același format, dar datele sunt aranjate astfel încât fișierul poate fi mapat direct în memorie și rulat. Conține un header de program care urmează headerului ELF din fișier și definește segmentele care trebuie mapate. 
- 
-Un executabil are în general doar câteva segmente: unul read-only pentru cod și datele read-only și unul read-write pentru datele read-write. Toate secțiunile sunt împachetate în segmentele corespunzătoare astfel încât sistemul poate mapa simplu fișierul în memorie. 
-===== Linker Command Language ===== 
- 
-Utilitarul ''​ld''​ poate fi configurat atât din linia de comandă, cât și printr-un limbaj specific: //Linker Command Language//. Fișierele de configurare au extensia ''​.lds''​ (ld script) și constituie o înșiruire de comenzi: 
-  * cuvânt cheie [argumente] 
-  * atribuiri unui simbol 
-  * comentarii 
- 
-=== SECTIONS === 
- 
-Secțiuniile de cod sunt definite folosind comanda ''​SECTIONS'',​ urmată de diverse atribute. Exemplu: 
-<​code>​ 
-SECTIONS 
-{ 
-       /​* ​ 
-        * „.” (contorul de locație) este setat la 0x10000 ​ 
-        * implicit, la începutul comenzii SECTIONS are valoarea 0 
-        * este incrementat automat la includerea unei secțiuni cu dimensiunea acesteia 
-        */ 
-       . = 0x10000; 
    
-       /* 
-        * adună toate secțiunile .text din fișierele de intrare ​ 
-        * și pune-le în secțiunea .text în fișierul de ieșiere 
-        * 
-        * dacă nu există nici o secțiune .text în nici un fișier de intrare 
-        * nu se generează în fișierul de ieșire secțiunea .text 
-        */ 
-       .text : { *(.text) } 
-  
-       /* adresa de la care se scriu date este 0x8000000 */ 
-       . = 0x8000000; 
-  
-        /* similar text */ 
-       .data : { *(.data) } 
-        /* similar text */ 
-       .bss : { *(.bss) } 
-} 
-</​code>​ 
- 
-=== ENTRY === 
- 
-Comanda ''​ENTRY''​ setează entry-pointul programului (prima instrucțiune de executat). Dacă comanda lipsește, atunci se va utiliza valoarea simbolului ''​.start''​ (dacă este definit), sau valoarea simbolului ''​.text''​ (dacă este definit), sau, dacă nu, valoarea 0. 
  
-=== PROVIDE === 
  
-Pentru a defini un simbol nou se poate folosi comanda ''​PROVIDE''​ în cadrul comenzii ''​SECTIONS''​. Exemplu: 
-<​code>​ 
-SECTIONS 
-{ 
-   .text : { *(.text) } 
    
-   ​PROVIDE(__data_start = .); +</​code> ​|
-   .data : { *(.data) } +
-   ​PROVIDE(__data_stop =  .); +
-  +
-   ​PROVIDE(__bss_start = .); +
-   .bss : { *(.bss) } +
-   ​PROVIDE(__bss_stop =  .); +
-+
-</​code>​ +
-Pentru a folosi simbolurile definite în exemplul anterior se poate proceda ca în exemplul următor: +
-<code c> +
-#include <​stdio.h>​+
  
-extern void * __data_start* __data_stop* __bss_start* __bss_stop;+Într-un basic block B având N predecesori P<​sub>​1</​sub>​P<​sub>​2</​sub>​...P<​sub>​N</​sub>,​ prin X = O(V<​sub>​1</​sub>,​ V<​sub>​2</​sub>,​ ..., V<​sub>​n</​sub>​) se înțelege că variabila ''​X''​ va avea valoarea V<​sub>​j</​sub>​ dacă fluxul de control intră în blocul B din blocul P<​sub>​j</​sub>,​ 1<​=j<​=N.
  
-int main(void) +===== Ierarhia de clase în LLVM ===== 
-{ +  * clasa [[http://​llvm.org/​docs/​ProgrammersManual.html#​Instruction|Instruction]] este subclasă a 
- printf(".data starts at %p and ends at %p\n", ​ &​__data_start,​ &​__data_stop)+    * clasei [[http://​llvm.org/​docs/​ProgrammersManual.html#​User|User]] care este subclasă a 
- printf(".bss starts at %p and ends at %p\n", ​ &​__bss_start,​ &​__bss_stop); +    * clasei [[http://​llvm.org/​docs/​ProgrammersManual.html#​Value|Value]] 
- +Mai jos este un exemplu de instrucțiune ​(Instruction)Ea este şi utilizator (Userale variabilelor ​(Value) a şi bÎn acelaşi timp reprezintă şi definirea variabilei (Valuec. 
- return 0; +<code asm> 
-}+%c = add i32 %a, %b
 </​code>​ </​code>​
  
-=== Atribute GCC ===+[[http://​llvm.org/​docs/​ProgrammersManual.html#​iterate-chains|Aici]] este un exemplu de cum pot fi parcurşi toţi utilizatorii unei variabile.
  
-În GCC se pot specifica pentru anumite tipuri de simboluri comportamente speciale. Sintaxa prin care se decorează un element cu un atribut este următoarea: +[[http://​llvm.org/​docs/​ProgrammersManual.html#​iterate-function|Aici]] este un exemplu de cum pot fi parcurse toate basic block-urile dintr-o funcţie.
-<​code>​ +
-type_of_element element_name __attribute__ ( attribute_goes_here ); +
-</code>+
  
-Unul din atributele pe care GCC le suportă este specificarea secțiunii în care va fi inclus simbolul generat: +[[http://llvm.org/docs/ProgrammersManual.html#​iterate-basicblock|Aici]] este un exemplu de cum pot fi parcurse toate instrucţiunile dintr-un basic block.
-<code c> +
-// definim variabila x, o inițializăm cu 7  +
-// și specificăm compilatorului să o pună în secținea ".cpl" +
-int x __attribute__ ((section (".cpl"​))) = 7; +
-</​code>​+
  
-===== Variabile ​de mediu folosite de linker =====+[[http://​llvm.org/​docs/​ProgrammersManual.html#​iterate-institer|Aici]] este un exemplu ​de cum pot fi parcurse toate instrucţiunile dintr-o funcţie.
  
-**LD_LIBRARY_PATH** ​o listă de directoare separate prin '':''​ ce specifică directoarele în care linkerul să caute biblioteci, înainte ​de a se uita în directoarele standard+[[http://​llvm.org/​docs/​ProgrammersManual.html#​iterate-preds|Aici]] este un exemplu ​de cum pot fi parcurşi toţi predecesorii şi succesorii unui basic block.
  
-**LD_PRELOAD** - calea către o bibliotecă dinamică ce va fi încărcată în locul unei alte biblioteci; spre exemplu ​astfel se poate înlocui biblioteca standard C +[[http://​llvm.org/​docs/​ProgrammersManual.html#​isa|Aici]] este un exemplu de cast folosit la exerciţiul 3.
-======= Exerciții ​de laborator (11p) =======+
  
-În rezolvarea laboratorului folosiți arhiva de sarcini {{:cpl:​labs:​lab11-tasks.zip|}}.+[[http://​llvm.org/​docs/​ProgrammersManual.html#​dss-valuemap|Aici]] este o scurtă descriere a structurii de date ValueMap folosită la exerciţiul 3.
  
-Pe măsura ce rezolvați ​exercițiile, nu uitați că modificarea optiunilor compilării implică și recompilarea fișierelor sursă. +[[http://​llvm.org/​docs/​ProgrammersManual.html#​dss-bitvector|Aici]] este o scurtă descriere a structurii de date BitVector folosită la exerciţiul 3.
-===== Exercițiul 1 - name mangling (4p) =====+
  
-Linkerul este responsabil de rezolvarea numelor. Fiecare fișier obiect conține o tabelă ​de simboluri. În continuare vom analiza cum arată aceste tabele pentru diverse tipuri de funcții în C și în C++.+====== Exerciții de laborator (15p) ======
  
-Pentru a vizualiza tabela ​de simboluri vom utiliza utilitarul ''​nm''​.+<​note>​ 
 +Laboratorul este compus dintr-o exerciții practice care includ analiza formei SSA și implementarea unor optimizări. 
 +Înainte ​de începerea exercițiilor downloadați arhiva de {{:​cpl:​labs:​lab11_2016.zip|aici}}.
  
-<note tip> +Compilati exercitiile cu -O0 pentru a nu lasa compilatorul sa aplice optimizari.
-Denumirea simbolurilor ​nu este standard și este strâns dependentă de compilator și de platformă.+
 </​note>​ </​note>​
  
-=== Name mangling în C (2p) ===+===== Exercițiul 1 =====
  
-Intrați în directorul ''​1-name/​c''​Vom analiza pe rând fiecare din cele 3 fișiere.+Scrieti un Pass de LLVM care sa elimine basic block-urile trivially deadHint: Un basic block este trivially dead daca nu exista niciun jump care sa duca catre acesta; cu alte cuvinte nu are predecesori.
  
-Pentru a genera fișierele obiect, rulați comanda ''​make obj''​Vom avea nevoie de acestea pentru a vizualiza tabelele de simboluriTabela de simboluri poate fi vizualizată rulând comanda: +<file c dead.c> 
-<code>nm -s $FIȘIER_OBIECT</code>+#​include<​stdio.h> 
 +#include<time.h> 
 +#include<stdlib.h>
  
-Deschideți fișierul ''​main-only.c''​. Programul apelează funcția ''​printf'',​ fără ​include niciun header specific. Vizualizați tabela de simboluri. Ce observați? Ce înseamnă ''​T''?​ Dar ''​U''?​ Consultați pagina de manual a utilitarului [[http://​linux.die.net/​man/​1/nm|nm]].+void main(void) { 
 +        int 1, b =2, c = 3;
  
-Deschideți fișierul ''​simple-functions.c''​. Programul definește mai multe funcții având diverse tipuri de argumente. Vizualizați tabela de simboluri. Ce observați? În ce ordine sunt listate funcțiile?+        if (a + b * (rand() % 3) == 3) { 
 +                goto end1; 
 +        } else { 
 +                goto end2; 
 +        }
  
-Deschideți fișierul ''​static-function.c''​. Programul definește o funcție statică și o apelează din funcția main. Vizualizați tabela de simboluri. Ce observați? Ce înseamnă ''​t''?​ Consultați pagina de manual a utilitarului [[http://​linux.die.net/​man/​1/​nm|nm]].+dead: 
 +        printf("​Unreachable\n"​);​
  
  
-=== Name mangling în C++ (2p===+end1: 
 +        printf("Still reachable\n"​)
 +end2: 
 +        printf("​Reachable\n"​);​ 
 +}
  
-Intrați în directorul ''​1-name/cpp''​. Vom analiza pe rând fiecare din cele 5 fișiere sursă.+</file>
  
-Pentru a genera fișierele obiect, rulați comanda ''​make obj''​. Vom avea nevoie de acestea pentru a vizualiza tabelele de simboluri. Tabela de simboluri poate fi vizualizată rulând comanda: +===== Exercițiul 2 =====
-<​code>​nm -s $FIȘIER_OBIECT</​code>​+
  
-Deschideți fișierul ''​main-only.cpp''​. Programul include header-ul ''​iostream''​ și importă în cadrul namespace-ului curent streamul ''​cout''​Vizualizați tabela ​de simboluri. Ce observați? Folosiți parametrul ''​C''​ al utilitarului ''​nm'' ​pentru a înțelege mai bine simbolistica outputuluiConsultați pagina ​de manual a utilitarului [[http://​linux.die.net/​man/​1/​nm|nm]].+Scrieti un pass care optimizeaza cazuri de tipul jump to jumpUn caz de jump to jump avem in momentul in care singura intructiune dintr-un basic block este un salt neconditionat intr-un alt blocIn acest caz toate instructiunile ​de salt din blocul initial pot fi optimizate ​pentru a duce direct la blocul destinatie. 
 +Blocul initial este in acest moment dead code si poate fi eliminat cu pass-ul ​de la exercitiul anterior.
  
-Deschideți fișierul ''​simple-functions.cpp''​Programul definește mai multe funcții având diverse tipuri de argumenteVizualizați tabela de simboluri.+<file c jump_to_jump.c> 
 +#​include<​stdio.h> 
 +#​include<​time.h> 
 +#​include<​stdlib.h>
  
-Deschideți fișierul ''​static-function.cpp''​. Programul definește o funcție statică și o apelează din funcția ​main. Vizualizați tabela de simboluri.+void main(void) { 
 +        int x = rand() % 100 - 10;
  
-Deschideți fișierele ''​myClass.hpp''​ și ''​myClass.cpp''​. Cele două definesc clasa ''​myClass''​. Vizualizați tabela de simboluri. De ce sunt două simboluri diferite pentru constructorul fără argumente? Citiți explicația de [[http://​stackoverflow.com/​questions/​6921295/​dual-emission-of-constructor-symbols/​6921467#​6921467|aici]].+        if ( x < 0 ) { 
 +                return; 
 +        } else { 
 +                printf("​Not negative\n"​);​ 
 +        }
  
-Deschideți fișierul ''​useClass.cpp''​. Programul folosește clasa definită anterior. Vizualizați tabela de simboluri. +        printf("End\n"); 
- +}
-===== Exercițiul 2 - rezolvarea simbolurilor ​(2p) ===== +
- +
-Rezolvarea simbolurilor de către linker se face în următoarea ordine: +
-  * simbolul este căutat în fișierul obiect în care este folosit prima oară +
-  * simbolul este căutat în oricare din fișierele obiect sau bibliotecile statice cu care este compilat (în ordinea în care acestea apar ca argumente ale linkerului) +
-  * simbolul este căutat în bibliotecile partajate (în ordinea în care acestea apar în linia de comandă) +
- +
-Intrați în directorul ''​2-symbols''​. Vizualizați conținutul fișierelor sursă c. O să găsiți trei implementări diferite ale funcției ''​function''​. +
- +
-Rulați comanda ''​make''​ pentru a genera: +
-  * fișierele obiect corespunzătoare fiecărui fișier sursă +
-  * biblioteca dinamică ''​libA.so''​ ce implementează funcția ''​function''​ în fișierul ''​a.c''​ +
-  * biblioteca dinamică ''​libB.so''​ ce implementează funcția ''​function''​ în fișierul ''​b.c''​ +
-  * executabilul ''​exe12''​ compilat astfel ''​gcc main.o -lA -lB -L. -o exe12''​ +
-  * executabilul ''​exe21''​ compilat astfel ''​gcc main.o -lB -lA -L. -o exe21''​ +
-  * executabilul ''​exe-static''​ compilat astfel ''​gcc main.o c.o -lA -lB -L. -o exe-static''​ +
- +
-Setați variabila de mediu [[http://​ocw.cs.pub.ro/​courses/​cpl/​labs/​12?&#​variabile_de_mediu_folosite_de_linker|LD_LIBRARY_PATH]] corespunzător,​ pentru ca linkerul să găsească bibliotecile generate în directorul curent. +
- +
-Executabilul ''​exe12''​ a fost generat linkând 2 biblioteci dinamice ce implementează funcția ''​function''​. Care este ordinea bibliotecilor dinamice în linia de comandă? Rulați binarul pentru a observa outputul produs. +
- +
-Executabilul ''​exe21''​ a fost generat linkând 2 biblioteci dinamice ce implementează funcția ''​function''​. Care este ordinea bibliotecilor dinamice în linia de comandă? Rulați binarul pentru a observa outputul produs. +
- +
-Executabilul ''​exe-static''​ a fost generat linkând 2 biblioteci dinamice și un fișier obiect ce implementează funcția ''​function''​. În ce ordine au fost specificate în linia de comandă? Rulați binarul pentru a observa outputul produs. +
- +
- +
-===== Exercițiul 3 - instrumentarea binarelor (3p) ===== +
- +
-Presupunem că avem un set de binare ale unei aplicații/​biblioteci ale cărei surse nu le mai avem (de ex. am cumpărat licența pentru o bibliotecă proprietară și compania care producea acea bibliotecă s-a închis). Dorim să modificăm aplicația/​biblioteca,​ fără a modifica acele binare. :) +
- +
-Neavând acces la surse putem spune linkerului să routeze toate apelurile către un anumit simbol către o funcție specială aflată sub controlul nostru. Dacă se trimite argumentul ''​--wrap=symbol''​ linkerului acesta: +
-  * va înlocui toate referințele către simbolul ''​symbol''​ cu referințe către ''​%%__%%wrap_symbol''​ +
-  * va genera un simbol ''​%%__%%real_symbol''​ care va trimite către varianta originală a lui ''​symbol''​ +
- +
-Intrați în directorul ''​3-wrap''​. Vizualizați fișierul ''​main.c''​. Programul încearcă să scrie un mesaj în fișierul ''/​bad/​path''​ și eșuează din lipsa permisiunilor și a existenței directoarelor superioare. Pentru a compila fișierul, rulați comanda ''​make default''​ și rulați executabilul ''​exe_default''​. +
- +
-**Fără a modifica fișierul ''​main.c''​**,​ veți suprascrie funcția ''​open''​ așa cum am descris mai sus, astfel încât să obțineți un file descriptor valid. Implementarea o veți realiza în cadrul fișierului ''​wrap.c''​. Pentru a compila, rulați comanda ''​make wrapped'',​ apoi rulați executabilul ''​exe_wrapped''​. +
- +
-<note tip> **Hint**: man ld, apoi căutați --wrap </​note>​ +
-===== Exercițiul 4 - Linker Command Language (2p) ===== +
- +
-<​note>​  +
-Dacă lucrați pe calculatoarele proprii și acestea sunt pe 64 de biți, instalați gcc-multilib pentru a putea compila cross-platform:​ +
-**sudo apt-get install gcc-multilib** +
-</​note>​ +
- +
-Consultați secțiunea [[http://​ocw.cs.pub.ro/​courses/​cpl/​labs/​11?&#​linker_command_language|Linker Command Language]] din laborator. +
- +
-Intrați în directorul ''​4-lcl''​. Aveți definit în cadrul fișierului ''​def.h''​ un macro care primește numele unei funcții și generează un pointer la funcție și-l pune în secținuea ''​.cpl'':​ +
-<code c> +
-// def.h +
-#define MODULE_INIT(X) ​\ +
-  static void * __ptr_init_cpl_##​X __attribute__ ((section (".cpl"))) = &X+
-</​code>​ +
-De asemenea, în director veți găsi un set de fișiere cu funcții de inițializare pentru care s-a apelat macro-ul de mai sus. +
- +
-Practic, în secțiunea ''​.cpl''​ se vor adăuga pointeri către toate funcțiile. +
- +
-Modificați scriptul de linker din cadrul directorului pentru a introduce o secțiune de date specială ''​.cpl''​ care să adune secțiunile ''​.cpl''​ din fiecare fișier obiect de intrare, conform **TODO-ului** din script (revedeți secțiunea [[http://​ocw.cs.pub.ro/​courses/​cpl/​labs/​11#​provide|PROVIDE]]). Apoi, în cadrul fișierului ''​main.c'',​ exportați două simboluri pentru începutul și sfârșitul acestei secțiuni și iterați prin toți pointerii, apelând pe rând funcția către care aceștia trimit (puteți pasa orice întreg ca parametru). +
-===== BONUS ===== +
-=== 1 cpl karma - LD_PRELOAD ===+
  
-Scopul acestui exercițiu este de a suprascrie o funcție de bibliotecă (similar ca la exercițiul 3).+</​file>​
  
-Implementați acest lucru folosindu-vă de variabila de mediu ''​LD_PRELOAD''​.+===== Exercițiul 3 =====
  
-=== 1 cpl karma - rpath ===+Folosind codul din fişierul ''​Hello.cpp''​ din arhiva laboratorului,​ urmăriţi modul în care poate fi implementată simple constant propagation în LLVM. 
 +  * cum poate fi identificată o instrucţiune inutilă? 
 +  * identificaţi metoda responsabilă pentru constant folding 
 +  * cum se înlocuiesc apariţiile viitoare ale variabilei în cauză cu o constantă?​ 
 +  * de ce se adaugă din nou în worklist unele instrucţiuni?​ 
 +  * Rulaţi acest pas folosind fisierul ''​test.c''​ din arhiva laboratorului.
  
-Refaceți exercițiul 2 fără a folosi variabila de mediu ''​LD_LIBRARY_PATH''​. ​ 
  
-Folosiți-vă de [[https://​en.wikipedia.org/​wiki/​Rpath|run-time search path]]. 
-Citiți secțiunea ''​GNU ld.so''​ pentru a înțelege algoritmul aplicat de linker pentru a căuta biblioteci dinamice. 
  
cpl/labs/11.1452028581.txt.gz · Last modified: 2016/01/05 23:16 by razvan.crainea
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