79 Stimmen

Einen Funktionsaufruf in C außer Kraft setzen

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.

Ein Beispiel: Ich verwende eine Funktion namens getObjectName Tausende von Malen in meinem Quellcode. Ich möchte diese Funktion manchmal vorübergehend außer Kraft setzen, weil ich das Verhalten dieser Funktion ändern möchte, um ein anderes 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 anderen Quelltexte wie gewohnt, aber ich verknüpfe sie zuerst mit dieser Funktion, bevor ich sie mit der API-Bibliothek verknüpfe. Dies funktioniert gut, außer ich kann offensichtlich nicht die echte Funktion innerhalb meiner überschreibenden Funktion aufrufen.

Gibt es einen einfacheren Weg zu "überschreiben" eine Funktion ohne Verknüpfung/Kompilierung Fehler/Warnungen zu erhalten? Idealerweise möchte ich in der Lage sein, die Funktion zu überschreiben, indem Sie nur kompilieren und verknüpfen eine zusätzliche Datei oder zwei statt fiddle um mit Verknüpfung Optionen oder Ändern der tatsächlichen Quellcode meines Programms.

9voto

Chris Peterson Punkte 2357

Sie können einen Funktionszeiger als globale Variable definieren. Die Syntax des Aufrufers würde sich nicht ändern. Wenn Ihr Programm startet, könnte es prüfen, ob ein Befehlszeilenflag 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 bräuchten kein spezielles "Logging enabled" Build. Die Benutzer könnten die Protokollierung "im Feld" aktivieren.

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

5voto

vaughan Punkte 6458

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

Geben Sie der Funktion, die Sie überschreiben möchten, einen Alias für 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; }

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

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

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

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

  • Damit 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 eigene Header-Datei verwenden müssen.

3voto

Joshua Punkte 37898

Es gibt auch eine knifflige Methode, dies im Linker zu tun, die zwei Stub-Bibliotheken beinhaltet.

Bibliothek Nr. 1 wird gegen die Host-Bibliothek gelinkt und stellt das umdefinierte Symbol unter einem anderen Namen dar.

Bibliothek Nr. 2 ist mit Bibliothek Nr. 1 verknüpft, fängt den Aufruf ab und ruft die neu definierte Version in Bibliothek Nr. 1 auf.

Seien Sie sehr vorsichtig mit Link-Bestellungen hier, sonst wird es nicht funktionieren.

2voto

smwikipedia Punkte 56976

Im Folgenden finden Sie meine Experimente. Es gibt 4 Schlussfolgerungen im Körper und am Ende.

Kurzfassung

Um eine Funktion erfolgreich außer Kraft zu setzen, müssen Sie im Allgemeinen einige Dinge beachten:

  • schwaches Attribut
  • Anordnung der Übersetzungseinheit

Lange Version

Ich habe diese Quelldateien.

.
 decl.h
 func3.c
 main.c
 Makefile1
 Makefile2
 override.c
 test_target.c
 weak_decl.h

main.c

#include <stdio.h>

void main (void)
{
    func1();    
}

test_target.c

#include <stdio.h>

void func3(void);

void func2 (void)
{
    printf("in original func2()\n");
}

void func1 (void)
{
    printf("in original func1()\n");
    func2();
    func3();
}

func3.c

#include <stdio.h>

void func3 (void)
{
    printf("in original func3()\n");
}

deklarieren.h

void func1 (void);
void func2 (void);
void func3 (void);

weak_decl.h

void func1 (void);

__attribute__((weak))
void func2 (void);

__attribute__((weak))
void func3 (void);

override.c

#include <stdio.h>

void func2 (void)
{
    printf("in mock func2()\n");
}

void func3 (void)
{
    printf("in mock func3()\n");
}

Makefile1:

ALL:
    rm -f *.o *.a
    gcc -c override.c -o override.o
    gcc -c func3.c -o func3.o
    gcc -c test_target.c -o test_target_weak.o -include weak_decl.h
    ar cr all_weak.a test_target_weak.o func3.o
    gcc main.c all_weak.a override.o -o main -include decl.h 

Makefile2:

ALL:
    rm -f *.o *.a
    gcc -c override.c -o override.o
    gcc -c func3.c -o func3.o
    gcc -c test_target.c -o test_target_strong.o -include decl.h # HERE -include differs!!
    ar cr all_strong.a test_target_strong.o func3.o
    gcc main.c all_strong.a override.o -o main -include decl.h 

