簡體   English   中英

如何使用 CaptureStackBackTrace 來捕獲異常堆棧,而不是調用堆棧?

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

我標記了以下代碼:

#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 上面的代碼是為使用 MSVC++ 的控制台應用程序運行的,在 Windows 8.1 x64 機器上啟用了 unicode,該應用程序在調試模式下作為 Win32 應用程序運行。

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

如果要捕獲代碼引發異常的點的堆棧回溯,則必須在異常對象的ctor中捕獲堆棧回溯並將其存儲在異常對象中。 因此,調用 CaptureStackBackTrace() 的部分應移至異常對象的構造函數,該構造函數還應提供將其作為地址向量或符號向量獲取的方法。 這正是 Java 中的 Throwable 和 C# 中的 Exception 的操作方式。

最后,請不要寫:

throw new exception;

在 C++ 中,就像在 C# 或 Java 中一樣。 這是產生內存泄漏和無法按類型捕獲異常的絕佳方法(因為您正在拋出指向這些類型的指針)。 而是使用:

throw exception();

我知道這是一個老問題,但人們(包括我自己)仍在尋找它。

你想念下面的電話嗎? SymInitialize(進程, NULL, TRUE); SymSetOptions(SYMOPT_LOAD_LINES);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM