2009-03-06 33 views
58

Chcę przesłonić niektóre wywołania funkcji do różnych interfejsów API w celu rejestrowania wywołań, ale także może zajść potrzeba manipulowania danymi przed wysłaniem ich do rzeczywistej funkcji.Zastępowanie wywołania funkcji w C

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

utworzyć nowy plik źródłowy takiego:

#include <apiheader.h>  

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

skompilować wszystkie moje inne źródła jak normalnie, ale połączyć go przed tym zanim powiązanie funkcji z biblioteki API. Działa to dobrze, z wyjątkiem tego, że nie mogę oczywiście nazwać prawdziwej funkcji w mojej funkcji nadrzędnej.

Czy istnieje łatwiejszy sposób na "przesłonięcie" funkcji bez łączenia/kompilowania błędów/ostrzeżeń? Idealnie chciałbym móc zastąpić funkcję, kompilując i łącząc dodatkowy plik lub dwa, zamiast bawić się z opcjami łączenia lub zmieniając rzeczywisty kod źródłowy mojego programu.

+0

@Dreamlax, teraz przechodzimy od ogólnych (C) do konkretnych rozwiązań (gcc/linux) - dobrze byłoby wyjaśnić, na czym polega, aby lepiej ukierunkować odpowiedzi . – paxdiablo

+1

Cóż, rozwijam się na Linuksie, ale celami są Mac OS, Linux i Windows. W rzeczywistości jednym z powodów, dla których chcę zastąpić funkcje, jest podejrzenie, że zachowują się one inaczej w różnych systemach operacyjnych. – dreamlax

Odpowiedz

61

Jeśli to tylko do źródła, które chcesz przechwycić/modyfikacji połączeń, najprostszym rozwiązaniem jest stworzenie pliku nagłówka (intercept.h) z:

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

i realizacji funkcji w następujący sposób (w intercept.c który nie obejmują intercept.h):

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

następnie upewnij się, każdy plik źródłowy, w którym chcesz przechwycić wywołanie posiada:

#include "intercept.h" 

u góry.

Następnie, po kompilacji z "-DINTERCEPT", wszystkie pliki będą wywoływały twoją funkcję, a nie rzeczywistą, a twoja funkcja może nadal wywoływać prawdziwą. Kompilacja bez "-DINTERCEPT" uniemożliwi przechwycenie.

To trochę trudniejsze, jeśli chcesz przechwycić wszystkie połączenia (nie tylko te z twojego źródła) - można to zwykle zrobić przy dynamicznym ładowaniu i rozdzielczości prawdziwej funkcji (z wywołaniami typu dlload- i dlsym-), ale ja nie " Myślę, że jest to konieczne w twoim przypadku.

+0

Używanie definicji nie jest prawdziwą odpowiedzią polimorficzną, ale zgodzę się na użycie pomysłowości: P – Suroot

+0

Dzięki, to naprawdę dobry pomysł, ale jak powiedziałem, chcę spróbować uniknąć modyfikacji kodu źródłowego. Jeśli nie znajdę innej drogi, to muszę to zrobić tak, jak przypuszczam. Musi być łatwo wyłączyć przechwycenie. – dreamlax

+1

Użyj flagi kompilacji do kontrolowania przechwytywania (patrz zaktualizowana odpowiedź). Znowu można to zrobić również w środowisku wykonawczym, wystarczy je wykryć w myGetObjectName() i zawsze wywoływać funkcję getObjectName, jeśli ustawiono flagę środowiska wykonawczego (tzn. Nadal przechwytuje, ale zmienia zachowanie). – paxdiablo

3

Istnieje również trudny sposób robienia tego w linkerze z udziałem dwóch bibliotek pośredniczących.

Biblioteka # 1 jest połączona z biblioteką hosta i ujawnia symbol będący przedefiniowany pod inną nazwą.

Biblioteka # 2 jest połączona z biblioteką nr 1, odbierając połączenie i wywołując ponownie zdefiniowaną wersję w bibliotece nr 1.

Zachowaj ostrożność w zamówieniach linków tutaj lub nie będzie działać.

+0

