Attraper des exceptions de violation d'accès?

89

Exemple

int *ptr;
*ptr = 1000;

puis-je intercepter une exception de violation d'accès à la mémoire en utilisant C ++ standard sans utiliser de Microsoft spécifique.

Ahmed Said
la source

Réponses:

42

Nan. C ++ ne lève pas d'exception lorsque vous faites quelque chose de mauvais, cela entraînerait une baisse des performances. Des choses comme les violations d'accès ou la division par zéro erreur ressemblent plus à des exceptions «machine» qu'à des choses au niveau de la langue que vous pouvez attraper.

se détendre
la source
Je sais que ce sont des exceptions HW, mais il existe des mots clés spécifiques à Microsoft pour gérer cela (__ essayez __except)?
Ahmed Said le
2
@Ahmed: oui, mais si vous les utilisez, des choses «impossibles» peuvent arriver. Par exemple, certaines des instructions après la ligne de code AV peuvent avoir déjà été exécutées, ou des instructions avant AV ne se sont pas exécutées.
Aaron
Voir ma réponse ci-dessous comment activer cette gestion des exceptions à l'aide du bloc try ... catch régulier dans VC ++.
Volodymyr Frytskyy
@Aaron pouvez-vous élaborer sur la partie "des choses impossibles qui se passent"? est-ce à cause des instructions de réorganisation du compilateur et / ou du processeur?
Weipeng L
Le système d'exploitation sous-jacent fournira souvent des mécanismes pour détecter ces problèmes, et ils n'entraîneront aucun coût, car l'exception est générée par l'architecture du processeur. Ceci est démontré par la façon dont les débogueurs peuvent intercepter les exceptions pour vous permettre de déboguer, sans ralentir l'exécution du code.
Dino Dini
108

Lit ça et pleure!

Je l'ai compris. Si vous ne lancez pas le gestionnaire, le gestionnaire continuera simplement, tout comme l'exception.

La magie se produit lorsque vous lancez votre propre exception et gérez cela.

#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
la source
Bon conseil, surtout parce que __try / __ sauf n'attrapera pas non plus AV.
Fabio Ceconello
16
Cela ne fonctionne PAS dans gcc mais fonctionne dans VC ++ mais uniquement dans la version "Debug". Je vote toujours pour une solution intéressante. Le gestionnaire de signal serait appelé mais l'exception ne sera pas levée.
Natalie Adams
2
Cela ne fonctionne pas de manière portable. Lorsqu'un gestionnaire de signaux est invoqué, la trame de pile et le munging de registre ne sont pas les mêmes que quand une trame de pile de fonctions normale (il peut même ne pas utiliser la même pile sur certains systèmes). Le mieux que vous puissiez faire est de définir un indicateur pour indiquer que le gestionnaire de signaux a été activé. Ensuite, dans votre code, testez cet indicateur et lancez.
Martin York
2
Cela a de fortes chances d'introduire un comportement indéfini. Pour que cela fonctionne sur POSIX, aucune pile de signaux alternative ( sigaltstack) ne doit être installée (à moins que l'implémentation de déroulement d'exception C ++ ne le permette), et chaque fonction d'exécution gérant le mécanisme de déroulement lui-même doit être sans danger pour le signal.
minmaxavg
1
Si vous voulez renvoyer le gestionnaire par défaut au signal (SIGSEGV dans ce cas), utilisez simplement ce qui suit:signal(SIGSEGV, SIG_DFL);
kocica
67

Il existe un moyen très simple d'intercepter tout type d'exception (division par zéro, violation d'accès, etc.) dans Visual Studio en utilisant le bloc try -> catch (...). Une petite modification des paramètres du projet suffit. Activez simplement l'option / EHa dans les paramètres du projet. Voir Propriétés du projet -> C / C ++ -> Génération de code -> Modifiez les exceptions Activer C ++ sur "Oui avec les exceptions SEH" . C'est ça!

Voir les détails ici: http://msdn.microsoft.com/en-us/library/1deeycx5(v=vs.80).aspx

Volodymyr Frytskyy
la source
Il n'existe pas de valeur de paramètre de ce type dans Visual Studio .NET 2003, il n'y a que «Non» et «Oui (/ EHsc)». Pouvez-vous préciser de quelle version minimale de Visual Studio vous avez besoin pour pouvoir activer ce paramètre?
izogfif
Le lien semble spécifier "Visual Studio 2005"
Drew Delano
2
Et si avec gcc ou MinGW?
user1024
10

Au moins pour moi, l' signal(SIGSEGV ...)approche mentionnée dans une autre réponse ne fonctionnait pas sur Win32 avec Visual C ++ 2015 . Ce qui a fonctionné pour moi, c'est d'utiliser _set_se_translator()found in eh.h. Cela fonctionne comme ceci:

Étape 1 ) Assurez-vous d'activer Oui avec les exceptions SEH (/ EHa) dans Propriétés du projet / C ++ / Génération de code / Activer les exceptions C ++ , comme mentionné dans la réponse de Volodymyr Frytskyy .

