2011-08-30 7 views
7

Usiłuję portu niektóre kodu przy użyciu VC++' Porting VC++ s try-except do MinGW:'__try s/__ wyjątkiem EXCEPTION_STACK_OVERFLOW do MinGW

bool success = true; 

__try { 
    //... 
} __except ((EXCEPTION_STACK_OVERFLOW == GetExceptionCode()) 
      ? EXCEPTION_EXECUTE_HANDLER 
      : EXCEPTION_CONTINUE_SEARCH) { 
    success = false; 
    _resetstkoflw(); 
} 
return success; 

to możliwe, aby napisać kod, który łapie przepełnienia stosu wyjątku za pomocą MinGW g ++?

+4

Zawsze wydawało mi się to zabawne, jak ludzie nazywają MORE funkcjami, gdy dostają wyjątek przepełnienia stosu ... Niektóre wyjątki są naprawdę fatalne i powinny zostać pozostawione, aby zabić program. – Blindy

+4

@Blindy: Po wygenerowaniu wyjątku stos zostaje usunięty, a stan przepełnienia stosu zostaje złagodzony. Windows wyrzuca ten wyjątek, gdy pozostała jeszcze strona lub dwie stosy; gdybyś naprawdę przepełnił stos, twój proces zostałby zakończony bez ostrzeżenia. –

+0

Nie zostanie rozwinięty, jeśli jesteś już w ramce stosu, która złamała grzbiet przysłowiowego wielbłąda. Nie wiedziałem o tym, że wciąż mam dodatkowy pokój po wyjściu z wyjątku, ale nawet jeśli to prawda, czy nie dostaniesz wyjątku ponownie w środku 'GetExceptionCode'? – Blindy

Odpowiedz

9

Będziesz musiał ręcznie wywołać funkcje API Windows, które rejestrują obsługę wyjątków; mianowicie AddVectoredExceptionHandler. Zauważ, że używając MinGW, który nie respektuje wyjątków SEH, wyrzucenie dowolnego wyjątku SEH lub próba złapania takiego wyjątku spowoduje niezdefiniowane zachowanie, ponieważ normalne rozwijanie znaczników w C++ nie jest wykonywane. (W jaki sposób system Windows potrafi odrzucić wszystkie te std::string s na stosie?)

Musisz również zadzwonić pod numer RemoveVectoredExceptionHandler pod koniec czasu, w którym wywoływana jest funkcja obsługi wyjątków SEH.

Ogólnie MinGW brakuje w obsłudze funkcji systemu Windows, takich jak SEH i COM. Każdy powód, dla którego próbujesz użyć tego zamiast MSVC++ (biorąc pod uwagę, że oba kompilatory są bezpłatne?)

+0

Cześć Billy, dziękuję za odpowiedź. Mam zainstalowane zarówno MinGW g ++, jak i Visual C++, i używam obu, z wyjątkiem tego, że chciałem eksperymentować z szablonami variadic, których VC++ jeszcze nie obsługuje. –

+0

@Daniel: Hmm .. może LLVM? Czytałem gdzieś, że mogą używać CRT VC++ i Windows SDK, ale nie jestem pewien. –

1

MinGW nie obsługuje słów kluczowych dla strukturalnych wyjątków; ale, jak mówi Billy O'Neal w swojej odpowiedzi, możesz wywołać pewne funkcje natywne, aby uzyskać ten sam efekt.

Pytanie brzmi, czy chcesz uzyskać ten sam efekt. Jestem głęboko przekonany, że ustrukturyzowane wyjątki są błędem. The list of structured exceptions, o którym system operacyjny poinformuje Cię o takich rzeczach jak "próbował podzielić liczbę całkowitą przez 0", "nie mógł użyć parametru HANDLE przekazanego do funkcji," "próbował wykonać nieprawidłową instrukcję kodu maszynowego" i " próbowałem uzyskać dostęp do pamięci bez pozwolenia, aby to zrobić. " Naprawdę nie można zrobić nic inteligentnego w odniesieniu do tych błędów, ale ustrukturyzowane wyjątki dają możliwość (1) twierdzenia, że ​​masz i (2) pozwalają programowi na dłuższe kulowanie. Znacznie lepiej jest dowiedzieć się, dlaczego kod próbował podzielić przez 0, przekazał nieprawidłowy parametr HANDLE, próbował uzyskać dostęp do pamięci bez pozwolenia, itp. I naprawić kod, aby nigdy tego nie robić.

