Informática Numaboa - Tutoriais e Programação
Assembly - Tratamento de erros
Dom 21 Jun 2009 18:48 |
- Detalhes
- Categoria: Assembly Numaboa (antigo oiciliS)
- Atualização: Domingo, 21 Junho 2009 19:53
- Autor: vovó Vicki
- Acessos: 11603
O tratamento adequado de erros é uma ciência à parte, talvez até mais complexa do que a própria programação. Durante a programação, toda a atenção está voltada para eventos previsíveis, o objetivo do programa. No tratamento de erros, no entanto, lidamos com eventos indesejados, quase que "imprevisíveis". É como brincar com uma bola de cristal
Neste texto serão abordados apenas os mecanismos envolvidos no caso de ocorrência de erros. Serve apenas como orientação para a aplicação de um sistema eficiente de um tratamento estruturado de erros, indispensável para garantir a robustez e a consistência de aplicativos mais elaborados.
Erros
Denominamos erro ou exceção um evento que ocorre durante a execução de um programa e que requer uma execução fora do fluxo normal de controle. Existem exceções de hardware e de software. Exceções de hardware podem ser resultado da execução de sequências de instruções que tentam acessar endereços de memória inválidos ou efetuar uma divisão por zero. Exceções de software podem ser resultado do uso de parâmetros com valores inválidos ou serem iniciadas explicitamente através do uso da função RaiseException. Em todo caso, são "pecados" cometidos contra o sistema - o "mau comportamento" que gera uma exceção pode comprometer o funcionamento do sistema operacional o qual, para se precaver, interrompe o processo e apresenta uma mensagem de erro do tipo:
Tratamento Estruturado de Exceções
A API do Windows oferece um mecanismo próprio para o tratamento de exceções geradas tanto por hardware quanto por software: é o chamado tratamento estruturado de exceções. Este mecanismo permite um controle absoluto no tratamento de exceções, oferece suporte para depuradores (debuggers) e pode ser usado em todas as linguagens de programação e em todo tipo de máquina.
A API do win32 também permite a manipulação de conclusão. Este tipo de manipulação garante que, sempre que uma área de código vigiado for executada, um bloco de código de conclusão específico também seja executado, tenha ou não ocorrido uma exceção. O código de conclusão é executado independentemente da maneira como o fluxo de controle tenha saído da área vigiada. Por exemplo, o manipulador de conclusão pode garantir que determinadas tarefas, como as de "faxina", sejam cumpridas mesmo que ocorra uma exceção ou algum outro erro durante a execução do código da área vigiada.
Se você programa em "C/C++" ou "Delphi", com certeza conhece as plavras-chave try, except e finally. Try (tentar) identifica a área vigiada, except (exceto) identifica o manipulador de exceções e finally (no final) o manipulador de conclusão. Pondo em linguagem corrente seria o mesmo que "tente executar o código desta área; caso ocorra algum erro, faça uso do manipulador de exceções; no final, acione o manipulador de conclusão". Esta estrutura de atendimento permite que se programe aplicativos mais robustos e confiáveis e é conhecida como SEH ou Structured Exception Handling.
Tipos de Exceção
Como já foi citado, as exceções podem ser iniciadas pelo hardware ou pelo software. Tanto uma quanto a outra podem ocorrer no modo kernel (no "interior" do sistema) ou no código modo usuário (o código que você programou). Daí os tipos de exceção:
- de hardware no modo kernel
- de hardware no modo usuário
- de software no modo kernel
- de software no modo usuário
Estruturas das Exceções
Quando ocorre uma exceção, o processador pára a execução no ponto onde ela ocorreu e transfere o controle para o sistema. A primeira providência do sistema é a de guardar o "estado de máquina" da linha de processo atual (o thread atual) e as informações que descrevem a exceção. Logo a seguir, o sistema procura por um manipulador de exceções para tratar o erro.
O "estado de máquina" é armazenado numa estrutura do tipo CONTEXT. Esta informação, denominada registro do contexto (context record), vai permitir que o sistema continue a execução a partir do ponto gerador da exceção se esta for tratada com sucesso.
A descrição da exceção, chamada de registro da exceção (exception record), é armazenada numa estrutura do tipo EXCEPTION_RECORD.
Devido ao fato das informações referentes à máquina e as informações referentes à exceção serem guardadas em estruturas diferentes, o mecanismo de tratamento de exceções torna-se portável para as mais diversas plataformas.
A estrutura EXCEPTION_RECORD
A definição desta estrutura é:
O membro DWORD ExceptionFlags pode ser zero, indicando uma exceção recuperável, ou ser EXCEPTION_NONCONTINUABLE, que indica uma exceção irrecuperável. Qualquer tentativa de continuar a execução após uma exceção irrecuperável causa uma nova exceção do tipo EXCEPTION_NONCONTINUABLE_EXCEPTION.
O membro struct _EXCEPTION_RECORD *ExceptionRecord aponta para uma estrutura do tipo EXCEPTION_RECORD. Os registros de exceção podem ser encadeados para fornecerem informações adicionais quando ocorrer uma exceção aninhada.
O membro PVOID ExceptionAddress especifica o endereço onde ocorreu a exceção.
O membro DWORD NumberParameters especifica o número de parâmetros associados à exceção. Este é o número de elementos definidos na matriz (array) ExceptionInformation.
O membro DWORD ExceptionInformation [EXCEPTION_MAXIMUM_PARAMETERS] especifica uma matriz com argumentos de 32 bits adicionais que descrevem a exceção. A função RaiseException pode especificar esta matriz de argumentos. Para a maior parte dos códigos de exceção os elementos da matriz estão indefinidos.
O membro DWORD ExceptionCode indica a razão pela qual a exceção ocorreu. Os valores possíveis estão na tabela abaixo:
EXCEPTION_ACCESS_VIOLATION | O thread tentou ler/escrever num endereço virtual ao qual não tinha acesso. |
---|---|
EXCEPTION_ARRAY_BOUNDS_EXCEEDED | O thread tentou acessar um elemento de array fora dos limites e o hardware possibilita a checagem de limites. |
EXCEPTION_BREAKPOINT | Foi encontrado um ponto de parada (breakpoint). |
EXCEPTION_DATATYPE_MISALIGNMENT | O thread tentou ler/escrever dados desalinhados em hardware que não oferece alinhamento. Por exemplo, valores de 16 bits precisam ser alinhados em limites de 2 bytes; valores de 32 bits em limites de 4 bytes, etc. |
EXCEPTION_FLT_DENORMAL_OPERAND | Um dos operandos numa operação de ponto flutuante está desnormatizado. Um valor desnormatizado é um que seja pequeno demais para poder ser representado no formato de ponto flutuante padrão. |
EXCEPTION_FLT_DIVIDE_BY_ZERO | O thread tentou dividir um valor em ponto flutuante por um divisor em ponto flutuante igual a zero. |
EXCEPTION_FLT_INEXACT_RESULT | O resultado de uma operação de ponto flutuante não pode ser representado como uma fração decimal exata. |
EXCEPTION_FLT_INVALID_OPERATION | Qualquer operação de ponto flutuante não incluída na lista. |
EXCEPTION_FLT_OVERFLOW | O expoente de uma operação de ponto flutuante é maior que a magnitude permitida pelo tipo correspondente. |
EXCEPTION_FLT_STACK_CHECK | A pilha ficou desalinhada ("estourou" ou "ficou abaixo") como resultado de uma operação de ponto flutuante. |
EXCEPTION_FLT_UNDERFLOW | O expoente de uma operação de ponto flutuante é menor que a magnitude permitida pelo tipo correspondente. |
EXCEPTION_ILLEGAL_INSTRUCTION | O thread tentou executar uma instrução inválida. |
EXCEPTION_IN_PAGE_ERROR | O thread tentou acessar uma página que não estava presente e o sistema não foi capaz de carregar a página. Esta exceção pode ocorrer, por exemplo, se uma conexão de rede é perdida durante a execução do programa via rede. |
EXCEPTION_INT_DIVIDE_BY_ZERO | O thread tentou dividir um valor inteiro por um divisor inteiro igual a zero. |
EXCEPTION_INT_OVERFLOW | O resultado de uma operação com inteiros causou uma transposição (carry) além do bit mais significativo do resultado. |
EXCEPTION_INVALID_DISPOSITION | Um manipulador (handle) de exceções retornou uma disposição inválida para o tratador de exceções. Uma exceção deste tipo nunca deveria ser encontrada em linguagens de médio/alto nível. |
EXCEPTION_NONCONTINUABLE_EXCEPTION | O thread tentou continuar a execução após a ocorrência de uma exceção irrecuperável. |
EXCEPTION_PRIV_INSTRUCTION | O thread tentou executar uma instrução cuja operação não é permitida no modo de máquina atual. |
EXCEPTION_SINGLE_STEP | Um interceptador de passos ou outro mecanismo de instrução isolada sinalizou que uma instrução foi executada. |
EXCEPTION_STACK_OVERFLOW | O thread esgotou sua pilha (estouro de pilha). |
Quando ocorre uma exceção no código modo usuário o sistema realiza a seguinte procura por um manipulador de exceções:
- Primeiro, o sistema tenta notificar o depurador do processo (debugger), se este existir.
- Se o processo não estiver sendo depurado ou se o depurador não tratar a exceção, o sistema tenta localizar um manipulador de exceções emolduradas (frame-based) fazendo uma procura nas molduras da pilha do thread onde ocorreu a exceção. O sistema procura primeiro na moldura atual da pilha, depois segue procurando em molduras de pilha precedentes. (Leia sobre molduras de pilha logo abaixo).
- Se não foi possível encontrar um manipulador emoldurado ou se nenhum deles tratar a exceção, o sistema faz uma segunda tentativa de notificar o depurador do processo.
- Se o processo não estiver sendo depurado ou se o depurador associado não tratar a exceção, o sistema fornece um tratamento padrão de acordo com o tipo de exceção. Para a maioria das exceções, a ação padrão é chamar a função ExitProcess.
Quando ocorre uma uma exceção no código modo kernel, o sistema procura nas molduras da pilha do kernel na tentativa de localizar um manipulador de exceções. Se o manipulador não puder ser encontrado ou se nenhum dos manipuladores tratar a exceção, o sistema encerra sua operação como se a função ExitProcess tivesse sido chamada.
Moldura de Pilha
Imagine uma função A que chama uma função B que chama uma função C. Cada uma destas funções possui um manipulador de exceções próprio, ou seja, possuem áreas vigiadas. Quando a função A é chamada, o aspecto da pilha é o seguinte:
Endereço de retorno da Função B |
Uso da pilha pela função A ... |
Manipulador de Exceções A |
Dados Locais da Função A |
Endereço de retorno da Função A |
... |
Após a chamada das funções B e C, o aspecto da pilha fica assim:
Uso da pilha pela função C ... |
Manipulador de Exceções C |
Dados Locais da Função C |
Endereço de retorno da Função C |
Uso da pilha pela função B ... |
Manipulador de Exceções B |
Dados Locais da Função B |
Endereço de retorno da Função B |
Uso da pilha pela função A ... |
Manipulador de Exceções A |
Dados Locais da Função A |
Endereço de retorno da Função A |
... |
As molduras de pilha são os endereços da pilha que pertencem a um determinado processo. Caso a função C e a função B sejam encerradas, a moldura da função C e a moldura da função B serão retiradas e a pilha volta a ter o aspecto do primeiro exemplo, onde a moldura da função A volta a ser a moldura do topo da pilha. Este processo é o ALINHAMENTO de pilha.
Se ocorrer uma exceção na área vigiada da função C, o manipulador de exceções C é "engatilhado". Se este manipulador não tratar da exceção, o sistema procura o manipulador B e o "engatilha". Caso este também não trate da exceção, o sistema continua a procura e acha o manipulador A, que também é "engatilhado". Fica claro que o sistema "caminha pilha abaixo", percorrendo a cadeia de manipuladores.
Digamos que o manipulador A trate a exceção. Neste caso ele passa de "engatilhado" para "desarmado". A partir daí, já que o problema foi resolvido, os outros manipuladores também precisam ser "desarmados". O manipulador A se encarrega de "desarmar" todos os manipuladores anteriores a ele. Começa pelo manipulador C, faz o mesmo caminho pilha abaixo e "desarma" toda a cadeia de manipuladores. Este processo chama-se stack unwind, literalmente, "desdobramento da pilha".
Tratamento de Exceções Emolduradas (Frame-based)
Um manipulador de exceções emoldurado é um mecanismo através do qual o programador lida com a possibilidade de que uma exceção possa ocorrer numa determinada sequência de código. Um manipulador de exceções emoldurado é composto pelos seguintes elementos:
- Uma área de código vigiada
- Uma expressão de filtro
- Um bloco de tratamento de exceções
Em linguagens de nível mais alto que o assembly, como o C/C++ por exemplo, os manipuladores de exceções emolduradas são implementados pelas declarações try-except.
A área de código vigiada é constituída por uma ou mais declarações para as quais a expressão de filtro e o bloco de tratamento de exceções fornece proteção. A área de código pode ser um bloco de código, um conjunto de blocos aninhados ou a totalidade de um procedimento ou função.
A expressão de filtro é uma expressão que é avaliada pelo sistema caso ocorra uma exceção na área vigiada. A avaliação resulta em uma das seguintes ações do sistema:
- O sistema interrompe a sua procura por um manipulador de exceções, restaura o estado da máquina e continua a execução do thread no ponto onde a exceção ocorreu.
- O sistema continua procurando um manipulador de exceções.
- O sistema transfere o controle para o manipulador de exceções e a execução do thread continua sequencialmente na moldura de pilha na qual o manipulador foi encontrado. Se o manipulador de exceções não se encontrar na moldura de pilha na qual a exceção ocorreu, o sistema realinha a pilha abandonando a moldura de pilha atual e quaisquer outras molduras até que retorne à moldura de pilha onde se encontra o manipulador de exceções.
O bloco de tratamento das exceções pode ser tão simples quanto apenas anotar o erro e ativar um indicador que será analisado posteriormente ou imprimir uma mensagem de erro ou um aviso. Se a execução continuar, pode ser necessário alterar o estado de máquina modificando o registro de contexto (context record).
Observação da vó
Gostou do assunto? Então, se for do tipo maluco beleza, dê uma lida no tutorial "Lidando com exceções".