Ich möchte bestimmte Funktionsaufrufe an verschiedene APIs überschreiben, um die Aufrufe zu protokollieren, aber ich möchte möglicherweise auch Daten bearbeiten, bevor sie an die eigentliche Funktion gesendet werden.
Angenommen, ich verwende eine Funktion, die getObjectName
in meinem Quellcode tausende Male aufgerufen wird . Ich möchte diese Funktion manchmal vorübergehend überschreiben, weil ich das Verhalten dieser Funktion ändern möchte, um das unterschiedliche 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, verknüpfe sie jedoch zuerst mit dieser Funktion, bevor ich sie mit der API-Bibliothek verknüpfe. Dies funktioniert gut, außer dass ich die reale Funktion in meiner überschreibenden Funktion offensichtlich nicht aufrufen kann.
Gibt es eine einfachere Möglichkeit, eine Funktion zu "überschreiben", ohne Fehler / Warnungen beim Verknüpfen / Kompilieren zu erhalten? Idealerweise möchte ich in der Lage sein, die Funktion zu überschreiben, indem ich nur ein oder zwei zusätzliche Dateien kompiliere und verknüpfe, anstatt mit Verknüpfungsoptionen herumzuspielen oder den tatsächlichen Quellcode meines Programms zu ändern.
quelle
Antworten:
Wenn Sie die Aufrufe nur für Ihre Quelle erfassen / ändern möchten, besteht die einfachste Lösung darin, eine Header-Datei (
intercept.h
) zusammenzustellen mit:#ifdef INTERCEPT #define getObjectName(x) myGetObectName(x) #endif
und implementieren Sie die Funktion wie folgt (in
intercept.c
der nicht enthalten istintercept.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 abfangen möchten, Folgendes enthält:
#include "intercept.h"
oben.
Wenn Sie dann mit "
-DINTERCEPT
" kompilieren , rufen alle Dateien Ihre Funktion auf und nicht die echte, und Ihre Funktion kann weiterhin die echte aufrufen.Das Kompilieren ohne "
-DINTERCEPT
" verhindert das Abfangen.Es ist etwas schwieriger, wenn Sie alle Anrufe abfangen möchten (nicht nur die von Ihrer Quelle) - dies kann im Allgemeinen durch dynamisches Laden und Auflösen der realen Funktion (mit
dlload-
unddlsym-
Typ von Anrufen) erfolgen, aber ich denke nicht, dass dies bei Ihnen erforderlich ist Fall.quelle
Unter gcc können Sie unter gcc das
--wrap
Linker-Flag wie folgt verwenden:und definieren Sie Ihre 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 an
getObjectName()
Ihre Wrapper-Funktion umgeleitet werden (zur Verbindungszeit). Dieses sehr nützliche Flag fehlt jedoch in gcc unter Mac OS X.Denken Sie daran, die Wrapper-Funktion mit zu deklarieren,
extern "C"
wenn Sie mit g ++ kompilieren.quelle
--wrap
Flagge nicht unterstützt .extern char *__real_getObjectName(object *anObject)
.Sie können eine Funktion mit
LD_PRELOAD
trick - see überschreibenman ld.so
. Sie kompilieren die gemeinsam genutzte Bibliothek mit Ihrer Funktion und starten die Binärdatei (Sie müssen die Binärdatei sogar nicht ändern!) WieLD_PRELOAD=mylib.so myprog
.Im Hauptteil Ihrer Funktion (in der gemeinsam genutzten Bibliothek) schreiben Sie wie folgt:
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 überschreiben, auch aus stdlib, ohne das Programm zu ändern / neu zu kompilieren, sodass Sie den Trick für Programme ausführen können, für die Sie keine Quelle haben. Ist es nicht schön
quelle
Wenn Sie GCC verwenden, können Sie Ihre Funktion festlegen
weak
. 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?
Leider funktioniert das bei anderen Compilern nicht. Sie können jedoch die schwachen Deklarationen, die überschreibbare Funktionen enthalten, in ihrer eigenen Datei haben und nur ein Include in die API-Implementierungsdateien einfügen, wenn Sie mit GCC kompilieren:
schwachdecls.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 davon ist, dass es nicht ganz funktioniert, ohne etwas mit den API-Dateien zu tun (diese drei Zeilen und die Schwachstellen werden benötigt). 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.
quelle
Dies ist eine großartige PDF-Datei, die beschreibt, wie dies unter OS X, Linux und Windows gemacht wurde.
Es gibt keine erstaunlichen Tricks, die hier nicht dokumentiert wurden (dies ist übrigens 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 (aus Redundanzgründen) .
Und schließlich, sollten die beiden vorherigen Quellen irgendwie in Flammen aufgehen, hier ein Google-Suchergebnis dafür .
quelle
Sie können einen Funktionszeiger als globale Variable definieren. Die Syntax des Anrufers würde sich nicht ändern. Wenn Ihr Programm gestartet wird, kann es überprüfen, ob ein Befehlszeilenflag oder eine Umgebungsvariable gesetzt ist, um die Protokollierung zu aktivieren. Speichern Sie dann den ursprünglichen Wert des Funktionszeigers und ersetzen Sie ihn durch Ihre Protokollierungsfunktion. Sie würden keinen speziellen Build "Protokollierung aktiviert" benötigen. Benutzer können die Protokollierung "vor Ort" aktivieren.
Sie müssen in der Lage sein, den Quellcode des Anrufers zu ändern, nicht jedoch den Angerufenen (dies würde also funktionieren, wenn Sie Bibliotheken von Drittanbietern aufrufen).
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(); }
quelle
Aufbauend auf der Antwort von @Johannes Schaub mit einer Lösung, die für Code geeignet ist, den Sie nicht besitzen.
Alias die Funktion, die Sie überschreiben möchten, auf eine schwach definierte Funktion und implementieren Sie sie dann selbst neu.
override.h
#define foo(x) __attribute__((weak))foo(x)
foo.c
function foo() { return 1234; }
override.c
function foo() { return 5678; }
Verwenden Sie musterspezifische Variablenwerte in Ihrem Makefile, um das Compiler-Flag hinzuzufügen
-include override.h
.%foo.o: ALL_CFLAGS += -include override.h
Nebenbei: Vielleicht können Sie auch
-D 'foo(x) __attribute__((weak))foo(x)'
Ihre Makros definieren.Kompilieren Sie die Datei und verknüpfen Sie sie mit Ihrer Neuimplementierung (
override.c
).Auf diese Weise 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 müssen.
quelle
Es gibt auch eine schwierige Methode, dies im Linker mit zwei Stub-Bibliotheken zu tun.
Bibliothek Nr. 1 ist mit der Hostbibliothek verknüpft und zeigt das Symbol an, das unter einem anderen Namen neu definiert wird.
Bibliothek Nr. 2 ist mit Bibliothek Nr. 1 verknüpft, wobei der Aufruf abgefangen und die neu definierte Version in Bibliothek Nr. 1 aufgerufen wird.
Seien Sie sehr vorsichtig mit Linkbestellungen hier, sonst funktioniert es nicht.
quelle
Sie können auch eine gemeinsam genutzte Bibliothek (Unix) oder eine DLL (Windows) verwenden, um dies zu tun (wäre ein kleiner Leistungsverlust). Sie können dann die DLL / so ändern, dass sie geladen wird (eine Version für das Debuggen, eine Version für das Nicht-Debuggen).
Ich habe in der Vergangenheit etwas Ähnliches getan (nicht um das zu erreichen, was Sie erreichen wollen, aber die Grundvoraussetzung ist dieselbe) und es hat gut geklappt.
[Bearbeiten basierend auf OP-Kommentar]
Es gibt zwei gängige Methoden (die ich kenne), um damit umzugehen, die gemeinsame 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) hätten Sie foo_linux.c, foo_osx.c, foo_win32.c (oder besser Linux / foo.c, osx / foo.c und win32 / foo.c) und dann kompilieren und mit dem entsprechenden verknüpfen.
Wenn Sie sowohl nach unterschiedlichem Code für unterschiedliche Plattformen als auch nach Debug -vs- Release suchen, würde ich wahrscheinlich die gemeinsam genutzte lib / DLL-Lösung wählen, da diese am flexibelsten ist.
quelle