Você está na página 1de 6

Funes em Assembly usando a conveno de Chamadas do C

http://thecthulhu.wordpress.com/2011/05/07/funcoes-em-assembly-usando-a-
convencao-de-chamadas-do-c/
Pilha do computador?
Voc no consegue escrever funes em assembly sem entender como a pilha do
computador funciona. Cada programa do computador, quando executado, usa uma regio de
memria chamada de pilha que permite que funes funcionem corretamente. Pense em uma
pilha como uma pilha de papis que podem ser adicionados indefinidamente. Voc geralmente
mantm as coisas que voc est trabalhando mais prximas do topo, e vai tirando de acordo
com que vai terminando de trabalhar com elas.
Seu computador tem uma pilha tambm. A pilha do computador, chamada de stack,
fica no maior endereo de memria. Voc pode empurrar valores para o topo da pilha atravs
de uma instruo chamada push, que empurra ou um registrador ou um valor, no topo da pilha.
Bem, dizemos que o topo, mas o topo da pilha na verdade a base da memria da pilha.
Embora isso seja meio confuso, a razo disso que quando pensamos em uma pilha de
qualquer coisa pratos, papis, etc ns pensamos em adicionar e remover do topo dela.
Entretanto, na memria a pilha comea do topo da memria e cresce pra baixo devido a outras
consideraes de arquitetura. Portanto, quando ns nos referimos ao topo da pilha lembre-se
que ela a base da pilha na memria. Voc tambm pode tirar valores do topo da pilha usando
a instruo chamada pop.
Push?
Quando ns damos push de um valor na pilha, o topo da pilha se move para acomodar
o valor adicionado. Se continuarmos dando push de valores na pilha, a pilha ir crescer cada
vez mais e mais para baixo na memria at que atinja seu cdigo ou seus dados. Ento como
ns sabemos aonde atualmente est o topo da pilha? O registrador da pilha, %esp (Stack
Pointer), sempre contm o topo atual da pilha, seja l aonde ele estiver.





Toda vez que ns damos push de alguma coisa na pilha com pushl (push
long), %esp subtrado por 4 para que ele aponte para o novo topo da pilha (lembre-se,
cada word tem comprimento 4 bytes, e a pilha cresce para baixo). Quando desejamos remover
alguma coisa da pilha, simplesmente usamos a instruo popl, que adicionar 4 em %esp, e o
topo da pilha voltar a ser o topo anterior. pushl e popl precisam de um operando: o registrador
para empurrar na pilha, ou para receber a informao que tirada da pilha.

