Search code examples
c++windowsstack-traceseh

How to generate stack trace from SEH exception


I am catching an exception using Win32 SEH:

try
{
    // illegal operation that causes access violation
}
__except( seh_filter_func(GetExceptionInformation()) )
{
    // abort
}

where the filter function looks like:

int seh_filter_func(EXCEPTION_POINTERS *xp)
{
    // log EIP, other registers etc. to file

    return 1;
}

This works so far and the value in xp->ContextRecord->Eip tells me which function caused the access violation (actually - ntdll.dll!RtlEnterCriticalSection , and the value for EDX tells me that this function was called with a bogus argument).

However, this function is called in many places, including from other WinAPI functions, so I still don't know which code is responsible for calling this function with the bogus argument.

Is there any code I can use to generate a trace of the chain of function calls leading up to where EIP is now, based on the info in EXCEPTION_POINTERS or otherwise? (Running the program under an external debugger isn't an option).

Just EIP values would be OK as I can look them up in the linker map and symbol tables, although if there is a way to automatically map them to symbol names that'd be even better.

I am using C++Builder 2006 for this project, although an MSVC++ solution might work anyway.


Solution

  • It seems that in 64-bit mode the SEH filter function is executed on the same stack without unwinding it, so you can indeed look at the suffix of boost::stacktrace::stacktrace() to see where the error has happened, as shown in another answer.

    However, it does not work for me in 32-bit mode. I had to walk the stack using the StackWalk64 function from DbgHelp.h/DbgHelp.lib. Although it needs the STACKFRAME64 to start, it is possible populate it with corresponding registers obtained from the CONTEXT struct at xp->ContextRecord.

    The following code works for me in 32-bit mode:

    #include <boost/stacktrace.hpp>  // For symbols only
    #include <DbgHelp.h>
    
    #pragma comment(lib, "DbgHelp.lib")
    
    int seh_filter_func(EXCEPTION_POINTERS* xp) {
        CONTEXT context = *xp->ContextRecord;
        STACKFRAME64 s;
        ZeroMemory(&s, sizeof(s));
        s.AddrPC.Offset = context.Eip;
        s.AddrPC.Mode = AddrModeFlat;
        s.AddrFrame.Offset = context.Ebp;
        s.AddrFrame.Mode = AddrModeFlat;
        s.AddrStack.Offset = context.Esp;
        s.AddrStack.Mode = AddrModeFlat;
    
        // Not thread-safe!
        for (int i = 0;
            StackWalk64(
                IMAGE_FILE_MACHINE_I386, 
                GetCurrentProcess(), 
                GetCurrentThread(), 
                &s, 
                &context, 
                NULL, 
                NULL, 
                NULL, 
                NULL);
            i++) {
            std::cout << i << ": " << boost::stacktrace::frame((void*)s.AddrPC.Offset) << "\n";
        }
        return 1;
    }
    

    For some reason, that does not work in 64-bit mode even if I replace 32-bit registers with corresponding 64-bit registers. It prints the first frame correctly and prints something unclear later.