How can you use CaptureStackBackTrace to capture the exception stack, not the calling stack?

16,649

Solution 1

On Windows, unhandled C++ exception automatically generates SEH exception. SEH __except block allows to attach a filter that accepts _EXCEPTION_POINTERS structure as a parameter, which contains the pointer to the processor's context record in the moment exception was thrown. Passing this pointer to StackWalk64 function gives the stack trace in the moment of exception. So, this problem can be solved by using SEH-style exception handling instead of C++ style.

Example code:

#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include <tchar.h>

#include <process.h>
#include <iostream>
#include <Windows.h>
#include "dbghelp.h"

using namespace std;

const int MaxNameLen = 256;

#pragma comment(lib,"Dbghelp.lib")

void printStack( CONTEXT* ctx ) //Prints stack trace based on context record
{
    BOOL    result;
    HANDLE  process;
    HANDLE  thread;
    HMODULE hModule;

    STACKFRAME64        stack;
    ULONG               frame;    
    DWORD64             displacement;

    DWORD disp;
    IMAGEHLP_LINE64 *line;

    char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
    char name[MaxNameLen];
    char module[MaxNameLen];
    PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;

    memset( &stack, 0, sizeof( STACKFRAME64 ) );

    process                = GetCurrentProcess();
    thread                 = GetCurrentThread();
    displacement           = 0;
#if !defined(_M_AMD64)
    stack.AddrPC.Offset    = (*ctx).Eip;
    stack.AddrPC.Mode      = AddrModeFlat;
    stack.AddrStack.Offset = (*ctx).Esp;
    stack.AddrStack.Mode   = AddrModeFlat;
    stack.AddrFrame.Offset = (*ctx).Ebp;
    stack.AddrFrame.Mode   = AddrModeFlat;
#endif

    SymInitialize( process, NULL, TRUE ); //load symbols

    for( frame = 0; ; frame++ )
    {
        //get next call from stack
        result = StackWalk64
        (
#if defined(_M_AMD64)
            IMAGE_FILE_MACHINE_AMD64
#else
            IMAGE_FILE_MACHINE_I386
#endif
            ,
            process,
            thread,
            &stack,
            ctx,
            NULL,
            SymFunctionTableAccess64,
            SymGetModuleBase64,
            NULL
        );

        if( !result ) break;        

        //get symbol name for address
        pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        pSymbol->MaxNameLen = MAX_SYM_NAME;
        SymFromAddr(process, ( ULONG64 )stack.AddrPC.Offset, &displacement, pSymbol);

        line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
        line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);       

        //try to get line
        if (SymGetLineFromAddr64(process, stack.AddrPC.Offset, &disp, line))
        {
            printf("\tat %s in %s: line: %lu: address: 0x%0X\n", pSymbol->Name, line->FileName, line->LineNumber, pSymbol->Address);
        }
        else
        { 
            //failed to get line
            printf("\tat %s, address 0x%0X.\n", pSymbol->Name, pSymbol->Address);
            hModule = NULL;
            lstrcpyA(module,"");        
            GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 
                (LPCTSTR)(stack.AddrPC.Offset), &hModule);

            //at least print module name
            if(hModule != NULL)GetModuleFileNameA(hModule,module,MaxNameLen);       

            printf ("in %s\n",module);
        }       

        free(line);
        line = NULL;
    }
}

//******************************************************************************

void function2()
{
    int a = 0;
    int b = 0;
    throw exception();
}

void function1()
{
    int a = 0;
    function2();
}

void function0()
{
    function1();
}

int seh_filter(_EXCEPTION_POINTERS* ex)
{
    printf("*** Exception 0x%x occured ***\n\n",ex->ExceptionRecord->ExceptionCode);    
    printStack(ex->ContextRecord);

    return EXCEPTION_EXECUTE_HANDLER;
}

static void threadFunction(void *param)
{    

    __try
    {
         function0();
    }
    __except(seh_filter(GetExceptionInformation()))
    {       
        printf("Exception \n");         
    }
}

int _tmain(int argc, _TCHAR* argv[])
{   
    _beginthread(threadFunction, 0, NULL);
    printf("Press any key to exit.\n");
    cin.get();
    return 0;
}

Example output (first two entries are noise, but the rest correctly reflects functions that caused exception):

*** Exception 0xe06d7363 occured ***

        at RaiseException, address 0xFD3F9E20.
in C:\Windows\system32\KERNELBASE.dll
        at CxxThrowException, address 0xDBB5A520.
