Why doesn't stack walking work properly when using SetUnhandledExceptionFilter?

时光毁灭记忆、已成空白 提交于 2019-12-09 01:57:22

问题


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;
}

回答1:


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);
}



回答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++;
        }
    }
}


来源:https://stackoverflow.com/questions/22487887/why-doesnt-stack-walking-work-properly-when-using-setunhandledexceptionfilter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!