Monthly Archives: December 2014

[ PR0GRAMMAZi0NE UNiX: rIPASSO C ] – [ PARTE III : GCC Compilation step ]


----------------------[ PR0GRAMMAZi0NE UNiX: rIPASSO C ]----------------------
---------------------[ PARTE III : GCC Compilation step ]---------------------
--------------------------------[ abnm3nt4l1 ]--------------------------------

Indice

00. Introduzione
01. Consumo
03. Basi del processo di compilazione
04. Preprocessing, compilazione, assembly, Linking
05. Qualche esempio

00. iNtrODUZIONE
------------

Nel precedente post abbiamo trattato in maniera sommaria il "three-stage" process
di creazione di un programma binario eseguibile a partire dal suo codice sorgente
in C.
Di questi tre passi, il secondo, quello di compilazione del codice sorgente
che ha come risultato la produzione di un file binario verra' descritto nel
seguito del presente articolo in relazione a cio' che viene effettuato dal
compilatore GCC dietro le quinte.

02. cONSUMO
-------

1) ll contenuto di una caffettiera da 3 di caffe' 100% arabica tostatura forte
2) Una tazza di te' verde
3) ... acqua! (ragazzi sono appena trascorti 3 intensi giorni di mangiate
e bevute a base alcolica)

03. Basi del processo di compilazione
---------------------------------

In principio fu:

$ gcc -o salutomondo salutomondo.c

$ ./salutomondo
Hello, World!

Il sorgente e' il medesimo mostrato nel primo post della serie.

Ad ogni modo abbiamo un banalissimo programma scritto in C che stampa
sul vostro terminale la stringa "Hello, World!" seguita dalla sequenza
di escape \n.

Il codice sorgente viene compilato per mezzo di gcc e prodotto il file
eseguibile di output (-o) salutomondo.

Nel seguito diamo un'occhiata a cio' che esegue gcc (talvolta avvalendosi
di altri strumenti) per generare il file eseguibile salutomondo a partire
dal file sorgente salutomondo.c.

A tal riguardo aggiungiamo un'opzione alla nostra linea di commando per
istruire il nostro compilatore affinche' salvi i file temporanei che genera
nel corso della compilazione di un sorgente C, --save-temps.

$ gcc -save-temps -o salutomondo salutomondo.c

$ ls salutomondo*
salutomondo salutomondo.c salutomondo.i salutomondo.o salutomondo.s

A differenza dall'esecuzione del primo comando, ora nella directory nella quale abbiamo
eseguito la compilazione oltra al file sorgente salutomondo.c ed eseguibile salutomondo
troviamo altri 3 file con estensione .i, .o e .s.
Prima di procedere con la verifica del contenuto dei singoli file riporto il contenuto
della man page di gcc per comprendere cosa effettivamente gcc quando invocato Con
l'opzione -save-temps.

-save-temps
-save-temps=cwd
Store the usual "temporary" intermediate files permanently; place them in
the current directory and name them based on the source file.
Thus, compiling foo.c with -c -save-temps produces files foo.i and foo.s,
as well as foo.o. This creates a preprocessed foo.i output file even
though the compiler now normally uses an integrated preprocessor.

Quindi in poche parole non facciamo altro che indicare al compilatore di salvare
tutti i file temporanei che genera in ciascuno dei 4 passi che compie nel corso
del processo di compilazione e che elimina quando giunge al termine dello stesso.

04. Preprocessing, compilazione, assembly, Linking

Gli step che il compilatore compie sono:

1. Preprocessing;
2. Compilazione;
3. Assembly;
4. Linking.

1. Preprocessing

In questa fase tutti gli header file che si sono inclusi e utilizzati nel proprio
programma vengono espansi e inclusi nella posizione in cui compare la direttiva
#include relativa all'header stesso.
Oltre questo e' nel corso di questa fase che i valori di costanti e macro vengono
sostituiti e i commenti rimossi.

