Domanda

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

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

Creo un nuovo file sorgente come questo:

#include <apiheader.h>    

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

Compilo tutte le altre mie fonti come farei normalmente, ma le collego a questa funzione prima di collegarle alla libreria dell'API. Funziona bene, tranne che ovviamente non posso chiamare la vera funzione all'interno della mia funzione principale.

Esiste un modo più semplice per " sovrascrivere " una funzione senza ottenere collegamenti / compilare errori / avvisi? Idealmente, voglio essere in grado di sovrascrivere la funzione semplicemente compilando e collegando uno o due file extra piuttosto che giocherellare con le opzioni di collegamento o alterando il codice sorgente effettivo del mio programma.

È stato utile?

Soluzione

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 implementa 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 alto.

Quindi, quando compili con " -DINTERCEPT " ;, tutti i file chiameranno la tua funzione piuttosto che quella reale e la tua funzione potrà ancora chiamare quella reale.

Compilazione senza " -DINTERCEPT " impedirà l'intercettazione.

È un po 'più complicato se vuoi intercettare tutte le chiamate (non solo quelle dalla tua fonte) - questo può generalmente essere fatto con caricamento dinamico e risoluzione della funzione reale (con dlload- e < code> dlsym- digita chiamate) ma non credo sia necessario nel tuo caso.

Altri suggerimenti

Con gcc, sotto Linux puoi usare il flag linker --wrap 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). Questo utilissimo flag è tuttavia assente in gcc in Mac OS X.

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

Puoi sovrascrivere una funzione usando il trucco LD_PRELOAD - vedi man ld.so . Compili la libreria condivisa con la tua funzione e avvii il binario (non è nemmeno necessario modificarlo!) Come LD_PRELOAD = mylib.so myprog .

Nel corpo della tua funzione (in 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 sui programmi per i quali non hai una fonte. Non è carino?

Se usi GCC, puoi rendere la tua funzione debole . Quelle possono essere ignorate con 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, ciò non funzionerà con altri compilatori. Ma puoi avere le dichiarazioni deboli che contengono funzioni sostituibili nel proprio 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 rovescio della medaglia di questo è che non funziona interamente senza fare qualcosa per i file API (che necessitano di quelle tre linee e dei punti deboli). Ma una volta che hai apportato tale modifica, le funzioni possono essere facilmente ignorate scrivendo una definizione globale in un file e collegandola in.

  

È spesso desiderabile modificare il   comportamento delle basi di codice esistenti per   funzioni di avvolgimento o sostituzione. quando   modificando il codice sorgente di quelli   funzioni è un'opzione praticabile, questo può   essere un processo diretto. quando   la fonte delle funzioni non può essere   modificato (ad es. se le funzioni lo sono   fornito dalla libreria di sistema C),   allora sono tecniche alternative   necessario. Qui, presentiamo tale   tecniche per UNIX, Windows e   Piattaforme Macintosh OS X.

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

Non ci sono trucchi sorprendenti che non sono stati documentati qui (questa è una serie incredibile di risposte BTW) ... ma è una bella lettura.

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

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

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

È possibile definire un puntatore a funzione come variabile globale. La sintassi dei chiamanti non cambierebbe. All'avvio del programma, è possibile verificare se alcuni flag della riga di comando o variabili di ambiente sono impostati per abilitare la registrazione, quindi salvare il valore originale del puntatore a funzione e sostituirlo con la funzione di registrazione. Non avrai bisogno di uno speciale "quoting logging" abilitato " costruire. Gli utenti possono abilitare la registrazione " nel campo " ;.

Dovrai essere in grado di modificare il codice sorgente dei chiamanti, ma non il chiamante (quindi questo funzionerebbe quando si chiamano 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();
}

Esiste anche un metodo complicato per farlo nel linker che coinvolge due librerie di stub.

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

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

Fai molta attenzione con gli ordini di link 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 in una funzione debolmente definita, quindi reimplementarla da soli.

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 pattern 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 __ ((debole)) foo (x)' per definire le tue macro.

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

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

  • Il rovescio della medaglia è che è necessario utilizzare un file di intestazione separato per ogni file che si desidera sostituire.

Puoi usare anche una libreria condivisa (Unix) o una DLL (Windows) per fare questo (sarebbe un po 'una penalità per le prestazioni). È quindi possibile modificare la DLL / in modo che venga caricata (una versione per il debug, una versione per il non debug).

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

[Modifica in base al commento OP]

  

In effetti uno dei motivi che voglio   sovrascrivere le funzioni è perché io   sospetto che si comportino diversamente   diversi sistemi operativi.

Esistono due modi comuni (di cui sono a conoscenza) di gestirlo, il modo lib / dll condiviso o la scrittura di diverse implementazioni a cui ti colleghi.

Per entrambe le soluzioni (librerie condivise o collegamenti diversi) avresti 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 stai cercando entrambi codici diversi per piattaforme diverse E debug -vs- release probabilmente sarei propenso ad andare con la soluzione lib / DLL condivisa in quanto è la più flessibile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top