Étape 2 ) Appelez _set_se_translator(), en passant un pointeur de fonction (ou lambda) pour le nouveau traducteur d' exceptions . Il est appelé un traducteur car il prend simplement l'exception de bas niveau et la renvoie comme quelque chose de plus facile à attraper, tel que 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());
});

Étape 3 ) Attrapez l'exception comme vous le feriez normalement:

try{
    MakeAnException();
}
catch(std::exception ex){
    HandleIt();
};
Michael
la source
1
Ce site contient quelques exemples simples sur les méthodes _set_se_translator () et fonctionne pour moi, msdn.microsoft.com/en-us/library/5z4bw5h5.aspx
Pabitra Dash
8

Ce type de situation dépend de l'implémentation et par conséquent, il faudra un mécanisme spécifique au fournisseur afin de piéger. Avec Microsoft, cela impliquera SEH, et * nix impliquera un signal

En général, attraper une exception de violation d'accès est une très mauvaise idée. Il n'y a presque aucun moyen de récupérer à partir d'une exception AV et tenter de le faire conduira simplement à des bogues plus difficiles à trouver dans votre programme.

JaredPar
la source
1
Donc, votre conseil est de savoir quelle est la cause de l'exception AV, n'est-ce pas?
Ahmed Said le
4
Absolument. Les AV sont représentatifs d'un bogue dans votre code et attraper l'exception ne fera que masquer le problème.
JaredPar
1
Pour clarifier, le standard C ++ fait une distinction entre non défini, non spécifié et implémentation définie. La mise en œuvre définie signifie que la mise en œuvre doit spécifier ce qui se passe. Le code de la question n'est pas défini, ce qui signifie que tout peut arriver et être différent à chaque fois.
KeithB
15
Attraper une violation d'accès n'est pas une mauvaise idée - c'est bon pour l'expérience utilisateur. Cependant, la seule chose significative que je fais dans ce cas est - lancez un autre processus avec l'interface graphique de rapport de bogue et essayez de créer un vidage de processus actuel. Le frai d'un processus est toujours une opération réussie. Ensuite, je fais TerminateProcess () pour m'auto-tuer.
Петър Петров
12
C'est une mauvaise idée d'attraper une exception et de l'ignorer silencieusement. C'est une très bonne idée lorsque cela est possible de détecter une exception et d'enregistrer des informations sur l'état de l'application à des fins de diagnostic. Une fois, j'ai écrit une interface utilisateur pour une bibliothèque graphique principale qui nécessitait un débogage. Chaque fois qu'il tombait en panne, des gens venaient me voir parce qu'ils savaient que j'avais écrit l'interface utilisateur. J'ai mis un sig trap autour du backend qui a déclenché une alerte indiquant à l'utilisateur que la bibliothèque s'était plantée. Les gens ont commencé à aller voir l'auteur de la bibliothèque.
Kent
8

Comme indiqué, il n'existe aucun moyen non Microsoft / fournisseur de compilateur de le faire sur la plate-forme Windows. Cependant, il est évidemment utile d'attraper ces types d'exceptions de la manière normale try {} catch (exception ex) {} pour signaler les erreurs et plus encore une sortie gracieuse de votre application (comme le dit JaredPar, l'application est maintenant probablement en difficulté) . Nous utilisons _se_translator_function dans un simple wrapper de classe qui nous permet d'attraper les exceptions suivantes dans un gestionnaire 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)

La classe originale est issue de cet article très utile:

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

Damien
la source
8
Je vois que l'utilisation d'un compilateur Microsoft est traitée de la même manière qu'une instruction illégale ou une violation d'accès. Intéressant.
David Thornley
3

Pas le mécanisme de gestion des exceptions, mais vous pouvez utiliser le mécanisme signal () fourni par le C.

> man signal

     11    SIGSEGV      create core image    segmentation violation

L'écriture sur un pointeur NULL va probablement provoquer un signal SIGSEGV

Martin York
la source
@maidamai signal()fait partie du standard posix. Windows implémente le standard posix (tout comme Linux et unix)
Martin York
-1

Une violation comme celle-là signifie qu'il y a quelque chose qui ne va pas dans le code et que ce n'est pas fiable. Je peux voir qu'un programme peut vouloir essayer de sauvegarder les données de l'utilisateur d'une manière que l'on espère ne pas écraser sur les données précédentes, dans l'espoir que les données de l'utilisateur ne sont pas déjà corrompues, mais il n'y a par définition pas de méthode standard de gérer un comportement indéfini.

David Thornley
la source
6
La récupération d'une violation d'accès peut être possible. La récupération de la voilation de saut EIP n'est jamais possible à moins d'être douteux et de conserver des pointeurs d'instructions au niveau de l'assemblage. Cependant, attraper une violation d'accès est bon pour engendrer un autre processus pour la fonctionnalité GUI de rapport de bogue.
Петър Петров