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.
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
@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. –
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