1

I am using the following code to walk the stack on an exception (note: you must run it in release in order to properly receive the desired output of the stack trace to the console, not in debug mode or else it will only show a popup):

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

using namespace std;

#define TRACE_MAX_FUNCTION_NAME_LENGTH 1024
#define TRACE_LOG_ERRORS FALSE
#define TRACE_DUMP_NAME L"Exception.dmp"

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)
{
    function0();
}

LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS exception)
{
    CONTEXT context = *(exception->ContextRecord);
    HANDLE thread = GetCurrentThread();
    HANDLE process = GetCurrentProcess();
    STACKFRAME64 frame;
    memset(&frame, 0, sizeof(STACKFRAME64));
    DWORD image;
#ifdef _M_IX86
    image = IMAGE_FILE_MACHINE_I386;
    frame.AddrPC.Offset = context.Eip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Ebp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Esp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
    image = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rbp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    image = IMAGE_FILE_MACHINE_IA64;
    frame.AddrPC.Offset = context.StIIP;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.IntSp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrBStore.Offset = context.RsBSP;
    frame.AddrBStore.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.IntSp;
    frame.AddrStack.Mode = AddrModeFlat;
#else
#error "This platform is not supported."
#endif
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    DWORD displacement;
    SymInitialize(process, NULL, TRUE);
    while (StackWalk(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
    {
        if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
        {
            if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
            }
            else if (TRACE_LOG_ERRORS)
            {
                printf("Error from SymGetLineFromAddr64: %lu.\n", GetLastError());
            }
        }
        else if (TRACE_LOG_ERRORS)
        {
            printf("Error from SymFromAddr: %lu.\n", GetLastError());
        }
    }
    DWORD error = GetLastError();
    if (error && TRACE_LOG_ERRORS)
    {
        printf("Error from StackWalk64: %lu.\n", error);
    }
    HANDLE dumpFile = CreateFile(TRACE_DUMP_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
    exceptionInformation.ThreadId = GetCurrentThreadId();
    exceptionInformation.ExceptionPointers = exception;
    exceptionInformation.ClientPointers = FALSE;
    if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
    {
        printf("Wrote a dump.");
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

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

Output:

Press any key to exit.
        at threadFunction in c:\users\<youruseraccount>\documents\visual studio 2013\project
s\stacktracing\stacktracing\stacktracing.cpp: line: 135: address: 0x498B12D0
Wrote a dump.

The problem is that, the above trace contains only line: 135, which corresponds to the call to the call to function0(); in threadFunction. However, I would like it to include, as part of the stack trace, line: 29, where it does a throw new exception;. Why doesn't it include this as part of the stack trace? How can I make it also include this part of the stack trace? The only way I have been able to achieve this functionality so far is to use __try and __except(FatalExceptionFilter(GetExceptionCode(), GetExceptionInformation())) blocks around the call to function0(); and pass the except to a FatalExceptionFilter, but this is no good because it has its own caveats since it would have to be used everywhere, and I want a top-level solution. I want to catch all top level exceptions, and I want to know exactly where they were thrown.

P.S. This code is being run under a Windows 8.1, 64-bit machine. It is an MSVC++ console application compiled under a Release build/platform x64.

Update: I have tried the following using the _set_se_translator method and Petr's suggestions but it still doesn't seem to want to work. In fact, the divide by zero exception gets thrown unhandled, and nothing handles it:

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

using namespace std;

#define TRACE_MAX_FUNCTION_NAME_LENGTH 1024
#define TRACE_LOG_ERRORS FALSE
#define TRACE_DUMP_NAME L"Exception.dmp"

void function2()
{
    int a = 0;
    int b = 0;
    // The loop below should throw an unhandled exception.
    for (int *i = 0; *i < 100; i++)
    {
        *i = 10000;
    }
}

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

void function0()
{
    function1();
}

void ShowStackTrace(EXCEPTION_POINTERS* exception)
{
    CONTEXT context = *(exception->ContextRecord);
    HANDLE thread = GetCurrentThread();
    HANDLE process = GetCurrentProcess();
    STACKFRAME64 frame;
    memset(&frame, 0, sizeof(STACKFRAME64));
    DWORD image;
#ifdef _M_IX86
    image = IMAGE_FILE_MACHINE_I386;
    frame.AddrPC.Offset = context.Eip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Ebp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Esp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
    image = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rbp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    image = IMAGE_FILE_MACHINE_IA64;
    frame.AddrPC.Offset = context.StIIP;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.IntSp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrBStore.Offset = context.RsBSP;
    frame.AddrBStore.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.IntSp;
    frame.AddrStack.Mode = AddrModeFlat;
#else
#error "This platform is not supported."
#endif
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    DWORD displacement;
    SymInitialize(process, NULL, TRUE);
    while (StackWalk(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
    {
        if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
        {
            if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
            }
            else if (TRACE_LOG_ERRORS)
            {
                printf("Error from SymGetLineFromAddr64: %lu.\n", GetLastError());
            }
        }
        else if (TRACE_LOG_ERRORS)
        {
            printf("Error from SymFromAddr: %lu.\n", GetLastError());
        }
    }
    DWORD error = GetLastError();
    if (error && TRACE_LOG_ERRORS)
    {
        printf("Error from StackWalk64: %lu.\n", error);
    }
    HANDLE dumpFile = CreateFile(TRACE_DUMP_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
    exceptionInformation.ThreadId = GetCurrentThreadId();
    exceptionInformation.ExceptionPointers = exception;
    exceptionInformation.ClientPointers = FALSE;
    if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
    {
        printf("Wrote a dump.");
    }
}

void ShowStackTrace(CONTEXT *aContext)
{
    CONTEXT context = *aContext;
    HANDLE thread = GetCurrentThread();
    HANDLE process = GetCurrentProcess();
    STACKFRAME64 frame;
    memset(&frame, 0, sizeof(STACKFRAME64));
    DWORD image;
#ifdef _M_IX86
    image = IMAGE_FILE_MACHINE_I386;
    frame.AddrPC.Offset = context.Eip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Ebp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Esp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
    image = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rbp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    image = IMAGE_FILE_MACHINE_IA64;
    frame.AddrPC.Offset = context.StIIP;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.IntSp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrBStore.Offset = context.RsBSP;
    frame.AddrBStore.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.IntSp;
    frame.AddrStack.Mode = AddrModeFlat;
#else
#error "This platform is not supported."
#endif
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    DWORD displacement;
    SymInitialize(process, NULL, TRUE);
    while (StackWalk(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
    {
        if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
        {
            if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
            }
            else if (TRACE_LOG_ERRORS)
            {
                printf("Error from SymGetLineFromAddr64: %lu.\n", GetLastError());
            }
        }
        else if (TRACE_LOG_ERRORS)
        {
            printf("Error from SymFromAddr: %lu.\n", GetLastError());
        }
    }
    DWORD error = GetLastError();
    if (error && TRACE_LOG_ERRORS)
    {
        printf("Error from StackWalk64: %lu.\n", error);
    }
    HANDLE dumpFile = CreateFile(TRACE_DUMP_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, NULL, NULL, NULL))
    {
        printf("Wrote a dump.");
    }
}

class CustomException {
public:
    CustomException(EXCEPTION_POINTERS *exception = nullptr) 
    {
        CONTEXT context;
        ZeroMemory(&context, sizeof(CONTEXT));
        if (exception)
        {
            // In case of an SEH exception.
            ShowStackTrace(exception);
        }
        else
        {
            // In case of a C++ exception.
            RtlCaptureContext(&context);
            ShowStackTrace(&context);
        }
    }
};

void SEHExceptionTranslator(unsigned int, EXCEPTION_POINTERS *exception){
    throw CustomException(exception);
}

static void threadFunction(void *param)
{
    _set_se_translator(SEHExceptionTranslator);
    function0();
}

LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS exception)
{
    CONTEXT context = *(exception->ContextRecord);
    HANDLE thread = GetCurrentThread();
    HANDLE process = GetCurrentProcess();
    STACKFRAME64 frame;
    memset(&frame, 0, sizeof(STACKFRAME64));
    DWORD image;
#ifdef _M_IX86
    image = IMAGE_FILE_MACHINE_I386;
    frame.AddrPC.Offset = context.Eip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Ebp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Esp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
    image = IMAGE_FILE_MACHINE_AMD64;
    frame.AddrPC.Offset = context.Rip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.Rbp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.Rsp;
    frame.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
    image = IMAGE_FILE_MACHINE_IA64;
    frame.AddrPC.Offset = context.StIIP;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = context.IntSp;
    frame.AddrFrame.Mode = AddrModeFlat;
    frame.AddrBStore.Offset = context.RsBSP;
    frame.AddrBStore.Mode = AddrModeFlat;
    frame.AddrStack.Offset = context.IntSp;
    frame.AddrStack.Mode = AddrModeFlat;
#else
#error "This platform is not supported."
#endif
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    DWORD displacement;
    while (StackWalk(image, process, thread, &frame, &context, NULL, NULL, NULL, NULL))
    {
        if (SymFromAddr(process, frame.AddrPC.Offset, NULL, symbol))
        {
            if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, line))
            {
                printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
            }
            else if (TRACE_LOG_ERRORS)
            {
                printf("Error from SymGetLineFromAddr64: %lu.\n", GetLastError());
            }
        }
        else if (TRACE_LOG_ERRORS)
        {
            printf("Error from SymFromAddr: %lu.\n", GetLastError());
        }
    }
    DWORD error = GetLastError();
    if (error && TRACE_LOG_ERRORS)
    {
        printf("Error from StackWalk64: %lu.\n", error);
    }
    HANDLE dumpFile = CreateFile(TRACE_DUMP_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
    exceptionInformation.ThreadId = GetCurrentThreadId();
    exceptionInformation.ExceptionPointers = exception;
    exceptionInformation.ClientPointers = FALSE;
    if (MiniDumpWriteDump(process, GetProcessId(process), dumpFile, MiniDumpNormal, exception ? &exceptionInformation : NULL, NULL, NULL))
    {
        printf("Wrote a dump.");
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

int _tmain(int argc, _TCHAR* argv[])
{
    SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
    SymInitialize(GetCurrentProcess(), NULL, TRUE);
    _set_se_translator(SEHExceptionTranslator);
    SetUnhandledExceptionFilter(UnhandledExceptionFilter);
    _beginthread(threadFunction, 0, NULL);
    printf("Press any key to exit.\n");
    cin.get();
    SymCleanup(GetCurrentProcess());
    return 0;
}
Alexandru
  • 12,264
  • 17
  • 113
  • 208
  • 1
    well for one thing you're mixing SEH and C++ exceptions... they are very different and not really interoperable, or designed to be used together. – Mgetz Mar 18 '14 at 18:35
  • 1
    It can be me, but I cannot find declaration/definition of `function0()` in your code... – lapk Mar 18 '14 at 18:38
  • @PetrBudnik I forgot to include it. I edited the question and added it in. – Alexandru Mar 18 '14 at 18:40
  • @Mgetz Are you trying to imply that _set_se_translator can be used to transform the SEH exception into a C++ exception and that it might behave a little differently? http://www.codeproject.com/Articles/207464/Exception-Handling-in-Visual-Cplusplus has an example of this. – Alexandru Mar 18 '14 at 18:41
  • 1
    @Alexandru I'm saying that while C++ exceptions on windows are built on top of SEH (according to all the documentation I've seen), that is an implementation defined detail and may change at *ANY* time. Microsoft is free to change that even between versions. That is one of the many reasons that while you can use SEH in kernel mode you cannot use C++ exceptions (the other main reason is a lack of RTTI). – Mgetz Mar 18 '14 at 18:44
  • 1
    @Alexandru You should not call `SymInitialize()` in `UnhandledExceptionFilter()`. And you should call `SymSetOptions()` before that for deferred loading... After that I would check if none of your little functions get inlined. And try raising SE instead of throwing C++ exception as Mgetz pointed out just to check... Here is StackTracing that I know works, you can check to compare http://stackoverflow.com/questions/9424568/c-stack-tracing-issue/9425637#9425637 – lapk Mar 18 '14 at 18:47
  • @PetrBudnik When should I call SymSetOptions(SYMOPT_DEFERRED_LOADS)? – Alexandru Mar 18 '14 at 18:57
  • 1
    P.S. Typically, you put your StackTracing in C++ exception's class constructor and do tracing when exception is being thrown. You can also use `_set_se_translator()` to convert SE into C++ and do the tracing there, because `_set_se_translator()` gives you saved thread context. – lapk Mar 18 '14 at 18:58
  • @PetrBudnik But SymGetLineFromAddr64 takes in "A handle to the process that was originally passed to the SymInitialize function.": http://msdn.microsoft.com/en-us/library/windows/desktop/ms681330(v=vs.85).aspx – Alexandru Mar 18 '14 at 19:01
  • @PetrBudnik Do you mean to SymInitialize and set the deferred option in main? – Alexandru Mar 18 '14 at 19:02
  • @PetrBudnik GenerateReport seems to work but its too low level for me, since I'm not the best with memory address mapping! I need to see the line numbers that throw these exceptions back at me. – Alexandru Mar 18 '14 at 19:05
  • @PetrBudnik I noticed in your report you make use of RtlCaptureContext, and get the context from there. If I were to try and do this, it shows the following trace: First, it shows the line that calls RtlCaptureContext, and second, it still shows just the call to function0();, which is again the same problem I'm facing :/ – Alexandru Mar 18 '14 at 19:09
  • 1
    Please, see my answer. This is too long for comments' section ;) – lapk Mar 18 '14 at 19:31

3 Answers3

2

EDIT: After an extensive discussion in chat with the OP's author we found the explanation for the "unexpected" behavior when only topmost function function0() gets "registered" in a call-stack trace. Indeed, as was speculated very early on in comments' section, it is so because all other functions get inlined in the release build. Decorating all functions with __declspec(noinline) ensures they are not inlined. In this case, the expected call-stack trace is obtained... The scheme to handle C++/SE exception described below is still valid, albeit it does not help the OP author's problem where he cannot change the production code, and has to deal only with unhandled exceptions. End of EDIT.

Just making a quick answer before the comments' section gets too long.

  1. Check this thread for a stack-tracing routine that also allows you to get functions' names from .map files.

  2. Functions in DbgHelp.dll are single threaded and should be called once for the whole process. That means ::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);, ::SymInitialize(::GetCurrentProcess(), 0, 1); and ::SymCleanup(::GetCurrentProcess()); should be called in the beginning and in the end of main(), respectively.

  3. To trace call stack of C++ exception you put stack tracing in your custom C++ exception class constructor. This way, when you throw MyException(); and MyException object is being constructed you can trace the call stack.

  4. To do the same for when SE is raised (like division by zero), you use _set_se_translator() and make a translator function that throws a C++ exception class object with EXCEPTION_POINTERS * passed to its constructor. Then you use EXCEPTION_POINTERS * to saved thread context to trace the call stack.

Basically, you have a custom C++ exception class with a constructor that looks something this (warning: wasn't tested):

class MyException {
 public:
  MyException(EXCEPTION_POINTERS * _ptr = nullptr) {
   ::CONTEXT context_;
   ::ZeroMemory( &context_, sizeof(::CONTEXT));
   CONTEXT * pcontext_ = &context_;

   if(_ptr) pcontext_ = _ptr->ContextRecord; // in case of SE translator
   else ::RtlCaptureContext(&context_); // in case of 'throw MyException();'

   // Call-stack tracing using pcontext_ here...
  }

  // Other stuff for MyException class...
 };

So to use it with throw and _set_se_translator you use

throw MyException();

and

void my_translator(unsigned int, EXCEPTION_POINTERS * _ptr){
 throw MyException(_ptr);
}
Community
  • 1
  • 1
lapk
  • 3,838
  • 1
  • 23
  • 28
  • Hey, so at what point do you actually call _set_se_translator to set the translation method? Does it have to be in each thread? – Alexandru Mar 18 '14 at 19:33
  • 1
    Yes, you install translator for each thread - translators are maintained on per-thread basis. And you set it as early as possible in each thread. – lapk Mar 18 '14 at 19:36
  • Damn it, then I might as well just use __try/__except no? – Alexandru Mar 18 '14 at 19:41
  • Also, is this strange behavior? I call _set_se_translator(trans_func); in threadFunction, before function0() is called. Eventually my code will throw new exception();, but shouldn't it translate it through the trans_func? I don't see trans_func getting called if I put a break point in it...:/ – Alexandru Mar 18 '14 at 19:44
  • `__try/__except` only handles SE, it does not handle C++ exceptions. Using the way I suggest you deal ONLY with C++ exceptions in your code using `try/catch`... Translator does not get called on C++ exception, it gets called on SE. In your code, try putting `int i_ = 1 / 0;` instead of `throw MyException();` - this will raise SE and your translator will get called if installed. – lapk Mar 18 '14 at 20:28
  • It still doesn't seem to hit. I'll update my question with the code I tried to show you...but I'm trying to get this working. – Alexandru Mar 18 '14 at 22:24
  • I'm trying really hard. People will appreciate it if I get this working for them. I'm working over-overtime to try to implement this into our code. Thanks for the helpso far Petr. If you have any more ideas please let me know. I know, its a lot of code to look at, I wish this problem could be simpler :/ I just want to catch all exceptions and print the stack trace when they come up to show me exactly at what line of code it happens...like how C# does it. – Alexandru Mar 18 '14 at 22:28
  • It appears divide by zero is a CPU trap which is actually not something that will produce an unhandled exception, but something like this will: for (int *i = 0; *i < 100; i++) { *i = 10000; }. Yet, this still does not seem to call the translator...will edit into question. – Alexandru Mar 19 '14 at 01:10
  • Damn it, I know why its not working...I just read the remarks for this method...MSDN says the following: "The translator function that you write is called once for each function invocation on the stack that has try blocks." http://msdn.microsoft.com/en-us/library/5z4bw5h5.aspx – Alexandru Mar 19 '14 at 02:53
  • Also shouldn't SEH exceptions get handled in __try/__except blocks according to @HansPassant's answer here: http://stackoverflow.com/questions/3786647/difference-between-a-c-exception-and-structured-exception "on Windows a C++ exception is also a SEH exception." – Alexandru Mar 19 '14 at 02:59
  • @Alexandru SE will be handled by `__try/__except`, but C++ exception will not be caught in `__try/__except`. Using translator allows you to exactly translate SE into C++ exception so you only need `try/catch` for both SE and C++ exceptions. Your code looks way too bloated. In `MyUnhadledExceptionFilter()` you should just create an object `MyException()` with pointer to structure passed to `MyUnhadledExceptionFilter()` to trace the call stack. This filter will be called ONLY for exceptions that were not caught. – lapk Mar 19 '14 at 08:01
  • @Alexandru Also, are you sure none of you little functions get inlined? Check your compilation flags too (`/EHa`). – lapk Mar 19 '14 at 08:38
  • Petr, I'm not trying to be mean or play the devils advocate, but it seems that __try/__except does in fact catch C++ and SEH exceptions reliably and it is the only way to get this to work reliably, and setting the /EHa flag might be a bad idea because it would force __try/__except blocks to unwind the stack which might mess up my logic to trace the stack. You see, my above code is meant to print a stack trace when an application fails, at a point in time where it guarantees it will in fact fail. – Alexandru Mar 19 '14 at 10:53
  • Petr, check this out: http://stackoverflow.com/questions/3730654/whats-better-to-use-a-try-except-block-or-a-try-catch-block/3730751?noredirect=1#comment34224579_3730751 – Alexandru Mar 19 '14 at 10:56
  • Why would it matter if my functions were inlined by the compiler or not? Are you implying that it won't print the stack location of the inlined function if it was? If so, what flags would disable such optimizations? – Alexandru Mar 19 '14 at 11:02
  • @Alexandru Of course, if function is inlined it does not have it's own call stack - its code is injected in the first function up the call-tree which is not inlined... Maybe you should state your goal more clearly. My approach is to get call-stack trace for ANY exception using C++ exception mechanism. – lapk Mar 19 '14 at 18:59
  • Petr, try your code in a child thread that generates a C++ exception. My gut feeling is that the CRL will partly unwind the stack of the child thread. Check this out (the underlying problem here): http://stackoverflow.com/questions/22510758/how-do-you-prevent-stack-unwinding-from-a-child-thread?noredirect=1#comment34256109_22510758 – Alexandru Mar 19 '14 at 19:06
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/50062/discussion-between-petr-budnik-and-alexandru) – lapk Mar 19 '14 at 19:10
  • Petr, one more thing, you mentioned "it does not help the OP author's problem where he cannot change the production code, and has to deal only with unhandled exceptions." Actually, I can get the compiler to never inline-optimize functions using the C++ setting /Ob0 to disable this, and then...stack tracing would work perfectly. Obviously this is not advisable because it will hinder performance, but for anyone interested, please see: http://msdn.microsoft.com/en-us/library/47238hez.aspx – Alexandru Mar 19 '14 at 22:54
  • 1
    @Alexandru I meant that the scheme to handle C++ exceptions and SE together (described below the **EDIT**), while being valid, does not help you because you cannot introduce custom exception classes in your production code. That's all ;) – lapk Mar 19 '14 at 23:02
  • Gotcha! My bad lol sometimes I read selectively. Hans has called me out on that before haha :) thanks again. You've taught me a lot today. – Alexandru Mar 20 '14 at 00:04
2

With recent dbghelp.dll (and corresponding recent MSVC) you can find out the function/location of inlined frames as well.

Add this before StackWalk():

DWORD frameCount = 0;

Add this into the StackWalk() loop (but before SymFromAddr()):

DWORD64 addr = frame.AddrPC.Offset;
// make sure the location of the calling function is reported, and not of the next statement
if (frameCount != 0 && addr != 0) addr--;
frameCount++;
// number of inlined frames, if any
DWORD inlineTrace = SymAddrIncludeInlineTrace (process, addr);
if (inlineTrace != 0)
{
    DWORD inlineContext, frameIndex;
    // the inline context is needed
    if (SymQueryInlineTrace (process, addr, 0, addr, addr, &inlineContext, &frameIndex))
    {
        for (DWORD i = 0; i < inlineTrace; i++)
        {
            DWORD64 displacement64 = 0;
            // similar to SymFromAddr()
            if (SymFromInlineContext (process, addr, inlineContext, &displacement64, symbol))
            {
                DWORD displacement = 0;
                // similar to SymGetLineFromAddr64()
                if (SymGetLineFromInlineContext (process, addr, inlineContext, 0, &displacement, line))
                {
                    printf("inline: at %s in %s: line: %lu: address: 0x%0X\n",
                            symbol->Name, line->FileName, line->LineNumber, symbol->Address);
                }
            }

            // raise to get inline context of next inlined frame
            inlineContext++;
        }
    }
}
ssbssa
  • 1,261
  • 1
  • 13
  • 21
0

I stumbled on this page because I've been having trouble getting proper filenames/line numbers from exceptions specifically, despite manually created fatal errors producing proper stack traces. It turned out simply including the PDB ensured that all call stacks were as accurate as possible.

Mike Weir
  • 3,094
  • 1
  • 30
  • 46