我正在尝试使用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 ++编写捕获堆栈溢出异常的代码?
答案 0 :(得分:9)
您需要手动调用注册异常处理的Windows API函数;即AddVectoredExceptionHandler。请注意,通过使用不遵守SEH异常的MinGW,抛出任何SEH异常或尝试捕获任何此类异常将导致未定义的行为,因为未完成正常的C ++堆栈展开语义。 (Windows如何知道在堆栈中核对所有std::string
?)
您还需要在希望调用SEH异常处理程序的时间结束时调用RemoveVectoredExceptionHandler
。
一般来说,MinGW缺乏对SEH和COM等Windows功能的支持。您尝试使用它而不是MSVC ++的任何原因(假设两个编译器都是免费的吗?)
答案 1 :(得分:7)
这并不为人所知,但MinGW和MinGW-w64中的头文件<excpt.h>
提供了宏__try1
和__except1
来生成用于处理异常的gcc内联汇编。这些宏没有记录,也不容易使用。它变得更糟。 __try1
和__except1
的x86_64版本与32位版本不兼容。它们使用具有不同参数和不同返回值的不同回调。
几个小时后,我几乎在x86_64上运行了代码。我需要使用与_gnu_exception_handler
in MinGW's 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()
来处理堆栈溢出的原因。
答案 2 :(得分:2)
您可能希望查看LibSEH以便为MinGW添加结构化异常处理兼容性。
答案 3 :(得分:1)
MinGW不支持结构化异常的关键字;但是,正如Billy O'Neal在他的回答中所说,你可以调用某些原生函数来达到同样的效果。
问题是你是否想要同样的效果。我坚信结构化异常是一个错误。操作系统将告诉您的list of structured exceptions包括诸如“试图将整数除以0”之类的内容,“无法使用传递给函数的HANDLE
参数”,“”试图执行非法机器代码指令,“和”试图在未经许可的情况下访问内存。“你真的不能对这些错误做任何聪明的事情,但结构化的例外让你有机会(1)声称你拥有和(2)允许程序徘徊一段时间。最好找出为什么代码试图除以0,传递一个无效的HANDLE
参数,试图在没有许可的情况下访问内存等等,并且修复代码从不这样做强>
有一种观点认为,您可以使用结构化异常来检测问题,显示对话框并退出。我不确定这比让操作系统显示一个对话框并退出程序更好(特别是如果操作系统在此过程中向您发送了一个minidump),这是未处理异常的默认行为。