This shows you the differences between two versions of the page.
systems:uso:laboratoare:laborator-06 [2012/09/23 20:14] ioan.eftimie [2. Make pentru automatizări] |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== [importat] Laborator 06 - Compilare; Makefile; gcc ====== | ||
- | <hidden> | ||
- | ** Descriere ** | ||
- | * compilare, asamblare, link-editare, gcc | ||
- | * make | ||
- | * variabile Makefile | ||
- | * strace | ||
- | * biblioteci | ||
- | * ldd | ||
- | * nm | ||
- | * Biblioteci statice | ||
- | * Biblioteci dinamice | ||
- | |||
- | * Instalare din surse | ||
- | * make pentru deploy: make archive, make scp | ||
- | |||
- | ** Referințe ** | ||
- | * vim (lab 03) | ||
- | * cp, cat, touch (lab 02) | ||
- | * tar (lab 02) | ||
- | * scp (lab 04) | ||
- | |||
- | ** TODO ** | ||
- | * mai perie din text, este prea mult | ||
- | </hidden> | ||
- | |||
- | ======= Tutorial ====== | ||
- | |||
- | ===== 1. Compilatorul gcc. ===== | ||
- | |||
- | <hidden> | ||
- | * conceptul de compilare | ||
- | * NU se va insista pe diferența între compilare și interpretare | ||
- | </hidden> | ||
- | |||
- | Descărcați arhiva ''TODO''. Dezarhivați folosind tar: | ||
- | |||
- | <code bash> | ||
- | tar xf lab06.tar.gz | ||
- | </code> | ||
- | |||
- | Intrați în directorul ''tutorial/2''. Fișierul ''hello.c'' este o sursă C completă. Compilați fișierul utilizând ''gcc'': | ||
- | |||
- | <code bash> | ||
- | gcc hello.c | ||
- | </code> | ||
- | |||
- | Ce fișiere s-au creat? Aflați tipul acestora utilizând comanda ''file''. | ||
- | |||
- | <code bash> | ||
- | ls -l | ||
- | file a.out | ||
- | </code> | ||
- | |||
- | Rulați fișierele executabile. | ||
- | |||
- | <code bash> | ||
- | ./a.out | ||
- | </code> | ||
- | |||
- | Compilați sursa specificând și numele fișierului executabil: | ||
- | |||
- | <code bash> | ||
- | gcc hello.c -o stallman | ||
- | </code> | ||
- | |||
- | Rulați executabilul creat. | ||
- | |||
- | Compilați sursa afișând avertismente: | ||
- | |||
- | <code bash> | ||
- | gcc hello.c -o linus -Wall | ||
- | </code> | ||
- | |||
- | <note tip> | ||
- | Deoarece un program pentru care sunt afișate avertismente în timpul compilării conține (foarte probabil) o greșeală/un bug, se recomandă folosirea opțiunii ''-Wall'' pentru fiecare compilare. | ||
- | </note> | ||
- | |||
- | <note>**Pentru acasă**: aflați ce fac flag-urile ''-Wextra'' și ''-Werror''.</note> | ||
- | |||
- | Corectați warning-urile apărute. Recompilați sursa. | ||
- | |||
- | Rulați comenzile următoare: | ||
- | |||
- | <code bash> | ||
- | gcc -o jobs -Wall hello.c | ||
- | gcc -Wall -o turing hello.c | ||
- | gcc -Wall hello.c -o dijkstra | ||
- | </code> | ||
- | |||
- | Utilizând ''ls -l'' comparați dimensiunile celor 3 executabile create. Verificați că cele 3 executabile sunt identice vizualizând output-ul fiecăruia. | ||
- | |||
- | <note warning> | ||
- | Faptul că două executabile au aceeași dimensiune și același comportament (output) nu înseamnă neapărat că ele sunt identice. | ||
- | </note> | ||
- | |||
- | Calculați md5-ul fiecărui executabil și verificați că este identic. | ||
- | |||
- | <note tip> | ||
- | Folosind comanda ''md5sum'' putem obține un rezumat al conținutului unui fișier. Acest rezumat, cunoscut și sub numele de hash, are proprietatea interesantă că cea mai mică modificare posibilă în fișierul inițial va duce la modificarea completă a md5-ului. Din acest motiv, 2 fișiere cu același md5 pot fi considerate identice. | ||
- | </note> | ||
- | |||
- | <hidden> | ||
- | Dacă vedem că grupa e genială le putem zice ceva de birthday paradox și de faptul că 2 md5-uri identice nu provin neapărat de la același fișier. | ||
- | </hidden> | ||
- | |||
- | În general, ordinea parametrilor nu contează. Anumiți parametri au argumente. Aici avem parametrul ''-o''. Acest argument __trebuie__ urmat întotdeauna de numele executabilului. | ||
- | |||
- | ===== 2. Etapele compilării ===== | ||
- | |||
- | <hidden> | ||
- | Etapele compilării: ce se întâmplă în fiecare, de ce e necesară | ||
- | </hidden> | ||
- | |||
- | Intrați în directorul ''tutorial/3/1''. Vizualizați conținutul fișierului ''answer.c''. Rulați comanda | ||
- | |||
- | <code bash> | ||
- | gcc -E answer.c -o answer.i | ||
- | </code> | ||
- | |||
- | Cum s-a modificat fișierul sursă în urma etapei de __preprocesare__? Ce tip are fișierul generat (folosiți ''file'')? | ||
- | |||
- | <note> | ||
- | Etapa de preprocesare realizează substituții la nivelul codului pentru a expanda macro-uri, a include fișiere antet, etc. | ||
- | </note> | ||
- | |||
- | Intrați în directorul ''tutorial/3/2''. Vizualizați conținutul fișierului ''answer.c''. Rulați comanda | ||
- | |||
- | <code bash> | ||
- | gcc -S answer.c -o answer.s | ||
- | </code> | ||
- | |||
- | Ce tip are fișierul generat în etapa de __compilare__? | ||
- | |||
- | <note> | ||
- | Etapa de compilare transformă codul C în cod în limbaj de asamblare, dependent de sistemul pentru care se face compilarea. | ||
- | </note> | ||
- | |||
- | Folosind ''gcc'' efectuați etapa de __asamblare__: | ||
- | |||
- | <code bash> | ||
- | gcc -c answer.s -o answer.o | ||
- | </code> | ||
- | |||
- | Ce tip are fișierul obținut? | ||
- | |||
- | <note> | ||
- | Etapa de asamblare transformă codul în limbaj de asamblare în cod mașină, cod binar înțeles doar de calculator. | ||
- | </note> | ||
- | <note> | ||
- | Codul rezultat nu este întotdeauna complet, de exemplu, în cazul folosirii unei funcții definite în alt fișier ''.c'' adresa acestei funcții nu va fi cunoscută în momentul asamblării fișierului. Ea va fi completată în etapa următoare | ||
- | </note> | ||
- | |||
- | Ultima etapă din procesul de compilare este cea de __link-editare__. Folosind ''gcc'', invocați această etapă pentru a obține executabilul final: | ||
- | |||
- | <code bash> | ||
- | gcc answer.o | ||
- | </code> | ||
- | |||
- | Ce fișier a fost creat? Ce tip are acest fișier? Rulați executabilul. | ||
- | |||
- | <note> | ||
- | Etapa de link-editare presupune completarea codului mașină generat în etapa anterioară prin includerea informațiilor din alte surse compilate, biblioteci, etc... | ||
- | </note> | ||
- | |||
- | ===== 4. Makefile ===== | ||
- | |||
- | <hidden> | ||
- | Exercițiul este lung lung. Mergem lent, avem grijă să nu rămână prea mult în urmă. | ||
- | |||
- | Putem face analogia Makefile=Rețetă | ||
- | </hidden> | ||
- | |||
- | Intrați în directorul ''tutorial/4/1''. Fișierul ''sandwich.c'' conține un program C valid. Rulați | ||
- | |||
- | <code bash> | ||
- | make sandwich | ||
- | </code> | ||
- | |||
- | și vizualizați fișierele create (utilizând ''file'' pentru a vedea tipul acestora). Ce comandă a fost folosită pentru a compila sursa? | ||
- | |||
- | <note tip> | ||
- | Atunci când un fișier ''Makefile'' nu există și nu este specificat, ''make'' va folosi comenzi implicite pentru build, în funcție de extensia fișierului primit ca parametru. În acest caz, comanda implicită a fost ''cc sandwich.c -o sandwich''. | ||
- | </note> | ||
- | |||
- | Intrați în directorul ''tutorial/4/2''. Fișierul ''Makefile'' va fi folosit pentru a compila sursa ''sandwich.c''. Rulați comenzile: | ||
- | |||
- | <code bash> | ||
- | make | ||
- | ./sandwich | ||
- | </code> | ||
- | |||
- | Vizualizați fișierul ''Makefile''. Fișierul conține o singură linie ''sandwich:''. Aceasta îi indică utilitarului ''make'' să execute //target-ul// ''sandwich''. Deoarece nu există altceva, se va încerca folosirea unei reguli implicite. | ||
- | |||
- | Care este regula implicită folosită? (afișată în terminal). Diferă fată de cea anterioară? | ||
- | |||
- | <hidden> | ||
- | Target-ul ar putea fi asociat cu o etapă dintr-o rețetă (think cakes) | ||
- | </hidden> | ||
- | |||
- | Intrați în directorul ''tutorial/4/3''. Fisierul ''Makefile'' a fost completat astfel încât să se precizeze explicit ce sursă se compilează, pentru a putea diferenția între cele 2 surse prezente în director, ''sandwich.c'' și ''reply.c''. Rulați comenzile următoare și urmăriți ce se execută: | ||
- | |||
- | <code bash> | ||
- | make | ||
- | ls | ||
- | rm sandwich | ||
- | make sandwich | ||
- | ls | ||
- | make reply | ||
- | ls | ||
- | ./sandwich | ||
- | ./reply | ||
- | </code> | ||
- | |||
- | Un target poate avea o listă de dependențe. Acestea se scriu pe aceeași linie, după '':''. | ||
- | |||
- | <note tip> | ||
- | Observați că dacă nu precizăm un target se va executa primul prezentat în Makefile. | ||
- | </note> | ||
- | |||
- | Modificați ''reply.c'' adăugând porțiunea comentată. Rulați comenzile | ||
- | |||
- | <code bash> | ||
- | make reply | ||
- | make reply | ||
- | </code> | ||
- | |||
- | Deoarece între cele 2 comenzi nu am modificat sursa, nu se va executa nimic pentru a doua comandă. | ||
- | |||
- | <hidden> | ||
- | Le zicem că ''make'' are un beneficiu când vrem să compilăm din surse multiple. Le zicem de ce se recomandă să compilăm doar ce s-a modificat. | ||
- | </hidden> | ||
- | |||
- | Intrați în directorul ''tutorial/4/5''. Fișierul ''Makefile'' a fost completat astfel încât să se poată construi ambele executabile cu aceeași comandă și pentru a putea șterge toate fisierele create pe baza surselor. Rulați comenzile | ||
- | |||
- | <code bash> | ||
- | ls | ||
- | make | ||
- | ls | ||
- | make clean | ||
- | ls | ||
- | </code> | ||
- | |||
- | |||
- | Pentru targetul ''clean'' s-au introdus și instrucțiunile necesare producerii acestuia din dependențe. Acestea se pun //indentate cu un tab// pe liniile imediat următoare definirii targetului. | ||
- | |||
- | Intrați în directorul ''tutorial/4/6''. Fișierul ''Makefile'' este identic cu cel din exercițiul anterior (verificați cu ''diff''). Rulați comenzile | ||
- | |||
- | <code bash> | ||
- | touch clean | ||
- | ls | ||
- | make | ||
- | ls | ||
- | make clean | ||
- | ls | ||
- | </code> | ||
- | |||
- | <note warning>Verificați că ați rulat ''touch clean'' !!</note> | ||
- | |||
- | De ce nu se revine la situația initială a fișierelor din director? | ||
- | |||
- | Redenumiți fișierul ''Makefile.good'' în ''Makefile'' și rulați din nou comenzile de mai sus. Singura diferență este linia ''.PHONY: all clean''. | ||
- | |||
- | <note tip>Se recomandă includerea în ''.PHONY'' a tuturor target-urilor ce nu reprezintă fișiere pentru a preveni situația în care un fișier cu același nume există în directorul curent și targetul nu se va executa.</note> | ||
- | |||
- | <hidden> | ||
- | Variabile makefile. Câteva enumerate. Mod de folosire și definire. | ||
- | </hidden> | ||
- | |||
- | Intrați în directorul ''tutorial/4/7''. Rulați comenzile următoare și observați ce se execută: | ||
- | |||
- | <code bash> | ||
- | make story | ||
- | rm story | ||
- | make story CFLAGS=-Wall | ||
- | rm story | ||
- | make story CC=gcc | ||
- | rm story | ||
- | make story CC=gcc CFLAGS="-Wall -Wextra -O3" | ||
- | </code> | ||
- | |||
- | Ați folosit variabile ''Makefile'' pentru a modifica regula implicită. | ||
- | |||
- | Un mic grafic cu etapele compilării și legătura dintre acestea și variabilele makefile este următorul | ||
- | |||
- | {{http://elf.cs.pub.ro/uso/store/compilare.png}} | ||
- | |||
- | <hidden> | ||
- | Asistentul va explica variabilele ''Makefile'', va prezenta unde se află fiecare (în ce etapă de compilare), ce înseamnă fiecare. | ||
- | </hidden> | ||
- | |||
- | ===== 5. Apeluri de sistem ===== | ||
- | |||
- | <hidden> | ||
- | Ce înseamnă apel de sistem? Explicații la nivel de an 1, fără prea multe detalii. | ||
- | </hidden> | ||
- | |||
- | Intrați în directorul ''tutorial/5''. Compilați fișierul ''traceable.c'' utilizând ''gcc'', salvând executabilul ca ''traceme''. Rulați comenzile: | ||
- | |||
- | <code bash> | ||
- | ./traceme | ||
- | strace ./traceme | ||
- | </code> | ||
- | |||
- | Utilitarul ''strace'' permite evidențierea fiecărui apel de sistem din execuția programului. În cazul nostru, ''printf'' se reduce la un apel ''write''. | ||
- | |||
- | Putem evidenția doar anumite apeluri de sistem folosind argumentul ''-e <nume_apel>''. Rulați comanda: | ||
- | |||
- | <code bash> | ||
- | strace -e write ./traceme | ||
- | </code> | ||
- | |||
- | ===== 6. Biblioteci. nm. ldd. Legare dinamică/statică ===== | ||
- | |||
- | <hidden> | ||
- | De ce folosim biblioteci. Code reuse. | ||
- | </hidden> | ||
- | |||
- | Intrați în directorul ''tutorial/6''. Compilați sursa ''math.c'' utilizând ''gcc'': | ||
- | |||
- | <code bash> | ||
- | gcc math.c | ||
- | </code> | ||
- | |||
- | Se observă că avem o eroare ''undefined reference to `sinh'''. Compilați programul până la modulul obiect: | ||
- | |||
- | <code bash> | ||
- | gcc -c math.c -o math.o | ||
- | </code> | ||
- | |||
- | Utilitarul ''nm'' listează simbolurile dintr-un fișier obiect sau dintr-un executabil. Rulați comanda: | ||
- | |||
- | <code bash> | ||
- | nm math.o | ||
- | </code> | ||
- | |||
- | Liniile ce conțin 'U' listează simbolurile folosite dar nedefinite în ''math.o''. Liniile cu 'T' listează simbolurile definite (translatate). | ||
- | |||
- | Pentru a putea obține executabilul va trebui să definim și simbolul ''sinh''. Funcția este definită în biblioteca matematică ''libm.so''. Pentru a da biblioteca ca argument lui ''gcc'' pentru etapa de link-editare va trebui să folosim argumentul ''-l'' astfel: | ||
- | * din numele bibliotecii se elimină extensia și prefixul ''lib''. | ||
- | * ce rămâne se concatenează la ''-l'' | ||
- | |||
- | |||
- | Rulați: | ||
- | |||
- | <code bash> | ||
- | gcc -lm math.c | ||
- | </code> | ||
- | |||
- | Compilarea reușește. Pentru a vedea bibliotecile folosite de executabil vom folosi un alt utilitar - ''ldd'': | ||
- | |||
- | <code bash> | ||
- | ldd a.out | ||
- | </code> | ||
- | |||
- | <hidden> | ||
- | Asistentul va explica ce conțin ''libm'', ''linux-gate'' și ''libc''. | ||
- | </hidden> | ||
- | |||
- | Folosiți următoarea comandă pentru a compila sursa static: | ||
- | |||
- | <code bash> | ||
- | gcc math.c -static -o staticmath -lm | ||
- | </code> | ||
- | |||
- | Comparați dimensiunile fișierelor ''a.out'' și ''staticmath''. | ||
- | |||
- | <note tip> | ||
- | Executabilul ''a.out'' este mai mic decât ''staticmath'', deoarece nu conține codul obiect pentru biblioteca math. | ||
- | </note> | ||
- | |||
- | Pentru a observa simbolurile definite în executabilul legat static, rulați: | ||
- | |||
- | <code bash> | ||
- | ldd staticmath | ||
- | </code> | ||
- | |||
- | <hidden> | ||
- | Asistentul va explica diferențele între executabile dinamice și executabile statice. | ||
- | </hidden> | ||
- | |||
- | ====== Exerciții ====== | ||
- | <note>Următoarele exerciții vor simula rezolvarea unei teme de casă</note> | ||
- | |||
- | ===== 1. Organizare proiect. Fișiere multiple ===== | ||
- | |||
- | Creați fișierele ''README'', ''tema.c'', ''util.c'', ''util.h''. Fișierul ''tema.c'' va include headerul ''util.h'' și va defini funcția ''main''. | ||
- | |||
- | <solution> | ||
- | <code bash> | ||
- | touch README tema.c util.c util.h | ||
- | </code> | ||
- | <code C tema.c> | ||
- | #include "util.h" | ||
- | |||
- | int main() { | ||
- | return 0; | ||
- | } | ||
- | </code> | ||
- | </solution> | ||
- | |||
- | Adăugați un fișier ''Makefile'' pentru compilarea sursei. | ||
- | |||
- | <solution> | ||
- | <code make> | ||
- | tema: | ||
- | </code> | ||
- | </solution> | ||
- | |||
- | Modificați regula de compilare pentru a compila ambele fișiere ''.c''. | ||
- | <solution> | ||
- | <code make> | ||
- | tema: tema.c util.c | ||
- | </code> | ||
- | </solution> | ||
- | |||
- | ===== 2. Make pentru automatizări ===== | ||
- | |||
- | Adăugați o regulă ''clean'' pentru ștergerea fișierelor generate. | ||
- | |||
- | <solution> | ||
- | <code make> | ||
- | clean: | ||
- | rm *.o tema | ||
- | </code> | ||
- | </solution> | ||
- | |||
- | Pentru trimiterea temei spre corectare, fișierele vor fi arhivate. Adăugați o regulă ''pack'' în acest sens. Fișierul arhivă se va numi: ''tema.tar.gz''. | ||
- | |||
- | <solution> | ||
- | <code make> | ||
- | pack: | ||
- | tar czf tema.tar.gz | ||
- | </code> | ||
- | </solution> | ||
- | |||
- | Corectarea făcându-se automat (teoretic), adăugați o regulă ''send'' care copiază arhiva pe serverul ''corector.local'', folosind SCP și utilizatorul ''student'' cu parola ''student''. Regula va crea arhiva, dacă aceasta nu există. | ||
- | |||
- | <note tip>Revizuiți copierea fișierelor prin SSH, din laboratorul anterior.</note> | ||
- | |||
- | <solution> | ||
- | <code make> | ||
- | send: pack | ||
- | scp tema.tar.gz student:student@corector.local | ||
- | </code> | ||
- | </solution> | ||
- | |||
- | ===== 3. Variabile make ===== | ||
- | |||
- | Modificați fișierul ''Makefile'' astfel încât denumirea arhivei să conțină numele vostru și numele materiei, //uso//. | ||
- | |||
- | <solution> | ||
- | <code make> | ||
- | STUDENT=ion.popescu | ||
- | MATERIE=uso | ||
- | ARHIVA=$((MATERIE))_$((STUDENT)) | ||
- | |||
- | pack: | ||
- | tar czf $((ARHIVA)).tar.gz README tema.c util.c util.h | ||
- | </code> | ||
- | </solution> | ||
- | |||
- | Definiți o variabilă care să conțină numele fișierelor ce vor intra în arhivă. Modificați regula ''pack'' astfel încât să nu regenereze arhiva, decât atunci când unul dintre aceste fișiere s-a modificat. | ||
- | |||
- | <solution> | ||
- | <code make> | ||
- | FISIERE=README Makefile tema.c util.c util.h | ||
- | |||
- | pack: $((FISIERE)) | ||
- | tar czf $((ARHIVA)).tar.gz $((FISIERE)) | ||
- | </code> | ||
- | </solution> |