Question

Je souhaite remplacer certains appels de fonction vers différentes API afin de les consigner, mais je souhaite également manipuler les données avant qu'elles ne soient envoyées à la fonction.

Par exemple, disons que j'utilise une fonction appelée getObjectName des milliers de fois dans mon code source. Je souhaite parfois remplacer temporairement cette fonction car je souhaite modifier le comportement de cette fonction pour afficher un résultat différent.

Je crée un nouveau fichier source comme celui-ci:

#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 je le ferais normalement, mais je le lie à cette fonction avant de le lier à 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 obtenir d'erreurs de liaison / compilation / avertissements? Idéalement, je veux pouvoir remplacer la fonction en compilant et en liant un fichier supplémentaire ou en liant deux plutôt que de manipuler les options de liaison ou en modifiant le code source de mon programme.

Était-ce utile?

La solution

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 ne n'inclut pas intercept.h ):

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

Assurez-vous ensuite que chaque fichier source où vous souhaitez intercepter l'appel a:

#include "intercept.h"

en haut.

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.

Compiler sans le " -DINTERCEPT " empêchera l'interception de se produire.

C’est un peu plus délicat si vous voulez intercepter tous les appels (pas seulement ceux de votre source) - ceci peut généralement être fait avec le chargement dynamique et la résolution de la fonction réelle (avec dlload - et < code> dlsym - type appels) mais je ne pense pas que ce soit nécessaire dans votre cas.

Autres conseils

Avec gcc, sous Linux, vous pouvez utiliser le drapeau - wrap comme ceci:

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

et définissez votre fonction comme suit:

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 réacheminés vers votre fonction wrapper (au moment de la liaison). Ce drapeau très utile est toutefois absent de gcc sous Mac OS X.

N'oubliez pas de déclarer la fonction wrapper avec extern "" " C " si vous compilez avec g ++ si.

Vous pouvez remplacer une fonction à l'aide de LD_PRELOAD - 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 (dans la librairie 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. Vous pourrez ainsi faire l'affaire avec des programmes pour lesquels vous ne possédez pas de source. N'est-ce pas agréable?

Si vous utilisez GCC, vous pouvez rendre votre fonction faible . Ces peuvent être ignoré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 contenant 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, c’est que cela ne fonctionne pas intégralement sans modifier les fichiers api (nécessitant ces trois lignes et les fichiers faibles). 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 liant cette dernière.

  

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   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   fournie par la bibliothèque système C),   alors les techniques alternatives sont   Champs obligatoires. Ici, nous présentons ces   techniques pour UNIX, Windows et   Plateformes Macintosh OS X.

C’est un excellent PDF qui explique comment cela a été fait sous OS X, Linux et Windows.

Il n’a pas d’astuces incroyables qui n’aient pas été documentées ici (c’est un ensemble incroyable de réponses, BTW) ... mais c’est une bonne lecture.

Interception de fonctions arbitraires sous Windows, UNIX et Macintosh OS Plateformes X (2004), de Daniel S. Myers et Adam L. Bazinet .

Vous pouvez télécharger le fichier PDF directement à partir d'un autre emplacement (pour la redondance) .

Enfin, si les deux sources précédentes sont en flammes, 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 option spéciale "Journalisation activée". construire. Les utilisateurs peuvent activer la journalisation "sur le terrain".

Vous devez être en mesure de modifier le code source des appelants, mais pas celui qui est appelé (cela fonctionnerait donc lors de l'appel de 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.

Faites très attention aux ordres de liens ici, sinon 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.

Aliasez la fonction que vous souhaitez remplacer par une fonction faiblement définie, puis réimpliquez-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. ajouter l'indicateur de compilateur -include override.h .

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

De côté: vous pourriez peut-être aussi utiliser -D 'foo (x) __attribute __ ((faible)) foo (x)' pour définir vos macros.

Compilez le fichier et liez-le à votre nouvelle implémentation ( override.c ).

  • Ceci vous permet de remplacer une seule fonction depuis 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 une DLL (Windows) (ce qui constituerait une pénalité de performances). Vous pouvez ensuite modifier la DLL / afin que celle-ci soit chargée (une version pour le débogage, une version pour les non-débogages).

J'ai déjà 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 en fonction du commentaire 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.

Il y a deux façons courantes (que je sache) de gérer cela, 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 auriez foo_linux.c, foo_osx.c, foo_win32.c (ou un meilleur moyen est linux / foo.c, osx / foo.c et win32 / foo.c ) et ensuite compiler et lier avec celui qui convient.

Si vous recherchez à la fois du 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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top