[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 570.   Compilazione C dal basso in alto

Il valore del linguaggio C sta nel consentire una programmazione molto vicina a livello del linguaggio macchina, in modo relativamente indipendente dall'architettura. Ma ciò si può comprendere solo se si conosce il contesto operativo del linguaggio assemblatore, in modo particolare per quanto riguarda la gestione della memoria e tanto più per il modo in cui si utilizza la pila dei dati.

Per la compilazione dei programmi di esempio si fa riferimento a GCC(1) (GNU compiler collection) e precisamente al programma frontale gcc.

570.1   Compilazione di un programma che non fa uso di librerie

Un programma in linguaggio C che non faccia uso di librerie di alcun tipo, deve seguire alcune regole che riguardano i programmi scritti in linguaggio assemblatore. Il listato seguente contiene la procedura per il calcolo del fattoriale, partendo da un valore già presente in memoria (si calcola precisamente il fattoriale di 5), ma il risultato non viene visualizzato in alcun modo, dal momento che questa sarebbe un'operazione che richiede proprio l'uso di librerie apposite:

int x = 5;
int i = 0;
void _start (void)
{
    i = (x - 1);
    while (i > 0)
      {
        x = x * i;
        i--;
      }
}

Se si conoscono i rudimenti del linguaggio C, si può osservare che, al posto della funzione main(), appare invece _start(), come si fa in un programma scritto in linguaggio assemblatore.

Supponendo che il file che contiene quanto mostrato si chiami fact.c, la compilazione potrebbe iniziare dalla trasformazione in linguaggio assemblatore:

gcc -Wall -Werror -S -o fact.s fact.c \
  \    -nostdlib -nostartfiles -nodefaultlibs
[Invio]

Si otterrebbe il file fact.s, in linguaggio assemblatore, che, a seconda della versione di GCC, potrebbe essere molto simile al listato seguente:

        .file   "fact.c"
.globl x
        .data
        .align 4
        .type   x, @object
        .size   x, 4
x:
        .long   5
.globl i
        .bss
        .align 4
        .type   i, @object
        .size   i, 4
i:
        .zero   4
        .text
.globl _start
        .type   _start, @function
_start:
        pushl   %ebp
        movl    %esp, %ebp
        movl    x, %eax
        decl    %eax
        movl    %eax, i
        jmp     .L2
.L3:
        movl    x, %edx
        movl    i, %eax
        imull   %edx, %eax
        movl    %eax, x
        movl    i, %eax
        decl    %eax
        movl    %eax, i
.L2:
        movl    i, %eax
        testl   %eax, %eax
        jg      .L3
        popl    %ebp
        ret
        .size   _start, .-_start
        .ident  "GCC: (GNU) 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)"
        .section        .note.GNU-stack,"",@progbits

Tale file in linguaggio assemblatore può essere compilato con GNU AS e GNU ld nel modo consueto:

as -o fact.o fact.s[Invio]

ld -o fact fact.o[Invio]

Si può ispezionare il programma ottenuto con Objdump:

objdump -x fact[Invio]

fact:     file format elf32-i386
fact
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x08048094

Program Header:
    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
         filesz 0x000000cd memsz 0x000000cd flags r-x
    LOAD off    0x000000d0 vaddr 0x080490d0 paddr 0x080490d0 align 2**12
         filesz 0x00000004 memsz 0x00000008 flags rw-
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
         filesz 0x00000000 memsz 0x00000000 flags rw-

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000039  08048094  08048094  00000094  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000004  080490d0  080490d0  000000d0  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  080490d4  080490d4  000000d4  2**2
                  ALLOC
  3 .comment      0000003a  00000000  00000000  000000d4  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
08048094 l    d  .text  00000000 .text
080490d0 l    d  .data  00000000 .data
080490d4 l    d  .bss   00000000 .bss
00000000 l    d  .comment       00000000 .comment
00000000 l    d  *ABS*  00000000 .shstrtab
00000000 l    d  *ABS*  00000000 .symtab
00000000 l    d  *ABS*  00000000 .strtab
00000000 l    df *ABS*  00000000 fact.c
080490d0 g     O .data  00000004 x
080490d4 g     O .bss   00000004 i
08048094 g     F .text  00000039 _start
080490d4 g       *ABS*  00000000 __bss_start
080490d4 g       *ABS*  00000000 _edata
080490d8 g       *ABS*  00000000 _end

È possibile fare in modo che GCC interpelli automaticamente GNU AS, in modo da generare un file oggetto senza mostrare la creazione del file in linguaggio assemblatore (la trasformazione in linguaggio assemblatore avviene ugualmente, in un file temporaneo che poi viene cancellato in modo automatico). Pertanto, la compilazione si ridurrebbe ai due comandi seguenti:

gcc -Wall -Werror -c -o fact.o fact.c\
  \    -nostdlib -nostartfiles -nodefaultlibs
[Invio]

ld -o fact fact.o[Invio]

È il caso di osservare che il programma eseguibile ottenuto dal sorgente mostrato, produce un errore, dal momento che manca la chiamata della funzione del sistema operativo che ne conclude l'attività.

570.2   Uso di GDB e di DDD

Per poter sfruttare programmi come GDB, allo scopo di analizzare il funzionamento del programma, è necessario aggiungere delle informazioni aggiuntive durante la fase di trasformazione nel formato del linguaggio assemblatore. In pratica, si tratta di utilizzare l'opzione -gstabs, o altre simili, nella riga di comando di GCC. Riprendendo l'esempio della sezione precedente, la compilazione verrebbe eseguita con il comando seguente:

gcc -Wall -Werror -gstabs -S -o fact.s fact.c \
  \    -nostdlib -nostartfiles -nodefaultlibs
[Invio]

In questo caso, nel file in linguaggio assemblatore si troverebbero delle informazioni in più:

        .file   "fact.c"
        .stabs  "fact.c",100,0,2,.Ltext0
        .text
.Ltext0:
        .stabs  "gcc2_compiled.",60,0,0,0
        .stabs  "int:t(0,1)=r(0,1);-2147483648;2147483647;",128,0,0,0
        .stabs  "char:t(0,2)=r(0,2);0;127;",128,0,0,0
        .stabs  "long int:t(0,3)=r(0,3);-2147483648;2147483647;",128,0,0,0
        .stabs  "unsigned int:t(0,4)=r(0,4);0;4294967295;",128,0,0,0
        .stabs  "long unsigned int:t(0,5)=r(0,5);0;4294967295;",128,0,0,0
        .stabs  "long long int:t(0,6)=r(0,6);-0;4294967295;",128,0,0,0
        .stabs  "long long unsigned int:t(0,7)=r(0,7);0;-1;",128,0,0,0
        .stabs  "short int:t(0,8)=r(0,8);-32768;32767;",128,0,0,0
        .stabs  "short unsigned int:t(0,9)=r(0,9);0;65535;",128,0,0,0
        .stabs  "signed char:t(0,10)=r(0,10);-128;127;",128,0,0,0
        .stabs  "unsigned char:t(0,11)=r(0,11);0;255;",128,0,0,0
        .stabs  "float:t(0,12)=r(0,1);4;0;",128,0,0,0
        .stabs  "double:t(0,13)=r(0,1);8;0;",128,0,0,0
        .stabs  "long double:t(0,14)=r(0,1);12;0;",128,0,0,0
        .stabs  "void:t(0,15)=(0,15)",128,0,0,0
.globl x
        .data
        .align 4
        .type   x, @object
        .size   x, 4
x:
        .long   5
.globl i
        .bss
        .align 4
        .type   i, @object
        .size   i, 4
i:
        .zero   4
        .text
        .stabs  "_start:F(0,15)",36,0,0,_start
.globl _start
        .type   _start, @function
_start:
        .stabn  68,0,4,.LM0-_start
.LM0:
        pushl   %ebp
        movl    %esp, %ebp
        .stabn  68,0,5,.LM1-_start
.LM1:
        movl    x, %eax
        decl    %eax
        movl    %eax, i
        .stabn  68,0,6,.LM2-_start
.LM2:
        jmp     .L2
.L3:
        .stabn  68,0,8,.LM3-_start
.LM3:
        movl    x, %edx
        movl    i, %eax
        imull   %edx, %eax
        movl    %eax, x
        .stabn  68,0,9,.LM4-_start
.LM4:
        movl    i, %eax
        decl    %eax
        movl    %eax, i
.L2:
        .stabn  68,0,6,.LM5-_start
.LM5:
        movl    i, %eax
        testl   %eax, %eax
        jg      .L3
        .stabn  68,0,11,.LM6-_start
.LM6:
        popl    %ebp
        ret
        .size   _start, .-_start
.Lscope0:
        .stabs  "x:G(0,1)",32,0,0,0
        .stabs  "i:G(0,1)",32,0,0,0
        .stabs  "",100,0,0,.Letext0
.Letext0:
        .ident  "GCC: (GNU) 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)"
        .section        .note.GNU-stack,"",@progbits

Per la compilazione successiva non ci sono cambiamenti; va quindi osservato che non è più compito di GNU AS l'inserimento di tali informazioni:

as -o fact.o fact.s[Invio]

ld -o fact fact.o[Invio]

Per utilizzare GDB o DDD si procede come nel caso di un programma che parte direttamente da un sorgente in linguaggio assemblatore:

gdb fact[Invio]

(gdb) break _start[Invio]

Breakpoint 1 at 0x8048097: file fact.c, line 5.

(gdb) run[Invio]

Breakpoint 1, _start () at fact.c:5
5           i = (x - 1);

(gdb) stepi[Invio]

0x0804809c      5           i = (x - 1);

(gdb) stepi[Invio]

0x0804809d in _start () at fact.c:5
5           i = (x - 1);

(gdb) stepi[Invio]

6           while (i > 0)

Come si può osservare, occorrono più comandi di avanzamento per passare alla riga successiva del codice originale, perché in realtà si fa riferimento alle istruzioni in linguaggio macchina.

(gdb) print i[Invio]

$1 = 4

(gdb) print x[Invio]

$2 = 5

(gdb) quit[Invio]

Naturalmente, se si può utilizzare DDD, tutto diventa più semplice:

ddd fact[Invio]

Figura 570.12. DDD che mette in evidenza lo stato di due variabili (si attiva la loro visualizzazione facendo un clic sul pulsante a icona denominato <Display>) durante il funzionamento, passo passo, del programma.

ddd durante il funzionamento

570.3   Da «_start» a «main»

Per fare in modo che un programma in linguaggio C inizi dalla funzione main(), così come si prevede sia, si può istruire il collegatore (linker), attraverso uno script apposito che, in un sistema GNU/Linux, potrebbe essere come quello seguente:

ENTRY (main)
SECTIONS {
    . = 0x08048000 + SIZEOF_HEADERS;
    .text . : { *(.text) }
    .data ALIGN (0x1000) : { *(.data) }
    .bss . : {
        _sbss = .;
        *(.bss)
        *(COMMON)
        _ebss = .;
    }
}

Il nuovo sorgente C:

int x = 5;
int i = 0;
int main ()
{
    i = (x - 1);
    while (i > 0)
      {
        x = x * i;
        i--;
      }
    return x;
}

Per la compilazione, i passaggi sarebbero quelli seguenti, supponendo che lo script per GNU ld sia contenuto nel file config.ld:

gcc -Wall -Werror -S -o fact.s fact.c \
  \    -nostdlib -nostartfiles -nodefaultlibs
[Invio]

as -o fact.o fact.s[Invio]

ld -T config.ld -o fact fact.o[Invio]

Tuttavia, rimane ancora il problema della conclusione del programma che non avviene in modo grazioso. Se si osserva la nuova versione del programma, la funzione (che ora si chiama main()) restituisce un valore intero, corrispondente al risultato del calcolo eseguito, solo che non è stato chiarito in che modo quel valore debba essere acquisito dal sistema operativo. Si può quindi procedere in un modo diverso, creando un piccolo programma in linguaggio assemblatore, da associare a quello in linguaggio C:

.section .text
.globl _start
.extern main
_start:
    call  main
    mov   %eax, %ebx
    mov   $1, %eax
    int   $0x80

Supponendo che questo file si chiami start.s, la compilazione complessiva potrebbe essere svolta nel modo seguente:

gcc -Wall -Werror -gstabs -S -o fact.s fact.c \
  \    -nostdlib -nostartfiles -nodefaultlibs
[Invio]

as -o fact.o fact.s[Invio]

as --gstabs -o start.o start.s[Invio]

ld -o fact start.o fact.o[Invio]

Come si vede sono state aggiunte le opzioni -gstabs e --gstabs, dove appropriato; inoltre non serve più lo script per GNU ld. Se si avvia il programma, questo si arresta correttamente restituendo il fattoriale di 5:

./fact ; echo $?[Invio]

120

570.4   Compilazione naturale di un programma in linguaggio C

Quando non si utilizzano le opzioni -nostdlibs, -nostartfiles e -nodefaultlibs, la compilazione attraverso GCC avviene in modo più intuitivo, con l'inclusione automatica di tutto quello che è necessario per far sì che il programma parta dalla propria funzione main(); inoltre, se non si specifica il nome che si vuole produrre, si ottiene direttamente un file eseguibile con il nome a.out, secondo la tradizione.

In condizioni normali vengono inclusi nella compilazione alcuni file-oggetto che hanno un nome corrispondente al modello crt*.o e la libreria Libc. All'interno di uno di quei file-oggetto si trova la funzione _start(), dalla quale si arriva poi alla chiamata di main() in modo analogo a quanto mostrato nella sezione precedente, ma questi file potrebbero coinvolgere anche la libreria Libc.

L'opzione -nostartfiles serve a impedire che vengano incorporati automaticamente i file che contengono la funzione _start() e tutto ciò che altrimenti si prevede di far fare al programma prima di entrare nella funzione main(). L'opzione -nodefaultlibs serve a impedire l'inclusione automatica della libreria Libc. L'opzione -nostdlibs richiede entrambe le cose ed è stata usata negli esempi in modo ridondante.

Ecco la classica compilazione che produce direttamente il file eseguibile con il nome a.out:

gcc -Wall -Werror -gstabs fact.c[Invio]

Per buona abitudine è bene usare sempre l'opzione -Wall e possibilmente anche -Werror; inoltre, l'uso di -gstabs diventa essenziale per potersi avvalere di programmi come GDB.

Si può verificare che questo basta per arrivare al risultato voluto:

./a.out ; echo $?[Invio]

120

Se poi si vogliono usare comandi tradizionali, da gcc occorre passare a cc, ma in un sistema GNU si tratta normalmente di un collegamento simbolico a gcc stesso.

Tabella 570.18. Riepilogo delle opzioni utilizzate con gcc nel corso del capitolo.

Opzione Descrizione
-S
Genera un file in linguaggio assemblatore (prende il sopravvento sull'opzione -c).
-o nome_file
Dichiara il nome del file che si vuole ottenere.
-c
Fa sì che la compilazione salti la fase di collegamento (link). In condizioni normali serve a generare solo i file-oggetto. Se si usa questa opzione, ma non si specifica l'opzione -o, il file-oggetto ha un nome con la stessa radice del file sorgente e l'estensione .o.
-Wall
Richiede di mostrare tutti i messaggi che avvertono dell'uso imperfetto del linguaggio (warning).
-Werror
Fa sì che tutte le segnalazioni di avvertimento siano trattate come errori e portino al fallimento della compilazione.
-gstabs
Inserisce delle annotazioni, con le quali i programmi come GDB possono abbinare il sorgente originale all'esecuzione controllata del programma.

570.5   Riferimenti


1) GCC   GNU GPL


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome compilazione_c_dal_basso_in_alto.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory