8

I am trying to port some code using VC++'s try-except statement to MinGW:

bool success = true;

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

Is it possible to write code that catches a stack overflow exception using MinGW g++?

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
Daniel Trebbien
  • 38,421
  • 18
  • 121
  • 193
  • 4
    I've always found it funny how people call MORE functions when they get a stack overflow exception... Some exceptions really are fatal and should be left to kill the program. – Blindy Aug 30 '11 at 14:17
  • 4
    @Blindy: When the exception is thrown, the stack gets unwound, and the stack overflow condition is alleviated. Windows throws this exception when there's still a page or two of stack left; if you were to truly stack overflow your process would be terminated without warning. – Billy ONeal Aug 30 '11 at 14:27
  • Won't get unwound if you're already IN the stack frame that broke the proverbial camel's back. I didn't know about still having some extra room after the exception, but even if that's true, won't you just get the exception again in the middle of `GetExceptionCode`? – Blindy Aug 30 '11 at 14:30
  • 7
    @Blindy: Yes, it will get unwound. And no, if you truly blow up the stack you get terminated. Windows isn't complaining about actually blowing the stack; there has to be a stack to report to you that the stack is toast. You get warned roughly when there's one or two pages left (it's handled under the covers using a guard page) If you ignore the exception and continue consuming stack, really blowing it's top, you get terminated with no error message and no warning. – Billy ONeal Aug 30 '11 at 14:33
  • 2
    This page looks useful: http://www.programmingunlimited.net/siteexec/content.cgi?page=mingw-seh – Hans Passant Aug 30 '11 at 14:35

4 Answers4

10

This isn't well known, but the header file <excpt.h> in MinGW and MinGW-w64 provides macros __try1 and __except1 to produce gcc inline assembly for handling exceptions. These macros are not documented and are not easy to use. It gets worse. The x86_64 editions of __try1 and __except1 aren't compatible with the 32-bit editions. They use different callbacks with different arguments and different return values.

After a few hours, I almost had working code on x86_64. I needed to declare a callback with the same prototype as _gnu_exception_handler in MinGW's runtime. My callback was

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

And my try-except code was

    __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;

It was working until I enabled optimization with gcc -O2. This caused assembler errors so my program no longer compiled. The __try1 and __except1 macros are broken by an optimization in gcc 5.0.2 that moves functions from .text to a different section.

When the macros did work, the control flow was stupid. If a stack overflow happened, the program jumped through __except1. If a stack overflow didn't happen, the program fell through __except1 to the same place. I needed my weird __asm__ goto to jump to ok: and prevent the fall-through. I can't use goto ok; because gcc would delete __except1 for being unreachable.

After a few more hours, I fixed my program. I copied and modified the assembly code to improve the control flow (no more jump to ok:) and to survive gcc -O2 optimization. This code is ugly, but it works for me:

/* 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;
}

You might wonder how Windows can call ehandler() on a full stack. All those recursive calls to sum1to() must remain on the stack until my handler decides what to do. There is some magic in the Windows kernel; when it reports a stack overflow, it also maps an extra page of stack so that ntdll.exe can call my handler. I can see this in gdb, if I put a breakpoint on my handler. The stack grows down to address 0x54000 on my machine. The stack frames from sum1to() stop at 0x54000, but the exception handler runs on an extra page of stack from 0x53000 to 0x54000. Unix signals have no such magic, which is why Unix programs need sigaltstack() to handle a stack overflow.

George Koehler
  • 1,560
  • 17
  • 23
  • 5
    Interesting. +1 for being a [Real Man](http://www.ee.ryerson.ca/~elf/hack/realmen.html) and hacking things up ;) – alecov Mar 10 '16 at 22:43
10

You would need to manually call the Windows API functions which register exception handling; namely, AddVectoredExceptionHandler. Note that by using MinGW which does not respect SEH exceptions, throwing any SEH exception or attempting to catch any such exception will result in undefined behavior, because the normal C++ stack unwinding semantic isn't done. (How does Windows know to nuke all those std::strings on the stack?)

You would also need to call RemoveVectoredExceptionHandler at the end of the time you want that SEH exception handler to be called.

Generally MinGW is lacking in support of Windows features like SEH and COM. Any reason you're trying to use that instead of MSVC++ (given that both compilers are free?)

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • Hi Billy, thanks for your response. I have both MinGW g++ and Visual C++ installed, and I use both, except that I wanted to experiment with variadic templates, which VC++ does not yet support. – Daniel Trebbien Aug 30 '11 at 20:18
  • @Daniel: Hmm.. perhaps LLVM? I read somewhere that they can use VC++'s CRT and the Windows SDK, but I'm not sure. – Billy ONeal Aug 30 '11 at 21:15
2

You might want to look into LibSEH for adding Structured Exception Handling compatibility for MinGW.

Dave Rager
  • 8,002
  • 3
  • 33
  • 52
  • This is not really correct. MSVC++'s language extensions make sure C++ destructors are called on the stack. This library merely calls AddVectoredExceptionHandler under the covers, and will result in undefined behavior if stack destruction semantics are in use. – Billy ONeal Aug 30 '11 at 14:36
  • 3
    True, but given MinGW's lack of support for SEH this is about as good as it's going to get. The linked page describes the potential issues. – Dave Rager Aug 30 '11 at 14:45
  • Not saying otherwise. But it does not get you what MSVC++ does, so "adding Structured Exception Handling compatibility" is too strong - it adds nothing to MinGW. It merely provides a few macros which call the Windows API for you. – Billy ONeal Aug 30 '11 at 14:56
  • Sorry... That's the wording from the linked page... I make no warranties, guarantees or assertion of correctness for the claims of the third party library. :-) – Dave Rager Aug 30 '11 at 15:10
1

MinGW doesn't support the keywords for structured exceptions; but, as Billy O'Neal says in his answer, you can call certain native functions to get the same effect.

The question is whether you want the same effect. I strongly believe that structured exceptions are a mistake. The list of structured exceptions that the operating system will tell you about include things like "tried to divide an integer by 0," "couldn't use the HANDLE parameter passed to a function," "tried to execute an illegal machine code instruction," and "tried to access memory without permission to do so." You really can't do anything intelligent about these errors, but structured exceptions give you the opportunity to (1) claim that you have and (2) allow the program to hobble along a little longer. It's far better to find out why the code tried to divide by 0, passed an invalid HANDLE parameter, tried to access memory without permission to do so, etc. and fix the code to never do that.

There is an argument that you could use structured exceptions to detect problems, display a dialog box, and exit. I'm not sure how this is better than letting the operating system display a dialog box and exit the program (especially if the operating system sends you a minidump in the process), which is the default behavior for unhandled exceptions.

Some errors aren't recoverable.

Max Lybbert
  • 19,717
  • 4
  • 46
  • 69
  • 2
    1. You can do SEH, it's just a lot more tedious. (See the function I linked to in my answer) 2. EXCEPTION_STACK_OVERFLOW isn't really overflown; it does mean you've hit the last guard page on the stack though. – Billy ONeal Aug 30 '11 at 14:33
  • You should really point out *why* you think SEH is a mistake. It's a "clean" mechanism to protect your application against poorly coded plugins, as you can wrap plugin entry points in a SEH block and catch any access violations, stack overflows, etc. triggered by the plugin. Users make no distinction between the host application and the plugin. Preventing an application crash and reporting a failure in the plugin can improve the image of the application and the likelihood of getting support. I would hardly call this a "mistake". – André Caron Aug 30 '11 at 15:39
  • 1
    @André: That's a bad idea. If there are access violations going on in a plugin, the host program should crash. There's no telling what kind of corruption the bad plugin has done to your program's data at that point, and it would be bad to flush the state of that potentially corrupt memory to disk. If you really want to prevent a plugin from taking down your application you need to load it in a surrogate process. (That's what the Windows Shell does, for instance) – Billy ONeal Aug 30 '11 at 17:11
  • @Billy: I'm not saying the application should keep going. However, you can still pop up (or log) a message indicating the source of the problem before closing down (while avoiding to persist any current application state). A direct crash is a really nasty user experience. – André Caron Aug 30 '11 at 17:17
  • 1
    @Andre: Popping up an error box is still a really nasty user experience. If you need to keep running in the face of bad plugins use a surrogate process. – Billy ONeal Aug 30 '11 at 17:50
  • Edited to explain why SEH is a bad idea. – Max Lybbert Aug 30 '11 at 19:07