webentwicklung-frage-antwort-db.com.de

Überschreiben Sie einen Funktionsaufruf in C

Ich möchte bestimmte Funktionsaufrufe an verschiedene APIs zur Protokollierung der Aufrufe außer Kraft setzen, aber ich möchte auch Daten bearbeiten, bevor sie an die eigentliche Funktion gesendet werden.

Angenommen, ich verwende eine Funktion namens getObjectName in meinem Quellcode tausende Male. Ich möchte diese Funktion manchmal vorübergehend überschreiben, weil ich das Verhalten dieser Funktion ändern möchte, um das andere Ergebnis zu sehen.

Ich erstelle eine neue Quelldatei wie folgt:

#include <apiheader.h>    

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

Ich kompiliere alle meine anderen Quellen wie gewohnt, aber ich verlinke diese Funktion zuerst mit dieser Funktion, bevor sie mit der API-Bibliothek verbunden wird. Das funktioniert gut, außer ich kann die echte Funktion innerhalb meiner übergeordneten Funktion offensichtlich nicht aufrufen.

Gibt es eine einfachere Möglichkeit, eine Funktion zu "überschreiben", ohne dass Verknüpfungen/Kompilierungsfehler/Warnungen angezeigt werden? Im Idealfall möchte ich die Funktion überschreiben können, indem ich einfach ein oder zwei zusätzliche Dateien kompiliert und verlinkt, anstatt mit Verknüpfungsoptionen herumzuspielen oder den eigentlichen Quellcode meines Programms zu ändern.

64
dreamlax

Wenn Sie nur für Ihre Quelle die Aufrufe erfassen/ändern möchten, besteht die einfachste Lösung darin, eine Header-Datei (intercept.h) zusammenzustellen:

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

und implementiere die Funktion wie folgt (in intercept.c welche nicht include intercept.h):

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

Stellen Sie dann sicher, dass jede Quelldatei, in der Sie den Anruf abhören möchten, Folgendes aufweist:

#include "intercept.h"

oben.

Wenn Sie dann mit "-DINTERCEPT" kompilieren, rufen alle Dateien Ihre Funktion auf und nicht die eigentliche, und Ihre Funktion kann immer noch die echte aufrufen.

Beim Kompilieren ohne den "-DINTERCEPT" wird das Abfangen verhindert.

Es ist etwas komplizierter, wenn Sie alle Anrufe abfangen möchten (nicht nur die von Ihrer Quelle) - dies kann im Allgemeinen mit dynamischem Laden und der Auflösung der realen Funktion (mit dlload- und dlsym-type-Aufrufen) erfolgen, aber ich denke nicht, dass dies erforderlich ist dein Fall.

67
paxdiablo

Mit gcc können Sie unter Linux das Flag --wrap linker wie folgt verwenden:

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

und definiere deine Funktion als:

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

Dadurch wird sichergestellt, dass alle Aufrufe von getObjectName() an Ihre Wrapper-Funktion umgeleitet werden (zur Verbindungszeit). Diese sehr nützliche Flagge ist in gcc unter Mac OS X jedoch nicht vorhanden.

Vergessen Sie nicht, die Wrapper-Funktion mit extern "C" zu deklarieren, wenn Sie mit g ++ kompilieren.

74
codelogic

Sie können eine Funktion mit LD_PRELOAD trick überschreiben - siehe man ld.so. Sie kompilieren die shared lib mit Ihrer Funktion und starten die Binärdatei (Sie müssen die Binärdatei gar nicht modifizieren!), Wie LD_PRELOAD=mylib.so myprog.

In den Körper Ihrer Funktion (in shared lib) schreiben Sie so:

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
}

Sie können jede Funktion aus der gemeinsam genutzten Bibliothek, auch aus stdlib, überschreiben, ohne das Programm zu ändern oder neu zu kompilieren. So können Sie den Trick mit Programmen ausführen, für die Sie keine Quelle haben. Ist das nicht nett?

36
qrdl

Wenn Sie GCC verwenden, können Sie Ihre Funktion weak machen. Diese können durch nicht schwache Funktionen überschrieben werden:

test.c:

#include <stdio.h>

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

int main() {
    test();
}

Was tut es?

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

test1.c:

#include <stdio.h>

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

Was tut es?

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

Leider funktioniert das für andere Compiler nicht. Sie können jedoch die schwachen Deklarationen, die überschreibbare Funktionen enthalten, in ihrer eigenen Datei enthalten. Wenn Sie mit GCC kompilieren, fügen Sie nur ein Include in die API-Implementierungsdateien ein:

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

