Question

Je joue actuellement avec l'assemblage ARM sous Linux en tant qu'exercice d'apprentissage. J'utilise un assemblage "nu", c'est-à-dire sans libcrt ni libgcc. Quelqu'un peut-il m'indiquer dans quel état le pointeur de pile et les autres registres apparaîtront au début du programme avant l'appel de la première instruction? Il est évident que pc / r15 pointe au début et le reste semble être initialisé à 0, à deux exceptions près; sp / r13 pointe vers une adresse très éloignée de mon programme et r1 pointe vers une adresse légèrement supérieure.

Donc, pour quelques questions solides:

  • Quelle est la valeur dans r1?
  • La valeur de sp est-elle une pile légitime allouée par le noyau?
  • Sinon, quelle est la méthode préférée pour allouer une pile; utiliser brk ou allouer une section statique .bss?

Tous les indicateurs seraient appréciés.

Était-ce utile?

La solution

Voici ce que j'utilise pour démarrer un programme Linux / ARM avec mon compilateur:

/** The initial entry point.
 */
asm(
"       .text\n"
"       .globl  _start\n"
"       .align  2\n"
"_start:\n"
"       sub     lr, lr, lr\n"           // Clear the link register.
"       ldr     r0, [sp]\n"             // Get argc...
"       add     r1, sp, #4\n"           // ... and argv ...
"       add     r2, r1, r0, LSL #2\n"   // ... and compute environ.
"       bl      _estart\n"              // Let's go!
"       b       .\n"                    // Never gets here.
"       .size   _start, .-_start\n"
);

Comme vous pouvez le constater, je récupère simplement les éléments argc, argv et environ dans la pile située à [sp].

Un petit éclaircissement: le pointeur de la pile pointe vers une zone valide de la mémoire du processus. r0, r1, r2 et r3 sont les trois premiers paramètres de la fonction appelée. Je les remplis avec respectivement argc, argv et environ.

Autres conseils

Comme il s'agit de Linux, vous pouvez voir comment il est implémenté par le noyau.

Les registres semblent être définis par l'appel de 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

Clairement, vous avez une pile valide. Je pense que les valeurs de r0 - r2 sont indésirables et vous devriez plutôt tout lire dans la pile (vous verrez pourquoi je pense à cela plus tard). Maintenant, regardons ce qui est sur la pile. Ce que vous allez lire dans la pile est rempli par create_elf_tables .

Une chose intéressante à noter ici est que cette fonction est indépendante de l’architecture, de sorte que les mêmes choses (la plupart du temps) seront mises en pile sur toutes les architectures Linux basées sur ELF. Ce qui suit est sur la pile, dans l'ordre dans lequel vous le liriez:

  • Le nombre de paramètres ( argc dans main () ).
  • Un pointeur sur une chaîne C pour chaque paramètre, suivi d'un zéro (il s'agit du contenu de argv dans main () ; argv indiquerait le premier de ces pointeurs).
  • Un pointeur sur une chaîne C pour chaque variable d'environnement, suivi d'un zéro (il s'agit du contenu du troisième paramètre rarement vu de envp 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), se termine 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 glibc) en exécutant n’importe quel programme lié de manière dynamique avec la variable d’environnement LD_SHOW_AUXV définie sur 1 . (par exemple, LD_SHOW_AUXV = 1 / bin / true ). C’est aussi où les choses peuvent varier un peu selon l’architecture.

Cette structure étant identique pour toutes les architectures, vous pouvez par exemple vous reporter au dessin de la page 54 du SYSV 386 ABI pour avoir une meilleure idée de la façon dont les choses s’harmonisent (notez cependant que les constantes de type vecteur auxiliaire de ce document sont différentes de celles utilisées par Linux; en-têtes pour eux).

Vous pouvez maintenant voir pourquoi le contenu de r0 - r2 est illisible. Le premier mot de la pile est argc , le deuxième est un pointeur sur le nom du programme ( argv [0] ) et le troisième était probablement nul pour vous parce que vous avez appelé le programme sans argument (ce serait argv [1] ). Je suppose qu'ils sont configurés de cette manière pour l'ancien format binaire a.out , qui, comme vous pouvez le voir sur create_aout_tables met argc , argv et envp dans la pile (afin qu'ils se retrouvent dans r0 - r2 dans l'ordre attendu pour un appel à main () ).

Enfin, pourquoi r0 était-il zéro pour vous au lieu d'un ( argc devrait être égal à un si vous avez appelé le programme sans argument)? 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 égale à zéro puisque l'exec a réussi). Vous pouvez voir dans kernel_execve (qui n'utilise pas la machine syscall, car c'est ce que le noyau appelle lorsqu'il veut exécuter le mode noyau) qu'il écrase délibérément r0 avec la valeur de retour de do_execve .

Voici le la commande uClibc . Il semble suggérer que tous les registres sont indéfinis sauf r0 (qui contient un pointeur de fonction à enregistrer avec atexit () ) et sp qui contient une adresse de pile valide.

La valeur que vous voyez dans r1 n'est donc probablement pas une chose sur laquelle vous pouvez compter.

Certaines données sont placées sur la pile pour vous.

Je n’ai jamais utilisé ARM Linux, mais je vous suggère de consulter le code source de libcrt et de voir ce qu’ils font, ou d’utiliser gdb pour accéder à un exécutable existant. Vous ne devriez pas avoir besoin du code source, il suffit de parcourir le code d'assemblage.

Tout ce que vous devez savoir doit arriver dans le tout premier code exécuté par un exécutable binaire.

J'espère que cela vous aidera.

Tony

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top