2009-03-06 23 views
58

Desidero sovrascrivere determinate chiamate di funzione a varie API per il gusto di registrare le chiamate, ma potrei anche voler manipolare i dati prima che vengano inviati alla funzione effettiva.Ignora una chiamata di funzione in C

Ad esempio, supponiamo di utilizzare 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 in questo modo:

#include <apiheader.h>  

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

compilo tutta la mia altra fonte come farebbe normalmente, ma i Link contro questa funzione prima di collegamento con la biblioteca del 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 "ignorare" una funzione senza ottenere il collegamento/la 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.

+0

@dreamlax, ci stiamo muovendo dal generale (C) per specifici (GCC/linux) soluzioni ora - sarebbe una buona idea per chiarire che cosa si sta eseguendo in modo da orientare meglio le risposte . – paxdiablo

+1

Beh, sto sviluppando su Linux ma gli obiettivi sono Mac OS, Linux e Windows. Infatti uno dei motivi per cui voglio sovrascrivere le funzioni è perché sospetto che si comportino in modo diverso su sistemi operativi diversi. – dreamlax

risposta

61

Se è solo per l'origine che si desidera acquisire/modificare le chiamate, la soluzione più semplice è quello di 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 includono intercept.h):

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

quindi assicurarsi che ogni file sorgente in cui si desidera intercettare la chiamata ha:

#include "intercept.h" 

nella parte superiore.

Quindi, quando si compila con "-DINTERCEPT", tutti i file chiameranno la propria funzione anziché quella reale e la funzione potrà comunque chiamare quella reale.

La compilazione senza "-DINTERCEPT" impedisce l'intercettazione.

È un po 'più complicato se si desidera intercettare tutte le chiamate (non solo quelle dalla propria origine) - questo in genere può essere eseguito con caricamento dinamico e risoluzione della funzione reale (con chiamate di tipo dlload- e dlsym-) ma non lo faccio Penso che sia necessario nel tuo caso.

+0

L'utilizzo di un define non è una risposta polimorfa vera, ma sono d'accordo per l'uso dell'ingegno: P – Suroot

+0

Grazie, è davvero una buona idea ma come ho detto voglio provare ad evitare di modificare il codice sorgente. Se non riesco a trovare un altro modo, dovrò farlo in questo modo, suppongo. Deve essere facile disattivare l'intercettazione. – dreamlax

+1

Utilizzare un flag in fase di compilazione per controllare l'intercettazione (vedere la risposta aggiornata). Anche in questo caso, questo può essere fatto anche in runtime, è sufficiente rilevarlo in myGetObjectName() e chiamare sempre getObjectName se il flag di runtime è impostato (ad esempio, intercetta ancora ma cambia comportamento). – paxdiablo

3

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

La libreria n. 1 è collegata alla libreria host ed 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.

State molto attenti con gli ordini di collegamento qui o non funzionerà.

+0

Suoni ingannevoli, ma evita di modificare la fonte. Suggerimento molto bello. – dreamlax

+0

Non penso che si possa forzare getObjectName ad andare in una libreria specifica senza il trucco dlopen/dlsym. – paxdiablo

+0

Qualsiasi operazione di collegamento temporizzato che si trascina nella libreria host avrà come risultato un simbolo definito da più punti. – paxdiablo

7

È possibile definire un puntatore a funzione come variabile globale. La sintassi dei chiamanti non cambierebbe.All'avvio del programma, è possibile 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".

Sarà necessario essere in grado di modificare il codice sorgente dei chiamanti, ma non il chiamato (in modo che funzioni per chiamare 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(); 
} 
+0

Ho preso in considerazione questo metodo, ma richiede la modifica del codice sorgente, qualcosa che non voglio veramente fare a meno che non sia necessario. Sebbene questo abbia l'ulteriore vantaggio di cambiare durante l'esecuzione. – dreamlax

21

Se si utilizza GCC, potete fare la vostra funzione di weak. Coloro can be overridden da funzioni non deboli:

test.c:

#include <stdio.h> 

__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 <stdio.h> 

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 si può avere le dichiarazioni deboli che contengono funzioni overridable nel proprio file, mettendo solo un includere nei file di implementazione API se si sta compilando con 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 ... 

rovescio della medaglia è che non funziona del tutto senza fare qualcosa per i file API (che hanno bisogno di quelle tre linee e dei weakdecls). Ma una volta che hai fatto che il cambiamento, le funzioni possono essere sostituiti facilmente scrivendo una definizione globale in un unico file e di collegamento che in

+1