Brzmi trudno, ale unika modyfikacji źródła. Bardzo ładna sugestia. – dreamlax

+0

Nie sądzę, że można zmusić getObjectName do przejścia do określonej biblioteki bez oszustwa dlopen/dlsym. – paxdiablo

+0

Każda operacja czasu łącza, która przeciąga bibliotekę hosta, spowoduje powstanie wielokrotnie mnożonego symbolu. – paxdiablo

7

Możesz zdefiniować wskaźnik funkcji jako zmienną globalną. Składnia wywołujących nie ulegnie zmianie.Po uruchomieniu programu można sprawdzić, czy ustawiono flagę wiersza polecenia lub zmienną środowiskową, aby włączyć rejestrowanie, a następnie zapisać oryginalną wartość wskaźnika funkcji i zastąpić ją funkcją rejestrowania. Nie potrzebujesz specjalnej kompilacji z włączoną rejestracją. Użytkownicy mogą włączyć rejestrowanie "w terenie".

Będziesz musiał mieć możliwość modyfikacji kodu źródłowego dzwoniącego, ale nie będzie to osoba, która wyewidencjonuje (tak by działało to przy wywoływaniu 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(); 
} 
+0

Rozważałem tę metodę, ale wymaga ona modyfikacji kodu źródłowego, czegoś, czego naprawdę nie chcę robić, chyba że muszę. Chociaż ma to dodatkową zaletę przełączania podczas pracy. – dreamlax

21

Jeśli używasz GCC można dokonać funkcję weak. Tych can be overridden przez non-słabych funkcji:

test.c:

#include <stdio.h> 

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

int main() { 
    test(); 
} 

Co on robi?

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

test1.c:

#include <stdio.h> 

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

Co on robi?

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

Niestety, to nie zadziała dla innych kompilatorów. Ale można mieć słabe deklaracje, które zawierają funkcje przeciążać we własnym pliku, umieszczając po prostu należą do plików implementacji API, jeśli kompilacji przy użyciu GCC:

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

minusem tego jest to, że nie działa całkowicie bez robienia czegoś do plików api (potrzebujących tych trzech linii i słabych danych). Ale gdy już to zrobił zmiany, funkcje mogą być zastępowane łatwo pisząc ogólnej definicji w jednym pliku i powiązanie że w

+1

To wymagałoby modyfikacji API, prawda? – dreamlax

+0

Twoja nazwa funkcji będzie taka sama. również nie zmieni ABI ani API w żaden sposób. po prostu dołącz plik nadpisujący podczas łączenia, a wywołania będą przekazywane do niewłasnej funkcji. libc/pthread wykona tę sztuczkę: gdy pthread jest połączony, jego wątkowe funkcje są używane zamiast słabego pliku biblioteki libc –

+0

. Dodałem link. Nie wiem, czy pasuje do twoich celów (tj. czy możesz żyć z tym GCC thingy. Jeśli msvc ma coś podobnego, możesz #define WEAK it thoigh). ale jeśli na Linuksie, użyłbym tego (być może jest jeszcze lepszy sposób, nie mam pojęcia, zajrzyj też do wersji). –

67

z GCC pod Linuksem można użyć flagi --wrap łącznika tak:.

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

i określić swoją funkcję jako:

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

To zapewni, że wszystkie połączenia są przekierowywane do getObjectName() do funkcji otoki (w czasie Link). Ta bardzo użyteczna flaga jest jednak nieobecna w gcc w systemie Mac OS X.

Pamiętaj, aby zadeklarować funkcję opakowania przy pomocy extern "C", jeśli kompilujesz przy użyciu g ++.

+3

to dobry sposób. nie wiedziałem o tym. ale jeśli czytam stronę podręcznika, powinien to być "__real_getObjectName (anObject);" który jest kierowany do getObjectName przez linker. w przeciwnym razie ponownie wywołasz rekursywnie __wrap_getObjectName. czy coś mi brakuje? –

+0

Masz rację, musisz __real_getObjectName, dziękuję. Powinienem dwukrotnie sprawdzić na stronie man :) – codelogic

+1

Jestem rozczarowany, że ld na Mac OS X nie obsługuje flagi '--wrap'. –

