LIDANDO COM EXCEÇÕES - O Tratamento Estruturado de Erros III
Silício ReversooicìliS ©
Assembly Avançado
TEXTOS ACESSÓRIOS
Notice: Undefined variable: subtitulo in /home/numaboa.com.br/public_html/informatica/oiciliS/assembler/head.php on line 10

(ver 1.0 de 03.08.03)

Vamos aos finalmentes deste extenso artigo e para o download de dois programas exemplo com seus respectivos códigos fonte em GoAsm. Agradeço ao autor, Jeremy Gordon, pela excelência do texto. Espero que minha tradução ajude os interessados que tenham dificuldade com o Inglês (dificuldade com o texto todo mundo vai ter ;))))

7. RECUPERANDO-SE E REPARANDO UMA EXCEÇÃO
7a. Continuando de um lugar seguro

Você precisa continuar a execução num lugar do código que não cause mais problemas. A coisa mais importante que deve ser observada é que, se o seu programa foi escrito para trabalhar dentro do framework do Windows, seu objetivo é o de retornar ao sistema o mais rápido possível e de forma controlada de modo que você possa esperar pelo próximo evento do sistema. Se a exceção ocorreu durante uma chamada do sistema a um procedimento de uma janela, então um bom lugar seguro será próximo do ponto de saída do procedimento da janela para que o sistema retome o controle de maneira "limpa". Desta forma, o sistema terá a impressão de que sua aplicação retornou do procedimento da janela da forma usual.

Entretanto, se a exceção ocorrer num trecho de código onde não exista um procedimento de janela, então será preciso exercer um controle maior. Por exemplo, um thread estabelecido para determinadas tarefas provavelmente precisará ser terminado, avisando o thread principal que a tarefa não pode ser completada.

Outra consideração importante é a facilidade de colocar os valores corretos de EIP, ESP e EBP no lugar seguro. Veremos a seguir que não é nada complicado.

São tantas as possibilidades que podem ser exploradas que seria inútil tentar mencioná-las. O exato lugar seguro depende da natureza do seu código e do uso que você está fazendo da manipulação de exceções. Entretanto, dê novamente uma olhada no código acima referente a MINHAFUNCAO. Você pode ver o marcador de código "LUGAR_SEGURO". Isto é um endereço no código a partir do qual a execução poderia continuar com segurança com o manipulador tendo feito toda a "faxina" necessária.

No exemplo de código, para que a execução continue com sucesso, é preciso lembrar que, apesar de LUGAR_SEGURO estar dentro do mesmo quadro da pilha da exceção ocorrida, os valores de ESP e EBP precisam ser cuidadosamente estabelecidos pelo manipulador antes que a execução continue a partir de EIP.

Portanto, estes três registradores precisam ser estabelecidos pelas seguintes razões:

  • ESP - para que a instrução FS POP [0] esteja habilitada a trabalhar e, se necessário, para POP outros valores.
  • EBP - para assegurar que os dados locais possam ser endereçados dentro do manipulador e para restaurar o valor de retorno correto de MINHAFUNCAO em ESP.
  • EIP - para forçar que a execução continue a partir de LUGAR_SEGURO.

Agora é possível perceber que cada um destes calores pode ser rapidamente obtido dentro da função de manipulação. O valor correto de ESP é, de fato, exatamente o mesmo que o do topo da própria estrutura ERR (dado por [ESP+8h] quando o manipulador é chamado). O valor correto de EBP pode ser obtido de ERR+14h porque foi PUSHado para a pilha quando a estrutura ERR foi montada. E o endereço correto do código do LUGAR_SEGURO que deve ser passado para EIP está em ERR+8h.

Neste ponto estamos prontos para observar, caso ocorra um erro, como o manipulador pode garantir que a execução continue a partir do lugar seguro ao invés de permitir que o processo termine.

HANDLER: 
PUSH EBP 
MOV EBP,ESP 
;** agora [EBP+8]=ponteiro para EXCEPTION_RECORD
;** [EBP+0Ch]=ponteiro da estrutura ERR para o registro CONTEXT
;** [EBP+10h]=ponteiro; salva registradores requeridos pelo
; Windows para ter o registro da exceção em ebx
PUSH EBX,EDI,ESI
MOV EBX,[EBP+8]
TEST D[EBX+4],1h       ; ve se é uma exceção não-continuável
JNZ >L5                ; sim, precisa ser tratada
TEST D[EBX+4],2h       ; verifica se é EH_UNWINDING
JZ >L2                 ; não
...
...                    ; limpa código enquanto faz desdobramento
...
JMP >L5                ; precisa retornar 1 para ir ao próximo
                       ; manipulador