Ciò richiederebbe la modifica dell'API, no? – dreamlax

+0

il nome della tua funzione sarà lo stesso. inoltre non cambierà l'ABI o l'API in alcun modo. basta includere il file di override durante il collegamento e le chiamate saranno effettuate alla funzione non-debole. libc/pthread fa quel trucco: quando pthread è collegato, le sue funzioni thread-safe sono usate al posto di quella debole di libc –

+0

ho aggiunto un collegamento. Non so se sia adatto ai tuoi obiettivi (cioè se puoi vivere con quel qualcosa di GCC. Se msvc ha qualcosa di simile, potresti #definare WEAK it thoigh). ma se su Linux, lo userei (forse c'è anche un modo migliore, non ne ho idea, guardiamo anche alla versione). –

67

con GCC, sotto Linux è possibile utilizzare il flag --wrap linker in questo modo:.

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

e definire la funzione come:

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

Questo farà sì che tutte le chiamate a getObjectName() vengono reindirizzati alla funzione wrapper (in fase di collegamento). Questa bandiera molto utile è tuttavia assente in gcc sotto Mac OS X.

Ricordarsi di dichiarare la funzione wrapper con extern "C" se si sta compilando con g ++ però.

+3

è un bel modo. non lo sapevo. ma se sto leggendo la pagina di manuale, dovrebbe essere "__real_getObjectName (anObject);" che viene instradato per getObjectName dal linker. altrimenti chiamerai di nuovo __wrap_getObjectName in modo ricorsivo. O mi sta sfuggendo qualcosa? –

+0

Hai ragione che deve essere __real_getObjectName, grazie. Avrei dovuto ricontrollare nella pagina man :) – codelogic

+1

Sono deluso dal fatto che ld su Mac OS X non supporti il ​​flag '--wrap'. –

0

È possibile utilizzare una libreria condivisa (Unix) o una DLL (Windows) per fare ciò (sarebbe un po 'una penalizzazione delle prestazioni).È quindi possibile 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.

[Edit sulla base di commento OP]

In effetti uno dei motivi che voglio funzioni di override è perché io sospetto si comportano in modo diverso su diversi sistemi operativi.

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) si avrebbe foo_linux.c, foo_osx.c, foo_win32.c (o un modo migliore è linux/foo.c, osx/foo.c e win32/foo. c) e quindi compilare e collegare con quello appropriato.

Se si sta cercando codice diverso per piattaforme diverse E debug -vs-release sarei probabilmente incline ad andare con la soluzione lib/DLL condivisa in quanto è la più flessibile.

34

È possibile eseguire l'override di una funzione utilizzando il trucco LD_PRELOAD - vedere man ld.so. Compilate la lib condivisa con la vostra funzione e avviate il binario (non avete nemmeno bisogno di modificare il binario!) Come LD_PRELOAD=mylib.so myprog.

Nel corpo della funzione (in lib comune) si scrive in questo modo:

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 
} 

è possibile ignorare qualsiasi funzione dalla libreria condivisa, anche da stdlib, senza modificare/ricompilare il programma, così da poter fai il trucco su programmi per i quali non hai una fonte. Non è bello?

+2

No, puoi solo sovrascrivere una funzione fornita da una ** libreria condivisa ** in questo modo. –

+2

@ChrisStratton Hai ragione, syscall non può essere annullato in questo modo, ho modificato la mia risposta. – qrdl

9

Spesso è desiderabile modificare il comportamento di basi di codice esistenti funzioni avvolgimento o sostituzione. Quando il modifica il codice sorgente di quelle funzioni è un'opzione valida, questo può essere un processo diretto. Quando l'origine delle funzioni non può essere modificata da (ad esempio, se le funzioni sono fornite dalla libreria C di sistema), le tecniche alternative sono richieste . Qui, presentiamo tali tecniche per piattaforme UNIX, Windows e Macintosh OS X.

Questo è un grande 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.

http://wwwold.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf

+0

Cura di condividere ciò che questo PDF potrebbe essere? – HanClinto

+0

lattice.umiacs.umd.edu/ files/functions_tr.pdf - Link aggiunto –

+0

Link è morto, a proposito: – paxdiablo

1

Sulla risposta di @Johannes Schaub con una soluzione adatta per il codice non si possiede.

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 pattern-specific variable values nel tuo Makefile per aggiungere il flag del compilatore -include override.h.

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

parte: Forse si potrebbe anche usare -D 'foo(x) __attribute__((weak))foo(x)' per definire le macro.

Compilare e collegare il file alla reimplementazione (override.c).

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

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

Problemi correlati