Ausgabe für Makefile1 Ergebnis:

in original func1()
in mock func2()
in mock func3()

Ausgabe für Makefile2:

rm *.o *.a
gcc -c override.c -o override.o
gcc -c func3.c -o func3.o
gcc -c test_target.c -o test_target_strong.o -include decl.h # -include differs!!
ar cr all_strong.a test_target_strong.o func3.o
gcc main.c all_strong.a override.o -o main -include decl.h 
override.o: In function `func2':
override.c:(.text+0x0): multiple definition of `func2'  <===== HERE!!!
all_strong.a(test_target_strong.o):test_target.c:(.text+0x0): first defined here
override.o: In function `func3':
override.c:(.text+0x13): multiple definition of `func3' <===== HERE!!!
all_strong.a(func3.o):func3.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
Makefile4:2: recipe for target 'ALL' failed
make: *** [ALL] Error 1

Die Symboltabelle:

all_weak.a:

test_target_weak.o:
0000000000000013 T func1  <=== 13 is the offset of func1 in test_target_weak.o, see below disassembly
0000000000000000 W func2  <=== func2 is [W]eak symbol with default value assigned
                 w func3  <=== func3 is [w]eak symbol without default value
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

func3.o:
0000000000000000 T func3 <==== func3 is a strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

all_strong.a:

test_target_strong.o:
0000000000000013 T func1
0000000000000000 T func2 <=== func2 is strong symbol
                 U func3 <=== func3 is undefined symbol, there's no address value on the left-most column because func3 is not defined in test_target_strong.c
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

func3.o:
0000000000000000 T func3  <=== func3 is strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

In beiden Fällen ist die override.o Symbole:

0000000000000000 T func2  <=== func2 is strong symbol
0000000000000013 T func3  <=== func3 is strong symbol
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

Demontage:

test_target_weak.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <func2>: <===== HERE func2 offset is 0
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # b <func2+0xb>
   b:   e8 00 00 00 00          callq  10 <func2+0x10>
  10:   90                      nop
  11:   5d                      pop    %rbp
  12:   c3                      retq   

0000000000000013 <func1>: <====== HERE func1 offset is 13
  13:   55                      push   %rbp
  14:   48 89 e5                mov    %rsp,%rbp
  17:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 1e <func1+0xb>
  1e:   e8 00 00 00 00          callq  23 <func1+0x10>
  23:   e8 00 00 00 00          callq  28 <func1+0x15>
  28:   e8 00 00 00 00          callq  2d <func1+0x1a>
  2d:   90                      nop
  2e:   5d                      pop    %rbp
  2f:   c3                      retq   

Die Schlussfolgerung lautet also:

  1. Eine Funktion, die in .o Datei kann die gleiche Funktion überschreiben, die in .a Datei. In der obigen Makefile1 le func2() y func3() en override.o übersteuert die Gegenstücke in all_weak.a . Ich habe es mit beiden versucht .o Dateien, aber es funktioniert nicht.

  2. Für GCC Sie brauchen die Funktionen nicht in separate Funktionen aufzuteilen. .o Dateien wie gesagt in aquí para Visual Studio-Toolchain . Wie wir im obigen Beispiel sehen können, sind beide func2() (in derselben Datei wie func1() ) und func3() (in einer separaten Datei) kann überschrieben werden.

  3. Um eine Funktion außer Kraft zu setzen, muss beim Kompilieren der Verbraucher 's Übersetzungseinheit müssen Sie diese Funktion als schwach angeben. Dadurch wird die Funktion als schwach in der consumer.o . Im obigen Beispiel wird beim Kompilieren der test_target.c das verbraucht func2() y func3() müssen Sie Folgendes hinzufügen -include weak_decl.h in der erklärt wird func2() y func3() als schwach. Die Website func2() ist auch definiert in test_target.c aber das ist in Ordnung.

Einige weitere Experimente

Immer noch mit den oben genannten Quelldateien. Aber ändern Sie die override.c ein wenig:

override.c

#include <stdio.h>

void func2 (void)
{
    printf("in mock func2()\n");
}

// void func3 (void)
// {
//     printf("in mock func3()\n");
// }

Hier habe ich die Überschreibungsversion von func3() . Ich habe dies getan, weil ich auf das Original zurückgreifen möchte func3() Implementierung im func3.c .

