Überschreiben Sie einen Funktionsaufruf in C

Lesezeit: 9 Minuten

Benutzeravatar von dreamlax
Traumlax

Ich möchte bestimmte Funktionsaufrufe an verschiedene APIs überschreiben, um die Aufrufe zu protokollieren, aber ich möchte möglicherweise auch Daten manipulieren, bevor sie an die eigentliche Funktion gesendet werden.

Angenommen, ich verwende eine Funktion namens getObjectName tausendfach in meinem Quellcode. 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 sie zuerst mit dieser Funktion, bevor ich sie mit der API-Bibliothek verlinke. Das funktioniert gut, außer dass ich die eigentliche Funktion offensichtlich nicht innerhalb meiner überschreibenden Funktion aufrufen kann.

Gibt es eine einfachere Möglichkeit, eine Funktion zu “überschreiben”, ohne Verknüpfungs-/Kompilierungsfehler/Warnungen zu erhalten? Idealerweise möchte ich in der Lage sein, die Funktion zu überschreiben, indem ich einfach ein oder zwei zusätzliche Dateien kompiliere und verlinke, anstatt mit Verknüpfungsoptionen herumzuspielen oder den tatsächlichen Quellcode meines Programms zu ändern.

  • @dreamlax, wir wechseln jetzt von den allgemeinen (C) zu spezifischen (gcc/linux) Lösungen – es wäre eine gute Idee zu klären, was Sie ausführen, um die Antworten besser zu zielen.

    – paxdiablo

    6. März 2009 um 3:36 Uhr

  • Nun, ich entwickle auf Linux, aber die Ziele sind Mac OS, Linux und Windows. Tatsächlich ist einer der Gründe, warum ich Funktionen überschreiben möchte, der, dass ich vermute, dass sie sich auf verschiedenen Betriebssystemen unterschiedlich verhalten.

    – Traumlax

    6. März 2009 um 3:49 Uhr

  • Ein verwandter Thread: stackoverflow.com/questions/68689260/…

    – smWikipedia

    7. August 2021 um 12:19 Uhr

Benutzeravatar von codelogic
Codelogik

