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;
}
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.
Check this thread for a stack-tracing routine that also allows you to get functions' names from .map
files.
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.
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.
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);
}