Capturando exceções de violação de acesso?

89

Exemplo

int *ptr;
*ptr = 1000;

posso capturar exceção de violação de acesso à memória usando C ++ padrão sem usar qualquer microsoft específico.

Ahmed disse
fonte

Respostas:

43

Não. C ++ não lança uma exceção quando você faz algo ruim, o que causaria um impacto no desempenho. Coisas como violações de acesso ou divisão por zero erros são mais como exceções de "máquina", em vez de coisas no nível da linguagem que você pode capturar.

desanuviar
fonte
Eu sei que são exceções de HW, mas existem palavras-chave específicas da Microsoft para lidar com isso (__ tente __except)?
Ahmed disse
2
@Ahmed: sim, mas se você usá-los, coisas 'impossíveis' podem acontecer. Por exemplo, algumas das instruções após a linha de código do AV podem já ter sido executadas ou as instruções antes do AV não foram executadas.
Aaron
Veja minha resposta abaixo como habilitar o tratamento de exceções usando o bloco try ... catch regular no VC ++.
Volodymyr Frytskyy de
@Aaron, você pode explicar melhor a parte das "coisas impossíveis acontecendo"? é por causa do compilador e / ou instruções de reordenação da CPU?
Weipeng L
O sistema operacional subjacente geralmente fornece mecanismos para detectar esses problemas e eles não geram nenhum custo, pois a exceção é gerada pela arquitetura da CPU. Isso é evidenciado pela maneira como os depuradores são capazes de interceptar exceções para permitir a depuração, sem desacelerar a execução do código.
Dino Dini
108

Leia e chore!

Eu descobri. Se você não lançar do manipulador, o manipulador apenas continuará e também a exceção.

A mágica acontece quando você lança sua própria exceção e lida com isso.

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <tchar.h>

void SignalHandler(int signal)
{
    printf("Signal %d",signal);
    throw "!Access Violation!";
}

int main()
{
    typedef void (*SignalHandlerPointer)(int);

    SignalHandlerPointer previousHandler;
    previousHandler = signal(SIGSEGV , SignalHandler);
    try{
        *(int *) 0 = 0;// Baaaaaaad thing that should never be caught. You should write good code in the first place.
    }
    catch(char *e)
    {
        printf("Exception Caught: %s\n",e);
    }
    printf("Now we continue, unhindered, like the abomination never happened. (I am an EVIL genius)\n");
    printf("But please kids, DONT TRY THIS AT HOME ;)\n");

}
Bernhard Barker
fonte
Boa dica, especialmente porque __tente / __ exceto que também não pegará AV.
Fabio Ceconello
16
Isso NÃO funciona no gcc, mas funciona no VC ++, mas apenas no build "Debug". Ainda estou votando por uma solução interessante. O manipulador de sinais seria chamado, mas a exceção não seria lançada.
Natalie Adams
2
Isso não funciona de maneira portátil. Quando um manipulador de sinal é invocado, o quadro de pilha e o munging de registro não são iguais a um quadro de pilha de função normal (ele pode nem usar a mesma pilha em alguns sistemas). O melhor que você pode fazer é definir um sinalizador para indicar que o manipulador de sinais foi ativado. Em seguida, em seu código, teste essa bandeira e lance.
Martin York
2
Isso tem uma grande chance de introduzir um comportamento indefinido. Para que isso funcione no POSIX, não deve haver nenhum sinal alternativo stacks ( sigaltstack) instalado (a menos que a implementação de desenrolamento de exceção C ++ permita), e cada função de tempo de execução que manipula o próprio mecanismo de desenrolamento deve ser seguro para sinais.
minmaxavg
1
Se você quiser retornar o manipulador padrão para o sinal (SIGSEGV neste caso), basta usar o seguinte:signal(SIGSEGV, SIG_DFL);
kocica
67

