简体   繁体   English

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

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

I marked up the following code:我标记了以下代码:

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

What it does is, it logs a stack trace, but the problem is that the stack trace it logs does not give me the line numbers that I want.它的作用是记录堆栈跟踪,但问题是它记录的堆栈跟踪没有给我想要的行号。 I want it to log the line numbers of the places that threw the exception, on and up the call stack, kind of like in C#.我希望它在调用堆栈上记录引发异常的位置的行号,有点像在 C# 中。 But what it actually does right now, is it outputs the following:但它现在实际做的是输出以下内容:

        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.

The problem I am facing, once again, is that line: 68 in this trace corresponds to the line that calls the method printStackTrace();我再次面临的问题是该line: 68对应于调用方法printStackTrace();的行。 , while I would like it to give me line number 45, which corresponds to the line which throws the exception: throw new exception; ,而我希望它给我第 45 行,它对应于抛出异常的行: throw new exception; and then continue further up the stack.然后继续向上堆栈。

How can I achieve this sort of behavior and break into this thread exactly when it throws this exception in order to get a proper stack trace?我怎样才能实现这种行为并在它抛出这个异常时准确地进入这个线程以获得正确的堆栈跟踪?

PS The code above was run for a console application using MSVC++ with unicode enabled on Windows 8.1 x64 machine, with the application being run as a Win32 application in Debug mode. PS 上面的代码是为使用 MSVC++ 的控制台应用程序运行的,在 Windows 8.1 x64 机器上启用了 unicode,该应用程序在调试模式下作为 Win32 应用程序运行。

On Windows, unhandled C++ exception automatically generates SEH exception.在 Windows 上,未处理的 C++ 异常会自动生成 SEH 异常。 SEH __except block allows to attach a filter that accepts _EXCEPTION_POINTERS structure as a parameter, which contains the pointer to the processor's context record in the moment exception was thrown. SEH __except块允许附加一个过滤器,该过滤器接受_EXCEPTION_POINTERS结构作为参数,其中包含在抛出异常时指向处理器上下文记录的指针。 Passing this pointer to StackWalk64 function gives the stack trace in the moment of exception.将此指针传递给StackWalk64函数会在异常时刻提供堆栈跟踪。 So, this problem can be solved by using SEH-style exception handling instead of C++ style.所以,这个问题可以通过使用 SEH 风格的异常处理而不是 C++ 风格来解决。

Example code:示例代码:

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

Example output (first two entries are noise, but the rest correctly reflects functions that caused exception):示例输出(前两个条目是噪音,但其余条目正确反映了导致异常的函数):

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

Another option is to create custom exception class that captures context in constructor and use it (or derived classes) to throw exceptions:另一种选择是创建自定义异常类,在构造函数中捕获上下文并使用它(或派生类)抛出异常:

class MyException{
public:
    CONTEXT Context;

    MyException(){
        RtlCaptureContext(&Context);        
    }
};
    
void function2()
{    
    throw MyException();    
}

//...   

try
{
     function0();
}
catch (MyException& e)
{       
    printf("Exception \n");     
    printStack(&e.Context);                 
}

If you wanted to capture the stack backtrace of the point where the code threw an exception, you must capture the stack backtrace in the ctor of the exception object and store it within the exception object.如果要捕获代码引发异常的点的堆栈回溯,则必须在异常对象的ctor中捕获堆栈回溯并将其存储在异常对象中。 Hence the part calling CaptureStackBackTrace() should be moved to the constructor of the exception object, which should also provide methods to fetch it either as a vector of addresses or as a vector of symbols.因此,调用 CaptureStackBackTrace() 的部分应移至异常对象的构造函数,该构造函数还应提供将其作为地址向量或符号向量获取的方法。 This is exactly how Throwable in Java and Exception in C# operate.这正是 Java 中的 Throwable 和 C# 中的 Exception 的操作方式。

Finally, please do not write:最后,请不要写:

throw new exception;

in C++, as you would in C# or Java.在 C++ 中,就像在 C# 或 Java 中一样。 This is an excellent way to both produce memory leaks and to fail to catch the exceptions by type (as you are throwing pointers to these types).这是产生内存泄漏和无法按类型捕获异常的绝佳方法(因为您正在抛出指向这些类型的指针)。 Rather use:而是使用:

throw exception();

I'm aware that this is an old question but people (including myself) are still finding it.我知道这是一个老问题,但人们(包括我自己)仍在寻找它。

do you miss the call to below?你想念下面的电话吗? SymInitialize(process, NULL, TRUE); SymInitialize(进程, NULL, TRUE); SymSetOptions(SYMOPT_LOAD_LINES); SymSetOptions(SYMOPT_LOAD_LINES);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM