2009-06-20 12 views
4

À moins que je copiais mal, le code ci-dessus a été écrit dans le tableau dans une classe par un étudiant avec l'aide/corrections de l'enseignant:Assemblée: Y86 Stack et appeler, pushl/popl et instructions RET

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 $0, %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 $0, %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 voir, il y a un tas de commentaires dans toutes les instructions et j'ai (je pense) la plupart d'entre eux, ce qui me dérange est l'appel, pushl/popl et ret instructions. Je ne les comprends pas très bien et je ne comprends pas non plus ce qui se passe dans la pile et où tous les records pointent. Fondamentalement, les lignes avec des commentaires (//) qui n'ont rien d'écrit dessus.

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

Quelques notes sur mes commentaires:

  • 0xsum: Cela ne signifie pas que l'adresse est "somme", il serait impossible. C'est juste un moyen de comprendre de quoi je parle sans utiliser l'adresse mémoire exacte.
  • [sum = 0]: Cela signifie que dans notre code C, la somme des variables sera définie sur 0 à ce stade. Cela signifie que nous incrémentons la valeur de 'i' de un et que, dans la ligne suivante, 'i' représentera réellement cette valeur incrémentée.

Répondre

13

Regardons à une partie du code:

main: 
    call ini 

Cela va pousser la valeur du pointeur d'instruction à la pile (afin que vous puissiez revenir plus tard à ce poste dans le code), et sauter à l'adresse de l'étiquette ini. L'instruction 'ret' utilise la valeur stockée sur la pile pour revenir du sous-programme.

Ce qui suit est la séquence d'initialisation d'un sous-programme. Il enregistre les valeurs de certains registres sur la pile et configure une trame 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 la pile est décrémenté pour faire de la place aux variables de la pile, et le pointeur de base est utilisé pour accéder aux variables locales dans le cadre de la pile. Dans l'exemple, la seule variable locale est la valeur de retour (inutilisée).

L'instruction push décrémente le pointeur de la pile (esp) avec la taille des données de ce qui va être poussé, puis stocke la valeur à cette adresse. L'instruction pop fait le contraire, obtenant d'abord la valeur, puis incrémente le pointeur de la pile. (Notez que la pile grossit vers le bas, de sorte que l'adresse de pointeur de pile devient plus faible lorsque la pile grossit.)

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 $0, %eax  // eax = 0 
    rmmovl %eax, -8(%ebp) // store eax at ebp-8 (clear return value) 

Le code suit un modèle standard, donc il semble un peu gênant quand il n'y a pas de variables locales, et est 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.

Voici la séquence de sortie d'un sous-programme. Il restaure la pile à la position avant que le cadre de la pile ne soit créé, puis retourne au code qui a appelé le sous-programme.

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 

En réponse à vos commentaires:

Le registre EBX est poussé et éclatés pour préserver sa valeur. Le compilateur met apparemment toujours ce code là, 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 sur ebp même si ce n'est pas vraiment nécessaire.

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

Dans votre diagramme, le registre esp pointe constamment quatre octets trop haut dans la mémoire. Rappelez-vous que le pointeur de pile est décrémenté après avoir poussé une valeur, donc il pointera sur la valeur poussée, pas sur la valeur suivante. (Les adresses mémoire sont également éloignées, c'est quelque chose comme 0x600 plutôt que 0x20, car c'est là que le label Stack est déclaré.)

+0

En lisant votre article et en même temps faire quelques dessins de pile, je pense que je comprends un peu cela, il y a seulement quelques petites choses que je ne comprends toujours pas: a) Pourquoi poussons-nous et pop% ebx? Si je l'ai bien fait, c'est -4 (% ebp), auquel nous n'accédons jamais et il n'y a aucune valeur dans% ebx pour sauvegarder au début et revenir à la fin. b) Pourquoi poussons-nous% eax s'il n'y a pas de valeur dans% eax pour sauvegarder? Ne pourrions-nous pas décrémenter le% esp pour économiser l'espace nécessaire? Et pourquoi avons-nous besoin de cet espace? Nous sauvons la valeur de% eax dans la pile pour quoi? Nous n'en lisons jamais. –

+0

J'ai essayé de faire un diagramme pour voir si j'ai tout compris correctement, j'espère que tout va bien et je l'ai eu, mais j'ai le sentiment de ne pas l'avoir fait, le voici: http://images.nazgulled.net/ tmp/stack.jpg - Encore, les questions a) et b) dans le premier commentaire, s'appliquent toujours. Et j'en ai un nouveau c) Pourquoi avons-nous même besoin de pousser/pop% ebp et faire "rrmovl% esp,% ebp"? Il ne semble pas que ça change quelque chose ... –

+0

J'ai ajouté une réponse dans la réponse. – Guffa