Il file intermedio generato dalla fase di preprocessing e' quello con estensione .i.

2. Fase di compilazione

In questa fase il compilatore compila il sorgente producendo il codice assembly.
Il codice assembly consta di un insieme di istruzioni che determinano cio' che
il programma fa. Ciascuna piattaforma ha un specifico insieme di istruzioni
assembly che vengono direttamente mappate in istruzioni relative processore.

Vediamo il contenuto del file salutomondo.s

.file "salutomondo.c"
.section .rodata
.LC0:
.string "Hello, World!"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: what fuck?"
.section .note.GNU-stack,"",@progbits

3. Assemblaggio (assembly step)

A questo punto, un compilatore particolare, l'assemblatore converte le istruzioni
assembly generate nella fase di compilazione e le converte nelle corrispondenti
istruzioni macchina o meglio un bit stream che consta di 0 e 1. Questo codice,
direttamente comprensibile al processore, viene salvato nel file oggetto salutomondo.o.
Questo file e' costituito da differenti sezioni che il processore utilizza durante
l'esecuzione del programma.

Il contenuto di questo file non risulta leggibile e interpretabile all'uomo per mezzo
di strumenti quali editor a eccezione di pochi simboli (come printf, main o stringhe).

4. Linking

Eccoci all'ultimo step del processo di compilazione.
Il file oggetto contiene il codice macchina prodotto dall'assemblatore alla fase
precedente; il linker si preoccupa di garantire che tutti i simboli non
definiti nel sorgente perche' inclusi da librerie esterne vengano risolti
unendo tra loro i diversi file oggetto e creando un unico file eseguibile.

I 4 step possono essere eseguito singolarmente utilizzando gcc con le opzioni
-E, -C e -S e il linker ld separatamente. Vedremo nel seguito degli esempi.

05. Qualche esempio
---------------

-- Creiamo l'eseguibile salutomondo con un solo comando preservando i file
temporanei generati dagli step intermedi

$ gcc -save-temps salutomondo.c -o salutomondo

-- Creiamo un eseguibile eseguendo manualmente le singole fasi che dal sorgente
conducono alla creazione dell'eseguibile

a. preprocessore

$ cpp salutomondo.c > salutomondo.i

oppure

$ gcc -E salutomondo.c -o salutomondo.i

b. compilazione

$ gcc -S salutomondo.i -o salutomondo.s

c. assemblaggio

$ as salutomondo.s -o salutomondo.o

d. linking

$ ld -o salutomondo -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib64/crt1.o /usr/lib64/crti.o salutomondo.o -lc /usr/lib64/crtn.o

Provate e divertitevi!

— abnm3nt4l1st

[ PR0GRAMMAZi0NE UNiX: rIPASSO C ][ PARTE II : Problem officer! ]


----------------------[ PR0GRAMMAZi0NE UNiX: rIPASSO C ]----------------------
---------------------[ PARTE II : Problem officer! ]---------------------
--------------------------------[ abnm3nt4l1 ]--------------------------------

Indice

00. Introduzione
01. Consumo
03. Compilazione e Linking
04. Cenni sulla gestione degli errori e il debugging

00. iNtrODUZIONE
------------

Nulla da dichiarare!

02. cONSUMO
-------

1) ll contenuto di una caffettiera da 3 di caffe' 100% arabica tostatura forte
2) Una tazza di te' verde
3) biscotti di cereali integrali e frutta (con moderazione. Sono a dieta)

03. cOMP1L4zione 3 lINKING
----------------------

Per ottenere un eseguibile ossia un programma in linguaggio macchina direttamente
comprensibile al nostro computer occorre compiere 3 step.

Per prima cosa dobbiamo avere il file sorgente del nostro programma scritto in C
che per consuetudine terminano con estensione .c .
Il sorgente viene compilato dal compilatore C che genera un nuovo file, il file
oggetto che sui sistemi linux ha estensione .o .
Il file oggetto e' un file binario che non puo' pero' essere eseguito in
quanto potrebbe non essere completo di tutte le librerie che vengono incluse
durante l'ultimo step quello di linking. L'operazione di linking e' eseguita
da un programma speciale chiamato linker che viene generalmente incluso
nel package del compilatore.
Il linker, (ld su Linux) e' usato per unire tra loro i file oggetto, la
libreria standard C e altre librerie generate dall'utente per creare il
file eseguibile - il codice binario.
In questo passo, il codice binario delle librerie che sono invocate dal
sorgente vengono combinate con il file oggetto generato a partire dal sorgente
stesso.
Il risultato e' salvato in un nuovo file - il file eseguibile.
Occorre tenere a mente che i file oggetto e quelli eseguibili sono dipendenti
dall'architettura hardware e dal sistema; non possono essere semplicemente
copiati e trasferiti su piattaforme differenti nonostante lo sia (nel caso
in cui si sia fatto solo impiego della libreria ANSI C) il codice sorgente.

04. cENNI sULLa GeSTIOnE dEGLI eRRoRI e iL DeBUGGINg
------------------------------------------------

Una volta terminata la stesura del programma ci capitera' in fase di
compilazione di incorrere in diversi messaggi di errore o di warning.
Non preoccupatevi siamo umani! Pensate solo a gioire del fatto che il
compilatore si preoccupera' con voi di verificare la correttezza di quanto avete
prodotto prima di farvi fare ulteriori ....

Il compilatore infatti si preoccupera' di verificare la correttezza sintatica
del programma mentre il linker generera' degli errori qualora non sia possibile
recuperare le librerie contenenti la definizione delle funzioni che avete utilizzato.
Per esempio se utilizzate la funzione matematica sin() e avete sbagliato a digitare
scrivendo sinn() nel vostro codice la compilazione si interrompera' con un messaggio
simile a questo:

/tmp/ccLySlSb.o: In function `main':
error.c:(.text+0xf): undefined reference to `sinn'

Tutti gli errori individuati (dal compilatore o dal linker) devono essere corretti
prima che possa essere generato il file eseguibile.

Gli errori presenti nei programmi vengono chiamati BuG. In molti casi questi
verranno individuati da compilatore o linker ma molto piu' spesso di quanto
ci si possa aspettare la generazione dell'eseguibile giungera' a termine pero'
il programma non si comportera' come atteso e restituira' risultati non corretti.
Per individuare questi errori nascosti e' necessario utilizzare un debugger.

Generalmente la suite di sviluppo oltre al compilatore prevedere anche la
disponibilita' di un debugger.
Il debugger consente l'esecuzione del programma una riga per volta cosi'
da poter verificare cosa viene effettuato operazione per operazione.
Grazie al debugger e' poi possibile verificare il contenuto di variabili,
alterarne il contenuto, bloccare l'esecuzione di un programma in punti
o condizioni particolari.

In seguito tratteremo nel dettaglio l'utilizzo del debugger della
famiglia GNU: GDB.

Have Fun ;-)

-- abnm3nt4l1

[ PR0GRAMMAZi0NE UNiX: rIPASSO C ] – [ PARTE I : Hello, World! ]


----------------------[ PR0GRAMMAZi0NE UNiX: rIPASSO C ]----------------------
-------------------------[ PARTE I : Hello, World! ]-------------------------
--------------------------------[ abnm3nt4l1 ]--------------------------------

Indice

00. Introduzione
01. Consumo
02. Ready, set, go
03. Scriviamo un un po' di C
04. Ringraziamenti

00. iNtrODUZIONE
------------

If one learn from others but does not think, one will be bewildered;
If one thinks but does not learn from others, one will be in peril.

-- Confucius

rIPASSANDO iL C

Dopo aver letto un bellissimo e articolato ebook di BlackLight sulla
programmazione in C dal titolo Il linguaggio C, reperibile in rete e rilasciato
sotto CC, mi sono reso conto che abbandonare e riprendere un linguaggio di
programmazione non e' propriamente come "imparare ad andare in bicicletta".
C'e' poi un altro aspetto che mi ha portato a eseguire questo ripasso e
condividerlo con voi: quando si ripassa e ristudia un argomento si apprendono
sempre tematiche trascurate nel corso di una precedente lettura.

Giusto un'altra nota su questo insieme di "cose" che scrivero' nel corso del
ripasso. Tenuto conto del fatto che l'intento di questo blog e' la condivisione
delle mie ricerche, studi, esperienze e conoscenze ma soprattutto la mia
crescita professionale e il divertimento utilizzero' come template per i miei
articoli quella della ezine BFi che mi ha sempre affascinato quando ero un
giovane studentello delle superiori.

Per chi non sapesse cosa fosse BFi vi rimando a questa descrizione presente
in wikipedia

-> http://it.wikipedia.org/wiki/Butchered_From_Inside

A ogni modo, bando alle ciance e partiamo operativamente con questo ripasso.

02. cONSUMO
-------

1) ll contenuto di una caffettiera da 3 di caffe' 100% arabica tostatura forte
2) Una tazza di te' verde
3) dato il periodo :-) frutta secca a q.b.

03. Ready, set, go
--------------

Il C e' un linguaggio di programmazione di alto livello che vide la luce,
nei laboratori della AT&T Bell, nel 1972 a opera di Dennis Ritchie.
Questi, fece girare un primo prototipo del C su un DEC PDP-11 con sistema
operativo UNIX. Il C fu il risultato dello sviluppo di due linguaggi di
programmazione piu' vecchi: il B di Ken Thompson e il BCPL di Martin
Richards. Per anni il linugaggio C (il C per gli amici) fu strettamente legato
al sistema operativo UNIX (infatti dopo la progettazione del C tutte le
successive versioni del sistema operativo furono scritte in questo linguaggio
che ancora oggi costituisce la base per i sistemi UNIX-like inclusi Linux e
BSD).

Giusto un appunto: io e molti altri consideriamo il C non propriamente un
linguaggio di alto livello ma di medio livello in quanto utilizzato per
la programmazione di sistemi operativi (non solo applicazioni) e driver
per la gestione delle periferiche.

Essendo comunque un linguaggio di livello piu' elevato di quello macchina
questo offre tutti i vantaggi tipici dei linguaggi di alto livello, ovvero:

1) leggibilita';
2) manutenibilita';
3) portabilita'.

I benefici di leggibilita' e manutenibilita' sono direttamente legati dalla
sua vicinanza con i linguaggi umani, in particolare l'inglese.
Ogni linguaggio di alto livello ha poi bisogno di un compilatore o un interprete
che traduca le istruzioni in linauggio macchina che il computer possa comprendere
ed eseguire.
Cosi', differenti architetture o sistemi operativi potrebbero necessitare di
compilatori o interpreti differenti. Tuttavia se un programma viene scritto
senza l'utilizzo di funzioni strettamente dipendenti dal sistema operativo
o dall'architettura sulla quale viene programmato, puo' essere compilato
e successivamente eseguito senza modifiche su altre architetture e OS.

Tra gli altri vantaggi del C riporto la semplicita' (e' un linguaggio costituito
da poche parole chiave) e la facilita' di riuso del codice scritto tramite
la creazione di librerie.

*** Troppe varianti per un solo linguaggio: nascita dell'ANSI C ***