Se ns s queremos acessar o valor no topo da pilha, podemos usar o
registrador %esp com modo de endereamento indireto. Por exemplo, o seguinte cdigo move
o topo atual da pilha para %eax:
movl (%esp), %eax
Se ns fizessemos isso:
movl %esp, %eax
ento %eax guardaria o ponteiro para o topo da pilha, e no o valor que est no topo.
Colocando %esp entre parnteses, estamos falando para o computador usar o modo
de endereamento indireto, e portanto ns pegamos o valor apontado pelo %esp. Se ns
quisermos acessar o valor logo abaixo do topo da pilha, podemos usar esta instruo:
movl 4(%esp), %eax
Essa instruo usa o modo de endereamento por base ou deslocamento, que adiciona
4 em %esp (sem alterar o valor dele) antes de olhar para o valor que est apontando.
Na conveno de chamadas do C, a pilha o elemento chave para implementao
de variveis locais, parmetros e endereo de retorno da funo.
Antes de executar uma funo, o programa d push de todos os parmetros para a
funo na pilha, na ordem inversa em que eles esto documentados. Em seguida o programa
emite uma instruo de chamada call indicando qual funo deve ser iniciada.
Call?
A instruo call faz duas coisas. Primeiro ela d push do endereo da prxima
instruo, que o endereo de retorno, na pilha. Em seguida modifica o ponteiro de
instruo %eip (Instruction Pointer) para apontar para o incio da funo. Ento, logo que a
funo inicia, a pilha vai estar assim:
Parmetro #N
...
Parmetro 2
Parmetro 1
Endereo de retorno <---- (%esp) Topo da pilha
Cada um dos parmetros da funo foram empurrados para a pilha, e finalmente o
endereo de retorno. Agora a funo tem algum trabalho para fazer:
A primeira coisa salvar o registrador do ponteiro da base, %ebp (Base Pointer),
com pushl %ebp. O ponteiro da base um registrador especial usado para acessar os
parmetros e variveis locais da funo, conhecido como frame-pointer.
Continuando, ele copia o ponteiro do topo da pilha para %ebp com movl %esp, %ebp.
Isso permite que voc possa acessar os parmetros das funes como ndices fixos a partir
do frame-pointer. Talvez voc esteja pensando que pode usar o ponteiro da pilha para isso.
Porm, durante a execuo do seu programa voc pode fazer outras coisas com a pilha, como
dar push para argumentos de outras funes. Copiando o ponteiro da pilha para o ponteiro da
base no incio da funo, permite que voc saiba aonde esto os seus parmetros (e como
voc ir ver, variveis locais tambm), mesmo quando voc empurra e tira coisas da pilha.
O %ebp sempre ser o ponteiro de onde estava o topo da pilha quando a funo foi
iniciada, por isso mais ou menos uma referncia constante para o quadro da pilha (stack
frame, consiste de todas as variveis na pilha que so usadas pela funo, incluindo
parmetros, variveis locais, e o endereo de retorno).
At aqui, a pilha est assim:

Parmetro #N <--- N*4+4(%ebp)
...
Parmetro 2 <--- 12(%ebp)
Parmetro 1 <--- 8(%ebp)
Endereo de retorno <--- 4(%ebp)
%ebp antigo <--- (%esp) e (%ebp)
Como voc pode ver, cada parmetro pode ser acessado usando o modo de
endereamento por base, com o registrador %ebp.
Continuando, a funo reserva espao na pilha para as variveis locais que ela ir
precisar. Isso feito alterando o ponteiro do topo da pilha. Digamos que precisamos de
2 words de memria para rodar a funo. Podemos simplesmente mover o ponteiro da pilha
2 words (8 bytes) para baixo. Assim:

subl $8, %esp

Isso ir subtrair 8 de %esp (lembrando que uma palavra tem quatro bytes). Tambm,
s para lembrar, o smbolo $ antes de 8 indica que o modo de endereamento imediato,
subtraindo o valor 8, e no o valor do endereo 8.
Assim, ns podemos usar a pilha para armazenamento de variveis sem se preocupar
sobre a possibilidade de sobrepor elas com comandos push para chamar outra funo. Desde
que varivel foi alocada no quadro da pilha para a funo, a varivel somente ir existir
enquanto a funo executada. Quando a funo retornada, o quadro da pilha vai embora,
assim como suas variveis. por isso que elas so chamadas de variveis locais apenas
existem enquanto a funo chamada. Ento agora, ns temos 2 words para armazenamento
local. Nossa pilha est assim:

Parmetro #N <--- N*4+4(%ebp)
...
Parmetro 2 <--- 12(%ebp)
Parmetro 1 <--- 8(%ebp)
Endereo de retorno <--- 4(%ebp)
%ebp antigo <--- (%esp) e (%ebp)
Varivel local 1 <--- -4(%ebp)
Varivel local 2 <--- -8(%ebp) e (%esp)


Agora ns podemos acessar todos os dados que precisamos para essa funo usando o
modo de endereamento por base, com diferentes deslocamentos de %ebp. Esse registrador
foi feito especificamente para este propsito, por isso ele chamado de ponteiro da base, e
conhecido comoframe-pointer. Voc pode usar outros registradores no modo de
endereamento por base, mas a arquitetura x86 faz o uso do %ebp ser bem mais rpido.

