¿Detecta excepciones de infracción de acceso?

89

Ejemplo

int *ptr;
*ptr = 1000;

¿Puedo detectar una excepción de infracción de acceso a la memoria usando C ++ estándar sin usar ningún microsoft específico?

Ahmed dijo
fuente

Respuestas:

42

¡No! C ++ no lanza una excepción cuando haces algo malo, que incurriría en un impacto en el rendimiento. Cosas como las infracciones de acceso o la división por errores cero son más como excepciones de "máquina", en lugar de cosas a nivel de idioma que puede detectar.

relajarse
fuente
Sé que son excepciones de HW, pero hay palabras clave específicas de Microsoft que manejan esto (__ intente __excepto).
Ahmed Said
2
@Ahmed: sí, pero si los usas, pueden pasar cosas 'imposibles'. Por ejemplo, es posible que algunas de las declaraciones después de la línea de código AV ya se hayan ejecutado o que las declaraciones anteriores al AV no se hayan ejecutado.
Aaron
Vea mi respuesta a continuación sobre cómo habilitar el manejo de tales excepciones usando el bloque try ... catch regular en VC ++.
Volodymyr Frytskyy
@Aaron, ¿puedes dar más detalles sobre la parte de "suceden cosas imposibles"? ¿Se debe a las instrucciones de reordenación del compilador y / o CPU?
Weipeng L
El sistema operativo subyacente a menudo proporcionará mecanismos para detectar estos problemas y no incurrirán en ningún costo, ya que la arquitectura de la CPU genera la excepción. Esto se evidencia por la forma en que los depuradores pueden atrapar excepciones para permitirle depurar, sin ralentizar la ejecución del código.
Dino Dini
108

¡Léelo y llora!

Me lo imaginé. Si no lanza desde el controlador, el controlador simplemente continuará y también lo hará la excepción.

La magia ocurre cuando lanzas tu propia excepción y manejas eso.

#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
fuente
Buen consejo, especialmente porque __try / __, excepto que tampoco detecta AV.
Fabio Ceconello
16
Esto NO funciona en gcc pero funciona en VC ++ pero solo en la compilación "Debug". Sigo votando a favor de una solución interesante. Se llamaría al manejador de señales pero no se lanzaría la excepción.
Natalie Adams
2
Eso no funciona de forma portátil. Cuando se invoca un manejador de señales, el marco de pila y el registro munging no es lo mismo que cuando se invoca un marco de pila de función normal (es posible que ni siquiera use la misma pila en algunos sistemas). Lo mejor que puede hacer es colocar una bandera para indicar que se ha activado el controlador de señales. Luego, en tu código, prueba esa bandera y lanza.
Martin York
2
Esto tiene una alta probabilidad de introducir un comportamiento indefinido. Para que esto funcione en POSIX, no debe haber ninguna pila de señales alternativa ( sigaltstack) instalada (a menos que la implementación de desenrollado de excepción de C ++ lo permita), y cada función de tiempo de ejecución que maneja el mecanismo de desenrollado debe ser segura para señales.
minmaxavg
1
Si desea devolver el controlador predeterminado a la señal (SIGSEGV en este caso), use lo siguiente:signal(SIGSEGV, SIG_DFL);
kocica
67

Hay una manera muy fácil de detectar cualquier tipo de excepción (división por cero, violación de acceso, etc.) en Visual Studio usando el bloque try -> catch (...). Un pequeño ajuste de la configuración del proyecto es suficiente. Simplemente habilite la opción / EHa en la configuración del proyecto. Consulte Propiedades del proyecto -> C / C ++ -> Generación de código -> Modifique Habilitar excepciones C ++ a "Sí con excepciones SEH" . ¡Eso es!

Vea los detalles aquí: http://msdn.microsoft.com/en-us/library/1deeycx5(v=vs.80).aspx

Volodymyr Frytskyy
fuente
No existe tal valor de configuración en Visual Studio .NET 2003, solo hay "No" y "Sí (/ EHsc)". ¿Puede aclarar qué versión mínima de Visual Studio necesita para poder habilitar esta configuración?
izogfif
El enlace parece especificar "Visual Studio 2005"
Drew Delano
2
¿Qué pasa si con gcc o MinGW?
user1024
10

Al menos para mí, el signal(SIGSEGV ...)enfoque mencionado en otra respuesta no funcionó en Win32 con Visual C ++ 2015 . Lo que funcionó para mí fue usar _set_se_translator()encontrado en eh.h. Funciona así:

