Learn how easy it is to sync an existing GitHub or Google Code repo to a SourceForge project! See Demo

Close

#29 SetUnhandledExceptionFilter broken in mingw64?

open
nobody
None
5
2014-08-12
2010-02-25
Anonymous
No

Hey folks,

I'm porting a backtrace handler to win64, and I can't seem to get SetUnhandledExceptionFilter() to work when building with mingw64.

Here is a small sample to demonstrate. Essentially I would expect my_exception_handler() to be called due to the crash. This test works correctly on 32-bit windows via mingw (mingw-runtime 3.13), or on 64-bit windows via MSVC (2005/2008). But when I build/run this using mingw64 on a 64-bit machine it doesn't work.

$ cat test.cpp
#include <windows.h>
#include <stdio.h>

void crashit() {
printf("ok lets crashit\n");
fflush(stdout);
int *p = (int *)8;
*p = 4;
}

LONG CALLBACK my_exception_handler(LPEXCEPTION_POINTERS ecx) {
printf("my_exception_handler called!\n");
return EXCEPTION_EXECUTE_HANDLER;
}

int main(int argc, char *argv[]) {
// print some basic sizeof stuff
printf("in main\n");
printf("sizeof unsigned long %d\n", sizeof(unsigned long));
printf("sizeof unsigned long long %d\n", sizeof(unsigned long long));
int *x;
printf("sizeof pointer %d\n", sizeof(x));

// register my exception handler
SetUnhandledExceptionFilter(&my_exception_handler);
printf("just set exception filter\n");

// crash
crashit();
}

// here's the output building on win64 using mingw-64 (note my handler isn't called)
$ /path/to/mymingw64/bin/x86_64-w64-mingw32-g++.exe -static -g -o test -static-libgcc ; ./test.exe
in main
sizeof unsigned long 4
sizeof unsigned long long 8
sizeof pointer 8
just set exception filter
ok lets crashit

// this is building on 32-bit machine using older mingw
// building with MSVC on win64 has similar result (except sizeof changes of course)
$ /path/to/mingw-3.13/bin/g++.exe -g -static -o test test.cpp -static-libgcc ; ./test.exe
in main
sizeof unsigned long 4
sizeof unsigned long long 8
sizeof pointer 4
just set exception filter
ok lets crashit
my_exception_handler called!

Any ideas? I'm digging around the source in mingw-w64-crt/crt and I see that in crtexe.c it does SetUnhandledExceptionFilter(__gnu_exception_handler) and then calls __mingw_init_ehandler() on win64, which in turn uses RtlAddFunctionTable() to set __mingw_SEH_error_handler() as a toplevel handler in crt_handler.c. This is the handler that is ultimately called in my testcase. It sees the EXCEPTION_ACCESS_VIOLATION, and appears to check if there is a user defined handler to call but it doesn't find one.

So I'm wondering if a) SetUnhandledExceptionFilter() is broken in mingw64 and my handler is simply not being installed, or b) something replacing it later under the covers?

Thanks!

