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