in C:\Windows\system32\MSVCR110D.dll
        at function2 in c:\work\projects\test\test.cpp: line: 146: address: 0x3F9C6C00
        at function1 in c:\work\projects\test\test.cpp: line: 153: address: 0x3F9C6CB0
        at function0 in c:\work\projects\test\test.cpp: line: 158: address: 0x3F9C6CE0
        at threadFunction in c:\work\projects\test\test.cpp: line: 174: address: 0x3F9C6D70
        at beginthread, address 0xDBA66C60.
in C:\Windows\system32\MSVCR110D.dll
        at endthread, address 0xDBA66E90.
in C:\Windows\system32\MSVCR110D.dll
        at BaseThreadInitThunk, address 0x773C6520.
in C:\Windows\system32\kernel32.dll
        at RtlUserThreadStart, address 0x775FC520.
in C:\Windows\SYSTEM32\ntdll.dll

Another option is to create custom exception class that captures context in constructor and use it (or derived classes) to throw exceptions:

class MyException{
public:
    CONTEXT Context;

    MyException(){
        RtlCaptureContext(&Context);        
    }
};

void function2()
{    
    throw MyException();    
}

//...   

try
{
     function0();
}
catch (MyException& e)
{       
    printf("Exception \n");     
    printStack(&e.Context);                 
}

Solution 2

If you wanted to capture the stack backtrace of the point where the code threw an exception, you must capture the stack backtrace in the ctor of the exception object and store it within the exception object. Hence the part calling CaptureStackBackTrace() should be moved to the constructor of the exception object, which should also provide methods to fetch it either as a vector of addresses or as a vector of symbols. This is exactly how Throwable in Java and Exception in C# operate.

Finally, please do not write:

throw new exception;

in C++, as you would in C# or Java. This is an excellent way to both produce memory leaks and to fail to catch the exceptions by type (as you are throwing pointers to these types). Rather use:

throw exception();

I'm aware that this is an old question but people (including myself) are still finding it.

Share:
16,649

Related videos on Youtube

Alexandru
Author by

Alexandru

"To avoid criticism, say nothing, do nothing, be nothing." - Aristotle "It is wise to direct your anger towards problems - not people; to focus your energies on answers - not excuses." - William Arthur Ward "Science does not know its debt to imagination." - Ralph Waldo Emerson "Money was never a big motivation for me, except as a way to keep score. The real excitement is playing the game." - Donald Trump "All our dreams can come true, if we have the courage to pursue them." - Walt Disney "Mitch flashes back to a basketball game held in the Brandeis University gymnasium in 1979. The team is doing well and chants, 'We're number one!' Morrie stands and shouts, 'What's wrong with being number two?' The students fall silent." - Tuesdays with Morrie

Updated on June 05, 2022

