Informática Numaboa - Tutoriais e Programação
Assembly - Lidando com exceções
Seg 22 Jun 2009 19:53 |
- Detalhes
- Categoria: Assembly Numaboa (antigo oiciliS)
- Atualização: Segunda, 22 Junho 2009 22:47
- Autor: vovó Vicki
- Acessos: 11862
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
- 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.
- 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.
- O NestedExceptionRecord aponta para um outra estrutura EXCEPTION_RECORD se o próprio manipulador tenha causado uma nova exceção.
- ExceptionAddress é o endereço no código onde ocorreu a exceção.
- NumberParameters é o número de dwords que devem ser seguidos na AdditionalInformation.
- 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:
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:
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.