Comme c'est Linux, vous pouvez voir comment il est implémenté par le noyau.
Les registres semblent être définis par l'appel à start_thread
à la fin de load_elf_binary
(si vous utilisez un système Linux moderne, il utilisera presque toujours le format ELF). Pour ARM, les registres semblent être configurés comme suit:
r0 = first word in the stack
r1 = second word in the stack
r2 = third word in the stack
sp = address of the stack
pc = binary entry point
cpsr = endianess, thumb mode, and address limit set as needed
Vous avez clairement une pile valide.Je pense que les valeurs de r0
- r2
sont indésirables, et vous devriez plutôt lire tout de la pile (vous verrez pourquoi je pense cela plus tard). Maintenant, regardons ce qui est sur la pile. Ce que vous lirez dans la pile est rempli par create_elf_tables
. Une chose intéressante à remarquer ici est que cette fonction est indépendante de l'architecture, donc les mêmes choses (la plupart du temps) seront mises sur la pile sur chaque architecture Linux ELF. Ce qui suit est sur la pile, dans l'ordre que vous lisez:
- Le nombre de paramètres (ce qui est
argc
dans main()
).
- Un pointeur vers une chaîne C pour chaque paramètre, suivi d'un zéro (c'est le contenu de
argv
dans main()
; argv
pointe vers le premier de ces pointeurs).
- Un pointeur vers une chaîne C pour chaque variable d'environnement, suivi d'un zéro (c'est le contenu du troisième paramètre
envp
rarement vu de main()
; envp
pointerait vers le premier de ces pointeurs).
- Le "vecteur auxiliaire", qui est une séquence de paires (un type suivi d'une valeur), terminé par une paire avec un zéro (
AT_NULL
) dans le premier élément. Ce vecteur auxiliaire contient des informations intéressantes et utiles, que vous pouvez voir (si vous utilisez la glibc) en exécutant un programme lié dynamiquement avec la variable d'environnement LD_SHOW_AUXV
définie sur 1
(par exemple LD_SHOW_AUXV=1 /bin/true
). C'est aussi là que les choses peuvent varier un peu en fonction de l'architecture.
Étant donné que cette structure est la même pour toutes les architectures, vous pouvez regarder par exemple le dessin à la page 54 de la SYSV 386 ABI pour obtenir une meilleure idée de la façon dont les choses se emboîtent (note, cependant, que le type auxiliaire de vecteur les constantes sur ce document sont différentes de ce que Linux utilise, donc vous devriez regarder les en-têtes Linux pour eux).
Maintenant, vous pouvez voir pourquoi le contenu de r0
- r2
sont des ordures. Le premier mot de la pile est argc
, le second est un pointeur vers le nom du programme (argv[0]
), et le troisième était probablement nul pour vous parce que vous appeliez le programme sans arguments (ce serait argv[1]
). Je suppose qu'ils sont mis en place ainsi pour le plus a.out
format binaire, qui, comme vous pouvez le voir à create_aout_tables
met argc
, argv
et envp
dans la pile (donc ils finiraient dans r0
- dans l'ordre r2
attendu pour un appel à main()
).
Enfin, pourquoi r0
zéro pour vous au lieu d'un (argc
devrait être un si vous avez appelé le programme sans arguments)? Je suppose que quelque chose de profond dans la machine syscall l'a écrasé avec la valeur de retour de l'appel système (qui serait nul depuis que l'exec a réussi). Vous pouvez voir dans kernel_execve
(qui n'utilise pas la machine syscall, puisque c'est ce que le noyau appelle quand il veut exec à partir du mode kernel) qu'il écrase délibérément r0
avec la valeur de retour de do_execve
.
Merci. Cette configuration est-elle documentée à un endroit que vous connaissez? –
Je suis sûr que ça doit l'être mais je dois admettre que je l'ai compris en utilisant gdb. –