Je souhaite redéfinir certains appels de fonction vers différentes API afin de les consigner, mais je souhaite également manipuler les données avant leur envoi à la fonction.
Par exemple, disons que j'utilise une fonction appelée getObjectName
des milliers de fois dans mon code source. Je veux parfois remplacer temporairement cette fonction parce que je veux changer le comportement de cette fonction pour voir le résultat différent.
Je crée un nouveau fichier source comme ceci:
#include <apiheader.h>
const char *getObjectName (object *anObject)
{
if (anObject == NULL)
return "(null)";
else
return "name should be here";
}
Je compile tous mes autres sources comme d'habitude, mais je le lie à cette fonction avant de me connecter à la bibliothèque de l'API. Cela fonctionne bien, sauf que je ne peux évidemment pas appeler la fonction réelle dans ma fonction principale.
Existe-t-il un moyen plus simple de "remplacer" une fonction sans générer d'erreurs de liaison/compilation/avertissements? Dans l’idéal, je veux pouvoir remplacer la fonction en compilant et en liant un fichier supplémentaire ou deux au lieu de manipuler les options de liaison ou en modifiant le code source de mon programme.
Si c'est uniquement pour votre source que vous souhaitez capturer/modifier les appels, la solution la plus simple consiste à créer un fichier d'en-tête (intercept.h
) avec:
#ifdef INTERCEPT
#define getObjectName(x) myGetObectName(x)
#endif
et implémentez la fonction comme suit (dans intercept.c
qui n'a pas include intercept.h
):
const char *myGetObjectName (object *anObject) {
if (anObject == NULL)
return "(null)";
else
return getObjectName(anObject);
}
Ensuite, assurez-vous que chaque fichier source où vous voulez intercepter l'appel a:
#include "intercept.h"
au sommet.
Ensuite, lorsque vous compilez avec "-DINTERCEPT
", tous les fichiers appellent votre fonction plutôt que le réel et votre fonction peut toujours appeler le vrai.
La compilation sans "-DINTERCEPT
" empêchera l'interception de se produire.
C'est un peu plus compliqué si vous voulez intercepter tous les appels (pas seulement ceux de votre source) - cela peut généralement être fait avec un chargement dynamique et la résolution de la fonction réelle (avec des appels de type dlload-
et dlsym-
) mais je ne pense pas que ce soit nécessaire ton cas.
Avec gcc, sous Linux, vous pouvez utiliser l'indicateur d'éditeur de liens --wrap
comme suit:
gcc program.c -Wl,-wrap,getObjectName -o program
et définissez votre fonction comme:
const char *__wrap_getObjectName (object *anObject)
{
if (anObject == NULL)
return "(null)";
else
return __real_getObjectName( anObject ); // call the real function
}
Cela garantira que tous les appels à getObjectName()
sont redirigés vers votre fonction wrapper (au moment de la liaison). Ce drapeau très utile est cependant absent dans gcc sous Mac OS X.
N'oubliez pas de déclarer la fonction wrapper avec extern "C"
si vous compilez avec g ++ cependant.
Vous pouvez remplacer une fonction à l'aide de LD_PRELOAD
astuce - voir man ld.so
. Vous compilez une bibliothèque partagée avec votre fonction et démarrez le binaire (vous n'avez même pas besoin de modifier le binaire!) Comme LD_PRELOAD=mylib.so myprog
.
Dans le corps de votre fonction (en bibliothèque partagée), vous écrivez comme ceci:
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
}
Vous pouvez remplacer n'importe quelle fonction d'une bibliothèque partagée, même de stdlib, sans modifier/recompiler le programme, de sorte que vous puissiez faire l'affaire avec des programmes pour lesquels vous n'avez pas de source. N'est-ce pas gentil?
Si vous utilisez GCC, vous pouvez rendre votre fonction weak
. Ceux peuvent être remplacés par des fonctions non-faibles:
test.c:
#include <stdio.h>
__attribute__((weak)) void test(void) {
printf("not overridden!\n");
}
int main() {
test();
}
Qu'est ce que ça fait?
$ gcc test.c
$ ./a.out
not overridden!
test1.c:
#include <stdio.h>
void test(void) {
printf("overridden!\n");
}
Qu'est ce que ça fait?
$ gcc test1.c test.c
$ ./a.out
overridden!
Malheureusement, cela ne fonctionnera pas pour les autres compilateurs. Mais vous pouvez avoir les déclarations faibles qui contiennent des fonctions pouvant être remplacées dans leur propre fichier, en plaçant simplement une inclusion dans les fichiers d'implémentation de l'API si vous compilez avec GCC:
faibledecls.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 ...
L'inconvénient est que cela ne fonctionne pas entièrement sans modifier les fichiers api (nécessitant ces trois lignes et les faibles arguments). Mais une fois que vous avez fait ce changement, les fonctions peuvent être facilement remplacées en écrivant une définition globale dans un fichier et en le liant.
Il est souvent souhaitable de modifier le comportement des bases de code existantes par habillage ou remplacement de fonctions. Quand éditer le code source de ceux-ci fonctions est une option viable, cela peut être un processus simple. Quand la source des fonctions ne peut pas être édité (par exemple, si les fonctions sont fournies par la bibliothèque système C), alors les techniques alternatives sont Champs obligatoires. Ici, nous présentons tel techniques pour UNIX, Windows et Plates-formes Macintosh OS X.
C’est un excellent PDF qui explique comment cela a été fait sous OS X, Linux et Windows.
Il ne contient aucune astuce incroyable qui n’a pas été documentée ici (c’est un ensemble incroyable de réponses, BTW) ... mais c’est une bonne lecture.
Vous pouvez télécharger le PDF directement depuis un autre emplacement (pour la redondance) .
Et enfin, si les deux sources précédentes s’étaient embrasées, voici un résultat de recherche Google .
Vous pouvez définir un pointeur de fonction en tant que variable globale. La syntaxe des appelants ne changerait pas. Lorsque votre programme démarre, il peut vérifier si un indicateur de ligne de commande ou une variable d'environnement est défini pour activer la journalisation, puis enregistrez la valeur d'origine du pointeur de la fonction et remplacez-la par votre fonction de journalisation. Vous n'avez pas besoin d'une construction spéciale "activée pour la journalisation". Les utilisateurs peuvent activer la journalisation "sur le terrain".
Vous devrez pouvoir modifier le code source des appelants, mais pas celui de l'appelé (cela fonctionnerait donc lorsque vous appelez des bibliothèques tierces).
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();
}
Il existe également une méthode délicate de le faire dans l'éditeur de liens impliquant deux bibliothèques de stub.
La bibliothèque n ° 1 est liée à la bibliothèque hôte et expose le symbole en cours de redéfinition sous un autre nom.
La bibliothèque n ° 2 est liée à la bibliothèque n ° 1, interceptant l'appel et appelant la version redéfinie dans la bibliothèque n ° 1.
Soyez très prudent avec les commandes de liens ici ou cela ne fonctionnera pas.
S'appuyant sur la réponse de @Johannes Schaub avec une solution adaptée au code que vous ne possédez pas.
Alias la fonction que vous voulez remplacer par une fonction faiblement définie, puis réimplémentez-la vous-même.
override.h
#define foo(x) __attribute__((weak))foo(x)
foo.c
function foo() { return 1234; }
override.c
function foo() { return 5678; }
Utilisez les valeurs de variable spécifiques au modèle dans votre Makefile pour ajouter le drapeau du compilateur -include override.h
.
%foo.o: ALL_CFLAGS += -include override.h
Aside: Peut-être pourriez-vous également utiliser -D 'foo(x) __attribute__((weak))foo(x)'
pour définir vos macros.
Compilez et liez le fichier avec votre réimplémentation (override.c
).
Cela vous permet de remplacer une seule fonction à partir de n'importe quel fichier source, sans avoir à modifier le code.
L'inconvénient est que vous devez utiliser un fichier d'en-tête distinct pour chaque fichier que vous souhaitez remplacer.
Vous pouvez également utiliser une bibliothèque partagée (Unix) ou un DLL (Windows) (ce qui constituerait une pénalité de performances). Vous pouvez ensuite changer la DLL/afin que celle-ci soit chargée (une version pour le débogage, une version pour le non-débogage).
J'ai fait la même chose dans le passé (pas pour atteindre ce que vous essayez d'atteindre, mais le principe de base est le même) et cela a bien fonctionné.
[Modifier basé sur le commentaire de l'OP]
En fait, une des raisons pour lesquelles je veux remplacer les fonctions est parce que je soupçonne qu'ils se comportent différemment sur différents systèmes d'exploitation.
À ma connaissance, il existe deux méthodes courantes pour gérer ce problème: la méthode partagée lib/dll ou l'écriture de différentes implémentations auxquelles vous vous associez.
Pour les deux solutions (bibliothèques partagées ou liens différents), vous devez utiliser foo_linux.c, foo_osx.c, foo_win32.c (ou un meilleur moyen est linux/foo.c, osx/foo.c et win32/foo.c), puis compiler et lier avec celui qui convient.
Si vous recherchez à la fois un code différent pour différentes plates-formes ET la version debug -vs-, je serais probablement enclin à choisir la solution lib/DLL partagée car elle est la plus flexible.