Ausnahmen von Zugriffsverletzungen abfangen?

87

Beispiel

int *ptr;
*ptr = 1000;

Kann ich eine Ausnahme von Speicherzugriffsverletzungen mit Standard-C ++ abfangen, ohne Microsoft-spezifisch zu verwenden?

Ahmed Said
quelle

Antworten:

41

Nee. C ++ löst keine Ausnahme aus, wenn Sie etwas Schlechtes tun, das zu einem Leistungseinbruch führen würde. Dinge wie Zugriffsverletzungen oder Division durch Null-Fehler sind eher "Maschinen" -Ausnahmen als Dinge auf Sprachebene, die Sie abfangen können.

entspannen
quelle
Ich weiß, dass es sich um HW-Ausnahmen handelt, aber es gibt Microsoft-spezifische Schlüsselwörter, die dies behandeln (__ try __except).
Ahmed Said
2
@Ahmed: Ja, aber wenn Sie sie verwenden, können "unmögliche" Dinge passieren. Beispielsweise wurden einige der Anweisungen nach der AV-Codezeile möglicherweise bereits ausgeführt, oder Anweisungen vor der AV-Codezeile wurden nicht ausgeführt.
Aaron
Siehe meine Antwort unten, wie Sie die Behandlung solcher Ausnahmen mit dem regulären try ... catch-Block in VC ++ aktivieren können.
Volodymyr Frytskyy
@ Aaron kannst du den Teil "Unmögliche Dinge passieren" näher erläutern? liegt es an Anweisungen zur Neuordnung des Compilers und / oder der CPU?
Weipeng L
Das zugrunde liegende Betriebssystem bietet häufig Mechanismen, um solche Probleme zu beheben, und sie verursachen keine Kosten, da die Ausnahme von der CPU-Architektur generiert wird. Dies wird durch die Art und Weise belegt, in der Debugger Ausnahmen abfangen können, um das Debuggen zu ermöglichen, ohne die Codeausführung zu verlangsamen.
Dino Dini
107

Lies das und weine!

Ich habe es herausgefunden. Wenn Sie nicht vom Handler werfen, fährt der Handler einfach fort, ebenso wie die Ausnahme.

Die Magie passiert, wenn Sie Ihre eigene Ausnahme auslösen und damit umgehen.

#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
quelle
Netter Tipp, vor allem, weil __try / __ außer AV auch nicht fängt.
Fabio Ceconello
16
Dies funktioniert NICHT in gcc, sondern in VC ++, sondern nur im Build "Debug". Immer noch positiv für eine interessante Lösung. Der Signalhandler würde aufgerufen, aber die Ausnahme wird nicht ausgelöst.
Natalie Adams
2
Das funktioniert nicht portabel. Wenn ein Signalhandler aufgerufen wird, ist der Stapelrahmen und das Register-Munging nicht dasselbe wie bei einem normalen Funktionsstapelrahmen (auf einigen Systemen wird möglicherweise nicht einmal derselbe Stapel verwendet). Das Beste, was Sie tun können, ist, ein Flag zu setzen, um anzuzeigen, dass der Signalhandler aktiviert wurde. Dann in Ihrem Code Test für diese Flagge und werfen.
Martin York
2
Dies hat eine hohe Wahrscheinlichkeit, undefiniertes Verhalten einzuführen. Damit dies unter POSIX funktioniert, dürfen keine alternativen Signalstapel ( sigaltstack) installiert sein (es sei denn, die C ++ - Ausnahme-Abwicklungsimplementierung erlaubt dies), und jede Laufzeitfunktion, die den Abwicklungsmechanismus selbst handhabt, sollte signal-sicher sein.
Minmaxavg
1
Wenn Sie den Standard-Handler auf Signal zurücksetzen möchten (in diesem Fall SIGSEGV), verwenden Sie Folgendes:signal(SIGSEGV, SIG_DFL);
Kocica
67

Es gibt eine sehr einfache Möglichkeit, jede Art von Ausnahme (Division durch Null, Zugriffsverletzung usw.) in Visual Studio mit dem Block try -> catch (...) abzufangen. Eine geringfügige Änderung der Projekteinstellungen ist ausreichend. Aktivieren Sie einfach die Option / EHa in den Projekteinstellungen. Siehe Projekteigenschaften -> C / C ++ -> Codegenerierung -> Ändern Sie die C ++ - Ausnahmen aktivieren in "Ja mit SEH-Ausnahmen" . Das ist es!

Details finden Sie hier: http://msdn.microsoft.com/en-us/library/1deeycx5(v=vs.80).aspx

Volodymyr Frytskyy
quelle
In Visual Studio .NET 2003 gibt es keinen solchen Einstellungswert. Es gibt nur "Nein" und "Ja (/ EHsc)". Können Sie klären, welche Mindestversion von Visual Studio Sie benötigen, um diese Einstellung aktivieren zu können?
izogfif
Der Link scheint "Visual Studio 2005" anzugeben
Drew Delano
1
Was ist, wenn mit gcc oder MinGW?
user1024
9

Zumindest für mich funktionierte dersignal(SIGSEGV ...) in einer anderen Antwort erwähnte Ansatz unter Win32 mit Visual C ++ 2015 nicht . Was tat Arbeit für mich war zu verwenden _set_se_translator()gefunden eh.h. Es funktioniert so:

Schritt 1 ) Stellen Sie sicher, dass Sie Ja mit SEH-Ausnahmen (/ EHa) in Projekteigenschaften / C ++ / Codegenerierung / C ++ - Ausnahmen aktivieren aktivieren , wie in der Antwort von Volodymyr Frytskyy erwähnt .

