1

我正在使用以下代码在异常上遍历堆栈(注意:您必须在发布中运行它才能正确地将堆栈跟踪的所需输出接收到控制台,而不是在调试模式下,否则它只会显示一个弹出窗口):

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

输出:

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.

问题是,上面的跟踪只包含,它对应于对inline: 135的调用。但是,我希望它包含作为堆栈跟踪的一部分,它在哪里执行. 为什么不将其包含在堆栈跟踪中?我怎样才能让它也包括堆栈跟踪的这一部分?到目前为止,我能够实现此功能的唯一方法是使用并阻止对;的调用。并将 except 传递给 a,但这并不好,因为它有自己的警告,因为它必须在任何地方使用,我想要一个顶级解决方案。我想捕获所有顶级异常,并且我想确切地知道它们被抛出的位置。function0();threadFunctionline: 29throw new exception;__try__except(FatalExceptionFilter(GetExceptionCode(), GetExceptionInformation()))function0()FatalExceptionFilter

PS 此代码在 Windows 8.1、64 位机器下运行。它是一个在 Release 构建/平台 x64 下编译的 MSVC++ 控制台应用程序。

更新:我已经尝试使用 _set_se_translator 方法和 Petr 的建议进行以下操作,但它似乎仍然不想工作。事实上,除以零异常被抛出未处理,并且没有任何处理它:

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

3 回答 3

2

编辑:在与 OP 的作者进行广泛讨论后,我们找到了“意外”行为的解释,即只有最顶层的函数function0()在调用堆栈跟踪中“注册”。事实上,正如评论部分很早就推测的那样,之所以如此,是因为所有其他功能都在发布版本中内联。使用__declspec(noinline)装饰所有函数可确保它们没有被内联。在这种情况下,获得了预期的调用堆栈跟踪... 下面描述的处理 C++/SE 异常的方案仍然有效,尽管它对 OP 作者无法更改生产代码的问题没有帮助,并且必须处理只有未处理的异常。编辑结束。

在评论部分变得太长之前快速回答一下。

  1. 检查此线程以获取堆栈跟踪例程,该例程还允许您从.map文件中获取函数的名称。

  2. 中的函数DbgHelp.dll是单线程的,应该在整个过程中调用一次。这意味着::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);,::SymInitialize(::GetCurrentProcess(), 0, 1);并且::SymCleanup(::GetCurrentProcess());应该分别在 的开头和结尾调用main()

  3. 要跟踪 C++ 异常的调用堆栈,请将堆栈跟踪放在自定义 C++ 异常类构造函数中。这样,当您throw MyException();MyException对象正在构建时,您可以跟踪调用堆栈。

  4. 要在引发 SE 时执行相同的操作(例如除以零),您可以使用_set_se_translator()并创建一个转换函数,该函数抛出一个 C++ 异常类对象并EXCEPTION_POINTERS *传递给它的构造函数。然后您使用EXCEPTION_POINTERS *保存的线程上下文来跟踪调用堆栈。

基本上,您有一个自定义 C++ 异常类,其构造函数看起来像这样(警告:未经测试):

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

所以使用它throw_set_se_translator你使用

throw MyException();

void my_translator(unsigned int, EXCEPTION_POINTERS * _ptr){
 throw MyException(_ptr);
}
于 2014-03-18T19:22:19.613 回答
2

使用最近的 dbghelp.dll(和相应的最近的 MSVC),您还可以找出内联帧的功能/位置。

在之前添加StackWalk()

DWORD frameCount = 0;

将此添加到StackWalk()循环中(但在之前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++;
        }
    }
}
于 2018-04-20T21:55:42.207 回答
0

我偶然发现了这个页面,因为我一直无法从异常中获取正确的文件名/行号,尽管手动创建了产生正确堆栈跟踪的致命错误。事实证明,简单地包括 PDB 就可以确保所有调用堆栈都尽可能准确。

于 2021-02-24T16:49:20.840 回答