M.M
M.M

Reputation: 141648

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.

Upvotes: 7

Views: 2678

Answers (2)

yeputons
yeputons

Reputation: 9248

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.

Upvotes: 3

Fedor
Fedor

Reputation: 21347

I think you can use Boost.Stacktrace for this:

#include <boost/stacktrace.hpp>

int seh_filter_func(EXCEPTION_POINTERS *xp)
{
    const auto stack = to_string( boost::stacktrace::stacktrace() );
    LOG( "%s", stack.c_str() );

    return 1;
}

Upvotes: 3

Related Questions