Łapiesz wyjątki dotyczące naruszeń dostępu?

89

Przykład

int *ptr;
*ptr = 1000;

czy mogę przechwycić wyjątek naruszenia dostępu do pamięci przy użyciu standardowego C ++ bez użycia jakichkolwiek specyficznych dla firmy Microsoft.

Ahmed Said
źródło

Odpowiedzi:

42

Nie. C ++ nie zgłasza wyjątku, gdy robisz coś złego, co spowodowałoby spadek wydajności. Takie rzeczy jak naruszenia zasad dostępu lub błędy dzielenia przez zero są bardziej jak wyjątki „maszynowe”, a nie rzeczy na poziomie języka, które można wykryć.

rozwijać
źródło
Wiem, że to wyjątki sprzętowe, ale istnieją słowa kluczowe specyficzne dla firmy Microsoft, które to obsługują (__ try __except)?
Ahmed powiedział
2
@Ahmed: tak, ale jeśli ich użyjesz, mogą się wydarzyć „niemożliwe” rzeczy. Na przykład niektóre instrukcje po linii kodu AV mogły już zostać wykonane lub instrukcje przed AV nie zostały wykonane.
Aaron
Zobacz moją odpowiedź poniżej, jak włączyć obsługę takich wyjątków za pomocą zwykłego bloku try ... catch w VC ++.
Volodymyr Frytskyy
@Aaron, czy możesz rozwinąć część dotyczącą „dziejących się rzeczy niemożliwych”? czy to z powodu instrukcji zmiany kolejności kompilatora i / lub procesora?
Weipeng L
Podstawowy system operacyjny często zapewnia mechanizmy do wychwytywania takich problemów i nie wiąże się to z żadnymi kosztami, ponieważ wyjątek jest generowany przez architekturę procesora. Świadczy o tym sposób, w jaki debugery są w stanie przechwytywać wyjątki, aby umożliwić debugowanie, bez spowalniania wykonywania kodu.
Dino Dini
108

Przeczytaj i płacz!

Rozgryzłem to. Jeśli nie wyrzucisz z handlera, handler będzie kontynuował, podobnie jak wyjątek.

Magia dzieje się, gdy rzucasz własny wyjątek i radzisz sobie z tym.

#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
źródło
Dobra wskazówka, zwłaszcza, że ​​__try / __ except nie złapie również AV.
Fabio Ceconello,
16
To NIE działa w gcc, ale działa w VC ++, ale tylko w kompilacji "Debug". Wciąż głosuję za ciekawym rozwiązaniem. Program obsługi sygnału zostałby wywołany, ale wyjątek nie zostanie zgłoszony.
Natalie Adams
2
To nie działa przenośnie. Gdy funkcja obsługi sygnału jest wywoływana, ramka stosu i mungowanie rejestrów nie jest takie samo, jak w przypadku normalnej ramki stosu funkcji (może nawet nie używać tego samego stosu w niektórych systemach). Najlepsze, co możesz zrobić, to ustawić flagę, aby wskazać, że program obsługi sygnału został aktywowany. Następnie w kodzie przetestuj tę flagę i wyrzuć.
Martin York
2
Ma to duże szanse na wprowadzenie nieokreślonego zachowania. Aby to zadziałało w POSIX, nie mogą być sigaltstackzainstalowane żadne alternatywne stosy sygnałów ( ) (chyba, że ​​implementacja cofania wyjątku C ++ na to pozwala), a każda funkcja środowiska uruchomieniowego obsługująca sam mechanizm rozwijania powinna być bezpieczna dla sygnałów.
minmaxavg
1
Jeśli chcesz przywrócić domyślny program obsługi do sygnalizacji (w tym przypadku SIGSEGV), użyj poniższego:signal(SIGSEGV, SIG_DFL);
kocica
67

Istnieje bardzo łatwy sposób na wychwycenie dowolnego rodzaju wyjątku (dzielenie przez zero, naruszenie zasad dostępu itp.) W programie Visual Studio przy użyciu bloku try -> catch (...). Wystarczy drobna zmiana ustawień projektu. Po prostu włącz opcję / EHa w ustawieniach projektu. Zobacz Właściwości projektu -> C / C ++ -> Generowanie kodu -> Zmodyfikuj opcję Włącz wyjątki C ++ na „Tak z wyjątkami SEH” . Otóż ​​to!

Zobacz szczegóły tutaj: http://msdn.microsoft.com/en-us/library/1deeycx5(v=vs.80).aspx

Volodymyr Frytskyy
źródło
W programie Visual Studio .NET 2003 nie ma takiej wartości ustawienia, są tylko „Nie” i „Tak (/ EHsc)”. Czy możesz wyjaśnić, jakiej minimalnej wersji programu Visual Studio potrzebujesz, aby móc włączyć to ustawienie?
izogfif
Wydaje się, że łącze wskazuje „Visual Studio 2005”
Drew Delano,
2
co jeśli z gcc lub MinGW?
user1024
10

Przynajmniej dla mnie signal(SIGSEGV ...)podejście wymienione w innej odpowiedzi nie działało na Win32 z Visual C ++ 2015 . Co zrobiłem pracę dla mnie było wykorzystanie _set_se_translator()znaleźć w eh.h. Działa to tak:

Krok 1 ) Upewnij się, że włączyłeś Tak z SEH Exceptions (/ EHa) we właściwościach projektu / C ++ / Code Generation / Enable C ++ Exceptions , jak wspomniano w odpowiedzi Volodymyr Frytskyy .

Krok 2 ) Wywołaj _set_se_translator(), przekazując wskaźnik funkcji (lub lambda) dla nowego translatora wyjątków . Nazywa się to tłumaczem, ponieważ po prostu bierze wyjątek niskiego poziomu i ponownie rzuca go jako coś łatwiejszego do złapania, na przykład 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());
});

Krok 3 ) Złap wyjątek tak, jak zwykle:

try{
    MakeAnException();
}
catch(std::exception ex){
    HandleIt();
};
Michał
źródło
1
Ta strona zawiera kilka prostych przykładów metod _set_se_translator () i działa dla mnie, msdn.microsoft.com/en-us/library/5z4bw5h5.aspx
Pabitra Dash
8

Tego typu sytuacja jest zależna od implementacji, w związku z czym pułapka będzie wymagała specjalnego mechanizmu dostawcy. W przypadku Microsoftu będzie to wiązało się z SEH, a * nix będzie wiązało się z sygnałem

Ogólnie jednak wyłapywanie wyjątku naruszenia zasad dostępu jest bardzo złym pomysłem. Nie ma prawie żadnego sposobu na odzyskanie po wyjątku AV, a próba wykonania tego spowoduje trudniejsze znalezienie błędów w programie.

JaredPar
źródło
1
Więc radzisz wiedzieć, jaka jest przyczyna wyjątku AV, prawda?
Ahmed powiedział
4
Absolutnie. AV są reprezentatywne dla błędu w twoim kodzie i wychwycenie wyjątku po prostu ukryje problem.
JaredPar
1
Aby wyjaśnić, standard C ++ wprowadza rozróżnienie między niezdefiniowaną, nieokreśloną i zdefiniowaną implementacją. Zdefiniowana implementacja oznacza, że ​​implementacja musi określać, co się dzieje. Kod w pytaniu jest niezdefiniowany, co oznacza, że ​​wszystko może się zdarzyć i za każdym razem być inne.
KeithB
15
Wyłapywanie naruszenia zasad dostępu nie jest złym pomysłem - jest dobre dla doświadczenia użytkownika. Jednak jedyną znaczącą rzeczą, jaką robię w tym przypadku, jest - odrodzenie innego procesu z GUI raportowania błędów i próba utworzenia bieżącego zrzutu procesu. Powstawanie procesu zawsze kończy się sukcesem. Następnie wykonuję TerminateProcess (), aby zabić.
Петър Петров
12
Złym pomysłem jest wychwycenie wyjątku i po cichu go zignorowanie. W miarę możliwości bardzo dobrym pomysłem jest wychwycenie wyjątku i zapisanie informacji o stanie aplikacji do celów diagnostycznych. Kiedyś napisałem interfejs użytkownika dla wewnętrznej biblioteki graficznej, która wymagała debugowania. Za każdym razem, gdy się zawiesił, ludzie przychodzili do mnie, ponieważ wiedzieli, że napisałem interfejs użytkownika. Umieściłem pułapkę SIG wokół backendu, który wyskoczył z ostrzeżeniem, który powiedział użytkownikowi, że biblioteka uległa awarii. Ludzie zaczęli chodzić do autora biblioteki.
Kent
8

Jak wspomniano, nie ma sposobu, w jaki producent inny niż Microsoft / kompilator może to zrobić na platformie Windows. Jednak ewidentnie przydatne jest wychwycenie tego typu wyjątków w normalny sposób try {} catch (wyjątek ex) {} do raportowania błędów i bardziej wdzięcznego wyjścia z aplikacji (jak mówi JaredPar, aplikacja prawdopodobnie ma teraz kłopoty) . Używamy _se_translator_function w prostym opakowaniu klasy, które pozwala nam wychwycić następujące wyjątki w obsłudze aa 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)

Oryginalna klasa pochodzi z tego bardzo przydatnego artykułu:

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

Damien
źródło
8
Widzę, że używanie kompilatora firmy Microsoft jest traktowane tak samo, jak nielegalna instrukcja lub naruszenie zasad dostępu. Ciekawy.
David Thornley,
3

Nie mechanizm obsługi wyjątków, ale możesz użyć mechanizmu signal (), który jest udostępniany przez C.

> man signal

     11    SIGSEGV      create core image    segmentation violation

Zapis do wskaźnika NULL prawdopodobnie spowoduje sygnał SIGSEGV

Martin York
źródło
@maidamai signal()jest częścią standardu POSIX . Windows implementuje standard POSIX (podobnie jak Linux i unix)
Martin York,
-1

Takie naruszenie oznacza, że ​​z kodem jest coś poważnego i jest on zawodny. Widzę, że program może chcieć spróbować zapisać dane użytkownika w sposób, który ma nadzieję, że nie zostanie zapisany na poprzednich danych, w nadziei, że dane użytkownika nie są już uszkodzone, ale z definicji nie ma standardowej metody radzenia sobie z niezdefiniowanym zachowaniem.

David Thornley
źródło
6
Odzyskiwanie po naruszeniu dostępu może być możliwe. Odzyskiwanie z voilacji skokowej EIP nigdy nie jest możliwe, chyba że jesteś podejrzany i zachowujesz wskazówki dotyczące poziomu montażu. Jednak wychwycenie naruszenia zasad dostępu jest dobre do wywołania kolejnego procesu funkcji interfejsu GUI do zgłaszania błędów.
Петър Петров