L2:
PUSH 0                 ; valor de retorno (não usado)
PUSH [EBP+8h]          ; ponteiro para este registro de exceção
PUSH ADDR UN23         ; endereço do código para RtlUnwind retornar
PUSH [EBP+0Ch]         ; ponteiro para esta estrutura ERR
CALL RtlUnwind
UN23: 
MOV ESI,[EBP+10h]      ; obtém registro de contexto em esi
MOV EDX,[EBP+0Ch]      ; obtém ponteiro para a estrutura ERR
MOV [ESI+0C4h],EDX     ; usa como novo esp
MOV EAX,[EDX+8]        ; obtém lugar seguro dado na estrutura ERR
MOV [ESI+0B8h],EAX     ; insere novo eip
MOV EAX,[EDX+14h]      ; obtém ebp no lugar seguro de ERR
MOV [ESI+0B4h],EAX     ; insere novo ebp
XOR EAX,EAX            ; recarrega contexto
                       ; e retorna eax=0 ao sistema
JMP >L6
L5:
MOV EAX,1              ; vai para o próximo manipulador
                       ; retorna eax=1
L6:                    ; retorno normal (sem argumentos)
POP ESI,EDI,EBX 
MOV ESP,EBP 
POP EBP 
RET
7b. Reparando a exceção

No exemplo acima você viu que o contexto sendo carregado com o novo eip, ebp e esp faz com que a execução continue a partir de um lugar seguro. Também é possível, usando o mesmo método de substituir os valores de alguns registradores do contexto, "consertar" a exceção e permitir que a execução continue a partir de um local próximo do código ofensivo para que a tarefa atual possa ser realizada.

Um exemplo típico seria a divisão por zero, que pode ser reparada por um manipulador que substitua o valor do divisor por 1 e depois retorne EAX=0 (se for um manipulador thread-específico) fazendo com que o sistema recarregue o contexto e continue a execução.

No caso de violações de memória, você pode utilizar o fato de que o endereço da violação de memória é passado como o segundo dword no campo additional information do registro da exceção. O manipulador pode usar este valor, passá-lo para VirtualAlloc e abrir mais memória a partir deste ponto. Se obtiver sucesso, o manipulador pode então recarregar o contexto (não modificado) e retornar EAX=0 para continuar a execução (caso seja um manipulador thread-específico).

8. CONTINUANDO A EXECUÇÃO APÓS A CHAMADA DE UM MANIPULADOR FINAL

Se você quiser, é possível lidar com exceções no manipulador final. Você ainda deve se lembrar de que o manipulador final é chamado pelo sistema quando o processo está prestes a terminar.

Os retornos em EAX do manipulador final não são os mesmos dos manipuladores thread-específicos. Se o retorno for EAX=1, o processo termina sem a caixa de mensagem; se o retorno for EAX=1, a caixa de mensagem é mostrada.

Entretanto, existe um terceiro código de retorno, o EAX=-1. Este é descrito no SDK como "EXCEPTION_CONTINUE_EXECUTION". Este retorno tem o mesmo efeito do EAX=0 de um manipulador thread-específico, isto é, recarrega o registro de contexto no processador e continua a execução a partir do eip do contexto. É claro que o manipulador final pode alterar o registro de contexto antes de retornar ao sistema, da mesma forma que um manipulador thread-específico. Deste modo, um manipulador final pode recuperar uma exceção continuando a execução a partir de um lugar seguro apropriado ou pode tentar reparar a exceção. Apesar disso, perde-se alguma flexibilidade.

Em primeiro lugar, não é possível aninhar manipuladores finais. Só é possível ter um manipulador final ativo estabelecido por SetUnhandledExceptionFilter. Você pode, se quiser, mudar o endereço do manipulador final à medida que diferentes porções do seu código sejam processadas. SetUnhandledExceptionFilter retorna o endereço do manipulador final que está sendo substituído de modo que seria possível fazer o seguinte:

PUSH ADDR FINAL_HANDLER
CALL SetUnhandledExceptionFilter
PUSH EAX                          ; guarda endereço do
                                  ; manipulador anterior
...
...                               ; este é o código 
...                               ; sendo guardado
... 
CALL SetUnhandledExceptionFilter  ; restaura manipulador anterior

