A Aldeia Numaboa ancestral ainda está disponível para visitação. É a versão mais antiga da Aldeia que eu não quis simplesmente descartar depois de mais de 10 milhões de pageviews. Como diz a Sirley, nossa cozinheira e filósofa de plantão: "Misericórdia, ai que dó!"

Se você tiver curiosidade, o endereço é numaboa.net.br.

Leia mais...

Informática Numaboa - Tutoriais e Programação

Assembly - Lidando com exceções

Seg

22

Jun

2009


19:53

(1 voto de 5.00) 


A informação enviada aos manipuladores

A informação enviada aos manipuladores (handlers) precisa ser suficientemente clara para que estes 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 enviar suas próprias informações para os manipuladores aumentando a estrutura ERR para que possa conter mais informações.

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:

EXCEPTION_POINTERS +0 Ponteiro para a próxima estrutura EXCEPTION_RECORD
+4 Ponteiro para a estrutura do registro CONTEXT

A estrutura EXCEPTION_RECORD contém os seguintes campos:

EXCEPTION_RECORD +0 Código de exceção (ExceptionCode)
+4 Flag de exceção (ExceptionFlag)
+8 Registro de exceção aninhado (NestedExceptionRecord)
+C Endereço da exceção (ExceptionAddress)
+10 Parâmetros numéricos (NumberParameters)
+14 Dados adicionais (AdditionalData)

onde

  1. ExceptionCode dá o tipo de exceção que ocorreu. Existem muitos deles listados nos arquivos SDK e de cabeçalho (header) mas, na prática, os tipos que você geralmente encontra são:
    • C0000005h – Violação de escrita ou leitura da memória
    • C0000094h – Divisão por zero
    • C0000095h – Overflow de divisão
    • C00000FDh – A pilha ultrapassou o tamanho máximo disponível
    • 80000001h – Violação de uma página guard na memória gerada por Virtual Alloc
  • O seguinte apenas ocorre quando se lida com exceções:
    • C0000025h – Uma exceção não-continuável – o manipulador não deve tentar tratá-la
    • C0000026h – Código de exceção usado pelo sistema durante o tratamento de exceções. Este código pode ser utilizado se o sistema encontrar um retorno inesperado de um manipulador. Também é usado se um Registro de Exceção (ExceptionRecord) não for fornecido numa chamada RtlUnwind.
  • Para debugar são usados os seguintes:
    • 80000003h – Ocorreu uma parada (breakpoint) porque foi encontrado um INT3 no código.
    • 80000004h – Passo único (single step) durante o debugging.

Os códigos de exceção seguem as seguintes regras:

Bits 31-30 Bit 29 Bit 28 Bits 27-0
0=sucesso
1=Informação
2=Alerta
3=erro
0=Microsoft
1=Aplicação
Reservado
Precisa ser zero
Código

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.

  1. A ExceptionFlag dá instruções ao manipulador. Os valores podem ser:
    • 0 - uma exceção continuável (pode ser reparada).
    • 1 - exceção não-continuável (não pode ser reparada).
    • 2 - a pilha está se desdobrando - não tente reparar.
  2. O NestedExceptionRecord aponta para um outra estrutura EXCEPTION_RECORD se o próprio manipulador tenha causado uma nova exceção.
  3. ExceptionAddress é o endereço no código onde ocorreu a exceção.
  4. NumberParameters é o número de dwords que devem ser seguidos na AdditionalInformation.
  5. AdditionalInformation é um array de dwords com mais informações. Tanto pode ser informação enviada pela aplicação quando chamou RaiseException ou, se o código de exceção for C0000005h, será o seguinte: 1º dword - 0=violação de leitura, 1=violação de escrita; 2º dword - endereço da violação de acesso.

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):

+0 (usado quando se chama GetThreadContext)
REGISTRADORES DE DEBUG
+4 registrador debug número 0
+8 registrador debug número 1
+C registrador debug número 2
+10 registrador debug número 3
+14 registrador debug número 6
+18 registrador debug número 7
PONTO FLUTUANTE / REGISTRADORES MMX
+1C ControlWord
+20 StatusWord
+24 TagWord
+28 ErrorOffset
+2C ErrorSelector
+30 DataOffset
+34 DataSelector
+38 Registradores FP x 8 (10 bytes cada)
+88 CrONpxState
REGISTRADORES DE SEGMENTO
+8C Registrador gs
+90 Registrador fs
+94 Registrador es
+98 Registrador ds
REGISTRADORES COMUNS
+9C Registrador edi
+A0 Registrador esi
+A4 Registrador ebx
+A8 Registrador edx
+AC Registrador ecx
+B0 Registrador eax
REGISTRADORES DE CONTROLE
+B4 Registrador ebp
+B8 Registrador eip
+BC Registrador cs
+C0 Registrador eflags
+C4 Registrador esp
+C8 Registrador ss

Informação enviada ao manipulador thread-específico

Quando o manipulador thread-específico é chamado, ESP aponta para as seguintes três estruturas:

ESP +4 Ponteiro para a estrutura EXCEPTION_RECORD
ESP +8 Ponteiro para uma estrutura ERR própria
ESP +C Ponteiro para a estrutura registro CONTEXT

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 (reveja como criar um manipulador no tópico "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:

ERR +0 Ponteiro para a próxima estrutura ERR
ERR +4 Ponteiro para o manipulador de exceção próprio
ERR +8 Endereço no código de um "lugar seguro" para o manipulador
ERR +C Informação para o manipulador
ERR +10 Área para as flags
ERR +14 Valor de EBP no "lugar seguro"

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.

Fornecendo acesso a dados locais

Agora vamos analisar a melhor posição para a estrutura ERR na pilha, relativa à moldura da pilha, e que pode muito bem conter variáveis de dados locais. Isto é importante por que o manipulador eventualmente precisa acessar estes dados locais para poder realizar a "limpeza" convenientemente. Aqui está um código típico que pode ser usado para criar 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-se na pilha:

. pilha +ve
ERR +0 Ponteiro para a próxima estrutura ERR
ERR +4 Ponteiro para o manipulador de exceção próprio
ERR +8 Endereço no código de "lugar seguro" para o manipulador
ERR +C Informação para o manipulador
ERR +10 Área para as flags
ERR +14 Valor de EBP no "lugar seguro"
ERR +18 Dados locais
ERR +1C Dados locais
ERR +20 Dados locais
mais dados locais

Fica fácil perceber que, quando o manipulador recebe um ponteiro para a estrutura ERR, ele também pode achar o endereço dos dados locais na pilha. Isto é possível por que 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, ele também poderia ser usado como um ponteiro para os dados locais.

Informações adicionais