Variveis globais e variveis estticas so acessadas como se ns estivssemos
acessando a memria. A nica diferena entre variveis globais e estticas, que as estticas
so usadas apenas por uma funo, enquanto as globais so acessadas por vrias funes. O
assembly trata elas exatamente iguais, embora a maioria das linguagens de programao
tratem elas distintamente.
Quando uma funo termina de ser executada, so feitas trs coisas. Primeiro, ela
armazena o valor de retorno em %eax. Segundo, a pilha alterada para aonde ela estava
antes de ser chamada (o programa se livra do quadro da pilha atual e coloca o quadro da pilha
do cdigo que chamou a funo). Terceiro, retorna o controle do programa para de onde ele foi
chamado.
Ret?
A instruo ret usada para retornar o controle, dando um pop da pilha, alterando o
ponteiro de instruo, %eip, para o valor que estava no topo da pilha.
Ento, antes da funo retornar o controle para o cdigo que chamou ela, preciso restaurar o
quadro da pilha. Note tambm que sem fazer isso, a instruo ret no ir funcionar, porque no
nosso quadro da pilha atual, o endereo de retorno no est no topo da pilha. Portanto, antes
de retornarmos, precisamos restaurar o ponteiro da pilha %esp e o ponteiro da
base %ebp para o qu eles eram antes da funo ser chamada.
Por isso, para retornar da funo, voc precisa fazer o seguinte:
movl %ebp, %esp
popl %ebp
ret
At aqui, voc deve considerar que todas as variveis locais foram liberadas. Isso
porque voc restaurou o ponteiro da pilha, e futuros push na pilha iro sobrescrever tudo o que
voc ps l. Por isso que voc no deve guardar o endereo de uma varivel local depois que
a funo retornou.

Enfim, o controle voltou para o cdigo que chamou a funo, e agora ele pode
examinar o valor de retorno em %eax. O cdigo que chamou tambm precisa dar pop de todos
os parmetros que foram colocados na pilha, para que a a pilha volte para o que ela era antes
(ou ento simplesmente adicionar 4 * nmero de parmetros em %esp, usando a
instruo addl, caso no precise mais dos valores dos parmetros).
Notas?
Quando voc chama uma funo, voc deve assumir que tudo que est em seus
registradores ser perdido. O nico registrador que garantido continuar com o mesmo valor
de quando chamou, o%ebp. O outros provavelmente sero sobrescritos. Se deseja salvar os
valores dele, coloque eles na pilha antes de por os parmetros da funo na pilha.
Outras linguagens de programao podem ter convenes diferentes para a chamada
de funes. Se voc est criando uma biblioteca para outra linguagem, consulte se ela
compatvel com a conveno do C.
Exemplo

.section .data
.section .text
.globl _start
_start:
pushl $3
pushl $2
call power # power(2,3)
addl $8, %esp # restaura a pilha
pushl %eax
movl $1, %eax
int $0x80

# power(base, exp)
.type power, @function
power:
pushl %ebp # salva o ponteiro da base
movl %esp, %ebp # cria o frame-pointer
subl $4, %esp # aloca 4 bytes na pilha
movl 8(%ebp), %ebx # base
movl 12(%ebp), %ecx # expoente
movl %ebx, -4(%ebp)
power_loop_start:
cmpl $1, %ecx # if (expoente == 1) break
je end_power
movl -4(%ebp), %eax # resultado atual
imul %ebx, %eax # base * resultado atual
movl %eax, -4(%ebp) # salva a multiplicacao
decl %ecx # decrementa expoente
jmp power_loop_start
end_power:
movl -4(%ebp), %eax # coloca resultado em eax
movl %ebp, %esp # restaura a pilha
popl %ebp # restaura o ponteiro da base
ret

Você também pode gostar