Per molti anni lo standard de facto per il C era quello descritto nel libro
di Brian Kernighan e Dennis Ritchie (il padre fondatore, R.I.P.), il Linguaggio
C o C Programming Language, presente nelle librerie della stragrande
maggioranza dei programmatori e noto come K&R (dalle inziali dei due autori).
Tuttavia il libro era scritto come tutorial al linguaggio e non voleva essere
una definizione dello standard cosi' differenti vendor proponevano una loro
variante (perdendo anche uno dei beneifici primari dei linguaggi di alto
livello: la portabilita').
Fu cosi' che nel 1983 un gruppo di vendor e sviluppatori si rivolsero
all'ANSI (American National Standards Institute) per creare una
versione standard del linguaggio. Alla fine del 1989 venne approvato
lo standard ANSI C.
Lo standard miglio' il C proposto da K&R e defini' un gruppo di funzioni
che andarono a costituire una libreria nota come ANSI C standard library.
La maggior parte dei compilatori moderni include la libreria standard.

sETUP DEL SISTEMA

Per programmare in C abbiamo bisogno di ben poche cose: un PC con sistema
operativo Unix-Like (una qualsiasi distribuzione Linux andra' piu' che bene),
un editor di testo (vi/vim, emacs, kate o gedit), un compilatore e un linker.
Ovviamente gli esempi faranno riferimento solo a sistemi Unix-Like, in
particolare Linux e presuppongono che il lettore abbia un minimo di
dimestichezza con questi ultimi e la shell che utilizzeremo al minimo
per compilare ed eseguire i nostri programmi.

03. Scriviamo un un po' di C
------------------------

Taglia il tuo albero e ti riscalderai due volte.

--Proverbio cinese

Il primo programma che andremo a realizzare sara' breve e semplice.
Stampera' sul nostro schermo un messaggio di saluto ed uscira'.

1) Aprite il vostro editor di testo preferito e scrivete:
(scrivete non copiate e incollate; scrivere vi aiutera' a memorizzare,
ricordare e prendere dimestichezza con il C)

/* salutomondo.c */

#include

int main(void) {
printf("Hello, World!\n");
return 0;
}

Una volta scritto il codice nel vostro editor preferito, salvatelo come
salutomondo.c

2) Utilizzando gcc (GNU C Compiler) compilate il programma:

$ gcc -o salutomondo salutomondo.c

3) Eseguiamo il nostro programma che stampera' sullo schermo la stringa
"Hello, World!" e andra' a capo:

$ ./salutomondo
Hello, World!
$

La prima riga del programma che riporta lo statement:

/* salutomondo.c */

e' un commento. In C i commenti si aprono la combinazione di caratteri /* e
terminano con la sequenza */.
Il compilatore ignora qualsiasi cosa compaia tra i marcatori di apertura e
chiusura di commenti.
Lo solo scopo dei commenti e' quello di documentare cosa fa il programma o
specifiche porzioni dello stesso al fine di facilitarne la leggibilita',
la comprensione e la manutenibilita' a se stessi o altri programmatori.
Oltre allo stile standard di commento in C che prevede l'utilizzo dei
marker /* e */ oggi giorno i compilatori supportano e comprendono anche
i commenti C++ che iniziano con // e finiscono con la fine della riga.

c: /* questo e' un commento */
c++: // questo e' un commento
c: /* questo e' un commento
distribuito su piu' righe
*/
c++: // questo e' un commento
// distribuito su piu' righe

I commenti in stile C++ se si usa il compilatore con l'opzione
-Wall -pedantic generano un messaggio di warning.

salutomondo.c:1:1: warning: C++ style comments are not allowed in ISO C90 [enabled by default]
// salutomondo.c

Se viene invece richiesta al compilatore la piena attinenza allo standard ANSI
C90 questo generera' un errore e non creera' l'eseguibile.

