如何使用 CaptureStackBackTrace 捕获异常堆栈,而不是调用堆栈?

Posted

技术标签:

【中文标题】如何使用 CaptureStackBackTrace 捕获异常堆栈,而不是调用堆栈?【英文标题】:How can you use CaptureStackBackTrace to capture the exception stack, not the calling stack? 【发布时间】:2014-04-23 10:33:08 【问题描述】:

我标记了以下代码:

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

它的作用是记录一个堆栈跟踪,但问题是它记录的堆栈跟踪没有给我我想要的行号。我希望它在调用堆栈上记录引发异常的位置的行号,有点像在 C# 中。但它现在实际做的是输出以下内容:

        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.

我再次面临的问题是,此跟踪中的 line: 68 对应于调用方法 printStackTrace(); 的行,而我希望它给我第 45 行,它对应于抛出异常:throw new exception;,然后继续向上堆栈。

我怎样才能实现这种行为并在它抛出这个异常时准确地进入这个线程以获得正确的堆栈跟踪?

PS 上面的代码是在 Windows 8.1 x64 机器上使用 MSVC++ 并启用 unicode 的控制台应用程序运行的,该应用程序在调试模式下作为 Win32 应用程序运行。

【问题讨论】:

您当然需要跳过作为日志记录代码一部分的堆栈帧。简单地计算它们,__declspec(noinline) 是可取的。 @HansPassant 但是它只会跳过 printStackTrace 和 threadFunction...让我使用 beginthread,我猜它无法从子线程访问...看到我的困境了吗?我的意思是,为了澄清,您暗示在对 CaptureStackBackTrace(0, TRACE_MAX_STACK_FRAMES, stack, NULL); 的调用中传递跳过的帧数量,例如 CaptureStackBackTrace(2, TRACE_MAX_STACK_FRAMES, stack, NULL);正确的?它仍然不是我所追求的:P @HansPassant 这样说来,我希望我的堆栈跟踪包括函数 2、函数 1 和函数 0。尤其是 function2,以及引发异常的行。 当你使用 catch(...) 时这是不可能的,堆栈已经展开并且异常被解除。您必须使用 SetUnhandledExceptionFilter() 来捕获未处理的异常。 @HansPassant 一定有办法,因为即使通过设置向量异常处理程序,它仍然不会给我确切的行。我为一家在生产中没有堆栈跟踪 (LOL) 的公司工作,我需要添加它(显然人们懒得自己做,所以所有的工作都交给了我)。所以我正在寻找一种方法来创建我自己的堆栈跟踪库。一些小东西,只是捕获所有异常并抛出堆栈跟踪。你有什么建议吗?而且......为什么在上帝的名义下用 C++ 做这件事这么难?! 【参考方案1】:

在 Windows 上,未处理的 C++ 异常会自动生成 SEH 异常。 SEH __except 块允许附加一个过滤器,该过滤器接受 _EXCEPTION_POINTERS 结构作为参数,其中包含在抛出异常时指向处理器上下文记录的指针。将此指针传递给 StackWalk64 函数会在异常时刻提供堆栈跟踪。所以,这个问题可以通过使用 SEH 风格的异常处理而不是 C++ 风格来解决。

示例代码:

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

示例输出(前两个条目是噪音,但其余的正确反映了导致异常的函数):

*** 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

另一种选择是创建自定义异常类,在构造函数中捕获上下文并使用它(或派生类)抛出异常:

class MyException
public:
    CONTEXT Context;

    MyException()
        RtlCaptureContext(&Context);        
    
;

void function2()
    
    throw MyException();    


//...   

try

     function0();

catch (MyException& e)
       
    printf("Exception \n");     
    printStack(&e.Context);                 

【讨论】:

确实有效,尽管它会打印在当前函数返回后执行的下一行的行号(例如,如果调用在第 10 行,将打印下一行代码可能是 15) 还有一次 *.pdb 不是构建程序的地方,我们遇到了一些问题,请参阅***.com/a/49914102/8740349【参考方案2】:

如果要捕获代码抛出异常的点的堆栈回溯,则必须在异常对象的ctor中捕获堆栈回溯并将其存储在异常对象中。因此,调用 CaptureStackBackTrace() 的部分应移至异常对象的构造函数,该构造函数还应提供将其作为地址向量或符号向量获取的方法。这正是 Java 中的 Throwable 和 C# 中的 Exception 的操作方式。

最后,请不要写:

throw new exception;

在 C++ 中,就像在 C# 或 Java 中一样。这是产生内存泄漏和无法按类型捕获异常的绝佳方法(因为您正在抛出指向这些类型的指针)。而是使用:

throw exception();

我知道这是一个老问题,但人们(包括我自己)仍在寻找它。

【讨论】:

【参考方案3】:

你错过了下面的电话吗? SymInitialize(进程, NULL, TRUE); SymSetOptions(SYMOPT_LOAD_LINES);

【讨论】:

以上是关于如何使用 CaptureStackBackTrace 捕获异常堆栈,而不是调用堆栈?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用本机反应创建登录以及如何验证会话

如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]

如何使用 AngularJS 的 ng-model 创建一个数组以及如何使用 jquery 提交?

如何使用laravel保存所有行数据每个行名或相等

如何使用 Math.Net 连接矩阵。如何使用 Math.Net 调用特定的行或列?

WSARecv 如何使用 lpOverlapped?如何手动发出事件信号?