Это неизвестно, но заголовочный файл <excpt.h>
в MinGW и MinGW-w64 предоставляет макросы __try1
и __except1
для создания встроенной сборки gcc для обработки исключений. Эти макросы не документированы и не просты в использовании. Становится хуже. Редакции x86_64 __try1
и __except1
не совместимы с 32-битными редакциями. Они используют разные обратные вызовы с разными аргументами и разными возвращаемыми значениями.
Через несколько часов у меня почти появился рабочий код на x86_64. Мне нужно было объявить обратный вызов с тем же прототипом, что и _gnu_exception_handler
во время выполнения MinGW . Мой обратный звонок был
long CALLBACK
ehandler(EXCEPTION_POINTERS *pointers)
{
switch (pointers->ExceptionRecord->ExceptionCode) {
case EXCEPTION_STACK_OVERFLOW:
return EXCEPTION_EXECUTE_HANDLER;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
}
И мой пробный код был
__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;
Это работало, пока я не включил оптимизацию с помощью gcc -O2
. Это вызвало ошибки ассемблера, поэтому моя программа больше не компилируется. Макросы __try1
и __except1
ломаются из-за оптимизации в gcc 5.0.2, которая перемещает функции из .text
в другой раздел.
Когда макросы работали, поток управления был тупым. Если произошло переполнение стека, программа перепрыгнула через __except1
. Если переполнение стека не произошло, программа провалилась через __except1
в то же место. Мне нужно было свое странное __asm__ goto
, чтобы перейти к ok:
и предотвратить провал. Я не могу использовать goto ok;
, потому что gcc удалит __except1
из-за недоступности.
Через несколько часов я исправил свою программу. Я скопировал и изменил код сборки, чтобы улучшить поток управления (больше не переходить на ok:
) и выжить при оптимизации gcc -O2
. Этот код некрасив, но у меня работает:
/* 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;
}
Вас может удивить, как Windows может вызывать ehandler()
в полном стеке. Все эти рекурсивные вызовы sum1to()
должны оставаться в стеке, пока мой обработчик не решит, что делать. В ядре Windows есть какая-то магия; когда он сообщает о переполнении стека, он также отображает дополнительную страницу стека, чтобы ntdll.exe мог вызвать мой обработчик. Я могу видеть это в GDB, если я поставлю точку останова на мой обработчик. Стек увеличивается до адреса 0x54000 на моей машине. Кадры стека от sum1to()
останавливаются на 0x54000, но обработчик исключений запускается на дополнительной странице стека от 0x53000 до 0x54000. Сигналы Unix не имеют такой магии, поэтому программам Unix требуется sigaltstack()
для обработки переполнения стека.