Портирование VC ++ __try / __ кроме EXCEPTION_STACK_OVERFLOW в MinGW - PullRequest
7 голосов
/ 30 августа 2011

Я пытаюсь перенести некоторый код с помощью оператора try-кроме VC ++ на MinGW:

bool success = true;

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

Можно ли написать код, который перехватывает исключение переполнения стека, используя MinGW g ++?

Ответы [ 4 ]

9 голосов
/ 30 августа 2011

Вам нужно будет вручную вызывать функции Windows API, которые регистрируют обработку исключений; а именно, AddVectoredExceptionHandler . Обратите внимание, что при использовании MinGW, который не учитывает исключения SEH, создание любого исключения SEH или попытка перехватить любое такое исключение приведет к неопределенному поведению, потому что обычная семантика разматывания стека C ++ не выполняется. (Как Windows узнает, что нужно уничтожить все эти std::string в стеке?)

Вам также потребуется вызвать RemoveVectoredExceptionHandler в конце времени, когда вы хотите, чтобы вызывался обработчик исключений SEH.

Как правило, MinGW не поддерживает такие функции Windows, как SEH и COM. По какой-либо причине вы пытаетесь использовать это вместо MSVC ++ (учитывая, что оба компилятора бесплатны?)

7 голосов
/ 17 сентября 2015

Это неизвестно, но заголовочный файл <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() для обработки переполнения стека.

2 голосов
/ 30 августа 2011

Возможно, вы захотите заглянуть в LibSEH для добавления совместимости со структурной обработкой исключений для MinGW.

1 голос
/ 30 августа 2011

MinGW не поддерживает ключевые слова для структурированных исключений;но, как говорит Билли О'Нил в своем ответе, вы можете вызывать определенные нативные функции для получения того же эффекта.

Вопрос в том, хотите ли вы того же эффекта.Я твердо верю, что структурированные исключения являются ошибкой. список структурированных исключений , о которых операционная система расскажет вам, включает такие вещи, как «попытка разделить целое число на 0», «не удалось использовать параметр HANDLE, переданный функции», «попытался»выполнить недопустимую инструкцию машинного кода "и" пытался получить доступ к памяти без разрешения на это ".Вы действительно не можете ничего умного сделать с этими ошибками, но структурированные исключения дают вам возможность (1) заявить, что у вас есть, и (2) позволить программе немного дольше работать.Гораздо лучше выяснить, почему код пытался разделить на 0, передавал недопустимый параметр HANDLE, пытался получить доступ к памяти без разрешения и т. Д. И исправлял код, чтобы никогда не делать этого .

Существует аргумент, что вы можете использовать структурированные исключения для обнаружения проблем, отображения диалогового окна и выхода.Я не уверен, как это лучше, чем позволить операционной системе отображать диалоговое окно и выходить из программы (особенно, если операционная система отправляет вам мини-дамп в процессе), что является поведением по умолчанию для необработанных исключений.

Некоторые ошибки не могут быть исправлены .

...