Há uma maneira muito fácil de capturar qualquer tipo de exceção (divisão por zero, violação de acesso, etc.) no Visual Studio usando o bloco try -> catch (...). Um pequeno ajuste nas configurações do projeto é o suficiente. Basta habilitar a opção / EHa nas configurações do projeto. Consulte Propriedades do projeto -> C / C ++ -> Geração de código -> Modifique a opção Ativar exceções C ++ para "Sim com exceções SEH" . É isso aí!

Veja os detalhes aqui: http://msdn.microsoft.com/en-us/library/1deeycx5(v=vs.80).aspx

Volodymyr Frytskyy
fonte
Não existe tal valor de configuração no Visual Studio .NET 2003, existem apenas "Não" e "Sim (/ EHsc)". Você pode esclarecer qual versão mínima do Visual Studio é necessária para habilitar essa configuração?
izogfif
O link parece especificar "Visual Studio 2005"
Drew Delano
2
e se com gcc ou MinGW?
user1024
10

Pelo menos para mim, a signal(SIGSEGV ...)abordagem mencionada em outra resposta não funcionou no Win32 com Visual C ++ 2015 . O que funcionou para mim foi usar _set_se_translator()found in eh.h. Funciona assim:

Etapa 1 ) Certifique-se de habilitar Yes with SEH Exceptions (/ EHa) em Project Properties / C ++ / Code Generation / Enable C ++ Exceptions , conforme mencionado na resposta de Volodymyr Frytskyy .

Etapa 2 ) Chame _set_se_translator(), passando um ponteiro de função (ou lambda) para o novo tradutor de exceção . É chamado de tradutor porque basicamente pega a exceção de baixo nível e a re-lança como algo mais fácil de detectar, como std::exception:

#include <string>
#include <eh.h>

// Be sure to enable "Yes with SEH Exceptions (/EHa)" in C++ / Code Generation;
_set_se_translator([](unsigned int u, EXCEPTION_POINTERS *pExp) {
    std::string error = "SE Exception: ";
    switch (u) {
    case 0xC0000005:
        error += "Access Violation";
        break;
    default:
        char result[11];
        sprintf_s(result, 11, "0x%08X", u);
        error += result;
    };
    throw std::exception(error.c_str());
});

Etapa 3 ) Capture a exceção como faria normalmente:

try{
    MakeAnException();
}
catch(std::exception ex){
    HandleIt();
};
Michael
fonte
1
Este site contém alguns exemplos simples de métodos _set_se_translator () e funciona para mim, msdn.microsoft.com/en-us/library/5z4bw5h5.aspx
Pabitra Dash
8

Esse tipo de situação depende da implementação e, conseqüentemente, exigirá um mecanismo específico do fornecedor para fazer o trap. Com a Microsoft, isso envolverá SEH, e * nix envolverá um sinal

Em geral, capturar uma exceção de violação de acesso é uma ideia muito ruim. Quase não há maneira de se recuperar de uma exceção AV e tentar fazer isso apenas tornará mais difícil encontrar bugs no seu programa.

JaredPar
fonte
1
Portanto, seu conselho é saber qual é a causa da exceção AV, não é?
Disse Ahmed
4
Absolutamente. Os antivírus são representativos de um bug em seu código e detectar a exceção apenas ocultará o problema.
JaredPar
1
Para esclarecer, o padrão C ++ faz uma distinção entre indefinido, não especificado e definido pela implementação. Implementação definida significa que a implementação deve especificar o que ocorre. O código em questão é indefinido, o que significa que tudo pode acontecer e ser diferente a cada vez.
KeithB
15
Detectar violação de acesso não é má ideia - é bom para a experiência do usuário. No entanto, a única coisa significativa que faço neste caso é - gerar outro processo com Bug Reporting GUI e tentar criar um despejo de processo atual. Gerar um processo é sempre uma operação bem-sucedida. Então, eu faço TerminateProcess () para matar automaticamente.
Петър Петров
12
É uma má ideia capturar uma exceção e ignorá-la silenciosamente. É uma boa ideia, quando possível, capturar uma exceção e registrar informações sobre o estado do aplicativo para fins de diagnóstico. Certa vez, escrevi uma IU para uma biblioteca gráfica de back-end que precisava de alguma depuração. Cada vez que travava, as pessoas me procuravam porque sabiam que eu escrevi a IU. Eu coloquei uma armadilha de assinatura em torno do back-end que apareceu um alerta que disse ao usuário que a biblioteca travou. As pessoas começaram a ir ao autor da biblioteca.
Kent
8

Conforme declarado, não existe uma maneira que não seja da Microsoft / fornecedor de compilador de fazer isso na plataforma Windows. No entanto, é obviamente útil capturar esses tipos de exceções na maneira normal try {} catch (exception ex) {} para relatórios de erros e mais uma saída elegante de seu aplicativo (como diz JaredPar, o aplicativo agora provavelmente está com problemas) . Usamos _se_translator_function em um wrapper de classe simples que nos permite capturar as seguintes exceções em um manipulador try:

DECLARE_EXCEPTION_CLASS(datatype_misalignment)
DECLARE_EXCEPTION_CLASS(breakpoint)
DECLARE_EXCEPTION_CLASS(single_step)
DECLARE_EXCEPTION_CLASS(array_bounds_exceeded)
DECLARE_EXCEPTION_CLASS(flt_denormal_operand)
DECLARE_EXCEPTION_CLASS(flt_divide_by_zero)
DECLARE_EXCEPTION_CLASS(flt_inexact_result)
DECLARE_EXCEPTION_CLASS(flt_invalid_operation)
DECLARE_EXCEPTION_CLASS(flt_overflow)
DECLARE_EXCEPTION_CLASS(flt_stack_check)
DECLARE_EXCEPTION_CLASS(flt_underflow)
DECLARE_EXCEPTION_CLASS(int_divide_by_zero)
DECLARE_EXCEPTION_CLASS(int_overflow)
DECLARE_EXCEPTION_CLASS(priv_instruction)
DECLARE_EXCEPTION_CLASS(in_page_error)
DECLARE_EXCEPTION_CLASS(illegal_instruction)
DECLARE_EXCEPTION_CLASS(noncontinuable_exception)
DECLARE_EXCEPTION_CLASS(stack_overflow)
DECLARE_EXCEPTION_CLASS(invalid_disposition)
DECLARE_EXCEPTION_CLASS(guard_page)
DECLARE_EXCEPTION_CLASS(invalid_handle)
DECLARE_EXCEPTION_CLASS(microsoft_cpp)

A classe original veio deste artigo muito útil:

http://www.codeproject.com/KB/cpp/exception.aspx

Damien
fonte
8
Vejo que usar um compilador da Microsoft é tratado da mesma forma que uma instrução ilegal ou violação de acesso. Interessante.
David Thornley
3

Não é o mecanismo de tratamento de exceção, mas você pode usar o mecanismo signal () que é fornecido pelo C.

> man signal

     11    SIGSEGV      create core image    segmentation violation

Escrever para um ponteiro NULL provavelmente causará um sinal SIGSEGV

Martin York
fonte
@maidamai signal()faz parte do padrão posix. Windows implementa o padrão posix (assim como Linux e unix)
Martin York
-1

Uma violação como essa significa que há algo muito errado com o código e ele não é confiável. Eu posso ver que um programa pode querer tentar salvar os dados do usuário de uma forma que se espere não sobrescrever os dados anteriores, na esperança de que os dados do usuário já não estejam corrompidos, mas por definição não existe um método padrão de lidar com comportamento indefinido.

David Thornley
fonte
7
A recuperação da violação de acesso pode ser possível. A recuperação da voilação de salto EIP nunca é possível, a menos que você seja duvidoso e mantenha os ponteiros de instrução de nível de montagem. No entanto, detectar violação de acesso é bom para gerar outro processo para o recurso GUI de relatório de bug.
Петър Петров