Linux syscall, libc, VDSO et dissection de l’implémentation

Je dissèque l’appel syscall dans la dernière libc:

git clone git://sourceware.org/git/glibc.git 

Et j’ai ce code dans sysdeps / unix / sysv / linux / i386 / sysdep.h:

 # define INTERNAL_SYSCALL_MAIN_INLINE(name, err, nr, args...) \ LOADREGS_##nr(args) \ asm volatile ( \ "call *%%gs:%P2" \ : "=a" (resultvar) \ : "a" (__NR_##name), "i" (offsetof (tcbhead_t, sysinfo)) \ ASMARGS_##nr(args) : "memory", "cc") 

Si je comprends bien ce code, la macro LOADREGS _ ## nr (args) charge l’argument dans les registres ebx, ecx, edx, esi, edx et ebp.

sysdeps / unix / sysv / linux / i386 / sysdep.h

 # define LOADREGS_0() # define ASMARGS_0() # define LOADREGS_1(arg1) \ LOADREGS_0 () # define ASMARGS_1(arg1) \ ASMARGS_0 (), "b" ((unsigned int) (arg1)) # define LOADREGS_2(arg1, arg2) \ LOADREGS_1 (arg1) # define ASMARGS_2(arg1, arg2) \ ASMARGS_1 (arg1), "c" ((unsigned int) (arg2)) # define LOADREGS_3(arg1, arg2, arg3) \ LOADREGS_2 (arg1, arg2) # define ASMARGS_3(arg1, arg2, arg3) \ ASMARGS_2 (arg1, arg2), "d" ((unsigned int) (arg3)) # define LOADREGS_4(arg1, arg2, arg3, arg4) \ LOADREGS_3 (arg1, arg2, arg3) # define ASMARGS_4(arg1, arg2, arg3, arg4) \ ASMARGS_3 (arg1, arg2, arg3), "S" ((unsigned int) (arg4)) # define LOADREGS_5(arg1, arg2, arg3, arg4, arg5) \ LOADREGS_4 (arg1, arg2, arg3, arg4) # define ASMARGS_5(arg1, arg2, arg3, arg4, arg5) \ ASMARGS_4 (arg1, arg2, arg3, arg4), "D" ((unsigned int) (arg5)) # define LOADREGS_6(arg1, arg2, arg3, arg4, arg5, arg6) \ register unsigned int _a6 asm ("ebp") = (unsigned int) (arg6); \ LOADREGS_5 (arg1, arg2, arg3, arg4, arg5) # define ASMARGS_6(arg1, arg2, arg3, arg4, arg5, arg6) \ ASMARGS_5 (arg1, arg2, arg3, arg4, arg5), "r" (_a6) #endif /* GCC 5 */ enter code here 

Où est le code qui charge l’argument dans les registres ebx, ecx, edx, esi, edx et ebp? c’est ce code ci-dessus? Je ne comprends pas la mise en œuvre. le code suivant charge le 6ème argument dans le registre ebx?

 register unsigned int _a6 asm ("ebp") = (unsigned int) (arg6); 

Qu’est-ce que ce code:

 ASMARGS_0 (), "b" ((unsigned int) (arg1)) 

Il charge le premier argument dans le registre ebx?

Puis l’appel “%% gs:% P2” saute au code VDSO? ce code correspond à “call * gs: 0x10”?

alors, ce diagramme suivant pour l’écriture syscall, c’est bon ?:

 write(1, "A", 1) -----> LIBC -----> VDSO -----> KERNEL load reg ? jump to vdso |---------------------------------------------------|--------------| user land kernel land 

Je ne comprends pas l’utilitaire VDSO! le vdso choisit la méthode syscall (sysenter ou int 0x80).

Merci d’avance pour votre aide. Et désolé mon anglais est très mauvais.

Les macros impliquées dans les appels système de glibc vont se développer comme suit, pour l’exemple de l’appel syscall de sortie.

 LOADREGS_1(args) asm volatile ( "call *%%gs:%P2" : "=a" (resultvar) : "a" (__NR_exit), "i" (offsetof (tcbhead_t, sysinfo)) ASMARGS_1(args) : "memory", "cc") 

LOADREGS_1(args) sera étendu à LOADREGS_0() , qui s’étendra à rien – LOADREGS_*(...) n’a besoin que d’ajuster les registres lorsque plus de parameters sont fournis.

ASMARGS_1(args) développera ASMARGS_0 (), "b" ((unsigned int) (arg1)) , qui se développera en , "b" ((unsigned int) (arg1) .

__NR_exit est 1 sur x86.

En tant que tel, le code s’étendra à quelque chose comme:

 asm volatile ( "call *%%gs:%P2" : "=a" (resultvar) : "a" (1), "i" (offsetof (tcbhead_t, sysinfo)) , "b" ((unsigned int) (arg1) : "memory", "cc") 

ASMARGS_* pas réellement de code en soi – il est demandé à gcc de s’assurer que certaines valeurs (telles que (unsigned int) (arg1) ) figurent dans certains registres (tels que b , aka ebx ). En tant que telle, la combinaison de parameters asm volatile (qui n’est pas une fonction, bien sûr, mais seulement une commande intégrée gcc) spécifie simplement comment gcc doit se préparer à l’appel syscall et comment il doit continuer après la fin de l’appel système.

Maintenant, l’assemblage généré ressemblera à ceci:

 ; set up other registers... movl $1, %eax call *%gs:0x10 ; tear down 

%gs est un registre de segment qui référence le stockage thread-local – spécifiquement, glibc fait référence à une valeur enregistrée qui pointe vers le VDSO, qu’il stockait là quand il analysait les en-têtes ELF qui lui indiquaient l’emplacement.

Une fois que le code entre dans le VDSO, nous ne soaps pas exactement ce qui se passe – il varie en fonction de la version du kernel – mais nous soaps qu’il utilise le mécanisme le plus efficace pour exécuter un appel système, comme l’instruction sysenter ou int 0x80 instruction.

Donc, oui, votre diagramme est exact:

 write(1, "A", 1) -----> LIBC -----> VDSO -----> KERNEL load reg ? jump to vdso |---------------------------------------------------|--------------| user land kernel land 

Voici un exemple plus simple de code à appeler dans le VDSO, en particulier pour les appels système à un paramètre, à partir d’une bibliothèque que je gère appelée libsyscall :

 _lsc_syscall1: xchgl 8(%esp), %ebx movl 4(%esp), %eax call *_lsc_vdso_ptr(,1) movl 8(%esp), %ebx # pass %eax out ret 

Cela déplace simplement les parameters de la stack dans des registres, appelle le VDSO via un pointeur chargé depuis la mémoire, restaure les autres registres à leur état précédent et renvoie le résultat de l’appel système.