Discussion

  • Kai Tietz
    Kai Tietz
    2010-02-25

    Hello,

    this is a pretty hairy issue. And the mechanism you have found here in x64 case, that we are registering a top-level SEH handler is just the top of the issue.

    First you have to know that exception handling for x64 is pretty different to the x86 one. For x64 and complete exception handling, each function has an in section.pdata a function description element entry. This entry describes region of function and its unwind data and exception handlers (if there are).
    For a more detailed view on this topic see http://www.nynaeve.net/?p=107 and http://www.openrce.org/articles/full_view/21 articles.

    As gcc is at the moment not able to procude SEH information (neither for x86 nor for x64) we have to emulate this in __mingw_init_ehandler () function.

    The use of the function SetUnhandledExceptionFilter () has for newer msvcrt also changed. If an exception is raised and the internal msvcrt handler isn't able to resolve the exception it calls SetUnhandledExceptionFilter (NULL), which resets to the default exception handler provided by kernel32.dll. So your example can't work and this AFAIK even for VC x64.

    To solve your issue, I can suggest that your are using here a signal handler for SIGSEGV. This works pretty well.

    I am working at the moment on an implementation of base SEH information (unwind information) for gcc and it should become available for upcoming gcc 4.6. In cvs head version of binutils there are already the necessary pseudo-commands present to implement this feature.

    I hope I could help you.

    Regards,
    Kai

     
  • Bryan Fulton
    Bryan Fulton
    2010-05-07

    Thanks for the response Kai. Note that using SetUnhandledExceptionFilter() *does* work on MSVC for x64 as I mentioned before. Since it doesn't work with mingw64, I've had to dig into other methods.

    But even going the pure signal handler route doesn't work perfectly without needing to hack the CRT for two reasons:
    1. catching a SIGSEGV, then walking the backtrace will end at __mingw_init_ehandler() thus failing to get the entire trace
    1. linking with a MSVC object file that contains a .pdata section means that mingw's __mingw_init_ehandler() is never registered via RtlAddFunctionTable(). So catching crashes becomes impossible.

    Here's a toy example for each. First, using signal to catch SIGSEGV:

    I register my handler via signal() as usual, and ultimately determine the backtrace by starting with the frame pointer returned from __builtin_frame_address, ala fp = (stack_frame_t *)__builtin_frame_address(0);
    I then follow the frame backwards and print return addresses. Lastly I have a utility that maps addresses to symbols.

    Here's the crashing code, I would expect a backtrace all the way back to main.

    int main(int argc, char *argv[]) {
    int action = 1;
    install_handler(); // install signal handlers, does what one expects
    fl(action);
    return 0;
    }
    void f1(int action) {
    f2(action);
    }
    void f2(int action) {
    f3(action);
    }
    void f3(int action) {
    do_action(action);
    }
    void do_action(int action) {
    crashit();
    }
    // simple segfault
    void crashit() {
    int *p = (int*)8;
    *p = 4;
    }

    Running my sample, gets this backtrace. This is a very clean back trace, but it doesn't go beyond the mingw handler.

    $ ./test-backtrace.exe | find-symbols.pl test-backtrace.map
    0x00401b03: 00401b03 T get_backtrace
    0x00401bd4: 00401b58 T get_backtrace_with_context
    0x00401ea5: 00401d91 t _ZL27catastrophic_signal_handleriP9siginfo_tPv
    0x0040252a: 0040250c T _Z33relay_catastrophic_signal_handleri
    0x0040af8a: 0040aea0 T __mingw_SEH_error_handler

    The way I handled this way two fold:
    1. Completely mimic the code in the crt's crt_handler.c, to register my own handler via RtlAddFunctionTable(). Even with this the mingw handler was still being used, so
    2. Hack the mingw CRT, to not call RtlAddFunctionTable

    Now, my handler is registered the exact same way as __mingw_init_ehandler(), and catches the crash, and I get a nice pretty backtrace:

    $ ./test-backtrace.exe | find-symbols.pl test-backtrace.map
    0x00401b03: 00401b03 T get_backtrace
    0x004031c8: 004031b4 T _Z7crashitv
    0x004030f3: 004030a4 T _Z9do_actioni
    0x00403047: 00403034 T_Z2f3i
    0x00403032: 0040301f T _Z2f2i
    0x0040301d: 0040300a T _Z2f1i
    0x00403003: 00402fb8 T main
    0x0040140e: 004011b0 t __tmainCRTStartup

    Any thoughts here? Having to hack the CRT is kind of a pain, so if I could do this cleanly out of the box it would be great. Now onto my second issue.

     
  • Bryan Fulton
    Bryan Fulton
    2010-05-07

    Even aside from hacking the CRT to do the RtlAddFunctionTable() work myself, there is still a problem when linking with MSVC object files because they have .pdata sections, which causes the CRT to not register _mingw_init_ehandler().

    Here's an example using a slightly tweaked version of the crt's own testcase:
    $ cat pdata-test.c
    #include <stdio.h>
    #include <setjmp.h>
    #include <signal.h>

    jmp_buf buf;

    void catchSigSegV( int sig ) {
    printf("caught it!\n");
    fflush(stdout);
    longjmp(buf, 1);
    }

    int *ptr = 0;

    extern int printy(int x);

    int main(void) {
    volatile int v;
    signal(SIGSEGV, catchSigSegV);
    int x = printy(128);
    printf("ok x is %d\n", x);
    fflush(stdout);
    if (!setjmp(buf)) {
    puts("Ready to catch");
    v = *ptr;
    puts("Bad");
    }
    else puts("Ok");
    return 0;
    }

    $ cat printy.c
    int printy(int x) {
    printf("ok returning %d\n", x);
    return x;
    }

    * First, build everything with gcc, I can catch the crash just fine

    $ gcc -c printy.c
    $ gcc pdata-test.c printy.o
    $ ./a.exe
    ok returning 128
    ok x is 128
    Ready to catch
    caught it!
    Ok

    * Now, build printy.c with MSVC and link again with gcc
    $ cl -c printy.c
    $ gcc pdata-test.c printy.obj
    Warning: .drectve `/DEFAULTLIB:"LIBCMT" /DEFAULTLIB:"OLDNAMES" ' unrecognized
    $ ./pdatacl.exe
    ok returning 128
    ok x is 128

    * The crash is not caught. Digging around the CRT code I see it does this:
    if (_FindPESectionByName (".pdata") != NULL) {
    return 1;
    }

    And checking that object file, it *does* have a .pdata section.

    $ nm printy.obj
    0000000000000000 t $LN3
    0000000000000000 d $SG2142
    0000000000000000 p $pdata$printy
    0000000000000000 r $unwind$printy
    0000000000000000 d .data
    0000000000000000 N .debug$S
    0000000000000000 i .drectve
    0000000000000000 p .pdata
    0000000000000000 t .text
    0000000000000000 r .xdata
    00000000006dc627 a @comp.id
    U printf
    0000000000000000 T printy

    So, it I remove this section, all works well:
    $ objcopy --remove-section .pdata printy.obj
    $ gcc pdata-test.c printy.obj
    Warning: .drectve `/DEFAULTLIB:"LIBCMT" /DEFAULTLIB:"OLDNAMES" ' unrecognized
    $ ./a.exe
    ok returning 128
    ok x is 128
    Ready to catch
    caught it!
    Ok

    Any thoughts on this? It means that I need to physically remove the .pdata section from EVERY MSVC-build object file I want to link with - which is doable obviously, and works, but I'd rather not do that because it involves changing 3rd party libraries. Kai do you have any good workaround for either of these?

    What I ended up doing what both things mentioned there. I re-built the CRT to not register it's handler, then mimic'd the CRT's code to register my own via RtlAddFunctionTable(), then removed .pdata from all MSVC-built object files. In your expertise is there a better way to do this?

    Thanks!

    .:bryan

     
  • NightStrike
    NightStrike
    2010-06-11

    Do you still need help with this?

     
  • Bryan Fulton
    Bryan Fulton
    2010-06-11

    Sure. Is there a better workaround than what I've done?
    Namely, I hacked the CRT (crt_handler.c) to not call RtlAddFunction, and then I needed to strip the .pdata section from all 3rd party MSVC libraries. This is the only way I was able to register a signal handler and get a full backtrace. If there is a smoother way, I'm all ears!
    Thanks.

     
  • Terkhen
    Terkhen
    2010-09-12

    I have also run into this issue with SetUnhandledExceptionFilter. I have tried a small test case under MinGW64, MinGW32 and MSVC32.

    #include <windows.h>
    #include <cstdio>

    static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
    {
    printf("ExceptionHandler is used.");
    return EXCEPTION_EXECUTE_HANDLER;
    }

    int main()
    {
    #if defined(__MINGW32__)
    #if defined(__MINGW64__)
    printf("Program compiled under MinGW64.\n");
    #else
    printf("Program compiled under MinGW32.\n");
    #endif
    #elif defined(_MSC_VER)
    #if defined(_WIN64)
    printf("Program compiled under MSVC64.\n");
    #else
    printf("Program compiled under MSVC32.\n");
    #endif
    #endif

    printf("Call to SetUnhandledExceptionFilter\n");
    SetUnhandledExceptionFilter(ExceptionHandler);

    printf("Causing a segmentation fault.\n");

    int *segfault = NULL;
    (*segfault) = 10000;
    }

    In MinGW32 and MSVC32 the test case works as expected:

    Program compiled under MinGW32.
    Call to SetUnhandledExceptionFilter
    Causing a segmentation fault.
    ExceptionHandler is used.

    In MinGW64, the "ExceptionHandler is used" output never appears.

     
  • NightStrike
    NightStrike
    2010-09-14

    Please use GCC 4.6 once SEH support is finalized. This is still in flux.