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? n n n n 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: n n push (inserção) pop (remoção) 2 Exemplo de pilha inserção remoção 3 Implementação da pilha no Pentium n O segmento de pilha é usado para implementar a pilha n n n Os registradores SS e SP são usados SS:SP endereça o topo da pilha Características da pilha no Pentium n n n 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 n O Pentium provê duas instruções básicas: push pop n fonte e destino podem ser: n n n n fonte destino 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 n Operações de pilha sobre os FLAGS n n n n 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.) n Operações de pilha sobre os 8 registradores de uso geral n n n n 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 n Três usos principais n Armazenamento temporário de dados n Transferência de controle n Passagem de parâmetros (chamada de procedimento) 11 Usos da pilha – Armazenamento temporário n 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 push pop pop valor1 valor2 valor1 valor2 Somente Quat=o acessos na memória 12 Usos da pilha – Armazenamento temporário (cont.) n 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 n n 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 n n 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 n n 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 n n 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 n 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.) n n n 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 . . . << corpo do . . . nome_proc n PROC . procedimento >> . ENDP nome_proc deve ser igual nas diretivas PROC e ENDP 16 Instruções para Procedimentos n n n 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 n n 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.) n n 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? n n n 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.) n É possível especificar um parâmetro opcional inteiro para a instrução ret n n n O formato é: ret parâmetro_opcional Exemplo: ret 6 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) cs:000A cs:000D opcode cs:0019 cs:0028 cs:002B ForFard E8C000 8BD8 55 E8EEFF 8BD0 main PROC . . . . . . call sum mov BX,AX . . . . . . main ENDP “ret” sum PROC push BP . . . . . . sum ENDP “ret” avg PROC . . . . . . call sum mov DX,AX . . . . . . avg ENDP “ret” Backward 0019 - 000D = 000C Push BP offset relativo (liCle endian) IP offset relativo 0019 - 002B = FFEE Push BP IP offset relativo (comp de 2) 20 Passagem de parâmetros n n 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: n n n Tipos de áreas de trabalho usadas n n n Deve-se primeiro colocar todos os parâmetros em uma área de memória mutuamente acessível Então chamar o procedimento Registradores (são usados registradores de uso geral) Memória (é usada a pilha) Métodos comuns de passagem de parâmetros: n n Método Registrador Método Pilha 21 Passagem de parâmetros (cont.) n Método Registrador n n 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 n n PROGRAMA PROCEX1.ASM n Passagem por valor usando registradores n Uma rotina simples de soma PROGRAMA PROCEX2.ASM n Passagem por referência usando registradores n Uma rotina de cálculo de tamanho de strings 22 Passagem de parâmetros (cont.) n Prós e contras do método registrador n Vantagens n n n Mais conveniente e fácil Mais rápido Desvantagens n n Poucos parâmetros podem ser passados n Pequeno conjunto de registradores disponível Frequentemente os registradores de uso geral não estão livres n Usar a pilha para salvar o conteúdo dos registradores impacta a segunda vantagem 23 Passagem de parâmetros (cont.) n Método Pilha n n 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 n Como ter acesso aos parâmetros na pilha? n Podemos usar: add mov SP, 2 BX, [SP] ; arLazenar retorMo (IP) Consome regist=ador de Propósito geral Problema: sujeito a erros n n É necessário lembrar de atualizar SP para apontar para o valor de retorno do procedimento antes do seu término Existe uma alternativa melhor? n Usar o registrador BP para acessar parâmetros na pilha 25 Acesso aos parâmetros na pilha usando BP reg. n Método preferido de acesso aos parâmetros n Para acessar o número 2 do exemplo anterior: mov mov BP, SP BX, [BP+2] Problema: o conteúdo de BP foi perdido! Necessário preservar o conteúdo de BP n Usado nas operações em vários procedimentos (atenção: valor do deslocamento dos parâmetros é alterado) push BP mov BP, SP n 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.) n Dois maneiras de limpar os parâmetros indesejados na pilha n n 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: push push call add sum(number2,number2); number1 number2 sum SP, 4 28 Quem deve limpar os parâmetros indesejados n Na chamada do procedimento n n n n 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 n n 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 n Necessidade de preservar o estado para cada chamada de procedimento n n A pilha é utilizada para este propósito Que registradores devem ser salvos? n Salvar os que são usados pela chamada de procedimento mas eles são modificados pelo procedimento chamado n n Pode causar problemas Salvar todos os registradores (método força bruta) n n Feito usando pusha Incrementa sobrecarga n 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 n Na chamada do procedimento n n n n 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 n n 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 n Instrução ENTER n Facilita a alocação de stack frame enter bytes, level n Bytes = espaço de armazenamento local n Level = nível de aninhamento (nós usamos 0) n Exemplo enter XX, 0 n Equivalente a push BP mov BP, SP sub SP, XX 33 Instruções para Stack Frame (cont.) n Instrução LEAVE n Libera stack frame leave Não tem operandos n Equivalente a mov SP, BP pop BP n 34 Um modelo de procedimentos típico proc-name PROC proc-name enter XX,0 . . . . . . <procedure body> . . . . . . leave ret YY ENDP 35 Passagem de parâmetros na pilha: exemplos n PROCEX3.ASM n n n PROCSWAP.ASM n n n Chamada por valor usando método pilha Um simples procedimento de soma Chamada por referência usando método pilha Os dois primeiros caracteres da string são trocados BBLSORT.ASM n n Implementa o algoritmo “bubble sort” Usa pusha e popa para salvar e restaurar registradores 36