Istnieje argument, że można użyć strukturalnych wyjątków do wykrywania problemów, wyświetlania okna dialogowego i zamykania. Nie jestem pewien, jak to jest lepsze, niż pozwolić systemowi operacyjnemu wyświetlić okno dialogowe i wyjść z programu (zwłaszcza jeśli system operacyjny wyśle ​​ci minizompu w procesie), co jest domyślnym zachowaniem dla nieobsługiwanych wyjątków.

Some errors aren't recoverable.

+2

1. Możesz zrobić SEH, jest to o wiele bardziej nużące. (Zobacz funkcję, do której przyłączyłem w mojej odpowiedzi) 2. EXCEPTION_STACK_OVERFLOW naprawdę nie jest przekroczony; oznacza to, że trafiłeś na ostatnią stronę strażnika na stosie. –

+0

Masz rację; Edytowałem swoją odpowiedź. –

+0

Powinieneś zwrócić uwagę na * dlaczego * uważasz, że SEH jest błędem. Jest to "czysty" mechanizm do ochrony aplikacji przed źle zakodowanymi wtyczkami, ponieważ można zawijać punkty wejścia wtyczek w bloku SEH i wychwytywać wszelkie naruszenia dostępu, przepełnienia stosów itp. Uruchamiane przez wtyczkę. Użytkownicy nie rozróżniają między aplikacją hosta a wtyczką. Zapobieganie awarii aplikacji i zgłaszanie awarii wtyczki może poprawić obraz aplikacji i prawdopodobieństwo uzyskania wsparcia. Nie nazwałbym tego "pomyłką". –

2

Możesz zajrzeć do LibSEH w celu dodania obsługi Structured Exception Handling dla MinGW.

+0

To nie jest prawda. Rozszerzenia językowe MSVC++ powodują, że destruktory C++ są wywoływane na stosie. Ta biblioteka po prostu wywołuje AddVectoredExceptionHandler pod osłonami i spowoduje niezdefiniowane zachowanie, jeśli używana jest semantyka zniszczenia stosu. –

+3

To prawda, ale biorąc pod uwagę brak wsparcia ze strony MinGW dla SEH, jest to mniej więcej tak dobre, jak się da. Połączona strona opisuje potencjalne problemy. –

+0

Nie mówię inaczej. Ale to nie robi tego, co robi MSVC++, więc "dodanie kompatybilności z Structured Exception Handling" jest zbyt silne - nie dodaje nic do MinGW. Zawiera jedynie kilka makr, które wywołują dla Ciebie interfejs API systemu Windows. –

4

To nie jest znana, ale plik header <excpt.h> w MinGW i MinGW-W64 zapewnia makr __try1 i __except1 produkować gcc inline assembly do obsługi wyjątków. Te makra nie są dokumentowane i nie są łatwe w użyciu. Pogarsza się. Wersje x86_64 z __try1 i __except1 nie są kompatybilne z wersjami 32-bitowymi. Używają różnych wywołań zwrotnych z różnymi argumentami i różnymi wartościami zwracanymi.

Po kilku godzinach prawie miałem działający kod na x86_64. Musiałem zadeklarować wywołanie zwrotne z tym samym prototypem co _gnu_exception_handler in MinGW's runtime.Moja zwrotna była

long CALLBACK 
ehandler(EXCEPTION_POINTERS *pointers) 
{ 
    switch (pointers->ExceptionRecord->ExceptionCode) { 
    case EXCEPTION_STACK_OVERFLOW: 
     return EXCEPTION_EXECUTE_HANDLER; 
    default: 
     return EXCEPTION_CONTINUE_SEARCH; 
    } 
} 

A mój try-z wyjątkiem kodu było

__try1 (ehandler) { 
     sum = sum1to(n); 
     __asm__ goto ("jmp %l[ok]\n" :::: ok); 
    } __except1 { 
     printf("Stack overflow!\n"); 
     return 1; 
    } 