Ich benutze immer noch Makefile1 zu bauen. Der Build ist in Ordnung. Aber es tritt ein Laufzeitfehler auf wie unten:

xxx@xxx-host:~/source/override$ ./main
in original func1()
in mock func2()
Segmentation fault (core dumped)

Also habe ich die Symbole der letzten main :

0000000000000696 T func1
00000000000006b3 T func2
                 w func3

So können wir die func3 hat keine gültige Adresse. Deshalb tritt der Segmentfehler auf.

Und warum? Habe ich nicht die func3.o in die all_weak.a Archivdatei?

ar cr all_weak.a func3.o test_target_weak.o

Ich habe das Gleiche versucht mit func2 , wo ich die func2 Implementierung von ovrride.c . Aber dieses Mal gibt es keinen Segmentfehler.

override.c

#include <stdio.h>

// void func2 (void)
// {
//     printf("in mock func2()\n");
// }

void func3 (void)
{
    printf("in mock func3()\n");
}

Salida:

xxx@xxx-host:~/source/override$ ./main
in original func1()
in original func2()  <====== the original func2() is invoked as a fall back
in mock func3()

Meine Vermutung ist, weil func2 ist in der gleichen Datei/Übersetzungseinheit als func1 . Also func2 wird immer mitgebracht func1 . So kann der Linker immer auflösen func2 sei es vom test_target.c ou override.c .

Aber für func3 wird sie in einer separaten Datei/Übersetzungseinheit (func3.c). Wenn sie als schwach deklariert ist, wird der Verbraucher test_target.o wird weiterhin aufgezeichnet func3() als schwach. Aber Leider überprüft der GCC-Linker nicht die anderen .o Dateien aus demselben .a Datei, um nach einer Implementierung von func3() . Aber sie ist tatsächlich da.

all_weak.a:

func3.o:
0000000000000000 T func3 <========= func3 is indeed here!
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

test_target_weak.o:
0000000000000013 T func1
0000000000000000 W func2
                 w func3
                 U _GLOBAL_OFFSET_TABLE_
                 U puts

Ich muss also eine Überschreibungsversion in override.c sonst wird die func3() nicht aufgelöst werden kann.

Aber ich weiß immer noch nicht, warum sich der GCC so verhält. Wenn jemand erklären kann, bitte.

(Update 9:01 AM 8/8/2021: este Thread kann dieses Verhalten erklären, hoffentlich .)

Die weitere Schlussfolgerung lautet also:

  1. Wenn Sie ein Symbol als schwach deklarieren, sollten Sie besser Override-Versionen von tous die schwachen Funktionen. Andernfalls kann die ursprüngliche Version nicht aufgelöst werden, es sei denn, sie befindet sich innerhalb der gleichen Datei/Übersetzungseinheit des Anrufers/Verbrauchers.

0voto

TofuBeer Punkte 59410

Sie könnten auch eine gemeinsam genutzte Bibliothek (Unix) oder eine DLL (Windows) verwenden, um dies zu bewerkstelligen (dies würde jedoch zu einem gewissen Leistungsverlust führen). Sie können dann die DLL/so ändern, die geladen wird (eine Version für Debugging, eine Version für Nicht-Debugging).

Ich habe in der Vergangenheit etwas Ähnliches gemacht (nicht um das zu erreichen, was Sie versuchen, aber die Grundprämisse ist die gleiche) und es hat gut funktioniert.

[Bearbeiten aufgrund des OP-Kommentars]

Tatsächlich ist einer der Gründe, warum ich Funktionen außer Kraft setzen will, ist, dass ich weil ich vermute, dass sie sich auf verschiedenen verschiedenen Betriebssystemen unterschiedlich verhalten.

Es gibt zwei gängige Möglichkeiten (die ich kenne), damit umzugehen: die gemeinsame Lib/Dll oder das Schreiben verschiedener Implementierungen, gegen die Sie linken.

Für beide Lösungen (gemeinsam genutzte Bibliotheken oder unterschiedliches Linken) würden Sie foo_linux.c, foo_osx.c, foo_win32.c (oder besser linux/foo.c, osx/foo.c und win32/foo.c) haben und dann mit der entsprechenden kompilieren und linken.

Wenn Sie sowohl unterschiedlichen Code für verschiedene Plattformen UND Debug -vs Release suchen, würde ich wahrscheinlich geneigt sein, mit der gemeinsamen Lib/DLL-Lösung zu gehen, da es die flexibelste ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X