Observe que, no momento da segunda chamada a SetUnhandledExceptionFilter, o endereço do manipulador anterior já está na pilha devido à instrução PUSH EAX.

Outra dificuldade em usar o manipulador final é que a informação que lhe é enviada limita-se ao registro de exceção e ao registro de contexto. Portanto, será preciso manter o endereço do código do lugar seguro, além dos valores de ESP e EBP do lugar seguro, na memória estática. Isto pode ser facilmente implementado em tempo de execução. Por exemplo, quando se usa a mensagem WM_COMMAND dentro de um procedimento de janela

PROCESS_COMMAND:        ; chamado em uMsg=111h (WM_COMMAND)
MOV EBPSAFE_PLACE,EBP   ; mantém ebp num lugar seguro
MOV ESPSAFE_PLACE,ESP   ; mantém esp num lugar seguro
...
...                     ; código protegido aqui 
...
LUGAR_SEGURO:           ; marcador para o lugar seguro
XOR EAX,EAX             ; retorna eax=0=mensagem processada
RET

No exemplo acima, para reparar a exceção a partir de um lugar seguro, o manipulador iria inserir os valores de EBPLUGAR_SEGURO em CONTEXT+0B4h (ebp), ESPLUGAR_SEGURO em CONTEXT+0C4h (esp), ADDR LUGAR_SEGURO em CONTEXT+0B8h (eip) e depois retornar -1.

Note que, num desdobramento de pilha forçado pelo sistema devido a uma saída fatal, apenas são chamados os manipuladores "thread-específicos" (se houver algum) e não o manipulador final. Se não existirem manipuladores "thread-específicos", o manipulador final terá que administrar toda a "limpeza" antes de retornar ao sistema.

9. PASSO-A-PASSO COM UMA FLAG NO MANIPULADOR

Você pode fazer um testador passo-a-passo para o seu programa durante o seu desenvolvimento usando a propriedade dos manipuladores de poder armar uma flag no contexto dos registradores antes de retornar ao sistema. Você pode determinar que o manipulador mostre os resultados na tela ou então armazená-los num arquivo. Isto pode ser útil se você suspeitar que os resultados estejam sendo alterados durante o processo de debug ou então se você precisar observar rapidamente como um detrminado trecho de código responde a várias entradas. Insira o seguinte fragmento de código onde o passo-a-passo deve começar:

MOV D[SSCOUNT],5 
INT 3

SSCOUNT é um símbolo de dados e contém o número de passos que o manipulador deve dar antes de retornar à sua operação normal. A INT3 provoca uma exceção 80000003h assim que seu manipulador for chamado.

O código no seu programa em desenvolvimento deveria ser protegido por um manipulador thread-específico usando um código do tipo:

SS_HANDLER:
PUSH EBP
MOV EBP,ESP
PUSH EBX,EDI,ESI      ; como exigido pelo Windows,
                      ; salva os registradores
MOV EBX,[EBP+8]       ; pega registro de exceção em ebx
TEST D[EBX+4],01h     ; vê se é uma exceção não-continuável
JNZ >L14              ; sim
TEST D[EBX+4],02h     ; vê se EH_UNWINDING
JNZ >L14              ; sim
MOV ESI,[EBP+10h]     ; pega registro do contexto em esi
MOV EAX,[EBX]         ; pega ExceptionCode
CMP EAX,80000004h     ; vê se está aqui porque flag está
                      ; armada
JZ >L10               ; sim
CMP EAX,80000003h     ; vê se é INT3 para o passo-a-passo
JNZ >L14              ; não
L10:
DEC D[SSCOUNT]        ; pára se atingiu o número de passos
JZ >L12
OR D[ESI+0C0h],100h   ; arma a flag no contexto
L12:
...
...                   ; código para mostrar na tela
...
XOR EAX,EAX           ; eax=0 recarrega contexto e retorna
                      ; ao sistema
JMP >L17
L14:
MOV EAX,1             ; eax=1 sistema vai para o próximo
                      ; manipulador
L17:
POP ESI,EDI,EBX
MOV ESP,EBP
POP EBP
RET

