IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

creation d'un "faux" fichier pour dlopen


Sujet :

C

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Février 2007
    Messages
    34
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 34
    Par défaut creation d'un "faux" fichier pour dlopen
    Bonjour,

    j'effectue des tests unitaires et je voudrais hardcoder certaines fonctions.

    Dans mon code à tester, certaines fonctions ne peuvent etre appelées sans un minimum de contexte qu'il ne m'est pas possible d'avoir pendant les tests.
    Ces fonctions sont appelées par dlsym qui fait reference à un dlopen.

    Mon code est dans ce genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    pHandle = dlopen( "lib1.so", RTLD_NOW );
    if( pHandle  == NULL ) {
    sprintf( ErrorMsg, "Cannot dlopen() to lib1" );
     
    pFunc1 = ( Status(*) ( Message_t  *, 
    Message_t  ** )) dlsym( pHandle, "Func1");
     
    pFunc2 = ( void(*) ( Message_t  *)) dlsym( pHandle, "Func2");

    J'aimerais pouvoir créer un fichier du genre Mylib.c (pas forcement .so) qui permet de faire la meme chose que si dlopen appelait lib1.so mais, dans mon fichier crée j'aurais le lien vers Func1 et Func2 (dont j'aurais fait une implementation personnelle pour mes tests).

    En gros, je veux savoir de quelle facon créer les fichiers Mylib (en parametre de dlopen), Func1 et Func2 (pour ceux là, a priori, je crée des fichiers .c avec la fonction à l'interieur).

    Si quelqu'un a une idée.
    Merci d'avance.

    Ludovic

    PS : j'espere que ce que j'ai dis est assez clair pour que vous puissiez m'aider.

  2. #2
    Expert éminent
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Par défaut
    Citation Envoyé par kanonji
    Bonjour,

    j'effectue des tests unitaires et je voudrais hardcoder certaines fonctions.

    Dans mon code à tester, certaines fonctions ne peuvent etre appelées sans un minimum de contexte qu'il ne m'est pas possible d'avoir pendant les tests.
    Ces fonctions sont appelées par dlsym qui fait reference à un dlopen.
    Le principe des plug-ins (bibliothèques à lien dynamiques, DLL ou SO), c'est exactement de pouvoir remplacer une bibliothèque par une autre facilement. Il te suffit de créer ta propre bibliothèque .so de test (même nom, mêmes interfaces).

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Février 2007
    Messages
    34
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 34
    Par défaut
    Citation Envoyé par Emmanuel Delahaye
    Il te suffit de créer ta propre bibliothèque .so de test (même nom, mêmes interfaces).
    Par mêmes interfaces, vous entendez quoi ?
    Pour le nom, à la limite, si je mets un chemin absolu dans l'argument du dlopen, je peux tres bien mettre un autre nom.

    Auriez vous un exemple de creation de bibliotheque dynamique .so (je n'ai rien trouvé dans le tutoriel qui parle de .so).

    Merci encore

    Ludovic

  4. #4
    Expert éminent
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Par défaut
    Citation Envoyé par kanonji
    Par mêmes interfaces, vous entendez quoi ?
    Mêmes prototypes de fonctions (utilise le même .h, ça évitera les bêtises).
    Pour le nom, à la limite, si je mets un chemin absolu dans l'argument du dlopen, je peux tres bien mettre un autre nom.

    Auriez vous un exemple de creation de bibliotheque dynamique .so (je n'ai rien trouvé dans le tutoriel qui parle de .so).
    C'est comme une bibliothèque normale avec une option de plus à ar. Lire la doc de ar (archiver). Genre -fPIC -shared...

  5. #5
    Membre émérite

    Profil pro
    Inscrit en
    Août 2003
    Messages
    878
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 878
    Par défaut
    Solution "dégradée" (quoique...) pour éviter d'avoir à créer une bibliothèque avec les mêmes interfaces (sous-entendu : toutes) : créer une bibliothèque qui sera chargée via LD_PRELOAD et interceptera les appels aux seules fonctions que tu souhaites "hardcoder" (utile si la bibliothèque originale comporte énormément de fonctions alors que tes tests n'en utilisent que peu ou que peu d'entre elles doivent être "hardcodées").

  6. #6
    Membre expérimenté Avatar de Ksempac
    Inscrit en
    Février 2007
    Messages
    165
    Détails du profil
    Informations forums :
    Inscription : Février 2007
    Messages : 165
    Par défaut
    Si l'anglais ne te fait pas peur je te conseille cet excellent HOWTO qui m'a permis, en partant de 0, de tout comprendre sur les bibliotheques sous Linux

    http://www.faqs.org/docs/Linux-HOWTO...ary-HOWTO.html

  7. #7
    Membre averti
    Profil pro
    Inscrit en
    Février 2007
    Messages
    34
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 34
    Par défaut
    Merci pour les informations.

    Je vais voir ce que je peux faire avec tout ca.
    Je tiendrais au courant de l'evolution de mon travail.

    Merci.

    Ludovic

    PS : tout autre renseignement est bon à prendre, donc n'hésitez pas.

  8. #8
    Membre averti
    Profil pro
    Inscrit en
    Février 2007
    Messages
    34
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 34
    Par défaut
    J'ai commencé à utiliser le systeme avec LD_PRELOAD et j'ai une question en fait:

    que dois je mettre dans le fichier qui va etre compiler comme une librarie partagée ??

    Avez vous un exemple ou une idée par rapport à l'exemple que j'ai donné dans mon premier post. Je comprends la logique du LD_PRELOAD mais j'ai du mal à saisir ce que je dois mettre dans le premier fichier.

    Merci d'avance.

    Ludovic

  9. #9
    Membre émérite

    Profil pro
    Inscrit en
    Août 2003
    Messages
    878
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 878
    Par défaut
    Citation Envoyé par kanonji
    J'ai commencé à utiliser le systeme avec LD_PRELOAD et j'ai une question en fait:

    que dois je mettre dans le fichier qui va etre compiler comme une librarie partagée ??

    Avez vous un exemple ou une idée par rapport à l'exemple que j'ai donné dans mon premier post. Je comprends la logique du LD_PRELOAD mais j'ai du mal à saisir ce que je dois mettre dans le premier fichier.

    Merci d'avance.

    Ludovic
    Je vais essayer (de trouver le temps) d'écrire un petit exemple.

  10. #10
    Membre averti
    Profil pro
    Inscrit en
    Février 2007
    Messages
    34
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 34
    Par défaut
    Citation Envoyé par David.Schris
    Je vais essayer (de trouver le temps) d'écrire un petit exemple.
    Merci, si tu y arrives, ca serait vraiment sympa.

  11. #11
    Membre émérite

    Profil pro
    Inscrit en
    Août 2003
    Messages
    878
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 878
    Par défaut
    Citation Envoyé par David.Schris
    Je vais essayer (de trouver le temps) d'écrire un petit exemple.
    J'ai eu le temps.
    Maintenant, ne me reste plus qu'à écrire les explications qui vont avec : ça sera probablement demain ou après demain.

  12. #12
    Membre averti
    Profil pro
    Inscrit en
    Février 2007
    Messages
    34
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 34
    Par défaut
    Merci beaucoup.

    J'attends donc.

  13. #13
    Membre émérite

    Profil pro
    Inscrit en
    Août 2003
    Messages
    878
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 878
    Par défaut
    Allons-y...

    Imaginons que nous ayons :
    • une bibliothèque appelée "test" (fichier "libtest.so.1.0") qui exporte plus d'une centaine de fonctions (102 exactement) dont certaines ne peuvent être appelées lors de tests unitaires, les faisant échouer ;
    • un programme appelé "test" (fichier exécutable "test") qui (est censé) utilise(r) cette bibliothèque pour réaliser des tests ;
    • un script (fichier "test.sh") en shell (BASH) qui lance le programme "test" et affiche un message de réussite si tous les tests ont réussi et un message d'échec si l'un des tests (ou plus) échoue.

    Imaginons aussi que :
    • les fonctions exportées par la bibliothèque soient, pour beaucoup, complexes à réimplémenter ;
    • le nombre et la complexité de ces fonctions rendent difficile la réimplémentation d'une bibliothèque ayant la même interface ;
    • nous soyons sous Linux.


    Le code source (et les fichiers d'entêtes) de ces premiers éléments suit.

    "libtest.h" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #ifndef __LIBTEST_H__
    #define __LIBTEST_H__
     
    /* Prototypes */
    int fonctionA( int argA1 );
    int fonctionB( int argB1 );
    int fonctionC( int argC1 );
     
    /* Insérer ici les prototypes des 99 autres fonctions complexes */
     
    #endif /* __LIBTEST_H__ */
    "libtest.c" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include <stdio.h>
    #include "libtest.h"
     
    int fonctionA( int argA1 ) {
            fprintf( stderr, "ATTENTION: fonction '%s()' non implémentée (arg1=%i)\n", __FUNCTION__, argA1 );
            return 0;
    }
     
    int fonctionB( int argB1 ) {
            int retVal=0;
            int i;
     
            i = fonctionA( argB1 );
            if ( 0 != i ) {
                    retVal = i*2;
            }
     
            return retVal;
    }
     
    /* Imagine que cette fonction soit très complexe */
    int fonctionC( int argC1 ) {
            return (argC1 * 3);
    }
     
    /* Insérer ici le code de 99 fonctions complexes */
    "test.c" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #include <stdio.h>
    #include <stdlib.h>
    #include "libtest.h"
     
    #define TEST_FONCTION_X(__fct__, __val__, __ret__) { \
            int i; \
            printf( "test de '" #__fct__ "()': arg=%i, valeur attendue=%i ...", __val__, __ret__ ); \
            i = __fct__( __val__ ); \
            if ( __ret__ == i ) { \
                    fputs( "OK\n", stdout ); \
            } else { \
                    fputs( "ECHEC\n", stdout ); \
                    retVal = EXIT_FAILURE; \
            } \
    } /* TEST_FONCTION_X */
     
    int main ( void ) {
            int retVal = EXIT_SUCCESS;
     
            /* Premier test : fonctionB() doit renvoyer 0 */
            TEST_FONCTION_X( fonctionB, 0, 0 );
     
            /* Deuxième test : fonctionB() doit renvoyer 4 */
            TEST_FONCTION_X( fonctionB, 2, 4 );
     
            /* Troisième test : fonctionC() doit renvoyer 9 */
            TEST_FONCTION_X( fonctionC, 3, 9 );
     
            exit( retVal );
    }
    "test.sh" :
    Code shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #!/bin/bash
     
    export LD_LIBRARY_PATH=.
     
    ./test \
            && { echo "--== REUSSITE des tests ==--" >&2 ; } \
            || { echo "--== ECHEC des tests ==--" >&2 ; exit 1 ; }

    Compilons la bibliothèque (et créons les liens symboliques qui vont avec) et le programme :
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    $ gcc -Wall -Wextra -std=c99 -fPIC -rdynamic -c libtest.c -o libtest.o
    $ gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so.1.0 libtest.o
    $ ln -sf libtest.so.1.0 libtest.so.1
    $ ln -sf libtest.so.1.0 libtest.so
    $ gcc -Wall -Wextra -std=c99 -L. -ltest -o test test.c

    Maintenant, lançons le script "test.sh" :
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $ ./test.sh
    ATTENTION: fonction 'fonctionA()' non implémentée (arg1=0)
    test de 'fonctionB()': arg=0, valeur attendue=0 ...OK
    ATTENTION: fonction 'fonctionA()' non implémentée (arg1=2)
    test de 'fonctionB()': arg=2, valeur attendue=4 ...ECHEC
    test de 'fonctionC()': arg=3, valeur attendue=9 ...OK
    --== ECHEC des tests ==--
    $
    Echec

    En observant le code du programme et de la bibliothèque, on se rend compte :
    • que la fonction "fonctionA()" renvoie toujours 0 ;
    • que, par conséquent, la fonction "fonctionB()" renvoie elle aussi toujours 0 ;
    • que cela fait échouer le second test de cette fonction dans le programme.


    La fonction "fonctionB()" n'étant pas utilisable en l'état, pouvons-nous la "remplacer" par un fonction autre ? Peut-on le faire sans que le programme n'ait besoin d'être modifié ? Sans que la bibliothèque ne soit modifiée (les membres de l'équipe en charge de la bibliothèque seraient vexés qu'on y touche ) ?
    Oui, oui et oui.

    Comment ?
    En détournant les appels à "fonctionB()" vers une fonction de remplacement située dans une (autre) bibliothèque qui sera écrite pour l'occasion et préchargée juste avant l'exécution du programme.
    Cette nouvelle bibliothèque s'appellera "hijack" (fichier "libhijack.so").

    Le fichier d'entête et le code de cette nouvelle bibliothèque suivent, ainsi que le code du script qui servira à lancer le programme en demandant à ce que la bibliothèque "hijack" soit préchargée (fichier "hijack.sh").

    "libhijack.h" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #ifndef __LIBHIJACK_H__
    #define __LIBHIJACK_H__
     
    /* Prototype(s) */
    int fonctionB( int argB1 );
     
    #endif /* __LIBHIJACK_H__ */
    "libhijack.c" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #define _GNU_SOURCE /* pour RTLD_NEXT */
    #include <stdio.h>
    #include <dlfcn.h>
    #include "libhijack.h"
     
    typedef int (*PFCTB)(int);
     
    int fonctionB( int argB1 ) {
            PFCTB vraieFonctionB;
     
            fprintf( stderr, "-- Dans la fausse fonctionB(): argB1=%i\n", argB1 );
     
            vraieFonctionB = dlsym( RTLD_NEXT, "fonctionB" );
            /* **** INSERER GESTION D'ERREURS ICI **** */
     
            fprintf( stderr, "   La vraie fonction aurait renvoyé '%i'\n", vraieFonctionB( argB1 ) );
            fprintf( stderr, "   Nous renverrons '%i'\n", argB1==0?0:argB1*2 );
     
            return (argB1==0?0:argB1*2);
    }
    "hijack.sh" :
    Code shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
     
    export LD_LIBRARY_PATH=.
    export LD_PRELOAD=./libhijack.so
     
    ./test \
            && { echo "--== REUSSITE des tests ==--" >&2 ; } \
            || { echo "--== ECHEC des tests ==--" >&2 ; exit 1 ; }

    Comment cela fonctionne-t-il ?
    Tout d'abord, notons la différence entre les deux scripts shell : le second exporte une variable d'environnement appelée "LD_PRELOAD" qui contient le chemin vers la bibliothèque "hijack".
    Lorsque le programme est lancé, le chargeur de bibliothèques commence par charger "libhijack.so" en mémoire puis toutes les bibliothèques dont le programme a besoin.
    Les liens vers les symboles (notamment les noms de fonctions) importés par le programme sont résolus en les cherchant dans les différentes bibliothèques dans l'ordre de chargement.
    Donc :
    • la fonction "fonctionB()" importée par le programme sera en fait celle de "libhijack.so" (car elle aura été chargée en premier) ;
    • la fonction "fonctionC()" importée par le programme sera celle exportée par "libtest.so" car "libhijack.so" (ni les autres bibliothèques) n'exporte pas un tel symbole.


    Lorsque le programme appellera "fonctionC()", il appellera la vraie "fonctionC()", celle de "libtest.so".
    Lorsqu'il appellera "fonctionB()", il appellera la "fausse" "fonctionB()", celle de "libhijack.so".

    On notera aussi que la "fausse" "fonctionB()" appelle la "vraie" "fonctionB()" : elle utilise "dlsym()" avec le handle "RTLD_NEXT" qui veut dire, en gros, "rechercher le symbole dans les bibliothèques suivantes".

    Ok ?

    Compilons notre nouveau "joujou" :
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ gcc -Wall -Wextra -std=c99 -fPIC -rdynamic -c libhijack.c -o libhijack.o
    $ gcc -shared -ldl -o libhijack.so libhijack.o

    Laçons le script "hijack.sh" :
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $ ./hijack.sh
    -- Dans la fausse fonctionB(): argB1=0
    ATTENTION: fonction 'fonctionA()' non implémentée (arg1=0)
       La vraie fonction aurait renvoyé '0'
       Nous renverrons '0'
    test de 'fonctionB()': arg=0, valeur attendue=0 ...OK
    -- Dans la fausse fonctionB(): argB1=2
    ATTENTION: fonction 'fonctionA()' non implémentée (arg1=2)
       La vraie fonction aurait renvoyé '0'
       Nous renverrons '4'
    test de 'fonctionB()': arg=2, valeur attendue=4 ...OK
    test de 'fonctionC()': arg=3, valeur attendue=9 ...OK
    --== REUSSITE des tests ==--
    $
    Et voilà ! Problème résolu !


    Quoi ? Pas encore ?


    Ah...oui...c'est vrai : le programme ne doit pas être lié à "libtest.so" mais doit la charger dynamiquement.


    Ecrivons un tel programme ("test2.c") :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    #include <stdio.h>
    #include <stdlib.h>
    #include <dlfcn.h>
     
    typedef int (*PFCT)(int);
     
    #define TEST_FONCTION_X(__fct__, __val__, __ret__) { \
            int i;
            printf( "test de '" #__fct__ "()': arg=%i, valeur attendue=%i ...", __val__, __ret__ ); \
            i = __fct__( __val__ ); \
            if ( __ret__ == i ) { \
                    fputs( "OK\n", stdout ); \
            } else { \
                    fputs( "ECHEC\n", stdout ); \
                    retVal = EXIT_FAILURE; \
            } \
    } /* TEST_FONCTION_X */
     
    int main ( void ) {
            int retVal = EXIT_FAILURE;
            void *libtestHandle;
            PFCT fonctionA;
            PFCT fonctionB;
            PFCT fonctionC;
     
            /* Chargement de la bibliothèque libtest.so */
            libtestHandle = dlopen( "libtest.so", RTLD_NOW );
            if ( NULL == libtestHandle ) {
                    fputs( "ECHEC du chargement de libtest.so\n", stderr );
                    goto exit_main;
            }
     
            /* Récupération des pointeurs vers les fonctions */
            /* TODO: GESTION DES ERREURS !!! */
            fonctionA = dlsym( libtestHandle, "fonctionA" );
            fonctionB = dlsym( libtestHandle, "fonctionB" );
            fonctionC = dlsym( libtestHandle, "fonctionC" );
            retVal = EXIT_SUCCESS;
     
            /* Premier test : fonctionB() doit renvoyer 0 */
            TEST_FONCTION_X( fonctionB, 0, 0 );
     
            /* Deuxième test : fonctionB() doit renvoyer 4 */
            TEST_FONCTION_X( fonctionB, 2, 4 );
     
            /* Troisième test : fonctionC() doit renvoyer 9 */
            TEST_FONCTION_X( fonctionC, 3, 9 );
     
            dlclose( libtestHandle );
     
    exit_main:                                                                                                                   
            exit( retVal );                                                                                                      
    }
    Compilons-le :
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    $ gcc -Wall -Wextra -std=c99 -ldl -o test2 test2.c

    Ecrivons les deux scripts correspondant (ou modifions les précédents).

    "test2.sh" :
    Code shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #!/bin/bash
     
    export LD_LIBRARY_PATH=.
     
    ./test2 \
            && { echo "--== REUSSITE des tests ==--" >&2 ; } \
            || { echo "--== ECHEC des tests ==--" >&2 ; exit 1 ; }

    "hijack2.sh" :
    Code shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
     
    export LD_LIBRARY_PATH=.
    export LD_PRELOAD=./libhijack.so
     
    ./test2 \
            && { echo "--== REUSSITE des tests ==--" >&2 ; } \
            || { echo "--== ECHEC des tests ==--" >&2 ; exit 1 ; }

    Testons :
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $ ./test2.sh
    ATTENTION: fonction 'fonctionA()' non implémentée (arg1=0)
    test de 'fonctionB()': arg=0, valeur attendue=0 ...OK
    ATTENTION: fonction 'fonctionA()' non implémentée (arg1=2)
    test de 'fonctionB()': arg=2, valeur attendue=4 ...ECHEC
    test de 'fonctionC()': arg=3, valeur attendue=9 ...OK
    --== ECHEC des tests ==--
    $
    Tout va bien : "l'erreur est juste" ( ).
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $ ./hijack2.sh
    ATTENTION: fonction 'fonctionA()' non implémentée (arg1=0)
    test de 'fonctionB()': arg=0, valeur attendue=0 ...OK
    ATTENTION: fonction 'fonctionA()' non implémentée (arg1=2)
    test de 'fonctionB()': arg=2, valeur attendue=4 ...ECHEC
    test de 'fonctionC()': arg=3, valeur attendue=9 ...OK
    --== ECHEC des tests ==--
    $
    Aïe... Là c'est moins bon : notre bibliothèque chargée du détournement de la fonction "fonctionB()" n'est plus utilisable
    En effet, notre nouveau programme spécifie explicitement à dlsym() que la fonction "fonctionB()" dont il a besoin est à chercher dans la librairie "libtest.so" (en lui passant son handle).

    Bon, et maintenant ? On se morfond ?

    Ben non

    On détourne dlopen() et dlsym() !

    Notre nouveau "joujou" sera "libhijack2.so" et en voici le code et le fichier d'entête.

    "libhijack2.h" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #ifndef __LIBHIJACK2_H__
    #define __LIBHIJACK2_H__
     
    #ifndef LHJ2_GLIBC_VERSION
    #define LHJ2_GLIBC_VERSION "GLIBC_2.0"
    #endif
     
    /* Prototype(s) */
    void *dlopen( const char *filename, int flag );
    void *dlsym( void *handle, const char *symbol );
    int fonctionB( int argB1 );
     
    #endif /* __LIBHIJACK2_H__ */
    "libhijack2.c" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    #define _GNU_SOURCE /* pour RTLD_NEXT */
    #include <stdio.h>
    #include <dlfcn.h>
    #include <string.h>
    #include "libhijack2.h"
     
    typedef int (*PFCTB)( int );
    typedef void * (*PDLOPEN)( const char *, int );
    typedef void * (*PDLSYM)(void *, const char *);
     
    /* Handle de la vraie libtest.so */
    static void *vraieLibtest = NULL;
     
    void *dlopen( const char *filename, int flag ) {
            void *handle; /* le handle à retourner */
            PDLOPEN vraieDlopen;
     
            fprintf( stderr, "-- Dans la fausse dlopen(): filename='%s', flag=%X\n", filename, flag );
     
            vraieDlopen = dlsym( RTLD_NEXT, "dlopen" );
            /* **** INSERER GESTION D'ERREURS ICI **** */
     
            /*
             * On charge la bibliothèque et on récupère son handle
             * (qu'on retournera).
             */
            handle = vraieDlopen( filename, flag );
            /*
             * Pas besoin de gérer les erreurs ici : on agit de façon
             * transparente pour le programme appelant donc c'est à lui
             * de les gérer :)
             *   (i.e. : est-ce que dlopen() gère les erreurs retournées
             *    par dlopen() ???)
             */
     
            /* 
             * Si la bibliothèque demandée est libtest.so,
             * on garde une copie de son handle.
             */
            /*
             * TODO: modifier le test pour supporter libtest.so.1,
             * libtest.so.1.0 et les cas où un chemin est donné.
             */
            if ( 0 == strcmp( "libtest.so", filename ) ) {
                    if ( NULL == vraieLibtest ) {
                            vraieLibtest = handle;
                    }
            }
     
            return handle;
    }
     
    void *dlsym( void *handle, const char *symbol ) {                                                                            
            void *retPtr; /* pointeur qui sera retourné */                                                                      
            PDLSYM vraieDlsym;                                                                                                   
     
            /* Si l'appelant veut l'adresse de fonctionB dans libtest.so ... */                                                  
            if ( \                                                                                                               
                    ( NULL != handle ) \                                                                                         
                    && \                                                                                                         
                    ( handle == vraieLibtest ) \                                                                                 
                    && \                                                                                                         
                    ( 0 == strcmp( "fonctionB", symbol ) ) \                                                                     
                    ) {                                                                                                          
     
                    /* ...on lui donne l'adresse de notre fonctionB */                                                           
                    retPtr = fonctionB;                                                                                          
     
            } else {                                                                                                             
     
                    /* Sinon, on a besoin de récupérer son adresse */                                                          
     
                    /*                                                                                                           
                     * On commence par récupérer l'adresse de la vraie dlsym()                                                 
                     * en utilisant dlvsym() (avec un "v") car notre fonction                                                    
                     * s'appelle dlsym() (vu ?)                                                                                  
                     */                                                                                                          
                    vraieDlsym = dlvsym( RTLD_NEXT, "dlsym", LHJ2_GLIBC_VERSION );                                               
                    /* INSERER GESTION D'ERREURS ICI */                                                                          
     
                    retPtr = vraieDlsym( handle, symbol );                                                                       
            }                                                                                                                    
     
            return retPtr;                                                                                                       
    }                                                                                                                            
     
    int fonctionB( int argB1 ) {                                                                                                 
     
            fprintf( stderr, "-- Dans la fausse fonctionB(): argB1=%i\n", argB1 );                                               
     
            return (argB1==0?0:argB1*2);                                                                                         
    }
    Compilation :
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $ tmpGLCV=`nm libhijack.so | grep dlsym | sed 's/^.*@@//'`
    $ gcc -Wall -Wextra -std=c99 -fPIC -rdynamic -c libhijack2.c -o libhijack2.o -DLHJ2_GLIBC_VERSION=\"${tmpGLCV}\"
    $ gcc -shared -o libhijack2.so libhijack2.o -ldl
    $ unset tmpGLCV

    Script de test ("hijack3.sh") :
    Code shell : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
     
    export LD_LIBRARY_PATH=.
    export LD_PRELOAD=./libhijack2.so
     
    ./test2 \
            && { echo "--== REUSSITE des tests ==--" >&2 ; } \
            || { echo "--== ECHEC des tests ==--" >&2 ; exit 1 ; }

    Essayons :
    Code X : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $ ./hijack3.sh
    -- Dans la fausse dlopen(): filename='libtest.so', flag=2
    -- Dans la fausse fonctionB(): argB1=0
    test de 'fonctionB()': arg=0, valeur attendue=0 ...OK
    -- Dans la fausse fonctionB(): argB1=2
    test de 'fonctionB()': arg=2, valeur attendue=4 ...OK
    test de 'fonctionC()': arg=3, valeur attendue=9 ...OK
    --== REUSSITE des tests ==--

    Voilà
    Mission accomplie (enfin...j'espère...parce-que là...).

    Cordialement,
    DS.


    PS :
    Code Makefile : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    CC = gcc
    CCOPT = -Wall -Wextra -std=c99
     
    all: libtest.so libtest.so.1 test test2 libhijack.so libhijack2.so
     
    clean:
            rm *.o
            rm *.so
            rm libtest.so.1 libtest.so.1.0 test test2
     
    test: test.c
            $(CC) $(CCOPT) -L. -ltest -o test test.c
     
    test2: test2.c
            $(CC) $(CCOPT) -ldl -o test2 test2.c
     
    libtest.so: libtest.so.1.0
            ln -sf libtest.so.1.0 libtest.so
     
    libtest.so.1: libtest.so.1.0
            ln -sf libtest.so.1.0 libtest.so.1
     
    libtest.so.1.0: libtest.o
            $(CC) -shared -Wl,-soname,libtest.so.1 -o libtest.so.1.0 libtest.o
     
    libtest.o: libtest.c
            $(CC) $(CCOPT) -fPIC -rdynamic -c libtest.c -o libtest.o
     
    libhijack.so: libhijack.o
            $(CC) -shared -ldl -o libhijack.so libhijack.o
     
    libhijack.o: libhijack.c
            $(CC) $(CCOPT) -fPIC -rdynamic -c libhijack.c -o libhijack.o
     
    libhijack2.so: libhijack2.o
            $(CC) -shared -o libhijack2.so libhijack2.o -ldl
     
    libhijack2.o: libhijack2.c libhijack.so
            $(CC) $(CCOPT) -fPIC -rdynamic -c libhijack2.c -o libhijack2.o -DLHJ2_GLIBC_VERSION=\"`nm libhijack.so | grep dlsym | sed 's/^.*@@//'`\"

  14. #14
    Membre émérite Avatar de crocodilex
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    697
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 697
    Par défaut
    Un grand bravo à cet exemple que je trouve très pédagogique.

    Il mériterait une p'tite place dans les tutoriaux de DVP.net

    Merci David.


  15. #15
    Expert éminent
    Avatar de Emmanuel Delahaye
    Profil pro
    Retraité
    Inscrit en
    Décembre 2003
    Messages
    14 512
    Détails du profil
    Informations personnelles :
    Âge : 68
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2003
    Messages : 14 512
    Par défaut
    Citation Envoyé par David.Schris
    Allons-y...
    "libtest.h" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    #ifndef __LIBTEST_H__
    #define __LIBTEST_H__
    <...>
    OK sur le fond. Pour la forme, attention à ne pas utiliser d'identificateurs réservé à l'implémentation.

    Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    #ifndef H_LIBTEST
    #define H_LIBTEST
    <...>
    http://emmanuel-delahaye.developpez....htm#id_reserve

  16. #16
    Membre averti
    Profil pro
    Inscrit en
    Février 2007
    Messages
    34
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2007
    Messages : 34
    Par défaut
    Merci beaucoup pour cet exemple.

    Tout devrait rouler maintenant.

    Merci encore.

+ Répondre à la discussion
Cette discussion est résolue.

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo