2009-11-26 3 views
5

Je joue actuellement avec l'assemblage ARM sur Linux en tant qu'exercice d'apprentissage. J'utilise l'assemblage "nu", c'est-à-dire pas de libcrt ou de libgcc. Quelqu'un peut-il me diriger vers des informations sur l'état du pointeur de pile et des autres registres au début du programme avant que la première instruction ne soit appelée? Evidemment pc/r15 pointe sur _start, et le reste semble être initialisé à 0, avec deux exceptions; sp/r13 pointe vers une adresse bien en dehors de mon programme, et r1 pointe vers une adresse légèrement supérieure.État initial des registres de programme et de la pile sous Linux ARM

donc à quelques questions solides:

  • Quelle est la valeur 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; en utilisant brk ou allouer une section statique .bss?

Des pointeurs seraient appréciés.

Répondre

2

Voici ce que j'utiliser pour obtenir un programme Linux/ARM a commencé 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 voir, je viens d'obtenir le argc, argv et trucs environ de la pile à [sp] . Un petit éclaircissement: Le pointeur de pile pointe vers une zone valide dans la mémoire du processus. r0, r1, r2 et r3 sont les trois premiers paramètres de la fonction appelée. Je les peuple avec argc, argv, et environ, respectivement.

+0

Merci. Cette configuration est-elle documentée à un endroit que vous connaissez? –

+0

Je suis sûr que ça doit l'être mais je dois admettre que je l'ai compris en utilisant gdb. –

0

Je n'ai jamais utilisé ARM Linux, mais je vous suggère de regarder la source de la bibliothèque libcrt et de voir ce qu'elle fait, ou d'utiliser gdb pour accéder à un exécutable existant. Vous ne devriez pas avoir besoin du code source pour parcourir le code d'assemblage. Tout ce que vous devez savoir devrait se trouver dans le tout premier code exécuté par n'importe quel exécutable binaire.

Espérons que cela aide.

Tony

3

Voici le uClibc crt. 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. Donc, la valeur que vous voyez dans r1 n'est probablement pas quelque chose sur quoi vous pouvez compter.

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

+0

Lien utile, merci. –

5

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.

Questions connexes