Nadpisanie wywołania funkcji w C

Chcę nadpisać niektóre wywołania funkcji do różnych API w celu rejestrowania wywołań, ale mogę też chcieć manipulować danymi, zanim zostaną wysłane do rzeczywistej funkcji.

Na przykład, powiedzmy, że używam funkcji o nazwie getObjectName tysiące razy w moim kodzie źródłowym. Chcę tymczasowo nadpisać tę funkcję czasami, ponieważ chcę zmienić zachowanie tej funkcji, aby zobaczyć inny wynik.

Tworzę nowy plik źródłowy jak to:

#include <apiheader.h>    

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

Kompiluję wszystkie inne źródła tak, jak zwykle, ale najpierw łączę je z tą funkcją przed połączeniem z biblioteką API. To działa dobrze, z tym, że oczywiście nie mogę wywołać prawdziwej funkcji wewnątrz mojej nadrzędnej funkcji.

Czy istnieje łatwiejszy sposób na "nadpisanie" funkcji bez uzyskiwania linków / kompilacji błędów / ostrzeżeń? Idealnie chcę być w stanie nadpisać funkcję, kompilując i łącząc dodatkowy plik lub dwa, zamiast bawić się z linkowanie opcji lub zmiana kodu źródłowego mojego programu.

Author: Tim Post, 2009-03-06

9 answers

Jeśli tylko dla Twojego źródła chcesz przechwycić/zmodyfikować wywołania, najprostszym rozwiązaniem jest złożenie pliku nagłówka (intercept.h) z:

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

I zaimplementować funkcję w następujący sposób (w intercept.c który nie zawiera intercept.h):

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

Następnie upewnij się, że każdy plik źródłowy, w którym chcesz przechwycić połączenie, ma:

#include "intercept.h"

Na górze.

Wtedy, gdy skompilujesz za pomocą " -DINTERCEPT", wszystkie pliki będą wywoływać twoją funkcję, a nie rzeczywistą i twoja funkcja może nadal nazywać prawdziwą.

Kompilowanie bez "-DINTERCEPT " zapobiegnie przechwytywaniu.

Jest to nieco trudniejsze, jeśli chcesz przechwycić wszystkie połączenia (nie tylko te z twojego źródła) - zazwyczaj można to zrobić z dynamicznym ładowaniem i rozdzielczością rzeczywistej funkcji (z wywołaniami typu dlload- i dlsym-), ale nie sądzę, że jest to konieczne w Twoim przypadku.

 75
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-13 06:46:50

W gcc pod Linuksem możesz użyć znacznika--wrap linker w następujący sposób:

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

I zdefiniuj swoją funkcję jako:

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

Zapewni to, że wszystkie wywołania getObjectName() zostaną przekierowane do funkcji wrapper (w czasie połączenia). Ta bardzo przydatna flaga jest jednak nieobecna w gcc pod Mac OS X.

Pamiętaj, aby zadeklarować funkcję wrappera za pomocą extern "C", Jeśli kompilujesz z g++.

 84
Author: codelogic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-06 03:45:02

Możesz nadpisać funkcję za pomocą LD_PRELOAD trick-zobacz man ld.so. Kompilujesz współdzielony lib ze swoją funkcją i uruchamiasz plik binarny (nawet nie musisz go modyfikować!) jak LD_PRELOAD=mylib.so myprog.

W ciele swojej funkcji (w shared lib) piszesz tak:

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
}

Możesz nadpisać dowolną funkcję z biblioteki współdzielonej, nawet ze stdlib, bez modyfikowania / rekompilowania programu, więc możesz zrobić sztuczkę na programach, do których nie masz źródła. Czy to nie miłe?

 37
Author: qrdl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-29 19:12:03

Jeśli używasz GCC, możesz utworzyć swoją funkcję weak. Te mogą być nadpisane przez nie-słabe funkcje:

Test.c:

#include <stdio.h>

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

int main() {
    test();
}
Co to robi?
$ gcc test.c
$ ./a.out
not overridden!

Test1.c:

#include <stdio.h>

void test(void) {
    printf("overridden!\n");
}
Co to robi?
$ gcc test1.c test.c
$ ./a.out
overridden!

Niestety, to nie zadziała w przypadku innych kompilatorów. Ale możesz mieć słabe deklaracje, które zawierają nadpisywalne funkcje we własnym pliku, umieszczając tylko include w plikach implementacji API, jeśli kompilujesz za pomocą GCC:

Słabeusze.h :

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

Funkcje.c:

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

void test(void) { 
    ...
}

... other functions ...

Minusem tego jest to, że nie działa całkowicie bez robienia czegoś z plikami api (potrzebującymi tych trzech linii i weakdecls). Ale gdy już to zmienisz, funkcje można łatwo nadpisać, pisząc globalną definicję w jednym pliku i łącząc ją.

 26
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-06 04:31:52

