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

Bibliothèque standard C Discussion :

Redéfinition des appels systèmes


Sujet :

Bibliothèque standard C

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Août 2003
    Messages
    67
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 67
    Points : 50
    Points
    50
    Par défaut Redéfinition des appels systèmes
    Bonjour,

    Je suis entrain de développer une API qui doit faire en sorte que tous les appels systèmes agissant sur le système de fichiers se fassent à distance (rpc ou socket). Pour ce faire, je dois redéfiir les appels systèmes open, write, read... ce qui est déjà fait. Par contre pour le code user, rien ne doit être modifié, excepté au pire des cas, l'ajout d'une directive include, et la recompilation des sources en les linkant avec ma librairie.
    Ce que j'ai essayé de faire, c'est de faire des remplacement par le préprocesseur de tous ces appels par des appels correspondant à ma librairie :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    #define write my_write
    Ceci, fonctionne correctement. Seulement, dès qu'il s'agit d'appels de fonctions de la libc faisant des appels systèmes, ce sont les versions originales qui sont utilisées, ce qui est normal car le printf par exemple ne voit jamais mon #define write my_write.

    Comment pourrais-je faire ?

    Merci d'avance !

  2. #2
    Membre du Club
    Profil pro
    Inscrit en
    Août 2003
    Messages
    67
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 67
    Points : 50
    Points
    50
    Par défaut
    Pour ceux qui sont intéressés, j'ai posé ma question sur linuxquestions.org, voici la réponse qui m'a été donnée :

    There are two approaches to this, non of which is trivial. The first is to have a preloaded library (with LD_PRELOAD) which has access to the original C library through dlopen(). You can read about this approach if you study the sources of say installwatch (part of src2pkg maintained by Gilbert Ashley—gnashley).

    The other approach is to use ptrace to intercept system calls as they happen, and alter them. This is also nontrivial, but much more elegant. There are at least a few pieces of software which already do this (I cannot think of one offhand).

    J'ai finalement opté pour l'approche ptrace.

  3. #3
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2008
    Messages : 145
    Points : 170
    Points
    170
    Par défaut
    Pour changer sur tout le système : il faut manipuler le fichier /etc/ld.so.preload ou /etc/ld.so.conf et utiliser ldconfig.

    Sinon, pour modifier juste une ou certaines applications, il faut mettre à jour la variable LD_PRELOAD. Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    export LD_PRELOAD=/lib/mylib.so
    Inutile je suppose de vous rappeler la prudence avec ce type de commande

  4. #4
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2008
    Messages : 145
    Points : 170
    Points
    170
    Par défaut
    Disons que la première méthode a justement été prévue pour ce genre de choses, elle est donc standard. Par ailleurs, elle est très simple à implémenter.

    L'approche ptrace() me semble bien plus compliquée à mettre en oeuvre et elle n'est de toute façons pas prévue pour faire ce genre de choses. J'ai donc un peu de mal à voir en quoi elle est plus élégante

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Août 2003
    Messages
    67
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 67
    Points : 50
    Points
    50
    Par défaut
    J'ai pris la deuxième solution non pas qu'elle me semblait plus élegante, mais plus intéressante car je n'avais jamais utilisé ptrace. De plus, je ne vois pas comment faire pour la première approche, j'ai l'impression que j'aurais le même problème pour les appels de la librairie c qui font appel à mes fonctions redéfinis.
    Je m'explique. Si dans mon main.c
    J'ai :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    #include <unistd.h>
    #include <string.h>
    int main(){
      write(1, "Hello World", strlen("Hello World"));
    }
    et que dans mon libc_stub.c, à partir duquel je vais construire ma lib dyn, j'ai :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <string.h>
     
    ssize_t write(int fd, const void *buf, size_t count){
      return syscall(SYS_write, fd, "Hello World from Stub" , strlen("Hello World from Stub"));
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    LD_PRELOAD=/opt/lib/libc_stub.so ./main
    affiche bien "Hello World from Stub"

    Par contre, ce main :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    #include <stdio.h>
    int main(){
      printf("Hello World\n");
      return 0;
    }
    affiche "Hello World" !

    Le write invoqué par le printf n'est pas celui de ma librairie mais le write de la libc.

  6. #6
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2008
    Messages : 145
    Points : 170
    Points
    170
    Par défaut
    Le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    #include <stdio.h>
    int main(){
      printf("Hello World\n");
      return 0;
    }
    Si on compile et on regarde la fonction appelée dans la libc :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    $ gcc foo.c
    $ nm -u a.out
             w _Jv_RegisterClasses
             w __gmon_start__
             U __libc_start_main@@GLIBC_2.0
             U puts@@GLIBC_2.0
    On voit que printf ne fait pas appel à write mais à puts

    Note : je viens de relire le post dès le début et de réaliser que la méthode ci-dessus ne fait que remplacer des fonctions standard de la libc. Si on veut réellement insérer un bout de code avant l'appel système (sys_)write, cela force donc à réimplémenter toutes les primitives de la libc qui font une écriture (write, puts...). Je comprends mieux alors pourquoi ptrace est une bonne solution. Maxi mea culpa.

  7. #7
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2008
    Messages : 145
    Points : 170
    Points
    170
    Par défaut
    En farfouillant sur la toile :
    Playing with ptrace
    L'exemple est super clair et l'implémentation est vraiment très simple

  8. #8
    Membre du Club
    Profil pro
    Inscrit en
    Août 2003
    Messages
    67
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 67
    Points : 50
    Points
    50
    Par défaut
    Puts (Section 3) n'est pas un appel système il me semble, il fait sûrement appel à write (Section 2) derrière. Je me trompe ?
    A la limite avec la méthode LD_PRELOAD, ça ne me dérangerait pas de redéfinir toutes les primities systèmes (Section 2) faisant des accès au système de gestion de fichiers : (open, read, write, close...), c'est mon but. Le problème vient peut être du fait que les fonctions stub de la libc faisant appel à ces primitives ne considéreront pas ma .version mais la version originale.
    La preuve en est le code suivant :

    main.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
     
    #include <string.h>
    #include <unistd.h>
    #include <stdio.h>
     
    void toto();
     
    int main()
    { 
      toto();
      printf("aaa\n");
      write(1, "Hello World from main \n", strlen("Hello World from main \n"));
      return 0;
    }

    libc_stub.c
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count) {
      return count;
    }
    lib_toto.c
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    #include <unistd.h>
    #include <string.h>
     
    void toto(){
      write(1, "Hello World from Test \n", strlen("Hello World from Test \n"));
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    $>./main
    Hello World from Test
    aaa
    Hello World from main
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $>LD_PRELOAD=/opt/lib/libc_stub.so ./main
    aaa
    Donc le printf ne fait pas appel à mon write mais au write originel.

    Par ailleurs un strace de mon exec donne:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     write(1, "aaa\n", 4aaa
    )                    = 4
    ce qui prouve que printf fait bien appel à write.

    Serait pour des raisons de sécurité que printf ne fait appels qu'aux primitives originels ?

    En tout cas, j'aurais appris l'existence de nm et serais moins hésitant quant à l'utilisation de ptrace.

    Merci milouz123.

  9. #9
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2008
    Messages : 145
    Points : 170
    Points
    170
    Par défaut
    Attention, il ne faut pas confondre deux chose :
    - l'appel système sys_write
    - la fonction write de la libc

  10. #10
    Membre du Club
    Profil pro
    Inscrit en
    Août 2003
    Messages
    67
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 67
    Points : 50
    Points
    50
    Par défaut
    Ce n'est pas le topic de la discussion, mais je pensais que write était justement un appel système fourni par le kernel utilisé par les fonctions de plus haut niveau (telle printf) et que ce write lui faisait appel au _write du noyau pour le passage en mode noyau. Finalement, printf et write sont du même niveau car printf semble faire directement un appel au _write du noyau sans passer par le write de la libc. Ce qui expliquerait effectivement l'ignorance de mon write et le choix de ptrace pour catcher les appels système.

  11. #11
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2008
    Messages : 145
    Points : 170
    Points
    170
    Par défaut
    C'est ça, on a :
    printf (libc) -> puts (libc) -> write (l'appel systeme, visible avec strace)
    write (libc) -> write (l'appel systeme, visible avec strace)

  12. #12
    Membre du Club
    Profil pro
    Inscrit en
    Août 2003
    Messages
    67
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2003
    Messages : 67
    Points : 50
    Points
    50
    Par défaut
    Ok, merci de ton aide milouz123.
    J'ai bcp appris aujourd'hui !

  13. #13
    Membre éclairé Avatar de valefor
    Profil pro
    Inscrit en
    Décembre 2006
    Messages
    711
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 711
    Points : 790
    Points
    790
    Par défaut
    J'arrive avec un peu de retard mais j'aimerai dire deux ou trois trucs :

    1 - Vous avez peut-être oublié une solution. Compiler avec l'option --no-stdlib de gcc et linker avec sa propre libc.

    2 - Personne n'a parlé des symboles weak (je ne sais pas si c'est le LD_PRELOAD dont vous parliez ?). On peut très bien redéfinir les fonctions de la libc et elles écrasent automatiquement l'implémentation originale. Cela permet par exemple de tester ses propres implémentations de fonctions ou débuger. Bref, une petite recherche rapide avec "weak+symbol+glibc+overload" donne par exemple cela.

    Voila.

  14. #14
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2008
    Messages : 145
    Points : 170
    Points
    170
    Par défaut
    2 - Personne n'a parlé des symboles weak (je ne sais pas si c'est le LD_PRELOAD dont vous parliez ?). On peut très bien redéfinir les fonctions de la libc et elles écrasent automatiquement l'implémentation originale. Cela permet par exemple de tester ses propres implémentations de fonctions ou débuger. Bref, une petite recherche rapide avec "weak+symbol+glibc+overload" donne par exemple cela.
    Voila.
    C'est ça, c'est le rôle de LD_PRELOAD (c'est d'ailleurs expliqué dans le lien que vous fournissez).

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

Discussions similaires

  1. Réponses: 5
    Dernier message: 28/10/2011, 23h38
  2. [linux] utilisation des appels système
    Par Invité dans le forum x86 32-bits / 64-bits
    Réponses: 5
    Dernier message: 21/05/2011, 23h53
  3. Réponses: 2
    Dernier message: 16/08/2009, 13h31
  4. Réponses: 1
    Dernier message: 14/03/2007, 15h56
  5. appeler des API système
    Par pierre.zelb dans le forum VBScript
    Réponses: 12
    Dernier message: 29/11/2006, 13h47

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