Paso 1 ) Asegúrese de habilitar Sí con excepciones SEH (/ EHa) en Propiedades del proyecto / C ++ / Generación de código / Habilitar excepciones C ++ , como se menciona en la respuesta de Volodymyr Frytskyy .

Paso 2 ) Llame _set_se_translator(), pasando un puntero de función (o lambda) para el nuevo traductor de excepciones . Se llama traductor porque básicamente solo toma la excepción de bajo nivel y la vuelve a lanzar como algo más fácil de atrapar, 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());
});

Paso 3 ) Capture la excepción como lo haría normalmente:

try{
    MakeAnException();
}
catch(std::exception ex){
    HandleIt();
};
Miguel
fuente
1
Este sitio contiene un par de ejemplos simples sobre los métodos _set_se_translator () y funciona para mí, msdn.microsoft.com/en-us/library/5z4bw5h5.aspx
Pabitra Dash
8

Este tipo de situación depende de la implementación y, en consecuencia, requerirá un mecanismo específico del proveedor para realizar la captura. Con Microsoft esto involucrará SEH, y * nix involucrará una señal

En general, detectar una excepción de infracción de acceso es una muy mala idea. Casi no hay forma de recuperarse de una excepción de AV e intentar hacerlo solo hará que sea más difícil encontrar errores en su programa.

JaredPar
fuente
1
Entonces, su consejo es saber cuál es la causa de la excepción AV, ¿no es así?
Ahmed Said
4
Absolutamente. Los AV son representativos de un error en su código y detectar la excepción solo ocultará el problema.
JaredPar
1
Para aclarar, el estándar C ++ hace una distinción entre indefinido, no especificado y definido por implementación. Implementación definida significa que la implementación debe especificar lo que ocurre. El código de la pregunta no está definido, lo que significa que puede suceder cualquier cosa y ser diferente cada vez.
KeithB
15
Detectar una infracción de acceso no es una mala idea, es bueno para la experiencia del usuario. Sin embargo, lo único significativo que hago en este caso es generar otro proceso con la GUI de informes de errores e intentar crear un volcado de proceso actual. Generar un proceso siempre es una operación exitosa. Luego, hago TerminateProcess () para auto-matar.
Петър Петров
12
Es una mala idea detectar una excepción e ignorarla en silencio. Siempre que sea posible, es una muy buena idea detectar una excepción y registrar información sobre el estado de la aplicación con fines de diagnóstico. Una vez escribí una interfaz de usuario para una biblioteca de gráficos de backend que necesitaba depuración. Cada vez que fallaba, la gente acudía a mí porque sabían que yo escribía la interfaz de usuario. Puse una trampa sig alrededor del backend que apareció una alerta que le dijo al usuario que la biblioteca se bloqueó. La gente empezó a acudir al autor de la biblioteca.
Kent
8

Como se indicó, no existe una forma que no sea de Microsoft / proveedor de compiladores para hacer esto en la plataforma Windows. Sin embargo, obviamente es útil detectar este tipo de excepciones de la manera normal try {} catch (excepción ex) {} para informar de errores y más una salida elegante de su aplicación (como dice JaredPar, la aplicación ahora probablemente tenga problemas) . Usamos _se_translator_function en un contenedor de clase simple que nos permite capturar las siguientes excepciones en un controlador de prueba:

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)

La clase original provino de este artículo muy útil:

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

Damien
fuente
8
Veo que el uso de un compilador de Microsoft se trata de la misma forma que una instrucción ilegal o una infracción de acceso. Interesante.
David Thornley
3

No es el mecanismo de manejo de excepciones, pero puede usar el mecanismo signal () proporcionado por C.

> man signal

     11    SIGSEGV      create core image    segmentation violation

Escribir en un puntero NULL probablemente provocará una señal SIGSEGV

Martin York
fuente
@maidamai signal()es parte del estándar posix. Windows implementa el estándar posix (al igual que Linux y Unix)
Martin York
-1

Una infracción como esa significa que hay algo muy mal con el código y no es confiable. Puedo ver que un programa puede querer intentar guardar los datos del usuario de una manera que uno espera que no se escriban sobre los datos anteriores, con la esperanza de que los datos del usuario no estén ya corruptos, pero por definición no existe un método estándar. de lidiar con un comportamiento indefinido.

David Thornley
fuente
6
Es posible que se recupere de una infracción de acceso. Recuperarse de la voilación del salto EIP nunca es posible a menos que sea poco fiable y mantenga los consejos de instrucción de nivel de ensamblaje. Sin embargo, detectar una infracción de acceso es bueno para generar otro proceso para la función de GUI de informes de errores.
Петър Петров