Procedimentos Pilha Push Pop - PUC-Rio

Propaganda
Pilha
Procedimentos
Base da pilha
• Ao chamarmos um procedimento precisamos
passar dados & controle de uma parte do código
para outra
• Em particular, precisamos guardar o endereço
d retorno,
de
t
passar argumentos
t e salvar
l
o
conteúdo dos registradores.
• Afinal, em Assembly não temos variáveis locais
a procedimentos!
• Para isso, utiliza-se uma pilha (=um espaço de
memória gerenciado de forma controlada)
• Cresce em direção a
endereços menores da
memória
• A unidade de alocação é
uma palavra (4 bytes)
• Registrador %esp contém
endereço do elemento topo
• Push (alocação de uma
palavra na pilha): subtrair 4
Stack
de %esp
Pointer
• Pop (desalocar uma palavra %esp
da pilha): somar 4 a %esp
Endereços
crescentes
Pilha cresce
p/ baixo
Topo da pilha
Push
Pop
Base da pilha
pushl Src
Base da pilha
popl Dest
– Copie conteúdo de
memória indicado por
%esp para Dest
– Incrementa %esp
p de 4
– Decrementa %esp de 4
– Copia Src para
endereço dado por
%esp
p
Equivalente a:
Equivalente a:
subl $4, %esp
movl Src, (%esp)
Stack
Pointer
%esp
Pilha cresce
p/ baixo
movl (%esp),Dest
addl $4,%esp
Stack
Pointer
%esp
Pilha cresce
p/ baixo
+4
-4
Novo topo da pilha
Novo topo da pilha
1
Controle de Fluxo de Procedimentos
• Usa-se pilha para a transferência de controle na
chamada a procedimentos e retorno
• Chamada a procedimento:
Exemplo Chamada Procedimento
804854e:
8048553:
call
pushl
Antes do call
8048b90 <main>
%eax
Depois do call
call label
Empilha endereço de retorno na pilha, e desvia para label
0x110
Obs: end.de retorno é o endereço da instrução seguinte ao
call
0x108
0 10c
0x10c
0 10
0x10c
123
Atenção: tanto call como ret alteram o valor de %esp
Exemplo Retorno
8048591:
0x110
Depois do ret
0x110
0 10c
0x10c
0 10c
0x10c
123
0x108
0x104 0x8048553
%esp
123
%esp
0x108
%eip
0x804854e
%esp
0x104
Registradores
na CPU
%eip 0x8048b90
Obs: %eip é o contador de programa (Program Counter – PC)
Pilha
ret
Antes do ret
0x108
0x108
0x104 0x8048553
• Retorno:
ret
Desempilha endereço da pilha; e desvia para este endereço
Pilha
(na memória)
0x110
0x104
%eip 0x8048591
123
• Além de armazenar o endereço de
retorno, a pilha é usada para armazenar o
estado de cada instância de procedimento
chamada
c
a ada (e
(entre
t e o momento
o e to da cchamada
a ada
e do retorno)
0x8048553
%esp
0x108
%eip 0x8048553
• O espaço da pilha reservado para cada
instância é chamado de registro de
ativação
Obs: %eip é o PC
2
Registro de Ativação
• Contém
– Variáveis locais
– End. de retorno
– Espaço de mem. temporária
• Uso
– Espaço é alocado quando se entra no procedimento
– Espaço liberado quando retorna
• Ponteiros
– %esp (stack pointer) indica topo da pilha
– %ebp (base pointer) indica início do registro atual (da
chamada de procedimento em execução)
Salvamento da base do
Registro de Ativação (R.A.)
Segundo convenção da maioria dos compiladores no início
de cada procedimento:
pushl %ebp
movl %esp, %ebp
#salva ebp do R.A. anterior
#novo ebp passa a ser o topo pilha
E as últimas instruções de cada procedimento:
movl %ebp, %esp
#desaloca todo o R.A.
popl %ebp
#recupera o ebp do R.A. anterior
ret
#pop do end.retorno e desvia para lá
Entrada em procedimento
%ebp-ant
Saida de procedimento
%ebp
g
%ebp
g
End.ret
%ebp-ant
End.ret
%esp
%esp
%ebp-ant
Registro de ativação: Exemplo
Sequência
de Chamadas
g(…)
{
•
f();
•
}
h()
h
g()
f(…)
{
•
•
}
f()
g
%ebp
End.ret
%ebp-ant
f
%esp
• O endereço de retorno é o último dado no registro de
ativação
• A cada vez que é criado um novo registro de ativação o
%ebp do registro anterior é salvo (na própria pilha) e o
novo %ebp será o atual topo da pilha.
Topo
pilha
Conteúdo dos Registradores
• Precisam ser salvos, para não serem sobreescritos, p.ex. %edx
g:
• • •
movl $15213, %edx
call f
addl %edx, %eax
• • •
ret
Caller
f:
• • •
movl 8(%ebp), %edx
addl $91125, %edx
• • •
ret
Callee
• Questão: Quem (caller/ callee) deve ficar
responsável por salvar o conteúdo dos registradores
na pilha?
3
Convenção de uso registradores
IA32/Linux
Convenção de Salvamento de
Registradores
• Convenção no Linux:
– Caller (código chamador) deve salvar: %eax,%ecx e
%edx antes da chamada
– Callee (código chamado) deve salvar: %ebx, %esi,
%edi
– Portanto, dentro de um procedimento (callee) podese sobre-escrever %eax,%ecx e %edx, mas %ebx,
%esi e %edi precisam ser salvos na pilha (se
forem usados)
– Caller deve salvar: %eax, %ebx %ecx e %edx
%ecx
Salve antes de usar!
caller save
caller-save
%edx
%ebx
Callee Save
Callee-Save
%eax, %edx, %ecx
Salve antes de call!
%esi
%edi
Uso
Especial
%esp
%ebp
Exemplo de Chamada
Passagem de Parâmetros
int g (int a,int b) = return(a+b);
• Em C, a convenção é que o caller empilhe
os parâmetros na ordem inversa em que
aparecem na declaração do procedimento
int teste(int tam, int nums[])
g:
%ebp-ant
...
...
nums(end)
tam
End.ret
epb-ant
%ebx, %esi, %edi
%eax
Caller-Save
%eax sempre contém o
valor de retrono do
procedimento
• Convenção no Windows:
• O 1º. parâmetro está no
endereço (%ebp)+8, o
2º.parâmetro no (%ebp)+12
• Cada parâmetro é colocado
em 4 bytes, mesmo se
ocupar menos que isso.
• Por que não usar o %esp?
callee-save
%ebp
...
pushl %ecx
pushl %eax
call g
popl %eax
popl %ecx
...
Código do Caller
push %ebp
movl %esp, %ebp
push %ebx
movl
l 8(%
8(%ebp),
b ) %
%eax
movl 12(%ebp), %ebx
addl %ebx,%eax
popl %ebx
movl %ebp, %esp
popl %ebp
ret
Código do Callee
4
Download