Jest często pożądane, aby zmodyfikować zachowanie istniejących baz kodu przez owijanie lub zastępowanie funkcji. Kiedy edytowanie kodu źródłowego tych funkcje jest realną opcją, może to być prostym procesem. Kiedy źródłem funkcji nie może być edytowane (np. jeśli funkcje są dostarczane przez Bibliotekę system C), wtedy alternatywnymi technikami są wymagane. Tutaj prezentujemy takie techniki dla UNIX, Windows i Macintosh OS X perony.

Jest to świetny plik PDF opisujący, jak to było zrobione na OS X, Linux i Windows.

Nie ma żadnych niesamowitych sztuczek, które nie zostały tutaj udokumentowane(to jest niesamowity zestaw odpowiedzi BTW)... ale to miła lektura.

Przechwytywanie arbitralnych funkcji na platformach Windows, UNIX i Macintosh OS X (2004), przez Daniela S. Myersa i Adama L. Bazineta .

Możesz pobrać plik PDF bezpośrednio z alternatywnej lokalizacji (dla redundancja) .

I wreszcie, jeśli poprzednie dwa źródła w jakiś sposób pójdą w płomieniach, oto wynik wyszukiwania Google dla niego.

 11
Author: Kuba hasn't forgotten Monica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-14 02:29:05

Wskaźnik funkcji można zdefiniować jako zmienną globalną. Składnia wywołujących nie uległaby zmianie. Po uruchomieniu programu może sprawdzić, czy jakaś flaga wiersza poleceń lub zmienna środowiskowa jest ustawiona na włączanie logowania, a następnie zapisać oryginalną wartość wskaźnika funkcji i zastąpić ją funkcją logowania. Nie potrzebujesz specjalnej kompilacji "logging enabled". Użytkownicy mogli włączyć logowanie "w polu".

Musisz być w stanie zmodyfikować kod źródłowy wywołujących, ale nie callee (tak to działa podczas wywoływania bibliotek innych firm).

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
Author: Chris Peterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-06 03:03:48

Bazując na odpowiedzi @ Johannes Schaub z rozwiązaniem odpowiednim dla kodu, którego nie posiadasz.

Alias funkcji, którą chcesz nadpisać do słabo zdefiniowanej funkcji, a następnie ponownie ją zaimplementować.

Override.h

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

Foo.c

function foo() { return 1234; }

Override.c

function foo() { return 5678; }

Użyj wartości zmiennych specyficznych dla wzorca w pliku Makefile, aby dodać flagę kompilatora -include override.h.

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

Na bok: być może mógłbyś również użyć -D 'foo(x) __attribute__((weak))foo(x)' do zdefiniuj swoje makra.

Skompiluj i połącz Plik z reimplementacją (override.c).

  • Pozwala to na nadpisanie pojedynczej funkcji z dowolnego pliku źródłowego, bez konieczności modyfikowania kodu.

  • Minusem jest to, że musisz użyć oddzielnego pliku nagłówkowego dla każdego pliku, który chcesz nadpisać.

 4
Author: vaughan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-11 19:53:14

Istnieje również trudna metoda zrobienia tego w linkerze z udziałem dwóch bibliotek stubów.

Biblioteka #1 jest powiązana z biblioteką hosta i wyświetla symbol przedefiniowany pod inną nazwą.

Biblioteka # 2 jest powiązana z biblioteką # 1, intereceptując wywołanie i wywołując ponownie zdefiniowaną wersję w bibliotece #1.

Bądź bardzo ostrożny z zamówieniami linków tutaj, albo to nie zadziała.

 3
Author: Joshua,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-06 02:57:33

Możesz użyć biblioteki współdzielonej (Unix) lub DLL (Windows), aby to zrobić (byłoby to trochę kary za wydajność). Następnie można zmienić DLL / tak, że zostanie załadowany(jedna wersja dla debugowania, jedna wersja dla nie-debugowania).

Zrobiłem podobną rzecz w przeszłości (nie po to, aby osiągnąć to, co próbujesz osiągnąć, ale podstawowa przesłanka jest taka sama) i wyszło dobrze.

[Edit based on op comment]

W rzeczywistości jednym z powodów, dla których chcę override funkcji jest dlatego, że I podejrzewają, że zachowują się inaczej na różnych systemów operacyjnych.

Istnieją dwa wspólne sposoby (które znam) radzenia sobie z tym, dzielony sposób lib / dll lub pisanie różnych implementacji, które łączysz.

Dla obu rozwiązań (współdzielonych bibliotek lub różnych łączy) będzie foo_linux.C, foo_osx.C, foo_win32.c (lub lepszym sposobem jest linux / foo.c, osx / foo.c i win32 / foo.c) a następnie skompilować i połączyć z odpowiednim jeden.

Jeśli szukasz zarówno różnych kodów dla różnych platform, jak i debug-vs-release, prawdopodobnie byłbym skłonny wybrać rozwiązanie shared lib / DLL, ponieważ jest najbardziej elastyczne.

 0
Author: TofuBeer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-06 05:12:32