Question

Sauf erreur de ma part, le code ci-dessus a été écrit au tableau dans une classe par un étudiant avec l'aide / les corrections du professeur:

int array[100], sum, i;

void ini() {
  for(i = 0; i < 100; i++)
    array[i] = i;
}

int main() {
  ini();

  sum = 0;

  for(i = 0; i < 100; i++)
    sum += array[i];
}

.pos 0
  irmovl Stack, %esp
  rrmovl Stack, %ebp

  jmp main

array:
.pos 430

sum: .long 0
i: .long 0

main:
  call ini                     //

  irmovl <*>, %eax              // %eax = 0
  irmovl sum, %esi             // %esi = 0xsum
  rmmovl %eax, 0(%esi)         // 0(%esi) = %eax <=> 0(0xsum) = 0 [sum = 0]
  rmmovl %eax, 4(%esi)         // 4(%esi) = %eax <=> 4(0xsum) = 0 [i = 0]

compare:
  irmovl $100, %ebx            // %ebx = 100
  subl %eax, %ebx              // %ebx = %ebx - %eax <=> %ebx = 100 - i
  jle finish                   // Jumps to "finish" if SF=1 pr ZF=0

  mrmovl 0(%esi), %edx         // %edx = 0(%esi) <=> %edx = 0(0xsum) = sum
  addl %eax, %edx              // %edx = %edx + %eax <=> %edx = sum + i => sum
  rmmovl %edx, 0($esi)         // 0(%esi) = %edx <=> 0(0xsum) = sum

  irmovl $1, %ecx              // %ecx = 1
  addl %ecx, %eax              // %eax = %eax + %ecx <=> %eax = i + 1 => i
  rmmovl %eax, 4(%esi)         // 4($esi) = %eax <=> 4(0xsum) = i

  jmp compare                  // Jumps unconditionally to "compare"

ini:
  pushl %ebp                   //
  rrmovl %esp, %ebp            //
  pushl %ebx                   //
  pushl %eax                   //

  irmovl <*>, %eax              // %eax = 0
  rmmovl %eax, -8(%ebp)        //

ini_compare:
  irmovl $100, %ecx            // %ecx = 100
  subl %eax, %ecx              // %ecx = %ecx - %eax <=> %ecx = 100 - i
  jle ini_finish               // Jumps to "ini_finish" if SF=1 pr ZF=0

  rrmovl %eax, %ebx            // %ebx = %eax <=> %ebx = i
  addl %eax, $ebx              // %ebx = %ebx + %eax <=> %ebx = i + i = 2i
  addl %ebx, %ebx              // %ebx = %ebx + %ebx <=> %ecx = 2i + 2i = 4i
  rmmovl %eax, array(%ebx)     // array(%ebx) = %eax <=> array(0x4i) = i

  irmovl %1, %ecx              // %ecx = 1
  addl %ecx, %eax              // %eax = %eax + %ecx <=> %eax = i + 1 => i
  rmmovl %eax, -8(%ebp)        //

  jmp ini_compare              // Jumps unconditionally to "ini_compare"

ini_finish:
  irmovl $4, %ebx              //
  addl %ebx, %esp              //
  popl %ebx                    //
  popl %ebp                    //

  ret                          //

.pos 600
  Stack .long 0

Comme vous pouvez le constater, toutes les instructions contiennent de nombreux commentaires, et la plupart d’entre eux (je pense), ce qui me dérange, c’est que les instructions call, pushl / popl et ret. Je ne les comprends pas tout à fait et je ne comprends pas non plus ce qui se passe dans la pile et où pointent tous les enregistrements. En gros, les lignes avec des commentaires (//) sur lesquelles rien n’est écrit.

Il est vraiment important que je comprenne comment tout cela fonctionne. J'espère que certains d'entre vous pourront faire la lumière sur tout ce gâchis.

Quelques notes sur mes commentaires:

  • 0xsum: Cela ne signifie pas que l'adresse est "somme", cela serait impossible. C’est un moyen de comprendre ce dont je parle sans utiliser l’adresse mémoire exacte.
  • [sum = 0]: Cela signifie que, dans notre code C, la variable sum sera définie sur 0 à ce stade.
  • i + 1 = > i: Cela signifie que nous incrémentons la valeur de «i» de un et que, dans la ligne suivante, «i» représentera en fait cette valeur incrémentée.
Était-ce utile?

La solution

Regardons une partie du code:

main:
  call ini

Ceci poussera la valeur du pointeur d’instruction sur la pile (pour pouvoir revenir ultérieurement à cette position dans le code) et passera à l’adresse de l’étiquette ini. L’instruction 'ret' utilise la valeur stockée dans la pile pour revenir du sous-programme.

Voici la séquence d'initialisation d'un sous-programme. Il enregistre les valeurs de certains registres de la pile et définit un cadre de pile en copiant le pointeur de pile (esp) dans le registre de pointeur de base (ebp). Si le sous-programme a des variables locales, le pointeur de pile est décrémenté pour laisser de la place aux variables de la pile et le pointeur de base est utilisé pour accéder aux variables locales du cadre de pile. Dans l'exemple, la seule variable locale est la valeur de retour (non utilisée).

L'instruction push décrémente le pointeur de pile (esp) avec la taille de données de ce qui va être poussé, puis stocke la valeur à cette adresse. L'instruction pop fait le contraire, en obtenant d'abord la valeur, puis en incrémentant le pointeur de pile. (Notez que la pile croît vers le bas, donc l'adresse du pointeur de la pile diminue lorsque la pile croît.)

ini:
  pushl %ebp             // save ebp on the stack
  rrmovl %esp, %ebp      // ebp = esp (create stack frame)
  pushl %ebx             // save ebx on the stack
  pushl %eax             // push eax on the stack (only to decrement stack pointer)
  irmovl 
ini_finish:
   irmovl $4, %ebx   // ebx = 4
   addl %ebx, %esp   // esp += ebx (remove stack frame)
   popl %ebx         // restore ebx from stack
   popl %ebp         // restore ebp from stack
   ret               // get return address from stack and jump there
, %eax // eax = 0 rmmovl %eax, -8(%ebp) // store eax at ebp-8 (clear return value)

Le code suit un modèle standard, de sorte qu'il semble un peu gênant lorsqu'il n'y a pas de variables locales et qu'il existe une valeur de retour inutilisée. S'il y a des variables locales, une soustraction serait utilisée pour décrémenter le pointeur de pile au lieu de pousser eax.

Ce qui suit est la séquence de sortie d'un sous-programme. Il restaure la pile à la position antérieure à la création du cadre de la pile, puis retourne au code qui a appelé le sous-programme.

<*>

En réponse à vos commentaires:

Le registre ebx est poussé et sauté pour préserver sa valeur. Le compilateur y met apparemment toujours ce code, probablement parce que le registre est très utilisé, mais pas dans ce code. De même, un cadre de pile est toujours créé en copiant esp dans ebp, même si cela n’est pas vraiment nécessaire.

L'instruction qui pousse eax n'est là que pour décrémenter le pointeur de la pile. Cela est fait de cette façon pour les petites augmentations, car il est plus court et plus rapide que de soustraire le pointeur de pile. L'espace qu'il réserve est destiné à la valeur de retour. Là encore, le compilateur le fait toujours, même si la valeur de retour n'est pas utilisée.

Dans votre diagramme, le registre esp pointe systématiquement quatre octets de manière trop importante dans la mémoire. Rappelez-vous que le pointeur de la pile est décrémenté après avoir poussé une valeur, de sorte qu'il pointe vers la valeur poussée, pas vers la valeur suivante. (Les adresses mémoire sont également très différentes, c'est quelque chose comme 0x600 plutôt que 0x20, car c'est là que l'étiquette de pile est déclarée.)

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