How to generate stack trace from SEH exception
Asked Answered
P

2

7

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.

Pelham answered 6/11, 2014 at 21:48 Comment(7)
Try this article: codeproject.com/Articles/11132/Walking-the-callstackArdeliaardelis
dbghelp.dll is a great library. We use it, but here are some things to consider. If your process crashes because the heap is corrupt, dbghelp may not be able to allocate the memory for the object file symbol tables. Consider initializing dbghelp before a crash. You need to deliver debug information with your software. For some security sensitive software that may be an issue.Compilation
The other option is to use MS minidump files. These basically write the stack (and more if you want) memory to a minidump file. The MS debuggers can use a minidump file to show you a stack back trace in your code.Compilation
@Ardeliaardelis exactly what I was looking for, ty. I hope this article can be archived somehow in case the link dies later and other people want the same thing.Pelham
@brianbeuning thanks, the minidump also generated the file which has some useful info although I couldn't seem to get a callstack out of it , it just says "External Code" for my code. Viewing the dump using VS2013 ExpressPelham
@MattMcNabb When we tried minidumps, they worked for all threads except the one with the exception. The documentation says to have another process or another thread generate the minidump, we never tried that.Compilation
Also the "Walking the callstack" article was migrated to github.com/JochenKalmbach/StackWalkerSemanteme
S
3

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.

Semanteme answered 21/6, 2023 at 22:14 Comment(0)
L
3

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;
}
Lamplighter answered 11/12, 2021 at 16:54 Comment(0)
S
3

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.

Semanteme answered 21/6, 2023 at 22:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.