prototipo - lista funzioni c



Sovrascrivi una chiamata di funzione in C (6)

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 <apiheader.h>    

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.

https://ffff65535.com


È 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 piattaforme 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.

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


È 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".

Dovrai essere in grado di modificare il codice sorgente dei chiamanti, ma non il chiamato (quindi funzionerà quando chiamerai 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à.


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 è 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.





override