Mit gcc können Sie unter Linux die --wrap Linker-Flag wie folgt:

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

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 Anrufe zu getObjectName() werden zu Ihrer Wrapper-Funktion umgeleitet (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 jedoch mit g++ kompilieren.

  • das ist ein netter Weg. wusste nichts von dem. aber wenn ich die Manpage richtig lese, sollte es “__real_getObjectName ( anObject );” sein. die vom Linker an getObjectName weitergeleitet wird. Andernfalls rufen Sie __wrap_getObjectName erneut rekursiv auf. oder übersehe ich etwas?

    – Johannes Schaub – litb

    6. März 2009 um 3:41 Uhr

  • Sie haben Recht, es muss __real_getObjectName sein, danke. Ich hätte in der Manpage nachsehen sollen 🙂

    – Codelogik

    6. März 2009 um 3:46 Uhr

  • Ich bin enttäuscht, dass ld unter Mac OS X das nicht unterstützt --wrap Flagge.

    – Daryl Spitzer

    6. Januar 2011 um 20:04 Uhr

  • Übrigens sieht der Compiler die Deklaration des __real*-Symbols nicht vor. Sie müssen es selbst mit deklarieren extern char *__real_getObjectName(object *anObject).

    – Brenda

    13. Mai 2016 um 3:13 Uhr


Benutzeravatar von paxdiablo
paxdiablo

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

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

Anschließend implementieren Sie die Funktion wie folgt (in intercept.c die nicht enthalten intercept.h):

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

Stellen Sie dann sicher, dass jede Quelldatei, in der Sie den Anruf abfangen möchten, oben Folgendes enthält:

#include "intercept.h"

Beim Kompilieren mit “-DINTERCEPT“, werden alle Dateien aufgerufen dein Funktion statt der echten, während Ihre Funktion immer noch die echte aufrufen wird.

Kompilieren ohne “-DINTERCEPT” wird ein Abfangen verhindern.

Etwas kniffliger wird es, wenn Sie alle Aufrufe abfangen wollen (nicht nur die von Ihrer Quelle) – das geht generell mit dynamischem Laden und Auflösung der eigentlichen Funktion (mit dlload- und dlsym-geben Sie Anrufe ein), aber ich denke nicht, dass dies in Ihrem Fall erforderlich ist.

  • Verwenden Sie ein Kompilierzeit-Flag zum Steuern des Abfangens (siehe aktualisierte Antwort). Auch dies kann zur Laufzeit erfolgen, Sie müssen es nur in myGetObjectName() erkennen und immer getObjectName aufrufen, wenn das Laufzeit-Flag gesetzt ist (dh immer noch abfangen, aber Verhalten ändern).

    – paxdiablo

    6. März 2009 um 2:54 Uhr

  • Sie können die Option „-include file“ verwenden, damit GCC die Datei automatisch einbindet. Sie müssen dann nicht einmal eine Datei anfassen 🙂

    – Johannes Schaub – litb

    6. März 2009 um 4:40 Uhr

  • obwohl das automatische Einschließen Probleme beim Einschließen der ursprünglichen API-Datei verursachen kann. seine Deklarationen werden ebenfalls ersetzt. nicht ganz einfach 🙂

    – Johannes Schaub – litb

    6. März 2009 um 4:43 Uhr

  • Diese Lösung gefällt mir am besten, da sie auf jedem C-Compiler funktioniert. Es ist auch der seit Jahrzehnten bewährte Weg. Ziemlich einfach.

    – Craig S

    6. März 2009 um 4:56 Uhr

  • Dies funktioniert hervorragend für Unit-Test-Suites (wie die Verwendung von CGreen), bei denen Sie einige Abhängigkeiten innerhalb einer Funktion stubben möchten. War eine großartige Antwort für meine Zwecke.

    – nrjohnstone

    10. Juli 2013 um 2:13 Uhr

Benutzeravatar von qrdl
qrdl

Sie können eine Funktion mit überschreiben LD_PRELOAD Trick – siehe man ld.so. Sie kompilieren die Shared Lib mit Ihrer Funktion und starten die Binärdatei (Sie müssen die Binärdatei nicht einmal ändern!) like LD_PRELOAD=mylib.so myprog.

Im Hauptteil Ihrer Funktion (in Shared Lib) 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, sogar aus der stdlib, überschreiben, ohne das Programm zu ändern/neu zu kompilieren, sodass Sie den Trick bei Programmen ausführen können, für die Sie keine Quelle haben. Ist es nicht schön?

  • Nein, Sie können nur eine Funktion überschreiben, die von a bereitgestellt wird gemeinsame Bibliothek Hier entlang.

    – Chris Stratton

    19. Dezember 2012 um 22:30 Uhr


  • @ChrisStratton Sie haben Recht, Syscall kann auf diese Weise nicht überschrieben werden, ich habe meine Antwort bearbeitet.

    – qrdl

    20. Dezember 2012 um 6:24 Uhr

Johannes Schaub - Benutzerbild der litb
Johannes Schaub – litb

Wenn Sie GCC verwenden, können Sie Ihre Funktion ausführen weak. Diese kann überschrieben werden durch nichtschwache Funktionen:

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!

Bei anderen Compilern funktioniert das leider nicht. Aber Sie können die schwachen Deklarationen, die überschreibbare Funktionen enthalten, in ihrer eigenen Datei haben, indem Sie nur ein Include in die API-Implementierungsdateien einfügen, wenn Sie mit GCC kompilieren:

schwachdekls.h:

__attribute__((weak)) void test(void);
... other weak function declarations ...

Funktionen.c:

/* for GCC, these will become weak definitions */
#ifdef __GNUC__
#include "weakdecls.h"
#endif

void test(void) { 
    ...
}

... other functions ...

Nachteil davon ist, dass es nicht funktioniert völlig ohne etwas mit den API-Dateien zu tun (wobei diese drei Zeilen und die Weakdecls benötigt werden). Aber sobald Sie diese Änderung vorgenommen haben, können Funktionen einfach überschrieben werden, indem Sie eine globale Definition in eine Datei schreiben und diese einbinden.

Es ist oft wünschenswert, das Verhalten bestehender Codebasen durch Umschließen oder Ersetzen von Funktionen zu ändern. Wenn das Bearbeiten des Quellcodes dieser Funktionen eine praktikable Option ist, kann dies ein unkomplizierter Prozess sein. Wenn die Quelle der Funktionen nicht bearbeitet werden kann (z. B. wenn die Funktionen von der System-C-Bibliothek bereitgestellt werden), dann sind alternative Techniken erforderlich. Hier stellen wir solche Techniken für UNIX-, Windows- und Macintosh OS X-Plattformen vor.

Dies ist ein großartiges PDF, das zeigt, wie dies unter OS X, Linux und Windows gemacht wurde.

Es hat keine erstaunlichen Tricks, die hier nicht dokumentiert wurden (das ist übrigens eine erstaunliche Reihe von Antworten) … aber es ist eine nette Lektüre.

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

Du kannst Laden Sie das PDF direkt von einem alternativen Speicherort herunter (für Redundanz).

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

Benutzeravatar von Chris Peterson
Chris Peterson

Sie können einen Funktionszeiger als globale Variable definieren. Die Aufrufer-Syntax würde sich nicht ändern. Wenn Ihr Programm startet, könnte es überprüfen, ob ein Befehlszeilen-Flag oder eine Umgebungsvariable gesetzt ist, um die Protokollierung zu aktivieren, dann den ursprünglichen Wert des Funktionszeigers speichern und ihn durch Ihre Protokollierungsfunktion ersetzen. Sie würden keinen speziellen Build mit aktivierter Protokollierung benötigen. Benutzer könnten die Protokollierung „im Feld“ aktivieren.

Sie müssen in der Lage sein, den Quellcode des Aufrufers zu ändern, aber nicht den Aufgerufenen (dies würde also beim Aufrufen 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();
}

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

Versehen Sie die Funktion, die Sie überschreiben möchten, mit einem Alias ​​für eine schwach definierte Funktion und implementieren Sie sie dann selbst neu.

überschreiben.h

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

foo.c

function foo() { return 1234; }

überschreiben.c

function foo() { return 5678; }

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

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

Nebenbei: Vielleicht könntest du das auch gebrauchen -D 'foo(x) __attribute__((weak))foo(x)' um Ihre Makros zu definieren.

Kompilieren und verknüpfen Sie die Datei 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.

  • Verstanden: Ein schwerwiegender Fehler ist aufgetreten: Das Segment, das bei 0x3f000120 geladen wurde, landet in derselben 64-KB-Flash-Zuordnung wie das Segment, das bei 0x3f000020 geladen wurde. Binärdatei kann nicht generiert werden. Schlagen Sie vor, das Linker-Skript oder ELF zu ändern, um Abschnitte zusammenzuführen.

    – xtrinken

    8. Juni 2021 um 13:14 Uhr

  • Diese Methode kann keine statischen Funktionen verarbeiten.

    – smWikipedia

    29. Juli 2021 um 7:18 Uhr

  • Ich denke, der wichtige Teil ist, zuerst die einzubeziehen override.h ohne die Originalquelle zu verändern. Zweitens die foo.c wird mit kompiliert override.hwo das Original foo() ist als dekoriert weak Symbol. Während override.c wird ohne kompiliert override.h um ein nicht schwaches Symbol bereitzustellen foo().

    – smWikipedia

    29. Juli 2021 um 13:58 Uhr


  • Wenn die foo() Die Funktion wird auch dort verwendet, wo sie definiert ist, die gcc gibt error: expected declaration specifiers or ‘...’ before numeric constant.

    – smWikipedia

    29. Juli 2021 um 14:07 Uhr

  • @vaughan Bitte sehen Sie meine Antwort. Vielen Dank.

    – smWikipedia

    6. August 2021 um 15:35 Uhr

1417490cookie-checkÜberschreiben Sie einen Funktionsaufruf in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy