7
votes

État initial des registres de programme et pile sur le bras Linux

Je joue actuellement avec Bras Assembly sur Linux en tant qu'exercice d'apprentissage. J'utilise l'assemblée "nue", c'est-à-dire non libcrt ou libgcc. Quelqu'un peut-il me signaler à l'information sur l'état de la pile-pointeur et d'autres registres au début du programme avant la première instruction? Évidemment, PC / R15 points à _start, et le reste semble être initialisé à 0, à deux exceptions près; SP / R13 pointe vers une adresse bien à l'extérieur de mon programme et R1 pointe vers une adresse légèrement supérieure.

Donc, à quelques questions solides:

  • Quelle est la valeur dans R1?
  • est la valeur dans SP une pile légitime allouée par le noyau?
  • sinon, quelle est la méthode préférée d'allouer une pile; Utilisation de BRK ou allouer une section STATIQUE .BSS?

    Tous les pointeurs seraient appréciés.


0 commentaires

4 Réponses :


0
votes

Je n'ai jamais utilisé ARM Linux, mais je vous suggère de regarder la source de la libcrt et de voir ce qu'elles font ou utilisent le GDB pour entrer dans une exécutable existante. Vous ne devriez pas avoir besoin du code source simplement dans le code de montage.

Tout ce dont vous avez besoin pour savoir devrait arriver dans le tout premier code exécuté par n'importe quel exécutable binaire.

J'espère que cela aide.

Tony


0 commentaires

3
votes

Voici le UCLIBC CRT . Il semble suggérer que tous les registres ne 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 que vous pouvez compter sur.

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


0 commentaires

3
votes

Voici ce que j'utilise 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"
);


3 commentaires

Merci. Cette configuration est-elle documentée partout où vous connaissez?


Je suis sûr que cela doit être mais je dois admettre que je l'ai compris avec GDB.


Si le programme est appelé sans argument, il y a un moyen d'obtenir ENVP dans une seule instruction comme LDR RN, SP, #XXX?



7
votes

Comme il s'agit de Linux, vous pouvez regarder comment il est mis en œuvre par le noyau.

Les registres semblent être définis par l'appel vers start_thread à la fin de load_elf_binary (si vous utilisez un système Linux moderne, il va presque toujours utiliser le format elfe). Pour le bras, les registres semblent être définis comme suit: xxx

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

une chose intéressante à remarquer ici est que cette fonction est indépendante de l'architecture, de sorte que les mêmes choses (principalement) seront placées sur la pile sur chaque Linux basé sur Elfe architecture. Ce qui suit est sur la pile, dans l'ordre que vous le liriez:

  • Le nombre de paramètres (ceci est argc dans principal () ).
  • Un pointeur à une chaîne C pour chaque paramètre, suivi d'un zéro (ceci est le contenu de argv dans principal () ; argv voudrait indiquer le premier de ces pointeurs).
  • Un pointeur à une chaîne C pour chaque variable d'environnement, suivi d'un zéro (ceci est le contenu de l'ENVP Rarement vu troisième paramètre de principal () ; ENVP signalerait le premier de ces pointeurs).
  • Le "vecteur auxiliaire", qui est une séquence de paires (type suivi d'une valeur), terminée par une paire avec un zéro ( at_null ) dans le premier élément. Ce vecteur auxiliaire a des informations intéressantes et utiles, que vous pouvez voir (si vous utilisez GLIBC) en exécutant tout programme lié à dynamisme avec la variable d'environnement ld_show_auxv définie sur 1 (Par exemple ld_show_auxv = 1 / bin / true ). C'est aussi là où les choses peuvent varier un peu en fonction de l'architecture.

    Étant donné que cette structure est la même pour chaque architecture, vous pouvez rechercher une instance sur le dessin de la page 54 de la SYSV 386 ABI Pour obtenir une meilleure idée de la manière dont les choses correspondent ensemble (noter, cependant, que les constantes de type vecteur auxiliaires sur ce document sont différentes de ce que Linux utilise, de sorte que vous devriez donc Regardez les en-têtes Linux pour eux).

    Vous pouvez maintenant voir pourquoi le contenu de R0 - r2 sont des ordures. Le premier mot de la pile est argc , le second est un pointeur sur le nom du programme ( argv [0] ), et le troisième était probablement zéro pour vous parce que vous avez appelé Le programme sans arguments (ce serait argv [1] ). Je suppose qu'ils sont configurés de cette façon pour le format binaire plus ancien A.out , comme vous pouvez le voir sur CREATE_AOUT_TABLE METS ArgC , argv et Envple dans la pile (donc ils se retrouveraient dans R0 - r2 dans l'ordre prévu pour un appel à Main () ) Je suppose que quelque chose de profond dans la machinerie SysCall l'a écrasé avec la valeur de retour de l'appel système (qui serait zéro depuis que l'exécutif a réussi). Vous pouvez voir dans kernel_execve (qui n'utilise pas la machinerie SysCall, car c'est ce que le noyau appelle le noyau lorsqu'il souhaite exécuter en mode noyau) qu'il écrase délibérément R0 avec la valeur de retour du do_execve .


0 commentaires