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.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    Try this article: http://www.codeproject.com/Articles/11132/Walking-the-callstack – PaulMcKenzie Nov 06 '14 at 22:02
  • 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. – brian beuning Nov 07 '14 at 01:44
  • 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. – brian beuning Nov 07 '14 at 01:46
  • 1
    @PaulMcKenzie 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. – M.M Nov 09 '14 at 11:29
  • 1
    @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 Express – M.M Nov 09 '14 at 11:31
  • @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. – brian beuning Nov 09 '14 at 19:22
  • 1
    Also the "Walking the callstack" article was migrated to https://github.com/JochenKalmbach/StackWalker – yeputons Jun 29 '23 at 08:59

2 Answers2

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;
}
Fedor
  • 17,146
  • 13
  • 40
  • 131
2

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.

yeputons
  • 8,478
  • 34
  • 67