Comments

  • Alexandru
    Alexandru almost 2 years

    I marked up the following code:

    #include "stdafx.h"
    #include <process.h>
    #include <iostream>
    #include <Windows.h>
    #include <dbghelp.h>
    
    using namespace std;
    
    #define TRACE_MAX_STACK_FRAMES 1024
    #define TRACE_MAX_FUNCTION_NAME_LENGTH 1024
    
    int printStackTrace()
    {
        void *stack[TRACE_MAX_STACK_FRAMES];
        HANDLE process = GetCurrentProcess();
        SymInitialize(process, NULL, TRUE);
        WORD numberOfFrames = CaptureStackBackTrace(0, TRACE_MAX_STACK_FRAMES, stack, NULL);
        char buf[sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR)];
        SYMBOL_INFO* symbol = (SYMBOL_INFO*)buf;
        symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
        symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        DWORD displacement;
        IMAGEHLP_LINE64 line;
        line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
        for (int i = 0; i < numberOfFrames; i++)
        {
            DWORD64 address = (DWORD64)(stack[i]);
            SymFromAddr(process, address, NULL, symbol);
            if (SymGetLineFromAddr64(process, address, &displacement, &line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line.FileName, line.LineNumber, symbol->Address);
            }
            else
            {
                printf("\tSymGetLineFromAddr64 returned error code %lu.\n", GetLastError());
                printf("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address);
            }
        }
        return 0;
    }
    
    void function2()
    {
        int a = 0;
        int b = 0;
        throw new exception;
    }
    
    void function1()
    {
        int a = 0;
        function2();
    }
    
    void function0()
    {
        function1();
    }
    
    static void threadFunction(void *param)
    {
        try
        {
            function0();
        }
        catch (...)
        {
            printStackTrace();
        }
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        _beginthread(threadFunction, 0, NULL);
        printf("Press any key to exit.\n");
        cin.get();
        return 0;
    }
    

    What it does is, it logs a stack trace, but the problem is that the stack trace it logs does not give me the line numbers that I want. I want it to log the line numbers of the places that threw the exception, on and up the call stack, kind of like in C#. But what it actually does right now, is it outputs the following:

            at printStackTrace in c:\users\<yourusername>\documents\visual studio 2013\pr
    ojects\stacktracing\stacktracing\stacktracing.cpp: line: 17: address: 0x10485C0
            at threadFunction in c:\users\<yourusername>\documents\visual studio 2013\pro
    jects\stacktracing\stacktracing\stacktracing.cpp: line: 68: address: 0x10457C0
            SymGetLineFromAddr64 returned error code 487.
            at beginthread, address 0xF9431E0.
            SymGetLineFromAddr64 returned error code 487.
            at endthread, address 0xF9433E0.
            SymGetLineFromAddr64 returned error code 487.
            at BaseThreadInitThunk, address 0x7590494F.
            SymGetLineFromAddr64 returned error code 487.
            at RtlInitializeExceptionChain, address 0x7713986A.
            SymGetLineFromAddr64 returned error code 487.
            at RtlInitializeExceptionChain, address 0x7713986A.
    

    The problem I am facing, once again, is that line: 68 in this trace corresponds to the line that calls the method printStackTrace();, while I would like it to give me line number 45, which corresponds to the line which throws the exception: throw new exception; and then continue further up the stack.

    How can I achieve this sort of behavior and break into this thread exactly when it throws this exception in order to get a proper stack trace?

    PS The code above was run for a console application using MSVC++ with unicode enabled on Windows 8.1 x64 machine, with the application being run as a Win32 application in Debug mode.

    • user1703401
      user1703401 about 10 years
      You of course need to skip the stack frames that are part of your logging code. Simply count them off, __declspec(noinline) is advisable.
    • Alexandru
      Alexandru about 10 years
      @HansPassant But then it would just skip printStackTrace and threadFunction...leaving me with beginthread, which, I guess it doesn't have access to from the child thread...see my dilemma? I mean, just to clarify, you are implying passing in a skipped frame amount in the call to CaptureStackBackTrace(0, TRACE_MAX_STACK_FRAMES, stack, NULL);, such as CaptureStackBackTrace(2, TRACE_MAX_STACK_FRAMES, stack, NULL); right? Its still not what I'm after :P
    • Alexandru
      Alexandru about 10 years
      @HansPassant Put it this way, I want my stack trace to include function2, function1, and function0. Especially function2 though, and the line at which the exception is thrown at.
    • user1703401
      user1703401 about 10 years
      That's not possible when you use catch(...), the stack is already unwound and the exception dismissed. You must use SetUnhandledExceptionFilter() to trap the unhandled exception.
    • Alexandru
      Alexandru about 10 years
      @HansPassant There must be a way, because even by setting a vectored exception handler, it still won't give me the exact line. I work for a company that has no stack tracing in production (LOL), and I need to add it in (obviously people are too lazy to do it themselves so all the work gets pawned off to me). So I am looking for a way to create my own stack tracing library. Something small, to just catch all exceptions and throw up a stack trace. What do you recommend? And...why in Gods name is it so hard to do this in C++?!
    • Alexandru
      Alexandru about 10 years
      PS I don't wanna use StackWalker. I didn't write it and as far as I can tell this is almost the same in terms of the functionality I want, the only thing is that I need to find a way to break into a thread when an exception is thrown and get the trace from there; not after the exception, and not in any exception handler / consumer because that doesn't seem to work.
    • Alexandru
      Alexandru about 10 years
    • Alexandru
      Alexandru about 10 years
      I might have to use __try and __except and get the trace from the context of that...like stack walker does with StackWalk64...I really didn't want to go about it like this since __try and __except won't work if wrapped around other try-catch blocks...
    • Alexandru
      Alexandru about 10 years
      @HansPassant Spinoff, more problems...such a headache. stackoverflow.com/questions/22481126/…
    • Alexandru
      Alexandru about 10 years
      @HansPassant In the end, I had to decorate all functions with __declspec(noinline). It worked, and I got a new-found understanding of inline methods. I think when I tried initially, I did not decorate all my methods with the __declspec(noinline) directive.
    • Alexandru
      Alexandru about 10 years
      I'm sorry for not applying your concepts fully, @HansPassant. You're pretty damn good at what you do.
  • Top-Master
    Top-Master over 5 years
    Does work, though it prints line-number of next line that gets executed after current function does return (e.g. if call is on line 10 will print next line with code which could be 15)
  • Top-Master
    Top-Master over 5 years
    also once *.pdb is not where the program was build we got some problems see stackoverflow.com/a/49914102/8740349