Comment changer le chemin d’interpréteur et passer des arguments de ligne de commande à une bibliothèque partagée “exécutable” sous Linux?

Voici un exemple minimal pour une bibliothèque partagée “exécutable” (nom de fichier supposé: mini.c ):

 // Interpreter path is different on some systems //+definitely different for 32-Bit machines const char my_interp[] __atsortingbute__((section(".interp"))) = "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"; #include  #include  int entry() { printf("WooFoo!\n"); exit (0); } 

Si on le comstack avec par exemple: gcc -fPIC -o mini.so -shared -Wl,-e,entry mini.c “Running” le résultat .so ressemblera à ceci:

 confus@confusion:~$ ./mini.so WooFoo! 

Ma question est maintenant:
Comment dois-je modifier le programme ci-dessus pour transmettre des arguments de ligne de commande à un appel du fichier .so ? Un exemple de session shell après le changement pourrait par exemple ressembler à ceci:

 confus@confusion:~$ ./mini.so 2 bar 1: WooFoo! bar! 2: WooFoo! bar! confus@confusion:~$ ./mini.so 3 bla 1: WooFoo! bla! 2: WooFoo! bla! 3: WooFoo! bla! 5: WooFoo! Bar! 

Il serait également intéressant de détecter le moment de la compilation, que la cible soit un fichier binary 32 bits ou 64 bits pour modifier la chaîne d’interpréteur en conséquence. Sinon, on obtient un avertissement “Accès à une bibliothèque partagée corrompue” . Quelque chose comme:

 #ifdef SIXTY_FOUR_BIT const char my_interp[] __atsortingbute__((section(".interp"))) = "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"; #else const char my_interp[] __atsortingbute__((section(".interp"))) = "/lib/ld-linux.so.2"; #endif 

Ou mieux encore, détecter automatiquement le chemin approprié pour s’assurer qu’il convient au système sur lequel la bibliothèque est compilée.

Comment dois-je modifier le programme ci-dessus pour transmettre des arguments de ligne de commande à un appel du fichier .so?

Lorsque vous exécutez votre bibliothèque partagée, argc et argv seront transmis à votre fonction d’entrée sur la stack.

Le problème est que la convention d’appel utilisée lorsque vous comstackz votre bibliothèque partagée sur x86_64 Linux sera celle de l’ API System V AMD64 , qui ne prend pas d’arguments sur la stack mais dans les registres.

Vous aurez besoin d’un code de colle ASM qui récupère l’argument de la stack et les place dans les bons registres.

Voici un simple fichier .asm que vous pouvez enregistrer en tant que entry.asm et créer un lien avec:

 global _entry extern entry, _GLOBAL_OFFSET_TABLE_ section .text BITS 64 _entry: mov rdi, [rsp] mov rsi, rsp add rsi, 8 call .getGOT .getGOT: pop rbx add rbx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc jmp entry wrt ..plt 

Ce code copie les arguments de la stack dans les registres appropriés, puis appelle votre fonction d’ entry manière indépendante de la position.

Vous pouvez alors simplement écrire votre entry comme si c’était une fonction main régulière:

 // Interpreter path is different on some systems //+definitely different for 32-Bit machines const char my_interp[] __atsortingbute__((section(".interp"))) = "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"; #include  #include  int entry(int argc, char* argv[]) { printf("WooFoo! Got %d args!\n", argc); exit (0); } 

Et voici comment vous comstackriez votre bibliothèque:

 nasm entry.asm -f elf64 gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o 

L’avantage est que vous n’aurez pas d’instructions asm en ligne mélangées avec votre code C, au lieu de cela votre véritable point d’entrée est proprement extrait dans un fichier de démarrage.

Il serait également intéressant de détecter le moment de la compilation, que la cible soit un fichier binary 32 bits ou 64 bits pour modifier la chaîne d’interpréteur en conséquence.

Malheureusement, il n’y a pas de moyen complètement propre et fiable de le faire . Le mieux que vous puissiez faire est de vous fier à ce que votre compilateur préféré possède les bonnes définitions.

Puisque vous utilisez GCC, vous pouvez écrire votre code C comme ceci:

 #if defined(__x86_64__) const char my_interp[] __atsortingbute__((section(".interp"))) = "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"; #elif defined(__i386__) const char my_interp[] __atsortingbute__((section(".interp"))) = "/lib/ld-linux.so.2"; #else #error Architecture or comstackr not supported #endif #include  #include  int entry(int argc, char* argv[]) { printf("%d: WooFoo!\n", argc); exit (0); } 

Et avoir deux fichiers de démarrage différents.
Un pour 64 bits:

 global _entry extern entry, _GLOBAL_OFFSET_TABLE_ section .text BITS 64 _entry: mov rdi, [rsp] mov rsi, rsp add rsi, 8 call .getGOT .getGOT: pop rbx add rbx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc jmp entry wrt ..plt 

Et un pour 32bit:

 global _entry extern entry, _GLOBAL_OFFSET_TABLE_ section .text BITS 32 _entry: mov edi, [esp] mov esi, esp add esi, 4 call .getGOT .getGOT: pop ebx add ebx,_GLOBAL_OFFSET_TABLE_+$$-.getGOT wrt ..gotpc push edi push esi jmp entry wrt ..plt 

Ce qui signifie que vous avez maintenant deux manières légèrement différentes de comstackr votre bibliothèque pour chaque cible.

Pour 64bit:

 nasm entry.asm -f elf64 gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o -m64 

Et pour 32bit:

 nasm entry32.asm -f elf32 gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry32.o -m32 

Donc, pour résumer, vous avez maintenant deux fichiers de démarrage entry.asm et entry32.asm , un ensemble de définitions dans votre mini.c qui sélectionne automatiquement le bon interpréteur et deux manières légèrement différentes de comstackr votre bibliothèque en fonction de la cible.

Donc, si nous voulons vraiment aller jusqu’au bout, tout ce qui rest est de créer un Makefile qui détecte la bonne cible et construit votre bibliothèque en conséquence.
Faisons juste cela:

 ARCH := $(shell getconf LONG_BIT) all: build_$(ARCH) build_32: nasm entry32.asm -f elf32 gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry32.o -m32 build_64: nasm entry.asm -f elf64 gcc -fPIC -o mini.so -shared -Wl,-e,_entry mini.c entry.o -m64 

Et nous avons fini ici. Il suffit de lancer make pour construire votre bibliothèque et laisser la magie se produire.

Ajouter

 int argc; char **argv; asm("mov 8(%%rbp), %0" : "=&r" (argc)); asm("mov %%rbp, %0\n" "add $16, %0" : "=&r" (argv)); 

en haut de votre fonction d’ entry . Sur les plates-formes x86_64, cela vous donnera access aux arguments.

L’ article de LNW auquel John Bollinger a lié les commentaires explique pourquoi ce code fonctionne. Il pourrait vous intéresser pourquoi cela n’est pas nécessaire lorsque vous écrivez un programme C normal, ou plutôt, pourquoi il ne suffit pas de simplement donner à votre fonction d’ entry les deux arguments habituels int argc, char **argv : Le point d’entrée d’un programme C n’est normalement pas la fonction main , mais plutôt une fonction d’assembleur par glibc qui fait quelques préparations pour vous – entre autres, récupère les arguments de la stack – et cela (via certaines fonctions intermédiaires) appelle votre fonction main . Notez que cela signifie également que vous pouvez rencontrer d’autres problèmes, puisque vous ignorez cette initialisation! Pour certains antécédents, la page cdecl wikipedia , en particulier sur la différence entre x86 et x86_64, pourrait être intéressante.