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

How can you use CaptureStackBackTrace to capture the exception stack, not the calling stack?

本文关键字:堆栈 调用 CaptureStackBackTrace 捕获异常 何使用      更新时间:2023-10-16

我标记了以下代码:

#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%0Xn", 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>documentsvisual studio 2013pr
ojectsstacktracingstacktracingstacktracing.cpp: line: 17: address: 0x10485C0
        at threadFunction in c:users<yourusername>documentsvisual studio 2013pro
jectsstacktracingstacktracingstacktracing.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机器上使用启用unicode的msvc++运行的控制台应用程序,该应用程序作为Win32应用程序在Debug模式下运行。

在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;
    // On x64, StackWalk64 modifies the context record, that could
    // cause crashes, so we create a copy to prevent it
    CONTEXT ctxCopy;
    memcpy(&ctxCopy, ctx, sizeof(CONTEXT));
    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,
            &ctxCopy,
            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%0Xn", 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 %sn",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 ***nn",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:Windowssystem32KERNELBASE.dll
        at CxxThrowException, address 0xDBB5A520.
in C:Windowssystem32MSVCR110D.dll
        at function2 in c:workprojectstesttest.cpp: line: 146: address: 0x3F9C6C00
        at function1 in c:workprojectstesttest.cpp: line: 153: address: 0x3F9C6CB0
        at function0 in c:workprojectstesttest.cpp: line: 158: address: 0x3F9C6CE0
        at threadFunction in c:workprojectstesttest.cpp: line: 174: address: 0x3F9C6D70
        at beginthread, address 0xDBA66C60.
in C:Windowssystem32MSVCR110D.dll
        at endthread, address 0xDBA66E90.
in C:Windowssystem32MSVCR110D.dll
        at BaseThreadInitThunk, address 0x773C6520.
in C:Windowssystem32kernel32.dll
        at RtlUserThreadStart, address 0x775FC520.
in C:WindowsSYSTEM32ntdll.dll

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

class MyException{
public:
    CONTEXT Context;
    MyException(){
        RtlCaptureContext(&Context);        
    }
};
    
void function2()
{    
    throw MyException();    
}
//...   
try
{
     function0();
}
catch (MyException& e)
{       
    printf("Exception n");     
    printStack(&e.Context);                 
}

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

最后,请不要写:

throw new exception;
在c++中

,就像在c#或Java中一样。这是一种极好的方法,既会产生内存泄漏,又无法按类型捕获异常(因为您正在抛出指向这些类型的指针)。而使用:

throw exception();

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

你想念下面的电话吗?syminialize (process, NULL, TRUE);SymSetOptions (SYMOPT_LOAD_LINES);