8

我正在尝试使用 VC++ 的 try-except 语句将一些代码移植到 MinGW:

bool success = true;

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

是否可以使用 MinGW g++ 编写捕获堆栈溢出异常的代码?

4

4 回答 4

10

您需要手动调用注册异常处理的 Windows API 函数;即AddVectoredExceptionHandler。请注意,通过使用不尊重 SEH 异常的 MinGW,抛出任何 SEH 异常或尝试捕获任何此类异常将导致未定义的行为,因为没有完成正常的 C++ 堆栈展开语义。(Windows 怎么知道要std::string对堆栈上的所有 s 进行核攻击?)

您还需要在希望调用RemoveVectoredExceptionHandlerSEH 异常处理程序的时间结束时调用。

一般来说,MinGW 缺乏对 SEH 和 COM 等 Windows 功能的支持。您尝试使用它而不是 MSVC++ 的任何原因(假设两个编译器都是免费的?)

于 2011-08-30T14:29:20.293 回答
9

这不是众所周知的,但<excpt.h>MinGW 和 MinGW-w64 中的头文件提供了宏__try1__except1生成 gcc 内联程序集以处理异常。这些宏没有文档记录并且不容易使用。它变得更糟。x86_64 版本__try1__except132 位版本不兼容。它们使用具有不同参数和不同返回值的不同回调。

几个小时后,我几乎有了 x86_64 上的工作代码。_gnu_exception_handler我需要用与MinGW 的 runtime相同的原型声明一个回调。我的回调是

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

我的 try-except 代码是

    __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()处理堆栈溢出的原因。

于 2015-09-17T03:30:36.757 回答
2

您可能希望查看LibSEH以添加对 MinGW 的结构化异常处理兼容性。

于 2011-08-30T14:34:45.550 回答
1

MinGW 不支持结构化异常的关键字;但是,正如比利奥尼尔在他的回答中所说,您可以调用某些本机函数来获得相同的效果。

问题是你是否想要同样的效果。我坚信结构化异常是一个错误。操作系统将告诉您的结构化异常列表包括“试图将整数除以 0”、“无法使用传递给函数的参数HANDLE、“试图执行非法机器代码指令”等内容。和“试图在未经许可的情况下访问内存”。您确实无法对这些错误采取任何明智的做法,但是结构化异常使您有机会 (1) 声称您有,并且 (2) 让程序蹒跚地走得更久一些。最好找出为什么代码试图除以 0,传递了一个无效的HANDLE参数,修复代码永远不要这样做

有一种说法是您可以使用结构化异常来检测问题、显示对话框并退出。我不确定这比让操作系统显示一个对话框并退出程序更好(特别是如果操作系统在进程中向您发送一个小型转储),这是未处理异常的默认行为。

有些错误是不可恢复的

于 2011-08-30T14:31:45.690 回答