Sovrascrivi una chiamata di funzione in C

Voglio sovrascrivere determinate chiamate di funzione a varie API allo scopo di registrare le chiamate, ma potrei anche voler manipolare i dati prima che vengano inviati alla funzione effettiva.

Ad esempio, supponiamo che io usi una funzione chiamata getObjectName migliaia di volte nel mio codice sorgente. Voglio sovrascrivere temporaneamente questa funzione a volte perché voglio cambiare il comportamento di questa funzione per vedere il risultato diverso.

Creo un nuovo file sorgente come questo:

 #include  const char *getObjectName (object *anObject) { if (anObject == NULL) return "(null)"; else return "name should be here"; } 

Compilare tutta la mia altra fonte come farò normalmente, ma la collego a questa funzione prima di collegarmi alla libreria dell’API. Funziona bene, tranne che ovviamente non posso chiamare la funzione reale all’interno della mia funzione di override.

C’è un modo più semplice per “sovrascrivere” una funzione senza ottenere collegamenti / compilazione di errori / avvisi? Idealmente, voglio essere in grado di sovrascrivere la funzione semplicemente compilando e collegando un file aggiuntivo o due piuttosto che giocherellare con le opzioni di collegamento o alterando il codice sorgente reale del mio programma.

    Se è solo per la tua fonte che vuoi catturare / modificare le chiamate, la soluzione più semplice è mettere insieme un file di intestazione ( intercept.h ) con:

     #ifdef INTERCEPT #define getObjectName(x) myGetObectName(x) #endif 

    e implementare la funzione come segue (in intercept.c che non include intercept.h ):

     const char *myGetObjectName (object *anObject) { if (anObject == NULL) return "(null)"; else return getObjectName(anObject); } 

    Quindi assicurati che ogni file sorgente in cui desideri intercettare la chiamata abbia:

     #include "intercept.h" 

    in cima.

    Quindi, quando compili con ” -DINTERCEPT “, tutti i file chiameranno la tua funzione piuttosto che quella reale e la tua funzione potrà comunque chiamare quella reale.

    La compilazione senza ” -DINTERCEPT ” impedirà l’intercettazione.

    È un po ‘più complicato se vuoi intercettare tutte le chiamate (non solo quelle della tua sorgente) – questo in genere può essere fatto con il caricamento dinamico e la risoluzione della funzione reale (con le dlload- e dlsym- ) ma non penso è necessario nel tuo caso.

    Con gcc, sotto Linux puoi usare il flag --wrap linker in questo modo:

     gcc program.c -Wl,-wrap,getObjectName -o program 

    e definisci la tua funzione come:

     const char *__wrap_getObjectName (object *anObject) { if (anObject == NULL) return "(null)"; else return __real_getObjectName( anObject ); // call the real function } 

    Ciò garantirà che tutte le chiamate a getObjectName() vengano reindirizzate alla funzione wrapper (al momento del collegamento). Questa bandiera molto utile è tuttavia assente in gcc sotto Mac OS X.

    Ricorda di dichiarare la funzione wrapper con extern "C" se stai compilando con g ++ però.

    Puoi eseguire l’override di una funzione usando il trucco LD_PRELOAD – vedi man ld.so Si compila la lib condivisa con la propria funzione e si avvia il binario (non è nemmeno necessario modificare il binario!) Come LD_PRELOAD=mylib.so myprog .

    Nel corpo della tua funzione (nella lib condivisa) scrivi così:

     const char *getObjectName (object *anObject) { static char * (*func)(); if(!func) func = (char *(*)()) dlsym(RTLD_NEXT, "getObjectName"); printf("Overridden!\n"); return(func(anObject)); // call original function } 

    Puoi sovrascrivere qualsiasi funzione dalla libreria condivisa, anche da stdlib, senza modificare / ricompilare il programma, in modo da poter fare il trucco su programmi per i quali non hai una fonte. Non è bello?

    Se si utilizza GCC, è ansible rendere weak propria funzione. Quelli possono essere sostituiti da funzioni non deboli:

    test.c :

     #include  __attribute__((weak)) void test(void) { printf("not overridden!\n"); } int main() { test(); } 

    Che cosa fa?

     $ gcc test.c $ ./a.out not overridden! 

    test1.c :

     #include  void test(void) { printf("overridden!\n"); } 

    Che cosa fa?

     $ gcc test1.c test.c $ ./a.out overridden! 

    Purtroppo, questo non funzionerà con altri compilatori. Ma puoi avere le deboli dichiarazioni che contengono funzioni sovrascrivibili nel loro file, inserendo solo un inclusione nei file di implementazione dell’API se stai compilando usando GCC:

    weakdecls.h :

     __attribute__((weak)) void test(void); ... other weak function declarations ... 

    functions.c :

     /* for GCC, these will become weak definitions */ #ifdef __GNUC__ #include "weakdecls.h" #endif void test(void) { ... } ... other functions ... 

    Il lato negativo di questo è che non funziona interamente senza fare qualcosa per i file API (che hanno bisogno di quelle tre linee e dei weakdecls). Ma una volta che hai fatto questo cambiamento, le funzioni possono essere sovrascritte facilmente scrivendo una definizione globale in un file e collegandolo.

    È spesso preferibile modificare il comportamento delle basi di codice esistenti mediante il wrapping o la sostituzione di funzioni. Quando si modifica il codice sorgente di tali funzioni è un’opzione valida, questo può essere un processo diretto. Quando la fonte delle funzioni non può essere modificata (ad esempio, se le funzioni sono fornite dalla libreria C di sistema), sono necessarie tecniche alternative. Qui, presentiamo tali tecniche per piattaforms UNIX, Windows e Macintosh OS X.

    Questo è un ottimo PDF che illustra come è stato fatto su OS X, Linux e Windows.

    Non ha trucchi sorprendenti che non sono stati documentati qui (questo è un incredibile insieme di risposte BTW) … ma è una buona lettura.

    Intercettazione di funzioni arbitrarie su piattaforms Windows, UNIX e Macintosh OS X (2004), di Daniel S. Myers e Adam L. Bazinet .

    È ansible scaricare il PDF direttamente da una posizione alternativa (per ridondanza) .

    E infine, se le precedenti due fonti andassero in qualche modo in fiamme, ecco un risultato di ricerca su Google .

    È ansible definire un puntatore a funzione come variabile globale. La syntax dei chiamanti non cambierebbe. All’avvio del programma, è ansible verificare se è stato impostato un flag della riga di comando o una variabile di ambiente per abilitare la registrazione, quindi salvare il valore originale del puntatore della funzione e sostituirlo con la funzione di registrazione. Non avresti bisogno di una speciale build “logging enabled”. Gli utenti potrebbero abilitare la registrazione “sul campo”.

    Dovrai essere in grado di modificare il codice sorgente dei chiamanti, ma non il chiamato (quindi questo funzionerebbe quando si chiamano le librerie di terze parti).

    foo.h:

     typedef const char* (*GetObjectNameFuncPtr)(object *anObject); extern GetObjectNameFuncPtr GetObjectName; 

    foo.cpp:

     const char* GetObjectName_real(object *anObject) { return "object name"; } const char* GetObjectName_logging(object *anObject) { if (anObject == null) return "(null)"; else return GetObjectName_real(anObject); } GetObjectNameFuncPtr GetObjectName = GetObjectName_real; void main() { GetObjectName(NULL); // calls GetObjectName_real(); if (isLoggingEnabled) GetObjectName = GetObjectName_logging; GetObjectName(NULL); // calls GetObjectName_logging(); } 

    C’è anche un metodo complicato per farlo nel linker che coinvolge due librerie stub.

    La libreria n. 1 è collegata alla libreria host e espone il simbolo ridefinito con un altro nome.

    La libreria n. 2 è collegata alla libreria n. 1, intercettando la chiamata e chiamando la versione ridefinita nella libreria n.

    Stai molto attento con gli ordini di collegamento qui o non funzionerà.

    Basandosi sulla risposta di @Johannes Schaub con una soluzione adatta al codice che non possiedi.

    Alias ​​la funzione che si desidera sovrascrivere su una funzione debolmente definita e quindi reimplementarla autonomamente.

    override.h

     #define foo(x) __attribute__((weak))foo(x) 

    foo.c

     function foo() { return 1234; } 

    override.c

     function foo() { return 5678; } 

    Usa valori variabili specifici del modello nel tuo Makefile per aggiungere il flag del compilatore -include override.h .

     %foo.o: ALL_CFLAGS += -include override.h 

    A parte: Forse potresti anche usare -D 'foo(x) __attribute__((weak))foo(x)' per definire i tuoi macros.

    Compila e collega il file con la tua reimplementazione ( override.c ).

    • Ciò ti consente di sovrascrivere una singola funzione da qualsiasi file sorgente, senza dover modificare il codice.

    • Lo svantaggio è che è necessario utilizzare un file di intestazione separato per ogni file che si desidera sovrascrivere.

    Potresti usare una libreria condivisa (Unix) o una DLL (Windows) per fare altrettanto (sarebbe un po ‘una penalizzazione delle prestazioni). È quindi ansible modificare la DLL / in modo che venga caricato (una versione per il debug, una versione per non-debug).

    Ho fatto una cosa simile in passato (non per raggiungere quello che stai cercando di ottenere, ma la premessa di base è la stessa) e ha funzionato bene.

    [Modifica basata sul commento OP]

    Infatti uno dei motivi per cui voglio sovrascrivere le funzioni è perché sospetto che si comportino in modo diverso su sistemi operativi diversi.

    Ci sono due modi comuni (che io conosca) di occuparsi di questo, della lib / dll condivisa o della scrittura di diverse implementazioni a cui ci si collega.

    Per entrambe le soluzioni (librerie condivise o collegamenti diversi) dovresti avere foo_linux.c, foo_osx.c, foo_win32.c (o un modo migliore è linux / foo.c, osx / foo.c e win32 / foo.c) e poi compilare e colbind con quello appropriato.

    Se si sta cercando sia un codice diverso per piattaforms diverse che un debug -vs-release, probabilmente sarei propenso a utilizzare la soluzione lib / DLL condivisa poiché è la più flessibile.