Montagem: Y86 Stack e chamada, pushl / popl e instruções ret
Pergunta
A menos que eu copiado errado, o código acima foi escrito no quadro-negro em uma classe por um aluno com a ajuda / correções do professor:
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
Como você pode ver, há um monte de comentários em todas as instruções e eu tenho (eu acho) a maioria deles, o que está me confundindo é a chamada, pushl / popl e ret instruções. Eu não entendo muito bem deles e eu também não entendo o que está acontecendo com a pilha e onde todos os registros estão apontando. Basicamente, as linhas com comentários (//) que não têm nada escrito sobre eles.
É realmente importante que eu entendo como tudo isso funciona, espero, alguns de vocês podem lançar alguma luz sobre toda essa confusão.
Algumas notas sobre meus comentários:
- 0xsum: Isso não significa que o endereço é "sum", que seria impossível. É apenas um meio para entender o que eu estou falando sem usar o endereço de memória exata.
-
[soma = 0]:. Isto significa que no nosso código C, a soma variável serão definidas como 0, neste ponto - i + 1 => i:. Isto significa que estamos incrementando o valor do 'i' por um e que na seguinte linha 'i' vai realmente representar esse valor incrementado
Solução
Vamos olhar algum do código:
main:
call ini
Este vai empurrar o valor do ponteiro de instrução para a pilha (de modo que você possa mais tarde voltar a esta posição no código), e saltar para o endereço do rótulo ini. A instrução 'ret' usa o valor armazenado na pilha para retornar a partir da sub-rotina.
O seguinte é a sequência de inicialização de uma sub-rotina. Ele salva os valores de alguns registros na pilha e estabelece um quadro de pilha, copiando o ponteiro da pilha (ESP) para o registro de ponteiro de base (EBP). Se a sub-rotina tem variáveis ??locais, o ponteiro da pilha é reduzido para dar espaço para as variáveis ??na pilha, eo ponteiro base é usada para acessar as variáveis ??locais no quadro de pilha. No exemplo, a variável só local é a (não utilizado) valor de retorno.
A instrução PUSH decrementa o ponteiro de pilha (ESP) com o tamanho dos dados que vai ser empurrado, em seguida, armazena o valor nesse endereço. A instrução pop faz o contrário, primeiro obter o valor, em seguida, incrementa o ponteiro de pilha. (Note-se que a pilha cresce para baixo, então o endereço ponteiro da pilha fica mais baixa quando a pilha cresce.)
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)
O código segue um padrão standard, por isso parece um pouco estranho quando não há variáveis ??locais, e há um valor de retorno não utilizado. Se houver variáveis ??locais uma subtração seria usado para diminuir o ponteiro de pilha em vez de empurrar eax.
O seguinte é a sequência de saída de uma sub-rotina. Ele restaura a pilha para a posição antes do quadro de pilha foi criado, em seguida, retorna ao código que chamou a subrotina.
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
Em resposta a seus comentários:
O registo EBX é empurrado e bateu para preservar seu valor. O compilador aparentemente sempre coloca este código lá, provavelmente porque o registo é muito comumente usado, não apenas neste código. Da mesma forma um quadro de pilha é sempre criada copiando esp para ebp mesmo que não é realmente necessário.
A instrução que empurra eax está lá apenas para diminuir o ponteiro de pilha. É feito dessa maneira para pequenos decréscimos como é mais curto e mais rápido do que subtrair o ponteiro da pilha. O espaço que se reserva é para o valor de retorno, mais uma vez o compilador aparentemente sempre faz isso, mesmo que o valor de retorno não é usado.
Em seu diagrama quatro esp registo é constantemente apontando bytes muito alto na memória. Lembre-se que o ponteiro da pilha é reduzido depois de empurrar um valor, por isso vai apontar para o valor empurrado, não para o próximo valor. (Os endereços de memória estão longe também, é algo como 0x600 em vez de 0x20, já que é onde o rótulo Stack é declarado.)