lunedì 4 luglio 2011

Cambiare l’entry point di un programma con il GCC

NOTA: il procedimento descritto di seguito è riportato per fini puramente didattici/smanettonici. Non è una buona pratica nei programmi comuni, non è standard C e non è necessario salvo casi molto particolari.
Com’è noto, i programmi C/C++ vengono eseguiti dal sistema operativo a partire dalla funzione main. In realtà la stessa main viene richiamata da un’altra funzione, _start, che si occupa di inizializzare alcune cose, eseguire la funzione main ed infine di fare un po’ di pulizia con la funzione exit. La funzione _start è il codidetto entry point del programma, ovvero il punto dal quale inizia la sua esecuzione.
Il GCC consente di specificare un entry point personalizzato: in altre parole si possono scrivere programmi dove verrà eseguita una funzione a nostra scelta al posto della _start (e quindi anche della main). Vediamo un semplice esempio.

Innanzitutto creiamo un banale programma di test, ecco il file pippo.c:
#include <stdio.h>
#include <stdlib.h>

int f() {
 printf("ciao da f!\n");
 exit(0);
}

int main() {
 printf("saluti da main!\n");
 return 0;
}
Il sorgente contiene due funzioni: la main, che è lì solo per dimostrare che non verrà eseguita, ed f, che sarà il nostro nuovo entry point. Notate come sia necessario richiamare exit alla fine di f, perché f andrà a sostituire _start che faceva lo stesso: senza questa chiamata il programma verrà eseguito correttamente ma terminerà con un bel segfault.
Adesso generiamo il file oggetto pippo.o.
$ gcc -c pippo.c
Ora dovremo richiamare il linker (sempre mediante GCC) per creare l’eseguibile ed è a questo punto che dobbiamo specificare il nostro entry point personalizzato). Per fare ciò abbiamo bisogno di conoscere il simbolo che rappresenta la funzione f nel file oggetto pippo.o appena creato. Il comando nm ci viene in aiuto:
$ nm pippo.o
                 U exit
0000000000000000 T f
0000000000000018 T main
                 U puts
Come potete vedere abbiamo quattro simboli, corrispondenti alle funzioni che usiamo nel programma (puts è lì perché usiamo printf); tra questi simboli c’è anche la nostra funzione f, che è rappresentata proprio dal suo nome f. 1
Ora che conosciamo il simbolo associato alla nostra funzione possiamo procedere con il linking:
$ gcc -o pippo -e f pippo.o
Come vedete oltre a specificare il nome del file eseguibile con l’opzione -o ed il nome del file oggetto, grazie all’opzione -e possiamo specificare il simbolo del nostro entry point personalizzato. Vediamo se l’eseguibile si comporta come previsto:
$ ./pippo
ciao da f!
Funziona! :-D
  1. Questo è vero nel caso di programmi C: il simbolo corrispondente ad una funzione ha il nome della funzione stessa. Nel C++ invece la possibilità di fare overloading delle funzioni non consente di usare questa convenzione. Il processo di traduzione da nome della funzione al nome del suo simbolo è chiamato name mangling (letteralmente, “storpiatura del nome”), per ulteriori informazioni potete consultare il corrispondente articolo di Wikipedia.

Nessun commento:

Posta un commento