ok: 
    printf("The sum from 1 to %u is %u\n", n, sum); 
    return 0; 

on pracował aż włączona optymalizacja ze gcc -O2. To spowodowało błędy asemblera, więc mój program nie był już kompilowany. Makra __try1 i __except1 są przerywane przez optymalizację w gcc 5.0.2, która przenosi funkcje z .text do innej sekcji.

Gdy makra działały, przepływ kontrolny był głupi. Jeśli nastąpiło przepełnienie stosu, program przeskoczył przez __except1. Jeśli przepełnienie stosu się nie stało, program spadł do __except1 w tym samym miejscu. Potrzebowałem mojego dziwnego __asm__ goto, aby przeskoczyć na ok: i zapobiec przewróceniu. Nie mogę użyć goto ok;, ponieważ gcc usunąłoby __except1 za nieosiągalne.

Po kilku godzinach naprawiłem swój program. Skopiowałem i zmodyfikowałem kod zespołu, aby poprawić przepływ sterowania (nie ma więcej przeskoku do ok:) i przetrwać optymalizację gcc -O2. Ten kod jest brzydki, ale to działa na mnie:

/* gcc except-so.c -o except-so */ 
#include <windows.h> 
#include <excpt.h> 
#include <stdio.h> 

#ifndef __x86_64__ 
#error This program requires x86_64 
#endif 

/* This function can overflow the call stack. */ 
unsigned int 
sum1to(unsigned int n) 
{ 
    if (n == 0) 
     return 0; 
    else { 
     volatile unsigned int m = sum1to(n - 1); 
     return m + n; 
    } 
} 

long CALLBACK 
ehandler(EXCEPTION_POINTERS *pointers) 
{ 
    switch (pointers->ExceptionRecord->ExceptionCode) { 
    case EXCEPTION_STACK_OVERFLOW: 
     return EXCEPTION_EXECUTE_HANDLER; 
    default: 
     return EXCEPTION_CONTINUE_SEARCH; 
    } 
} 

int main(int, char **) __attribute__ ((section (".text.startup"))); 

/* 
* Sum the numbers from 1 to the argument. 
*/ 
int 
main(int argc, char **argv) { 
    unsigned int n, sum; 
    char c; 

    if (argc != 2 || sscanf(argv[1], "%u %c", &n, &c) != 1) { 
     printf("Argument must be a number!\n"); 
     return 1; 
    } 

    __asm__ goto (
     ".seh_handler __C_specific_handler, @except\n\t" 
     ".seh_handlerdata\n\t" 
     ".long 1\n\t" 
     ".rva .l_startw, .l_endw, ehandler, .l_exceptw\n\t" 
     ".section .text.startup, \"x\"\n" 
     ".l_startw:" 
      :::: except); 
    sum = sum1to(n); 
    __asm__ (".l_endw:"); 
    printf("The sum from 1 to %u is %u\n", n, sum); 
    return 0; 

except: 
    __asm__ (".l_exceptw:"); 
    printf("Stack overflow!\n"); 
    return 1; 
} 

Można się zastanawiać, w jaki sposób system Windows może wywołać ehandler() w pełnym stosie. Wszystkie te wywołania rekurencyjne do sum1to() muszą pozostać na stosie, dopóki mój przewodnik nie zdecyduje, co zrobić. W jądrze systemu Windows jest trochę magii; kiedy zgłasza przepełnienie stosu, mapuje również dodatkową stronę stosu, aby narzędzie ntdll.exe mogło wywołać mój moduł obsługi. Widzę to w gdb, jeśli umieściłbym punkt przerwania na moim treserze. Stos narasta, aby adres 0x54000 na moim komputerze. Ramki stosów od sum1to() zatrzymują się przy 0x54000, ale procedura obsługi wyjątków działa na dodatkowej stronie stosu od 0x53000 do 0x54000. Sygnały Unix nie mają takiej magii, dlatego też programy uniksowe potrzebują sigaltstack() do obsługi przepełnienia stosu.

+2

Interesujące. +1 za bycie [Prawdziwym człowiekiem] (http://www.ee.ryerson.ca/~elf/hack/realmen.html) i hakowanie rzeczy;) – alecov