0

Można również użyć biblioteki współdzielonej (Unix) lub DLL (Windows), aby to zrobić (byłaby to niewielka kara za wydajność).Następnie możesz zmienić bibliotekę DLL/tak, aby została załadowana (jedna wersja do debugowania, jedna wersja do debugowania bez debugowania).

Zrobiłem coś podobnego w przeszłości (nie po to, aby osiągnąć to, co próbujesz osiągnąć, ale podstawowe założenie jest takie samo) i to się udało.

[Edycja na podstawie OP komentarz]

W rzeczywistości jeden z powodów, dla których chcą funkcji sterowania jest bo podejrzany zachowują się inaczej na różnymi systemami operacyjnymi.

Są dwa popularne sposoby (o których wiem) radzenia sobie z tym, wspólna metoda lib/dll lub pisanie różnych implementacji, z którymi się łączysz.

Dla obu rozwiązań (wspólne biblioteki lub inne łączenie) miałbyś foo_linux.c, foo_osx.c, foo_win32.c (lub lepszy sposób to linux/foo.c, osx/foo.c i win32/foo. c), a następnie skompiluj i połącz z odpowiednim.

Jeśli szukasz zarówno różnych kodów dla różnych platform, jak i debugowania -vs-release, prawdopodobnie byłbym skłonny pójść z udostępnionym rozwiązaniem lib/DLL, ponieważ jest najbardziej elastyczny.

34

Można zmienić funkcję za pomocą sztuczki LD_PRELOAD - patrz man ld.so. Kompilujesz udostępnioną bibliotekę ze swoją funkcją i uruchamiasz plik binarny (nawet nie musisz modyfikować pliku binarnego!), Np. LD_PRELOAD=mylib.so myprog.

W ciele swojej funkcji (w udostępnionym 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żna zastąpić dowolną funkcję z udostępnionej biblioteki, nawet z stdlib, bez modyfikowania/kompilacji programu, więc może zrób sztuczkę na programach, dla których nie masz źródła. Czy to nie miłe?

+2

Nie, można tylko zastąpić dostarczoną funkcję przez ** wspólną bibliotekę ** w ten sposób: –

+2

@ChrisStratton Masz rację, syscall nie może zostać zmieniony w ten sposób, Edytowałem moją odpowiedź – qrdl

9

Jest często pożądane, aby modyfikować zachowanie istniejących bazach kodu przez owijania lub zastępowania funkcji. Kiedy edytowanie kodu źródłowego tych funkcji jest realną opcją, może to być prosta procedura. Gdy źródło funkcji nie może być edytowane (np. Jeśli funkcje są dostarczone przez bibliotekę systemu C), , wówczas wymagane są alternatywne techniki . Poniżej przedstawiamy takie techniki dla platform UNIX, Windows i Macintosh OS X.

To świetny plik PDF opisujący, jak to zrobić w systemie OS X, Linux i Windows.

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

http://wwwold.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf

+0

Chcesz się podzielić tym, czym może być ten plik PDF? – HanClinto

+0

krata.umiacs.umd.edu/ files/functions_tr.pdf - Link dodany –

+0

Link jest martwy, nawiasem mówiąc – paxdiablo

1

Opierając się na odpowiedź @Johannes Schaub za pomocą roztworu odpowiedniego kodu nie właścicielem.

Alias ​​funkcji, którą chcesz przesłonić do słabo zdefiniowanej funkcji, a następnie wprowadź ją ponownie.

override.h

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

foo.c

function foo() { return 1234; } 

override.c

function foo() { return 5678; } 

Zastosowanie pattern-specific variable values w pliku Makefile, aby dodać flagę kompilatora -include override.h.

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

marginesie: Może również użyć -D 'foo(x) __attribute__((weak))foo(x)' do definiowania makr.

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

  • Umożliwia to zastąpienie pojedynczej funkcji z dowolnego pliku źródłowego, bez konieczności modyfikacji kodu.

  • Wadą jest to, że musisz użyć osobnego pliku nagłówkowego dla każdego pliku, który chcesz przesłonić.

Powiązane problemy