Schritt 2 ) Aufruf _set_se_translator(), in einem Funktionszeiger Passing (oder Lambda) für den neuen Ausnahmesetzer . Es wird als Übersetzer bezeichnet, da es im Grunde genommen nur die Ausnahme auf niedriger Ebene nimmt und sie erneut als etwas abfängt, das leichter zu fangen ist, wie zum Beispiel :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());
});

Schritt 3 ) Fangen Sie die Ausnahme wie gewohnt ab:

try{
    MakeAnException();
}
catch(std::exception ex){
    HandleIt();
};
Michael
quelle
1
Diese Seite enthält ein paar einfache Beispiele für _set_se_translator () Methoden und funktioniert für mich, msdn.microsoft.com/en-us/library/5z4bw5h5.aspx
Pabitra Dash
8

Diese Art von Situation ist implementierungsabhängig und erfordert folglich einen herstellerspezifischen Mechanismus, um zu fangen. Bei Microsoft handelt es sich um SEH und bei * nix um ein Signal

Im Allgemeinen ist es jedoch eine sehr schlechte Idee , eine Ausnahme für Zugriffsverletzungen abzufangen . Es gibt fast keine Möglichkeit, sich von einer AV-Ausnahme zu erholen. Wenn Sie dies versuchen, wird es nur schwieriger, Fehler in Ihrem Programm zu finden.

JaredPar
quelle
1
Ihr Rat ist also zu wissen, was die Ursache für eine AV-Ausnahme ist, nicht wahr?
Ahmed Said
4
Absolut. AVs sind repräsentativ für einen Fehler in Ihrem Code. Wenn Sie die Ausnahme abfangen, wird das Problem nur ausgeblendet.
JaredPar
1
Zur Verdeutlichung unterscheidet der C ++ - Standard zwischen undefiniert, nicht spezifiziert und definierter Implementierung. Definierte Implementierung bedeutet, dass die Implementierung angeben muss, was stattfindet. Der Code in der Frage ist undefiniert, was bedeutet, dass alles passieren kann und jedes Mal anders sein kann.
KeithB
13
Das Abfangen von Zugriffsverletzungen ist keine schlechte Idee - es ist gut für die Benutzererfahrung. In diesem Fall ist es jedoch nur sinnvoll, einen weiteren Prozess mit der GUI für die Fehlerberichterstattung zu erstellen und einen aktuellen Prozessspeicherauszug zu erstellen. Das Laichen eines Prozesses ist immer eine erfolgreiche Operation. Dann mache ich TerminateProcess (), um mich selbst zu töten.
11етър Петров
11
Es ist eine schlechte Idee, eine Ausnahme abzufangen und sie stillschweigend zu ignorieren. Es ist eine sehr gute Idee, wenn möglich, eine Ausnahme abzufangen und Informationen über den Status der Anwendung zu Diagnosezwecken aufzuzeichnen. Ich habe einmal eine Benutzeroberfläche für eine Backend-Grafikbibliothek geschrieben, für die ein Debugging erforderlich war. Jedes Mal, wenn es abstürzte, kamen Leute zu mir, weil sie wussten, dass ich die Benutzeroberfläche geschrieben habe. Ich habe eine Sig-Falle um das Backend gelegt, die eine Warnung auslöste, die dem Benutzer mitteilte, dass die Bibliothek abgestürzt ist. Die Leute gingen zum Autor der Bibliothek.
Kent
8

Wie bereits erwähnt, gibt es auf der Windows-Plattform keine Möglichkeit, dies von Nicht-Microsoft- / Compiler-Anbietern zu tun. Es ist jedoch offensichtlich nützlich, diese Arten von Ausnahmen auf die normale try {} catch (Ausnahme ex) {} -Methode abzufangen, um Fehler zu melden und Ihre App ordnungsgemäß zu beenden (wie JaredPar sagt, ist die App jetzt wahrscheinlich in Schwierigkeiten). . Wir verwenden _se_translator_function in einem einfachen Klassen-Wrapper, mit dem wir die folgenden Ausnahmen in einem Try-Handler abfangen können:

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)

Die ursprüngliche Klasse stammt aus diesem sehr nützlichen Artikel:

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

Damien
quelle
8
Ich sehe, dass die Verwendung eines Microsoft-Compilers genauso behandelt wird wie eine illegale Anweisung oder eine Zugriffsverletzung. Interessant.
David Thornley
3

Nicht der Ausnahmebehandlungsmechanismus, aber Sie können den signal () -Mechanismus verwenden, der vom C bereitgestellt wird.

> man signal

     11    SIGSEGV      create core image    segmentation violation

Das Schreiben in einen NULL-Zeiger wird wahrscheinlich ein SIGSEGV-Signal verursachen

Martin York
quelle
@maidamai signal()ist Teil des Posix-Standards. Windows implementiert den Posix-Standard (wie auch Linux und Unix)
Martin York
-1

Eine solche Verletzung bedeutet, dass etwas ernsthaft mit dem Code nicht stimmt und unzuverlässig ist. Ich kann sehen, dass ein Programm möglicherweise versuchen möchte, die Daten des Benutzers so zu speichern, dass man hofft, dass sie nicht über vorherige Daten schreiben, in der Hoffnung, dass die Daten des Benutzers nicht bereits beschädigt sind, aber es gibt per Definition keine Standardmethode mit undefiniertem Verhalten umzugehen.

David Thornley
quelle
5
Möglicherweise ist eine Wiederherstellung nach einer Zugriffsverletzung möglich. Das Wiederherstellen von EIP-Sprung-Voilation ist nur möglich, wenn Sie zweifelhaft sind und Anweisungszeiger auf Baugruppenebene beibehalten. Das Abfangen einer Zugriffsverletzung ist jedoch gut, um einen weiteren Prozess für die GUI-Funktion zur Fehlerberichterstattung auszulösen.
11етър Петров