Aqui, a primeira chamada ao manipulador é causada pela INT3 (o sistema estrilou um bocado quando tentei usar INT1). Recebendo esta exceção, que poderia ter vindo apenas do fragmento de código inserido no código-a-ser-testado, o manipulador arma a flag no contexto antes de retornar. Isto faz com que uma exceção 80000004h alcance o manipulador na próxima instrução. Note que, com estas exceções, eip já está na próxima instrução, isto é, uma além da INT3 ou além da instrução executada com a flag armada. Tudo o que é preciso fazer no manipulador para continuar o passo-a-passo é armar a flag novamente e retornar ao sistema. (Agradeço ao G.W.Wilhelm, Jr da IBM pela idéia)

10. TRATAMENTO DE EXCEÇÕES EM APLICAÇÕES MULTI-THREAD

Quando se trata de aplicações multi-thread, o sistema oferece pouca ou nenhuma ajuda no tratamento das exceções. Você vai ter que planejar para falhas prováveis e organizar seus threads de acordo.

As regras que se aplicam ao tratamento de exceções pelo sistema, no contexto de aplicações multi-thread, são:

  1. Apenas um tipo 1 (manipulador final) pode existir a qualquer tempo para cada processo. Se um novo manipulador chamar SetUnhandledExceptionFilter, isto simplesmente substituirá o manipulador final - não existe uma cadeia de manipuladores finais como há para os manipuladores tipo 2 (thread-específicos). Portanto, o uso mais simples de um manipulador final provavelmente ainda é o melhor numa aplicação multi-thread - estabelecê-lo no thread principal o mais cedo possível após o ponto de entrada do programa.
  2. O manipulador final será chamado pelo sistema se o processo está para ser terminado, independentemente de qual thread tenha causado a exceção.
  3. Entretanto, um desdobramento final (imediatamente antes do término) só vai ocorrer nos manipuladores thread-específicos estabelecidos para o thread que tenha causado a exceção. Mesmo que outros threads (inocentes) possuam uma janela e um loop de mensagem, o sistema não irá avisá-los que o processo está para ser encerrado (nenhuma mensagem especial lhes será enviada além das mensagens usuais provenientes da perda de foco de outras janelas).
  4. Portanto, os outros manipulaodres (inocentes) não podem esperar um desdobramento final se o processo estiver para terminar. Continuarão sem saber que o término é iminente.
  5. Se, o que é provável, estes outros threads inocentes precisarem ser desarmados, você precisará informá-los a partir do manipulador final. O manipulador final precisará esperar até que estes outros threads tenham completado a "limpeza" para poder retornar ao sistema.
  6. O modo como os threads inocentes são informados sobre o término iminente do programa depende da acuidade do seu código. Se o thread inocente possuir uma janela e um loop de mensagem, então o manipulador final pode usar SendMessage para esta janela para enviar uma mensagem definida pela aplicação (precisa ser 400h ou acima) para informar este thread que deve terminar com elegância.
    Se não existir uma janela e um loop de mensagem, o manipulador final poderia armar uma variável pública flag, rastreada de tempos em tempos pelo outro thread. Alternativamente, você poderia usar SetThreadContext para forçar o thread a executar determinado código de encerramento fazendo com que eip apontasse para este código. Este método não funcionará se o thread estiver numa API, por exemplo, esperando o retorno de uma GetMessage. Neste caso, você também precisará enviar uma mensagem que garantir que o thread retorne da API e que o novo contexto seja configurado.
  7. RaiseException só funciona no thread chamador de modo que não pode ser usado para fazer a comunicação entre threads fazendo com que um thread inocente execute seu próprio código manipulador.
  8. Como é que o manipulador final sabe quando pode prosseguir após informar os outros threads de que o programa está para terminar? SendMessage não retorna enquanto o destinatário não tiver voltado do seu procedimento de janela e o manipulador final não pode esperar por este retorno. Alternativamente, ele poderia inscrever uma flag que esperasse por uma resposta de um outro thread que ele acabou de desobstruir (observe que você precisa chamar a API Sleep no loop de inscrição para evitar a sobrecarga do sistema). Ou, ainda melhor, o manipulador final poderia esperar até que o outro thread tenha terminado (isto pode ser feito usando a API WaitForSingleObject ou WaitForMultipleObjects se houver mais de um thread). Alternativamente poderiam ser usados as APIs Event ou Semaphore.
  9. Para um exemplo de como estes procedimentos poderiam funcionar na prática, imagine que um thread secundário tenha a função de reorganizar uma base de dados e depois escrevê-la em disco. Pode estar no meio da tarefa quando o thread principal causa uma exceção, entrando no seu manipulador final. Neste caso você poderia abortar a tarefa do thread secundário, forçando seu desdobramento e seu término elegante, deixando os dados originais no disco ou, alternativamente, você poderia permitir que ele terminasse a tarefa e depois avisasse o manipulador que tivesse terminado, de modo que o manipulador podesse voltar ao sistema. Você teria que impedir que o thread secundário iniciasse novas tarefas assim que seu manipulador fosse chamado. Neste caso o manipulador precisa armar uma flag que será testada pelo thread secundário antes de iniciar qualquer tarefa ou então usar as APIs Event.
  10. Se a comunicação entre threads é difícil, existe uma outra maneira de um thread acessar a pilha de outro thread e, deste modo, causar um desdobramento. Neste caso faz-se uso do fato de que, apesar de cada thread possuir sua própria pilha, a memória reservada para esta pilha está no espaço de endereços do próprio processo. Você pode confirmar este fato observando uma aplicação multi-thread usando um debugger. Quando você se movimentar entre threads, os valores de ESP e EBP mudarão, mas eles estarão dentro do espaço de endereços do próprio processo. O valor de FS também será diferente dependendo do thread e apontará para o Thread Information Block de cada thread. Assim, se você seguir os seguintes passos, um thread pode acessar a pilha e causar o desdobramento de outro:
    a. Guarde numa variável estática o valor do registrador FS de cada novo thread que for criado.
    b. Retorne as variáveis estáticas para zero quando cada um dos threads for fechado.
    c. O manipulador que precisar desdobrar outros threads deveria analisar cada uma das variáveis estáticas e, para as com valor diferente de zero (ou seja, o thread está ativo na hora da exceção), os manipuladores devem ser chamados com a flag de exceção 2 (EH_UNWINDING) e uma flag de usuário, digamos 400h, para mostrar que o manipulador de thread-específico está sendo chamado pelo seu manipulador final. Você não pode chamar um manipulador thread-específico num thread diferente usando RtlUnwind (que é thread-específico), mas pode fazê-lo usando o seguinte código (onde ebx contém o endereço do EXCEPTION_RECORD):
