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: 12737
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.