Der Nachteil ist, dass es nicht {vollständig funktioniert, ohne etwas mit den API-Dateien zu tun (diese drei Zeilen und die schwachen Deklarationen benötigen). Sobald Sie diese Änderung vorgenommen haben, können Funktionen leicht überschrieben werden, indem Sie eine globale Definition in eine Datei schreiben und diese verknüpfen.

Es ist oft wünschenswert, das .__ zu ändern. Verhalten vorhandener Codebasen durch Funktionen umwickeln oder ersetzen. Wann Bearbeiten des Quellcodes dieser Funktionen ist eine praktikable Option, dies kann ein direkter Prozess sein. Wann Die Quelle der Funktionen kann nicht .__ sein. bearbeitet (z. B. wenn die Funktionen von der System-C-Bibliothek zur Verfügung gestellt werden.), dann sind alternative Techniken erforderlich. Hier präsentieren wir solche Techniken für UNIX, Windows und Macintosh OS X-Plattformen.

Dies ist ein großartiger PDF Bericht darüber, wie dies unter OS X, Linux und Windows gemacht wurde.

Es gibt keine erstaunlichen Tricks, die hier nicht dokumentiert wurden (dies ist eine erstaunliche Reihe von Antworten), aber es ist eine schöne Lektüre.

Abfangen beliebiger Funktionen auf Windows-, UNIX- und Macintosh OS X-Plattformen (2004) von Daniel S. Myers und Adam L. Bazinet .

Sie können das PDF direkt von einem anderen Speicherort herunterladen (wegen Redundanz) .

Und schließlich, sollten die beiden vorherigen Quellen irgendwie in Flammen aufgehen, hier ist ein Google-Suchergebnis .

10
Orwellophile

Sie können einen Funktionszeiger als globale Variable definieren. Die Aufrufersyntax würde sich nicht ändern. Wenn Ihr Programm startet, könnte es prüfen, ob ein Befehlszeilenflag oder eine Umgebungsvariable für die Protokollierung aktiviert ist. Dann wird der ursprüngliche Wert des Funktionszeigers gespeichert und durch Ihre Protokollierungsfunktion ersetzt. Sie benötigen keine spezielle "Protokollierung aktiviert" - Erstellung. Benutzer können die Protokollierung "im Feld" aktivieren.

Sie müssen in der Lage sein, den Quellcode des Aufrufers zu ändern, nicht aber den Aufgerufenen (dies würde beim Aufruf von Bibliotheken von Drittanbietern funktionieren).

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();
}
9
Chris Peterson

Im Linker, der zwei Stub-Bibliotheken umfasst, gibt es auch eine schwierige Methode.

Bibliothek Nr. 1 ist mit der Host-Bibliothek verknüpft und macht das neu definierte Symbol unter einem anderen Namen verfügbar.

Bibliothek Nr. 2 ist mit Bibliothek Nr. 1 verbunden, nimmt den Aufruf an und ruft die neu definierte Version in Bibliothek Nr. 1 auf.

Seien Sie bei Link-Bestellungen hier sehr vorsichtig, sonst funktioniert es nicht.

3
Joshua

Aufbauend auf @Johannes Schaubs Antwort mit einer Lösung, die für Code geeignet ist, den Sie nicht besitzen.

Alias ​​die Funktion, die Sie in eine schwach definierte Funktion überschreiben möchten, und diese dann selbst erneut implementieren.

override.h

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

foo.c

function foo() { return 1234; }

override.c

function foo() { return 5678; }

Verwenden Sie pattern-spezifische Variablenwerte in Ihrem Makefile, um das Compiler-Flag -include override.h hinzuzufügen.

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

Nebenbei: Vielleicht können Sie auch -D 'foo(x) __attribute__((weak))foo(x)' verwenden, um Ihre Makros zu definieren.

Kompilieren und verknüpfen Sie die Datei mit Ihrer Neuimplementierung (override.c).

  • Dadurch können Sie eine einzelne Funktion aus einer beliebigen Quelldatei überschreiben, ohne den Code ändern zu müssen.

  • Der Nachteil ist, dass Sie für jede Datei, die Sie überschreiben möchten, eine separate Header-Datei verwenden muss.

3
vaughan

Sie können dazu auch eine gemeinsam genutzte Bibliothek (Unix) oder eine DLL (Windows) verwenden (dies würde die Leistung beeinträchtigen). Sie können dann die DLL so ändern, dass sie geladen wird (eine Version für Debugging, eine Version für Nicht-Debugging).

Ich habe in der Vergangenheit etwas Ähnliches getan (nicht um das zu erreichen, was Sie erreichen wollen, aber die Grundvoraussetzung ist die gleiche), und es hat gut funktioniert.

[Bearbeiten basierend auf OP-Kommentar]

In der Tat einer der Gründe, die ich möchte. Funktionen außer Kraft setzen, weil ich vermuten, dass sie sich auf .__ anders verhalten. verschiedene Betriebssysteme.

Es gibt zwei gängige Wege (wie ich weiß), um damit umzugehen, die freigegebene lib/dll-Methode oder das Schreiben verschiedener Implementierungen, mit denen Sie verknüpfen. 

Für beide Lösungen (gemeinsam genutzte Bibliotheken oder unterschiedliche Verknüpfungen) haben Sie foo_linux.c, foo_osx.c, foo_win32.c (oder eine bessere Möglichkeit sind linux/foo.c, osx/foo.c und win32/foo.c) und dann kompilieren und mit dem entsprechenden verknüpfen.

Wenn Sie nach unterschiedlichem Code für verschiedene Plattformen UND nach debug -vs- release suchen, würde ich mich wahrscheinlich für die gemeinsam genutzte lib/DLL-Lösung entscheiden, da sie am flexibelsten ist.

0
TofuBeer