Linguagem de Montagem Procedimentos e a Pilha Slides baseados em material associado ao livro Introduction to Assembly Language Programming, Sivarama Dandamudi 1
O que é a pilha? A pilha é uma estrutura de dados do tipo LIFO (last-in-first-out) Se vista como um array de elementos, as operações de inserção e remoção ocorrem somente de um lado do array Apenas o elemento no topo da pilha (TOS) está diretamente acessível Existem duas operações básicas: push (inserção) pop (remoção) 2
Exemplo de pilha inserção remoção 3
Implementação da pilha no Pentium O segmento de pilha é usado para implementar a pilha Os registradores SS e SP são usados SS:SP endereça o topo da pilha Características da pilha no Pentium Apenas palavras de 16 bits ou 32 bits podem ser salvas na pilha, nunca um byte simples A pilha cresce em direção ao menor endereço (ou seja, de cabeça para baixo ) O topo da pilha (TOS) sempre aponta para o último elemento inserido 4
Instruções de pilha O Pentium provê duas instruções básicas: push fonte pop destino fonte e destino podem ser: Registradores de uso geral de 16 ou 32 bits Um registrador de segmento Um dado de memória de 16 ou 32 bits O fonte de push pode também ser um operando imediato de tamanho 16 ou 32 bits Uma pilha vazia, de 256 bytes, é criada com:.stack 100H 5
Pilha no Pentium Exemplo 1 push 21ABH push 7FBD329AH Inserção na pilha cheia resultará overflow 6
Pilha no Pentium Exemplo 2 pop EBX Remoção em pilha vazia resulta em underflow resíduo 7
Tabela de operações da pilha 8
Instruções de pilha adicionais Operações de pilha sobre os FLAGS As instruções push e pop não podem ser usadas com o registrador de flags Existem duas instruções especiais para este fim: pushf (empilha os flags de 16 bits) popf (desempilha os flags de 16 bits) Nenhum operando é requerido pushfd e popfd devem ser usadas para os flags de 32 bits (EFLAGS) 9
Instruções de pilha adicionais (cont.) Operações de pilha sobre os 8 registradores de uso geral As instruções pusha e popa podem ser usadas para salvar os 8 registradores de uso geral AX, BX, CX, DX, SP, BP, SI e DI pusha empilha os oito registradores na ordem acima (AX primeiro e DI por último) popa desempilha os registradores, exceto que o valor de SP não é carregado no registrador SP Estas instruções são úteis na entrada de procedimentos para liberar os registradores de uso geral e depois recuperar seus valores 10
Usos da pilha Três usos principais Armazenamento temporário de dados Transferência de controle Passagem de parâmetros (chamada de procedimento) 11
Usos da pilha Armazenamento temporário Trocar o valor entre duas posições de memória pode ser feito usando a pilha como área temporária Dois regist=adores uso geral Quat=o acessos na memória push valor1 push valor2 pop valor1 pop valor2 Somente Quat=o acessos na memória 12
Usos da pilha Armazenamento temporário (cont.) Frequentemente usada para salvar o valor dos registradores ; salva BX e CX na pilha push BX push CX... << BX e CX podem ser usados agora>>... ; recupra o valor de BX e CX pop CX pop BX 13
Usos da pilha (cont.) Transferência de controle Em chamadas de procedimentos e interrupções, o endereço de retorno é armazenado na pilha A apresentação de procedimentos mais adiante irá esclarecer esta possibilidade Passagem de Parâmetros A pilha é extensivamente usada para a passagem de parâmetros em chamadas de procedimentos Mais adiante veremos como isso é possível 14
Diretivas para Procedimentos A LA provê duas diretivas para chamadas de procedimentos: PROC (inicio) e ENDP (fim) Opcionalmente, para definir um procedimento NEAR ( próximo ), usamos: nome_proc PROC [NEAR] Em procedimentos do tipo NEAR, ambos, código que chama e procedimento chamado devem ocupar o mesmo segmento de código Opcionalmente, um procedimento FAR ( distante ) pode ser definido como: nome_proc PROC [FAR] Em procedimentos do tipo FAR, o procedimento chamado e o procedimento que chama, ocupam dois segmentos distintos de código 15
Diretivas para Procedimentos (cont.) Se FAR ou NEAR não for especificado, é assumido NEAR (ou seja, NEAR é o default) Trabalharemos apenas com procedimentos do tipo NEAR Uma definição típica de procedimento NEAR: nome_proc PROC.... << corpo do procedimento >>.... nome_proc ENDP nome_proc deve ser igual nas diretivas PROC e ENDP 16
Instruções para Procedimentos O Pentium provê duas instruções para a chamada e retorno dos procedimentos: call e ret A instrução call é usada para invocar um procedimento Formato: call nome_proc nome_proc é o nome do procedimento Ações executadas durante uma chamada a procedimentos do tipo NEAR SP := SP - 2 ; reserva espaço na pilha da memória (SS:SP) := IP ; empilha o endereço atual de IP IP := IP + deslocamento_relativo_do_procedimento 17
Instruções para Procedimentos (cont.) A instrução ret deve ser usada para devolver o controle da execução de volta para o procedimento que chamou Como o processador sabe o endereço de retorno? Ele usa o endereço de retorno colocado na pilha como parte da execução da instrução call de chamada de procedimentos É fundamental que o topo da pilha (TOS) esteja apontando para o endereço de retorno na execução da instrução ret Ações tomadas na execução da instrução ret: IP := (SS:SP) ; desempilha o endereço de retorno SP := SP + 2 ; atualiza o topo da pilha 18
Instruções para Procedimentos (cont.) É possível especificar um parâmetro opcional inteiro para a instrução ret O formato é: ret Exemplo: ret 6 parâmetro_opcional As ações tomadas com a execução de ret com parâmetro opcional são: IP := (SS:SP) ; desempilha o endereço de retorno SP := SP + 2 + parametro_opcional ; ex. 6 19
Como é feita a transferência do controle? Offset(hex) machine code(hex) offset relativo main PROC opcode (licle endian)...... cs:000a E8C000 call sum cs:000d 8BD8 mov BX,AX...... main ENDP sum PROC cs:0019 55 push BP...... sum ENDP avg PROC...... cs:0028 E8EEFF call sum cs:002b 8BD0 mov DX,AX...... avg ENDP ForFard Backward 0019-000D = 000C 0019-002B = FFEE Push BP IP ret ret ret offset relativo Push BP IP offset relativo (comp de 2) 20
Passagem de parâmetros A passagem de parâmetros em assembly é uma tarefa diferente e um pouco mais complicada que em uma linguagem de alto nível Em assembly: Deve-se primeiro colocar todos os parâmetros em uma área de memória mutuamente acessível Então chamar o procedimento Tipos de áreas de trabalho usadas Registradores (são usados registradores de uso geral) Memória (é usada a pilha) Métodos comuns de passagem de parâmetros: Método Registrador Método Pilha 21
Passagem de parâmetros (cont.) Método Registrador O procedimento que chama coloca os parâmetros nos registradores de uso geral antes de invocar o procedimento chamado através do comando call Exemplos PROGRAMA PROCEX1.ASM Passagem por valor usando registradores Uma rotina simples de soma PROGRAMA PROCEX2.ASM Passagem por referência usando registradores Uma rotina de cálculo de tamanho de strings 22
Passagem de parâmetros (cont.) Prós e contras do método registrador Vantagens Mais conveniente e fácil Mais rápido Desvantagens Poucos parâmetros podem ser passados Pequeno conjunto de registradores disponível Frequentemente os registradores de uso geral não estão livres Usar a pilha para salvar o conteúdo dos registradores impacta a segunda vantagem 23
Passagem de parâmetros (cont.) Método Pilha Todos os parâmetros são empilhados antes de chamar o procedimento Exemplo: push number1 push number2 call soma 24
Acesso aos parâmetros na pilha Como ter acesso aos parâmetros na pilha? Podemos usar: add SP, 2 mov BX, [SP] Problema: sujeito a erros ; arlazenar retormo (IP) Consome regist=ador de Propósito geral É necessário lembrar de atualizar SP para apontar para o valor de retorno do procedimento antes do seu término Existe uma alternativa melhor? Usar o registrador BP para acessar parâmetros na pilha 25
Acesso aos parâmetros na pilha usando BP reg. Método preferido de acesso aos parâmetros Para acessar o número 2 do exemplo anterior: mov BP, SP mov BX, [BP+2] Problema: o conteúdo de BP foi perdido! Necessário preservar o conteúdo de BP Usado nas operações em vários procedimentos (atenção: valor do deslocamento dos parâmetros é alterado) push BP mov BP, SP Os valores dos parâmet=os, endereços de retormo e o BP anterior arlazenados na pilha, são chamados de stack Rame 26
Limpando os parâmetros da pilha Stack state after push BP Stack state after pop BP Stack state after executing ret 27
Limpando os parâmetros da pilha (cont.) Dois maneiras de limpar os parâmetros indesejados na pilha Usar inteiro opcional na instrução ret ret 4 ; usar no exemplo anterior Adicionar uma constante para SP na chamada do procedimento (C usa este método) Em C: sum(number2,number2); push number1 push number2 call sum add SP, 4 28
Quem deve limpar os parâmetros indesejados Na chamada do procedimento Atualizar SP a cada procedimento chamado Não é realmente necessário se os procedimentos utilizarem um número fixo de parâmetros C utiliza este método pois permite número variável de parâmetros No procedimento chamado O código se torna modular (compensação dos parâmetros é feito em um só lugar) Não pode ser usado com um número variável de parâmetros 29
Preservar o estado do procedimento de chamada Necessidade de preservar o estado para cada chamada de procedimento A pilha é utilizada para este propósito Que registradores devem ser salvos? Salvar os que são usados pela chamada de procedimento mas eles são modificados pelo procedimento chamado Pode causar problemas Salvar todos os registradores (método força bruta) Feito usando pusha Incrementa sobrecarga pusha leva 5 clocks para salvar todos os 8 registradores 30
Estado da pilha após usar pusha O offeset para number1 e number2 foi incrementado 31
Onde deve ser preservado o estado do registrador Na chamada do procedimento Precisa conhecer os registradores usados pelo procedimento chamado Necessário incluir instruções para salvar e restaurar os registradores para cada procedimento chamado Problemas de manutenção no programa No procedimento chamado Método preferido pois código se torna modular (preservação do estado é feito apenas uma vez e em um lugar) Evita problemas de manutenção no programa 32
Instruções para Stack Frame Instrução ENTER Facilita a alocação de stack frame enter bytes, level Bytes = espaço de armazenamento local Level = nível de aninhamento (nós usamos 0) Exemplo enter XX, 0 Equivalente a push BP mov BP, SP sub SP, XX 33
Instruções para Stack Frame (cont.) Instrução LEAVE Libera stack frame leave Não tem operandos Equivalente a mov SP, BP pop BP 34
Um modelo de procedimentos típico proc-name proc-name PROC enter XX,0...... <procedure body>...... leave ret ENDP YY 35
Passagem de parâmetros na pilha: exemplos PROCEX3.ASM Chamada por valor usando método pilha Um simples procedimento de soma PROCSWAP.ASM Chamada por referência usando método pilha Os dois primeiros caracteres da string são trocados BBLSORT.ASM Implementa o algoritmo bubble sort Usa pusha e popa para salvar e restaurar registradores 36