Vamos dar uma olhada no chamado "stack unwind", que pode ser traduzido como "desdobramento da pilha", para acabar com este "mistério". Um "desdobramento da pilha" soa um tanto dramático mas, na prática, consiste em simplesmente chamar os manipuladores de exceção cujos dados locais estejam localizados mais abaixo na pilha e depois (provavelmente) continuar a execução a partir de uma outra moldura (frame). Em outras palavras, o programa é preparado para ignorar o conteúdo da pilha entre estas duas posições. Caso tenha se esquecido do que é uma moldura de pilha, revise o conceito lendo o texto de nível intermediário Tratamento de Erros. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Imagine que você tenha uma cadeia de manipuladores thread específicos estabelecidos numa sequência onde a função A chama a Função B que chama a Função C. Neste caso a pilha teria o seguinte aspecto:
Neste caso, quando cada uma das funções é chamada, são PUSHadas coisas na pilha: em primeiro lugar o endereço de retorno, depois os dados locais e finalmente o manipulador de exceções (esta é a estrutura "ERR" mencionada anteriormente). Agora suponha que tenha ocorrido uma exceção na Função C. Como vimos, o sistema iniciará uma caminhada pela cadeia de manipuladores. O manipulador 3 será o primeiro a ser chamado. Imagine que o manipulador 3 não trate a exceção (retornando EAX=1), então o manipulador 2 será chamado. Se o manipulador 2 também retornar EAX=1, então o manipulador 1 será chamado. Se o manipulador 1 tratar a exceção, ele precisará "desarmar" os dados locais nas molduras da pilha criadas pelas Funções B e C. Isto é feito através do Desdobramento. O desdobramento simplesmente repete a caminhada na cadeia de manipuladores chamando inicialmente o manipulador 3, depois o 2 e finalmente o 1. As diferenças entre este tipo de caminhada pela cadeia de manipuladores e a caminhada iniciada pelo sistema quando a exceção ocorreu pela primeira vez são as seguintes:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5a. Como é feito o desdobramento |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
O manipulador pode iniciar um desdobramento usando a função da API RtlUnwind ou, como veremos adiante, usando o código que você escrever. Esta função pode ser chamada da seguinte forma: PUSH Valor de Retorno PUSH pRegistroDeExceção PUSH ADDR MarcadorDoCodigo PUSH UltimaMolduraDaPilha CALL RtlUnwind Valor de Retorno contém um valor de retorno depois do desdobramento e que, provavelmente, você não usará. pRegistroDeExceção é um ponteiro para o registro de exceção, o qual é uma das estruturas enviadas ao manipulador que ocorre uma exeção. MarcadorDoCodigo é o local a partir do qual a execução deve continuar depois do desdobramento e, tipicamente, é o endereço do código imediatamente após a chamada a RtlUnwind. Se não for especificado a função da API funciona normalmente porém é melhor não brincar com este tipo de função e garantir que funcione adequadamente. UltimaMolduraDaPilha é a moldura da pilha na qual o desdobramento deve parar. Normalmente é o endereço da pilha da estrutura ERR que contém o endereço do manipulador que iniciou o desdobramento. Observação: Diferentemente de outras funções da API, não deixe para RtlUnwind preservar os registradores EBX, ESI ou EDI – se você for usar esta função, o correto é preservá-los fazendo um PUSH antes do primeiro parâmetro e restaurá-los com POP após MarcadorDoCodigo. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5.b Código de Desdobramento próprio |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
O código seguinte simula o desdobramento (onde ebx guarda o endereço da estrutura EXCEPTION_RECORD enviada ao manipulador): MOV D[EBX+4],2h ;faz a flag de exceção EH_UNWINDING FS MOV EDI,[0] ;pega o endereço do 1° ;manipulador thread específico L2: CMP D[EDI],-1 ;vê se é o último JZ >L3 ;sim, então termina PUSH EDI,EBX ;push estrutura ERR, EXCEPTION_RECORD CALL [EDI+4] ;chama manipulador para desarme ADD ESP,8h ;remove os dois parâmetros PUSHados MOV EDI,[EDI] ;pega ponteiro para a próxima estrutura ERR JMP L2 ;e processa o próximo se não tiver terminado L3: ;marcador do código quando terminar Neste caso, cada manipulador é chamado com a flag de exceção 2h até que o último manipulador seja alcançado (o sistema possui o valor -1 na última estrutura ERR). O código acima não checa valores corrompidos em [EDI] e em [EDI+4]. O primeiro é um endereço da pilha e poderia ser checado verificando se está acima da base da pilha do thread indicada em FS:[8] e abaixo do topo da pilha do thread indicada em FS:[4]. O segundo é um endereço do código de modo que é possível checar se está situado entre dois marcadores de código, um no começo do seu código e outro no fim do mesmo. Alternativamente é possível checar se [EDI] e [EDI+4] podem ser lidos chamando a função da API IsBadReadPtr. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5c. Desdobrar pelo manipulador final e depois continuar |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Não é apenas um manipulador thread específico que pode iniciar um desdobramento de pilha. O desdobramento também pode ser realizado pelo manipulador final chamando RtlUnwind ou através de um código próprio que faça o desdobramento retornando posteriormente EAX=-1. (Veja "Continuar a execução depois de chamar o manipulador final"). |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5d. Desdobramento final e depois terminar |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Se um manipulador final estiver instalado e ele retornar EAX=0 ou EAX=1, o sistema fará com que o processo termine. Entretanto, antes do término acontece uma coisa interessante. O sistema faz um desdobramento final voltando ao primeiro manipulador da cadeia (ou seja, o manipulador que contém o código no qual ocorreu a exeção). Esta é a última oportunidade que seu manipulador tem de executar o código de desarme necessário em cada moldura da pilha. Você pode ver claramente este desdobramento final ocorrendo se configurar o programa demo Except.exe para permitir que a exceção vá até o manipulador final e pressionar F3 ou F5 quando alcançar este ponto. O mesmo acontece com o programa mais simples, Except1.exe. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
A informação enviada aos manipuladores (handlers) precisa ser suficientemente clara par que possam tentar corrigir a exceção, fazer logs de erros ou avisar o usuário. Como veremos adiante, esta informação, quando os manipuladores são chamados, é enviada pelo próprio sistema através da pilha. Adicionalmente, você pode suas próprias informações para os manipuladores aumentando a estrutura ERR de modo que possa conter mais informações. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6a. Informação enviada ao manipulador final |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
O manipulador final está documentado no Windows Software Development Kit (SDK) como API "UnhandledExceptionFilter". Ele recebe apenas um parâmetro, um ponteiro para a estrutura EXCEPTION_POINTERS. Esta estrutura é a seguinte:
A estrutura EXCEPTION_RECORD contém os seguintes campos:
onde
Os códigos de exceção seguem as seguintes regras:
Um código de erro personalizado típico enviado por RaiseException poderia ser, por exemplo, E0000100h (erro, applicação, código=100h). Se necessário, este é um modo rápido de sair do código e ir direto ao seu próprio manipulador.
A segunda parte da estrutura EXCEPTION_POINTERS que é enviada ao manipulador final aponta para a estrutura do registro CONTEXT, a qual contém os valores de todos os registros, específicos do processador, no momento em que ocorreu a exceção. WINNT.H contém as estruturas CONTEXT para vários processadores. Seu programa pode determinar o tipo de processador que está em uso chamando GetSystemInfo. A estrutura CONTEXT é a seguinte para o IA32 (Intel 386 e posteriores):
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6b. Informação enviada ao manipulador thread-específico |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Quando o manipulador thread-específico é chamado, ESP aponta para as seguintes três estruturas:
Diferentemente de outros CALLBACKs no Windows, quando um manipulador thread-específico é chamado, é usada a convenção de chamada C (o chamador precisa remover os argumentos da pilha) e não a convenção de chamada PASCAL (a função remove os argumentos da pilha). Pode-se verificar esta peculiaridade no código do Kernel32 usado para fazer a chamada: PUSH Param, CONTEXT record, ERR, EXCEPTION_RECORD CALL HANDLER ADD ESP,10h Na prática, o primeiro argumento, Param, parece não conter informações relevantes. As estruturas EXCEPTION_RECORD e registro CONTEXT já foram descritas acima. A estrutura ERR é a estrutura que você criou na pilha quando o manipulador foi constituído e precisa conter o ponteiro para a próxima estrutura ERR além do endereço do código do manipulador que está sendo instalado (Veja como criar um manipulador em Tipos de Manipuladores). O ponteiro para a estrutura ERR passada ao manipulador thread-específico está no topo desta estrutura. Portanto, é possível aumentar a estrutura ERR de modo que o manipulador possa receber informações adicionais. Num arranjo típico, a estrutura ERR poderia ser a seguinte, onde [ESP=8h] aponta para o topo desta estrutura quando o manipulador é chamado:
Como veremos adiante ("Seguindo a execução a partir de um lugar seguro"), os campos +8 e +14 podem ser utilizados pelo manipulador para fazer a recuperação desta exceção. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6c. Fornecendo acesso a dados locais |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Agora vamos analisar a melhor posição para a estrutura ERR na pilha, relativa ao quadro da pilha, e que pode muito bem conter variáveis de dados locais. Isto é importante porque o manipulador eventualmente precise acessar estes dados locais para poder realizar a "limpeza" convenientemente. Aqui está um código típico que pode ser usado para estabelecer um manipulador thread-específico onde haja dados locais:
MINHAFUNCAO: ; ponto de entrada do procedimento
PUSH EBP ; salva ebp (usado para endereçar o
; quadro da pilha
MOV EBP,ESP ; usa EBP como ponteiro para o quadro
; da pilha
SUB ESP,40h ; faz 16 dwords na pilha para
; dados locais
;******** dados locais agora em [EBP-4] a [EBP-40h]
;********** instala manipulador e sua estrutura ERR
PUSH EBP ; ERR+14h salva ebp
; (ebp em lugar seguro)
PUSH 0 ; ERR+10h área para flags
PUSH 0 ; ERR+0Ch informação para o manipulador
PUSH ADDR LUGAR_SEGURO ; ERR+8h novo eip em lugar seguro
PUSH ADDR HANDLER ; ERR+4h endereço do manipulador
FS PUSH [0] ; ERR+0h manter próximo ERR na cadeia
FS MOV [0],ESP ; apontar para ERR recém-criado na pilha
... ;
... ; código protegido vem aqui
... ;
JMP >L10 ; fim normal se não ocorrer
; nenhuma exceção
LUGAR_SEGURO: ; manipulador ajusta eip/esp/ebp
; para aqui
L10: ;
FS POP [0] ; restaura próxima ERR na cadeia
MOV ESP,EBP
POP EBP
RET
;*****************
HANDLER:
RET
Usando este código, quando o manipulador é chamado, [ESP+8h] está apontando para o topo da estrutura ERR (isto é, ERR+0h) e o seguinte encontra-s na pilha:
Fica fácil perceber que, uma vez que o manipulador receba um ponteiro para a estrutura ERR, ele também pode achar o endereço dos dados locais na pilha. Isto é possível porque o manipulador conhece o tamanho da estrutura ERR e a posição dos dados locais na pilha. Se o campo EBP for usado em ERR+14h, como no exemplo acima, isto também poderia ser usado como um ponteiro para os dados locais. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Localizador | ||
O texto sobre a utilização do Tratamento Estruturado de Erros é, além de complexo, um tanto longo. Por isso o tópico foi dividido em três páginas. A continuação do assunto você encontra em Lidando com Exceções III. | Localizador || @ Info NumaBoa > oicìliS > Assembly > Textos > Exceções II Créditos: vovó Vicki webdesign sobMedida by vickiSoft - /informatica/oiciliS/assembler/textos/excecoes2.php (17.04.03) versão 1.1 de 03.08.03 Licença Creative Commons 1998-2006 Aldeia NumaBoa Exceto onde especificamente declarado, todo material deste site é disponibilizado de acordo com a Licença Creative Commons. | ||