MOV D[EBX+4],402h     ; faz a flag de exceção EH_UNWINDING+400h
L1:
PUSH ES
MOV AX,[FS_VALUE]     ; pega o valor de FS do thread que deve
                      ; ser desdobrado
MOV ES,AX
ES MOV EDI,[0]        ; pega o endereço do primeiro manipulador
                      ; thread-específico
POP ES
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 o manipulador para rodar o
                      ; código de desobstrução
ADD ESP,8h            ; remove os dois parâmetros PUSHados
MOV EDI,[EDI]         ; pega o ponteiro para a próxima estrutura ERR
JMP L2                ; e faz o próximo se não estiver no fim
L3:                   ; marcador para quando terminar
; agora volta para L1 com um novo FS_VALUE até que todos os threads
; tenham sido processados

Aqui você vê que o Thread Information Block de cada thread inocente é lido usando o registrador ES, o qual, temporariamente, recebe o valor do registrador FS do thread.

Ao invés de usar FS para achar o Thread Information Block, você pode usar o seguinte código para obter um endereço linear de 32 bits. Neste código, LDT_ENTRY é uma estrutura de 2 dwords, ax contém o valor de 16 bits do seletor (FS_VALUE) que deve ser convertido e hThread é qualquer manipulador de thread válido:

AND EAX,0FFFFh
PUSH ADDR LDT_ENTRY,EAX,[hThread]
CALL GetThreadSelectorEntry
OR EAX,EAX                     ; vê se falhou
JZ >L300                       ; sim, então retorne zero
MOV EAX,ADDR LDT_ENTRY
MOV DH,[EAX+7]                 ; pega base alta
MOV DL,[EAX+4]                 ; pega meio da base
SHL EDX,16D                    ; desloca para o topo de edx
MOV DX,[EAX+2]                 ; e pega a base baixa
OR EDX,EDX                     ; agora edx=endereço linear de 32 bits
L300:                          ; retorna nz se sucesso

A razão da importância (usando a flag 400h) de informar o manipulador chamado de que está sendo chamado por outro thread (o manipulador final) é que o thread chamado ainda está rodando porque a exceção ocorreu num thread diferente. É claro que o manipulador, nesta circunstância, precisa suspender o thread para que a tarefa de desobstrução possa ser realizada pelo thread chamador. O thread inocente, então, recebe um lugar seguro para ir antes de chamar ResumeThread. Tudo isto precisa ser feito antes que o manipulador final receba permissão para voltar ao sistema porque, no retorno, o sistema simplesmente irá terminar todos os threads na força bruta.