$ gcc -Wall -pedantic -std=c90 salutomondo.c
salutomondo.c:1:1: error: expected identifier or ‘(’ before ‘/’ token
// salutomondo.c
^

Il secondo statement #include e' una direttiva al preprocessore. La direttiva
#include fa si che il processore cerchi il file indicato e ne sostituisca il
contenuto nella posizione nella quale si trova l'include.

#include

Il preprocessore e' un programma che esegue alcune operazioni di preparazione
prima che il compilatore esegua il suo compito.
Vedremo nel corso del ripasso il ruolo del preprocessore nell'operazione
di building.
Il file stdio.h sta per standard input output header file.
Questo contiene numerosi prototipi e macro che permettono di eseguire l'I/O
in C.

I file che vengono inclusi dalla direttiva al preprocessore #include, come
stdio.h, sono header file. Generalmente la loro estensione e' .h ma non e'
vincolante. Oltre all'header stdio.h la libreria standard ANSI si compone di
diversi header file. Gli header che vengono utilizzati nei programmi dipendono
dalle funzioni delle quali necessitiamo e conseguentemente delle librerie
che intendiamo utilizzare.
Gli header file possono essere specificati nelle direttive include rinchiusi
tra angle bracket o doppi apici.
Quando vengono usati i dopi apici si indica al preprocessore di cercare l'header
nella directory corrente; utilizzando la coppia gli si chiede di cercarli
all'esterno della directory corrente di default /usr/include.

La funzione main() e' una funziona molto speciale per il C; e' il vero e proprio
corpo del programma. Ogni programma in C deve avere una funzione main().
E' possibile mettere la funzione main() in qualsiasi punto del programma,
questa e' sempre la prima funzione eseguita. Il programma termina
quando vengono eseguiti tutti gli statement della funzione main().
L'int che precede la funzione main() specifica il tipo di dato che questa
restituisce al chiamante (lo stesso sistema operativo). Nel nostro caso
restituira' 0 (return 0).
A questo punto viene chiamata la funzione printf() definita' in stdio.h.
Questa funzione stampa un messaggio nello standard output. Il messaggio
e' racchiuso fra le parentesi tonde e i "". La sequenza \n e' una
sequenza di escape e sta per new-line (va a capo o muovi il cursore
all'inizio della riga successiva).
Tutte le funzioni in C possono restituire dei valori. Lo statement
return viene utilizzato per restiture al chiamante valori del tipo specificato
per la funzione nella sua definizione.

Vedremo il dettaglio della struttura dei programmi C nei prossimi ripassi.

int main(void) {
printf("Hello, World!\n");
return 0;
}

Piccola nota: tutte le istruzioni in C terminano con un punto e virgola ;

04. rInGRAZiAMENTI

Vorrei ringraziare anzittutto voi lettori e tutte le persone che mi
stanno vicine, sopportano, supportano e vogliono bene.

Salut, Mundi

Have Fun ;-)

-- abnm3nt4l1

Ritorno alle origini

Dopo anni, troppi, dove l’attivita’ principale diventa quella delle consulenze professionali ai clienti potrebbe accadere che la magia suscitata dal rumore dei tasti della tastiera e dai caratteri (bianchi o verdi) che si avvicendavano nel monitor di un agente del FBI o di un Hacker si vada perdendo e, l’entusiasmo, il divertimento, la fame di sapere e soprattutto comprendere nei minimi particolari il “perche'” delle cose (How does it work?) si affievoliscano e il tempo a loro destinato venga sottrato dalla superficialita’ e dallo studio di prodotti e soluzioni delle quali non ci importa un CAZZO ma che siamo costretti a impiegare per perseguire i nostri obiettivi di business guidati dal dio DENARO (in culo a deontologie ed etica).

Grazie a questo spazio vorrei cercare di ritornare alle origini; studiando e approffondendo le mie conoscenze sui vari ambiti dell’IT come facevo un tempo e condividendo il risultato di queste mie esperienze e “ri-esperienze” con chi avra’ piacere di seguirmi e fornirmi suggerimenti e commenti per una reciproca crescita conoscitiva e personale. Sono convinto che per migliorarsi la collaborazione, la condivisione e il confronto siano di fondamentale e primaria importanza ma, che questi, troppo spesso vengano rimpiazzati dall’individualismo e l’egoismo (le uniche posizioni morali, politiche e ideologiche promosse dalla societa’ moderna).

Ad maiora