Windows 解析托管堆栈跟踪和本机堆栈跟踪-使用哪种API?

Windows 解析托管堆栈跟踪和本机堆栈跟踪-使用哪种API?,windows,stack-trace,mixed-mode,Windows,Stack Trace,Mixed Mode,这是我上一个问题的继续——可以说是第二阶段 第一个问题是: 现在我已经解析了大量堆栈跟踪,现在想知道如何解析托管堆栈帧的符号信息 另外还有一个问题,就是以后解析堆栈跟踪可能不起作用。您可以看到—开发人员可以使用Jit引擎/IL生成器动态生成代码,并对其进行处理—因此,在获得“void*”/指令地址后,您应该立即解析符号信息,而不是事后解析。但我暂时不讨论这个问题,假设开发人员不是一个太花哨的编码员,不会一直生成和处理新代码,并且不会无需调用FreeLibrary。(如果稍后我将钩住FreeLi

这是我上一个问题的继续——可以说是第二阶段

第一个问题是:

现在我已经解析了大量堆栈跟踪,现在想知道如何解析托管堆栈帧的符号信息

<本地C++侧比较简单——

首先,指定从何处获取符号的进程:

HANDLE g_hProcess = GetCurrentProcess();
您可以在运行时使用如下代码替换流程:

g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_processId);

b = (g_hProcess != NULL );

if( !b )
    errInfo.AppendFormat(_T("Process id '%08X' is not running anymore."), g_processId );
else
    InitSymbolLoad();
extern HANDLE g_hProcess;

void StackFrame::Resolve()
{
    struct {
        union
        {
            SYMBOL_INFO symbol;
            char buf[sizeof(SYMBOL_INFO) + 1024];
        }u;
    }ImageSymbol = { 0 };

    HANDLE hProcess = g_hProcess;
    DWORD64 offsetFromSymbol = 0;

    ImageSymbol.u.symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
    ImageSymbol.u.symbol.Name[0] = 0;
    ImageSymbol.u.symbol.MaxNameLen = sizeof(ImageSymbol) - sizeof(SYMBOL_INFO);
    SYMBOL_INFO* pSymInfo = &ImageSymbol.u.symbol;

    // Get file / line of source code.
    IMAGEHLP_LINE64 lineStr = { 0 };
    lineStr.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

    function.clear();


    if( SymGetLineFromAddr64(hProcess, (DWORD64)ip, (DWORD*)&offsetFromSymbol, &lineStr) )
    {
        function = lineStr.FileName;
        function += "(";
        function += std::to_string((_ULonglong) lineStr.LineNumber).c_str();
        function += "): ";
    }

    // Successor of SymGetSymFromAddr64.
    if( SymFromAddr(hProcess, (DWORD64)ip, &offsetFromSymbol, pSymInfo) )
        function += ImageSymbol.u.symbol.Name;

}
并初始化符号加载:

void InitSymbolLoad()
{
    SymInitialize(g_hProcess, NULL, TRUE);
    DWORD dwFlags = SymGetOptions();
    SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_NO_IMAGE_SEARCH);
}
然后,解析本机符号,不知何故如下:

g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_processId);

b = (g_hProcess != NULL );

if( !b )
    errInfo.AppendFormat(_T("Process id '%08X' is not running anymore."), g_processId );
else
    InitSymbolLoad();
extern HANDLE g_hProcess;

void StackFrame::Resolve()
{
    struct {
        union
        {
            SYMBOL_INFO symbol;
            char buf[sizeof(SYMBOL_INFO) + 1024];
        }u;
    }ImageSymbol = { 0 };

    HANDLE hProcess = g_hProcess;
    DWORD64 offsetFromSymbol = 0;

    ImageSymbol.u.symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
    ImageSymbol.u.symbol.Name[0] = 0;
    ImageSymbol.u.symbol.MaxNameLen = sizeof(ImageSymbol) - sizeof(SYMBOL_INFO);
    SYMBOL_INFO* pSymInfo = &ImageSymbol.u.symbol;

    // Get file / line of source code.
    IMAGEHLP_LINE64 lineStr = { 0 };
    lineStr.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

    function.clear();


    if( SymGetLineFromAddr64(hProcess, (DWORD64)ip, (DWORD*)&offsetFromSymbol, &lineStr) )
    {
        function = lineStr.FileName;
        function += "(";
        function += std::to_string((_ULonglong) lineStr.LineNumber).c_str();
        function += "): ";
    }

    // Successor of SymGetSymFromAddr64.
    if( SymFromAddr(hProcess, (DWORD64)ip, &offsetFromSymbol, pSymInfo) )
        function += ImageSymbol.u.symbol.Name;

}
这看起来很有效

但现在也管理堆栈帧

我找到了两个接口:

  • IDebugClient/GetNameByOffset
  • 提及:

    • (*)(包括示例代码)
    使用人:

    • (4年内未触及代码)
    • 混合模式stackwalk文章提供了一个很好的例子

    • IXCLRDATAProcess/GetRuntimeNameByAddress
    • 上面的两个链接也提到了

    • 由process hacker使用(GPL许可证,C风格)
    实现似乎就在这里:

    • (基于提交,此代码非常活跃)

    • ICorProfiler/
    在(*)条末尾提到

    方法1似乎很过时,文章(*)也提到了一些相关问题

    方法3可能需要深入分析评测API。 我还发现了一个关于这些API的例子——在这里:

    ·cor.h,cordebug.h/idl,CorError.h,CorHdr.h,corhlpr.h, corprof.h/idl、corpub.h/idl和corsym.h/idl:所有这些头文件 已被删除。它们都是.NET的本机模式COM接口

    这句话我不太懂。这些接口是死了还是被替换了,或者发生了什么


    因此,根据我的简要分析,我猜方法2是唯一值得使用的好的/活跃的API接口?您是否遇到过与这些api相关的任何问题。

    以下是Jan Kotas对此的回答:

    From: Jan Kotas <jkotas@microsoft.com>
    To: Tarmo Pikaro <tapika@yahoo.com> 
    Sent: Tuesday, January 12, 2016 5:09 AM
    Subject: RE: Fast capture stack trace on windows 64 bit / mixed mode...
    
    Your solution based on IXCLRDATAProcess sounds good to me.
    
    PerfView (https://www.microsoft.com/en-us/download/details.aspx?id=28567) – 
    that does what you are trying to build as well as a lot of other stuff – is 
    using IXCLRDATA* as well. You may be interested in 
    https://github.com/Microsoft/clrmd . It is set of managed wrappers for 
    IXCLRDATA* that are easier to use than the COM interfaces.
    
    发件人:Jan Kotas
    收件人:Tarmo Pikaro
    发送时间:2016年1月12日星期二上午5:09
    主题:RE:windows 64位/混合模式下的快速捕获堆栈跟踪。。。
    我觉得你基于IXCLRDATAProcess的解决方案不错。
    性能视图(https://www.microsoft.com/en-us/download/details.aspx?id=28567) – 
    这就是你想要构建的东西,以及很多其他东西——是的
    还可以使用IXCLRDATA*。你可能对我感兴趣
    https://github.com/Microsoft/clrmd . 它是一组用于
    IXCLRDATA*比COM接口更易于使用。
    
    我简要试用过的内容—这需要Visual Studio 2015/C#6.0


    而且这种技术是无法使用的。像.net StackTrace/StackFrame一样,我们正在立即解析调用堆栈和符号信息,之后我需要解析符号信息(在捕获堆栈跟踪之后)。

    备选方案1/IDebugClient/GetNameByOffset不可用于托管堆栈跟踪,它只能用于本机代码-对于本机调用堆栈,我已经有了上面的演示代码snipet。不确定IDebugClient是否提供了SymGetLineFromAddr64/SymFromAddr没有提供的功能-不确定。

    在浏览了大量的代码示例和接口之后,我了解到没有任何简单易用的API接口。为本地C++开发的代码和API仅使用本机C++,而开发用于托管代码的代码和API仅使用托管代码。p> 另外还有一个问题,就是以后解析堆栈跟踪可能不起作用。您可以看到—开发人员可以使用Jit引擎/IL生成器动态生成代码,并对其进行处理—因此,在获得“void*”/指令地址后,您应该立即解析符号信息,而不是事后解析。但我暂时不讨论这个问题,假设开发人员不是一个太花哨的编码员,不会一直生成和处理新代码,并且不会无需调用FreeLibrary。(如果稍后我将钩住FreeLibrary/Jit组件,可能我可以解决这个问题。)

    解析函数名非常简单,通过IXCLRDataProcess使用一点魔法和运气-我能够得到函数名,但是-我想把它扩展得更深-精确到执行代码的源代码路径和源代码行,这变成了非常复杂的功能

    最后,我偶然发现了执行这类操作的源代码——它是在这里完成的:

    GetLineByOffset是该文件中的函数名

    我已经从源代码中分析、重新调整并制定了自己的解决方案,现在将其附在这里:

    更新的代码可从以下位置找到:

    但这里只是在某个时间点拍摄的相同代码的快照:

    第M.h节:

    #pragma once
    #include <afx.h>
    #pragma warning (disable: 4091)     //dbghelp.h(1544): warning C4091: 'typedef ': ignored on left of '' when no variable is declared
    #include <cor.h>                    //xclrdata.h requires this
    #include "xclrdata.h"               //IXCLRDataProcess
    #include <atlbase.h>                //CComPtr
    #include <afxstr.h>                 //CString
    #include <crosscomp.h>              //TCONTEXT
    #include <Dbgeng.h>                 //IDebugClient
    #pragma warning (default: 4091)
    
    class ResoveStackM
    {
    public:
        ResoveStackM();
        ~ResoveStackM();
        void Close(void);
    
        bool InitSymbolResolver(HANDLE hProcess, CString& lastError);
        bool GetMethodName(void* ip, CStringA& methodName);
        bool GetManagedFileLineInfo(void* ip, CStringA& lineInfo);
    
        HMODULE mscordacwks_dll;
        CComPtr<IXCLRDataProcess> clrDataProcess;
        CComPtr<ICLRDataTarget> target;
    
        CComPtr<IDebugClient>       debugClient;
        CComQIPtr<IDebugControl>    debugControl;
        CComQIPtr<IDebugSymbols>    debugSymbols;
        CComQIPtr<IDebugSymbols3>   debugSymbols3;
    };
    
    //
    // Typically applications don't need more than one instance of this. If you do, use your own copies.
    //
    extern ResoveStackM g_managedStackResolver;
    
    #pragma一次
    #包括
    #pragma warning(disable:4091)//dbghelp.h(1544):警告C4091:'typedef':当未声明变量时,忽略“”左侧的
    #include//xclrdata.h需要此
    #包括“xclrdata.h”//IXCLRDataProcess
    #包括//CComPtr
    #包括//CString
    #include//t上下文
    #包括//IDebugClient
    #pragma警告(默认值:4091)
    类ResoveStackM
    {
    公众:
    ResoveStackM();
    ~ResoveStackM();
    作废关闭(作废);
    bool InitSymbolResolver(句柄hProcess、CString和lastError);
    bool GetMethodName(void*ip、CStringA和methodName);
    bool getmanagedFileInfo(void*ip、CStringA和lineInfo);
    HMODULE mscordacwks_dll;
    CComPtr clrDataProcess;
    CComPtr目标;
    CComPtr调试客户端;
    CComQIPtr调试控制;
    CComQIPtr调试符号;
    CComQIPtr调试符号3;
    };
    //
    //通常,应用程序不需要多个实例。如果有,请使用您自己的副本。
    //
    管理的外部资源