O PROGRAMA EXCEPT1

Este programa é um exemplo simples de como a manipulação de exceções pode ser usada na prática em programas para Windows escritos em Assembly. O código fonte está no arquivo Except1.asm, escrito com a sintaxe GoAsm. Apesar de ser um programa GDI Windows, baseia-se somente em caixas de mensagem, motivo pelo qual não existem loops de mensagem.

O programa possui dois manipuladores de exceção, um final e um thread-específico. O manipulador final é criado primeiro, depois é chamado um procedimento, cujo código está protegido pelo manipulador thread-específico. Dentro deste procedimento ocorre uma exceção e o manipulador thread-específico é chamado. Dentro do manipulador, pergunta-se ao usuário se o manipulador deve ou não engolir a exceção. Se o usuário decidir que o manipulador deve engolir a exceção, o programa está preparado para continuar rodando, mas termina normalmente. Se o usuário decidir que a exceção não deve ser engolida pelo manipulador, então o manipulador final é acionado (durante o fechamento do programa). Na vida real, este manipulador seria responsável pelos logs e registros, pelo fechamento de manipuladores de arquivo, liberação de memória, etc. Mas, antes que o programa finalmente termine, algo muito interessante acontece. O sistema chama o manipulador de exceções thread-específico caso haja mais desobstruções sejam necessárias neste quadro de pilha particular que usa dados locais. Este é o desdobramento feito pelo sistema. Todos estes eventos são anunciados por várias caixas de mensagem que aparecem na tela.



O PROGRAMA EXCEPT2

Este é um programa mais complexo com a intenção de demonstrar o conteúdo deste artigo em maiores detalhes.

O código fonte do Except2.exe (Except2.asm e Except2.RC) também é fornecido e está na sintaxe GoAsm.

A janela principal é um diálogo modal. Um manipulador final é criado bem no início do processo. Quando o botão "Cause Exception" é clicado, primeiro o procedimento de diálogo é chamado com o comando e depois duas rotinas adicionais são chamadas - a terceira rotina causa uma exceção do tipo escolhido através dos botões de rádio. Quando a execução passa por este código são criados 3 manipuladores de exceção thread-específicos.

Se possível, a exceção é reparada in situ, ou então o programa se recupera dentro do manipulador escolhido a partir de um lugar seguro. Se for permitido que a exceção vá até o manipulador final, você pode sair pressionando F3 ou F5. Ou, se você perssionar F7, o manipulador final tentará se fazer a recuperação desta exceção.

Você pode acompanhar os eventos à medida que ocorrerem porque cada um dos manipuladores apresenta várias mensagens na listbox. Ocorre um pequeno delay entre cada mensagem para que você possa acompanhar com mais facilidade o que está ocorrendo, ou você pode rolar as mensagens para analisá-las melhor.

Quando o programa estiver para terminar acontece algo interessante. O sistema provoca um desdobramento final com a flag de exceção armada com 2h. As mensagens enviadas à listbox tornam-se ainda mais lentas porque o programa está prestes a terminar!

Você verá que o mesmo tipo de desdobramento ocorre se você especificar que a execução deve continuar a partir de um "lugar seguro" (safe-place) ou se F7 for pressionada quando o manipulador final estiver no comando. Este desdobramento é iniciado pelo próprio manipulador.



O AUTOR

COPYRIGHT NOTE - this article, Except1.asm, Except2.asm, Except2.RC and Except2.Exe are all

Copyright © Jeremy Gordon 1996-2002

[McDuck Software]

e-mail: JG@JGnet.co.uk

http://www.GoDevTool.com

LEGAL NOTICE - The author accepts no responsibility for losses of any type arising from this article. Whereas the author has used his best endeavours to ensure that the contents of this article are correct, you should not rely on this and you should do your own tests.



Localizador

Faça o download do artigo original acompanhado dos programas exemplo (57 Kb).



| AAAA | Página Inicial | Mapa do Site | Novidades | Busca | Indique esta página | Mestre da Teia | Voltar |
| Localizador || @ Info NumaBoa > oicìliS > Assembly > Textos > Exceções III
Créditos: vovó Vicki

webdesign sobMedida by vickiSoft - /informatica/oiciliS/assembler/textos/excecoes3.php (03.08.03) versão 1.0 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.