解析托管和本机堆栈跟踪——使用哪个API ?

时间:2022-09-01 18:36:21

This is continuation to my previous question - phase 2 so to say.

这是延续到我之前的问题-第二阶段。

First question was here: Fast capture stack trace on windows / 64-bit / mixed mode

第一个问题是:windows / 64位/混合模式上的快速捕获堆栈跟踪

Now I have resolved a huge amount of stack traces and now wondering how to resolve symbol information of managed stack frames.

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

For native C++ side it's relatively simple -

对于本地c++来说,它是相对简单的-

First you specify which process from where to take symbols:

首先指定从何处取符号的过程:

HANDLE g_hProcess = GetCurrentProcess();

Where you can replace process in run-time using code snipet like this:

您可以在运行时使用代码snipet替换流程,如下所示:

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();

And initialize symbol loading:

并初始化加载符号:

void InitSymbolLoad()
{
    SymInitialize(g_hProcess, NULL, TRUE);
    DWORD dwFlags = SymGetOptions();
    SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_NO_IMAGE_SEARCH);
}

And after that resolve native symbol , somehow like this:

在解析了本机符号之后,就像这样:

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;

}

This looks like working.

这看起来像工作。

But now also managed stack frames.

但是现在还管理堆栈帧。

There are two interfaces which I've located:

我有两个接口:

  1. IDebugClient / GetNameByOffset
  2. IDebugClient / GetNameByOffset

Mentioned in:

中提到:

Used by:

使用:

  • https://github.com/okigan/CrashInsight (Code not touched for 4 years)
  • https://github.com/okigan/CrashInsight(4年没碰过的代码)
  • Mixed mode stackwalk article provides good example.

    混合模式stackwalk文章提供了很好的例子。

    1. IXCLRDATAProcess / GetRuntimeNameByAddress
    2. IXCLRDATAProcess / GetRuntimeNameByAddress
  • Mentioned also in two links above.

    在上面的两个链接中也提到了。

  • Used by process hacker (GPL license, C style)
  • 进程黑客使用(GPL许可,C风格)

Implementation seems to reside in here:

实施似乎在这里:

Mentioned at the end of (*) article.

在(*)文章末尾提到。

Approach 1 seems to be quite old fashioned, also article (*) mentions some problems around it.

方法1似乎很过时,文章(*)也提到了围绕它的一些问题。

Approach 3 will probably require in-depth analysis of profiling API's. There is also one mention I have found about these API's - in here:

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

https://naughter.wordpress.com/2015/05/24/changes-in-the-windows-10-sdk-compared-to-windows-8-1-part-two/

https://naughter.wordpress.com/2015/05/24/changes-in-the-windows-10-sdk-compared-to-windows-8-1-part-two/

· cor.h, cordebug.h/idl, CorError.h, CorHdr.h, corhlpr.h, corprof.h/idl, corpub.h/idl & corsym.h/idl: All of these header files have been removed. They are all the native mode COM interface to .NET.

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

This sentence I don't fully understand. Are those interfaces dead or replaced or what happened to them ?

这个句子我不太明白。这些接口是死的还是被替换的?

So I guess based on my brief analysis approach 2 is only good / alive API interface which is worth of using ? Have you came across any problems related to those api's.

所以我想基于我的简要分析方法2是唯一好的/活着的API接口,值得使用吗?你遇到过与这些api相关的问题吗?

3 个解决方案

#1


5  

After walking through huge amount of code samples and interfaces, I've understood that there aren't any simple to use API interface. Code and API's developed for native C++ works only with native C++, and code and API's developed for managed code works only with managed code.

在浏览了大量的代码示例和接口之后,我了解到使用API接口并不简单。为本机c++开发的代码和API只适用于本机c++,为托管代码开发的代码和API只适用于托管代码。

There is additionally problem of resolving stack trace afterwards might not work. You see - developer can generate code dynamically on fly using Jit engine / IL Generator, and dispose it as well - so after you have "void*" / instruction address - you should resolve symbolic information right away, not afterwards. But I'll leave this for time being, will assume that developer is not too fancy coder and not generating and disposing new code all the times, and FreeLibrary will not be called without need. (May be I can address this later on if I'll hook FreeLibrary / Jit components.)

另外还有解决堆栈跟踪的问题,以后可能无法工作。开发人员可以动态地使用Jit引擎/ IL生成器生成代码,并对其进行处理——因此,在拥有“void*”/指令地址之后,您应该立即解析符号信息,而不是之后。但我暂时把这个问题留给大家,假设开发人员不太喜欢编码器,而且不会一直生成和处理新代码,而FreeLibrary将不会在不需要的情况下被调用。(也许我以后可以解决这个问题,如果我用的是FreeLibrary / Jit组件。)

Resolving function name was quite trivial, through IXCLRDataProcess with little bit of magic and luck - I was able to get function names, however - I want to expand it deeper - into exact source code path and source code line where code were executing, and this turned to be quite complex functionality to reach.

解决函数名很琐碎,通过与一些IXCLRDataProcess魔法和幸运,我能够得到函数名,然而-我想扩大它深入到完全源代码路径和源代码,代码被执行,这将非常复杂的功能。

Finally I've hit upon source code where such thing were performed - and it was done here:

最后,我找到了执行这类操作的源代码——在这里完成了:

https://github.com/dotnet/coreclr/blob/master/src/ToolBox/SOS/Strike/util.cpp

https://github.com/dotnet/coreclr/blob/master/src/ToolBox/SOS/Strike/util.cpp

GetLineByOffset is function name in that file.

GetLineByOffset是该文件中的函数名。

I've analyzed, retuned and made my own solution from that source code, which I'm now attaching here now:

我分析过,重新调整过,并从那个源代码中得到了我自己的解决方案,现在我把它附在这里:

Updated code can be found from here: https://sourceforge.net/projects/diagnostic/

更新后的代码可以在这里找到:https://sourceforge.net/projects/diagnostics /

But here is just a snapshot of same code taken at some point of time:

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

ResolveStackM.h:

ResolveStackM.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;

ResolveStackM.cpp:

ResolveStackM.cpp:

#include "ResolveStackM.h"
#include <Psapi.h>                      //EnumProcessModules
#include <string>                       //to_string
#pragma comment( lib, "dbgeng.lib" )


class CLRDataTarget : public ICLRDataTarget
{
public:
    ULONG refCount;
    bool bIsWow64;
    HANDLE hProcess;

    CLRDataTarget( HANDLE _hProcess, bool _bIsWow64 ) :
        refCount(1), 
        bIsWow64(_bIsWow64),
        hProcess(_hProcess)
    {
    }

    HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, PVOID* ppvObject)
    {
        if ( IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, __uuidof(ICLRDataTarget)) )
        {
            AddRef();
            *ppvObject = this;
            return S_OK;
        }

        *ppvObject = NULL;
        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef( void)
    {
        return ++refCount;
    }

    ULONG STDMETHODCALLTYPE Release( void)
    {
        refCount--;

        if( refCount == 0 )
            delete this;

        return refCount;
    }

    virtual HRESULT STDMETHODCALLTYPE GetMachineType( ULONG32 *machineType )
    {
        #ifdef _WIN64
            if (!bIsWow64)
                *machineType = IMAGE_FILE_MACHINE_AMD64;
            else
                *machineType = IMAGE_FILE_MACHINE_I386;
        #else
            *machineType = IMAGE_FILE_MACHINE_I386;
        #endif

        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE GetPointerSize( ULONG32* pointerSize )
    {
#ifdef _WIN64
    if (!bIsWow64)
#endif
        *pointerSize = sizeof(PVOID);
#ifdef _WIN64
    else
        *pointerSize = sizeof(ULONG);
#endif
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE GetImageBase( LPCWSTR imagePath, CLRDATA_ADDRESS *baseAddress )
    {
        HMODULE dlls[1024] = { 0 };
        DWORD nItems = 0;
        wchar_t path[ MAX_PATH ];
        DWORD whatToList = LIST_MODULES_ALL;

        if( bIsWow64 )
            whatToList = LIST_MODULES_32BIT;

        if( !EnumProcessModulesEx( hProcess, dlls, sizeof(dlls), &nItems, whatToList ) )
        {
            DWORD err = GetLastError();
            return HRESULT_FROM_WIN32(err);
        }

        nItems /= sizeof(HMODULE);
        for( unsigned int i = 0; i < nItems; i++ )
        {
            path[0] = 0;
            if( GetModuleFileNameEx(hProcess, dlls[i], path, sizeof(path) / sizeof(path[0])) )
            {
                wchar_t* pDll = wcsrchr( path, L'\\');
                if (pDll) pDll++;

                if (_wcsicmp(imagePath, path) == 0 || _wcsicmp(imagePath, pDll) == 0)
                {
                    *baseAddress = (CLRDATA_ADDRESS) dlls[i];
                    return S_OK;
                }
            }
        }
        return E_FAIL;
    }

    virtual HRESULT STDMETHODCALLTYPE ReadVirtual( CLRDATA_ADDRESS address, BYTE *buffer, ULONG32 bytesRequested, ULONG32 *bytesRead )
    {
        SIZE_T readed;

        if( !ReadProcessMemory(hProcess, (void*)address, buffer, bytesRequested, &readed) )
            return HRESULT_FROM_WIN32( GetLastError() );

        *bytesRead = (ULONG32) readed;
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE WriteVirtual( CLRDATA_ADDRESS address, BYTE *buffer, ULONG32 bytesRequested, ULONG32 *bytesWritten )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE GetTLSValue( ULONG32 threadID, ULONG32 index, CLRDATA_ADDRESS *value )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE SetTLSValue( ULONG32 threadID, ULONG32 index, CLRDATA_ADDRESS value )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE GetCurrentThreadID( ULONG32 *threadID )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE GetThreadContext( ULONG32 threadID, ULONG32 contextFlags, ULONG32 contextSize, BYTE *context )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE SetThreadContext( ULONG32 threadID, ULONG32 contextSize, BYTE *context)
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE Request( ULONG32 reqCode, ULONG32 inBufferSize, BYTE *inBuffer, ULONG32 outBufferSize, BYTE *outBuffer)
    {
        return E_NOTIMPL;
    }
}; //CLRDataTarget





ResoveStackM::ResoveStackM() :
    mscordacwks_dll(0)
{

}

ResoveStackM::~ResoveStackM()
{
    Close();
}

void ResoveStackM::Close( void )
{
    clrDataProcess.Release();
    target.Release();
    debugClient.Release();

    if( mscordacwks_dll != 0 )
    {
        FreeLibrary(mscordacwks_dll);
        mscordacwks_dll = 0;
    }
}

bool ResoveStackM::InitSymbolResolver(HANDLE hProcess, CString& lastError)
{
    wchar_t path[ MAX_PATH ] = { 0 };

    // According to process hacker - mscoree.dll must be loaded before loading mscordacwks.dll.
    // It's enough if base application is managed.

    if( GetWindowsDirectoryW(path, sizeof(path)/sizeof(wchar_t) ) == 0 )
        return false;   //Unlikely to fail.

#ifdef _WIN64
    wcscat(path, L"\\Microsoft.NET\\Framework64\\v4.0.30319\\mscordacwks.dll");
#else
    wcscat(path, L"\\Microsoft.NET\\Framework\\v4.0.30319\\mscordacwks.dll");
#endif

    mscordacwks_dll = LoadLibraryW(path);
    PFN_CLRDataCreateInstance pCLRCreateInstance = 0;

    if( mscordacwks_dll != 0 )
        pCLRCreateInstance = (PFN_CLRDataCreateInstance) GetProcAddress(mscordacwks_dll, "CLRDataCreateInstance");

    if( mscordacwks_dll == 0 || pCLRCreateInstance == 0)
    {
        lastError.Format(L"Required dll mscordacwks.dll from .NET4 installation was not found (%s)", path);
        Close();
        return false;
    }

    BOOL isWow64 = FALSE;
    IsWow64Process(hProcess, &isWow64);
    target.Attach( new CLRDataTarget(hProcess, isWow64 != FALSE) );

    HRESULT hr = pCLRCreateInstance(__uuidof(IXCLRDataProcess), target, (void**)&clrDataProcess );

    if( FAILED(hr) )
    {
        lastError.Format(L"Failed to initialize mscordacwks.dll for symbol resolving (%08X)", hr);
        Close();
        return false;
    }

    hr = DebugCreate(__uuidof(IDebugClient), (void**)&debugClient);
    if (FAILED(hr))
    {
        lastError.Format(_T("Could retrieve symbolic debug information using dbgeng.dll (Error code: 0x%08X)"), hr);
        return false;
    }

    DWORD processId = GetProcessId(hProcess);
    const ULONG64 LOCAL_SERVER = 0;
    int flags = DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND;

    hr = debugClient->AttachProcess(LOCAL_SERVER, processId, flags);
    if (hr != S_OK)
    {
        lastError.Format(_T("Could attach to process 0x%X (Error code: 0x%08X)"), processId, hr);
        Close();
        return false;
    }

    debugControl = debugClient;

    hr = debugControl->SetExecutionStatus(DEBUG_STATUS_GO);
    if ((hr = debugControl->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE)) != S_OK)
    {
        return false;
    }

    debugSymbols3 = debugClient;
    debugSymbols  = debugClient;
    // if debugSymbols3 == NULL - GetManagedFileLineInfo will not work
    return true;
} //Init

struct ImageInfo
{
    ULONG64 modBase;
};

// Based on a native offset, passed in the first argument this function
// identifies the corresponding source file name and line number.
bool ResoveStackM::GetManagedFileLineInfo( void* ip, CStringA& lineInfo )
{
    ULONG lineN = 0;
    char path[MAX_PATH];
    ULONG64 dispacement = 0;

    CComPtr<IXCLRDataMethodInstance> method;
    if (!debugSymbols || !debugSymbols3)
        return false;

    // Get managed method by address
    CLRDATA_ENUM methEnum;
    HRESULT hr = clrDataProcess->StartEnumMethodInstancesByAddress((ULONG64)ip, NULL, &methEnum);
    if( hr == S_OK )
    {
        hr = clrDataProcess->EnumMethodInstanceByAddress(&methEnum, &method);
        clrDataProcess->EndEnumMethodInstancesByAddress(methEnum);
    }

    if (!method)
        goto lDefaultFallback;

    ULONG32 ilOffsets = 0;
    hr = method->GetILOffsetsByAddress((CLRDATA_ADDRESS)ip, 1, NULL, &ilOffsets);

    switch( (long)ilOffsets )
    {
        case CLRDATA_IL_OFFSET_NO_MAPPING:
            goto lDefaultFallback;

        case CLRDATA_IL_OFFSET_PROLOG:
            // Treat all of the prologue as part of the first source line.
            ilOffsets = 0;
            break;

        case CLRDATA_IL_OFFSET_EPILOG:
        {
            // Back up until we find the last real IL offset.
            CLRDATA_IL_ADDRESS_MAP mapLocal[16];
            CLRDATA_IL_ADDRESS_MAP* map = mapLocal;
            ULONG32 count = _countof(mapLocal);
            ULONG32 needed = 0;

            for( ; ; )
            {
                hr = method->GetILAddressMap(count, &needed, map);

                if ( needed <= count || map != mapLocal)
                    break;

                map = new CLRDATA_IL_ADDRESS_MAP[ needed ];
            }

            ULONG32 highestOffset = 0;
            for (unsigned i = 0; i < needed; i++)
            {
                long l = (long) map[i].ilOffset;

                if (l == CLRDATA_IL_OFFSET_NO_MAPPING || l == CLRDATA_IL_OFFSET_PROLOG || l == CLRDATA_IL_OFFSET_EPILOG )
                    continue;

                if (map[i].ilOffset > highestOffset )
                    highestOffset = map[i].ilOffset;
            } //for

            if( map != mapLocal )
                delete[] map;

            ilOffsets = highestOffset;
        }
        break;
    } //switch

    mdMethodDef methodToken;
    void* moduleBase = 0;
    {
        CComPtr<IXCLRDataModule> module;

        hr = method->GetTokenAndScope(&methodToken, &module);
        if( !module )
            goto lDefaultFallback;

        //
        // Retrieve ImageInfo associated with the IXCLRDataModule instance passed in. First look for NGENed module, second for IL modules.
        //
        for (int extentType = CLRDATA_MODULE_PREJIT_FILE; extentType >= CLRDATA_MODULE_PE_FILE; extentType--)
        {
            CLRDATA_ENUM enumExtents;
            if (module->StartEnumExtents(&enumExtents) != S_OK )
                continue;

            CLRDATA_MODULE_EXTENT extent;
            while (module->EnumExtent(&enumExtents, &extent) == S_OK)
            {
                if (extentType != extent.type )
                    continue;

                ULONG startIndex = 0;
                ULONG64 modBase = 0;

                hr = debugSymbols->GetModuleByOffset((ULONG64) extent.base, 0, &startIndex, &modBase);
                if( FAILED(hr) )
                    continue;

                moduleBase = (void*)modBase;

                if (moduleBase )
                    break;
            }
            module->EndEnumExtents(enumExtents);

            if( moduleBase != 0 )
                break;
        } //for
    } //module scope

    DEBUG_MODULE_AND_ID id;
    DEBUG_SYMBOL_ENTRY symInfo;
    hr = debugSymbols3->GetSymbolEntryByToken((ULONG64)moduleBase, methodToken, &id);
    if( FAILED(hr) )
        goto lDefaultFallback;

    hr = debugSymbols3->GetSymbolEntryInformation(&id, &symInfo);
    if (FAILED(hr))
        goto lDefaultFallback;

    char* IlOffset = (char*)symInfo.Offset + ilOffsets;

    //
    // Source maps for managed code can end up with special 0xFEEFEE markers that
    // indicate don't-stop points.  Try and filter those out.
    //
    for (ULONG SkipCount = 64; SkipCount > 0; SkipCount--)
    {
        hr = debugSymbols3->GetLineByOffset((ULONG64)IlOffset, &lineN, path, sizeof(path), NULL, &dispacement );
        if( FAILED( hr ) )
            break;

        if (lineN == 0xfeefee)
            IlOffset++;
        else
            goto lCollectInfoAndReturn;
    }

    if( !FAILED(hr) )
        // Fall into the regular translation as a last-ditch effort.
        ip = IlOffset;

lDefaultFallback:
    hr = debugSymbols3->GetLineByOffset((ULONG64) ip, &lineN, path, sizeof(path), NULL, &dispacement);

    if( FAILED(hr) )
        return false;

lCollectInfoAndReturn:
    lineInfo += path;
    lineInfo += "(";
    lineInfo += std::to_string((_ULonglong) lineN).c_str();
    lineInfo += "): ";
    return true;
}


bool ResoveStackM::GetMethodName(void* ip, CStringA& symbol)
{
    symbol.Empty();

    GetManagedFileLineInfo(ip, symbol);

    USES_CONVERSION;
    CLRDATA_ADDRESS displacement = 0;
    ULONG32 len = 0;
    wchar_t name[1024];
    if (!clrDataProcess )
        return false;

    HRESULT hr = clrDataProcess->GetRuntimeNameByAddress( (CLRDATA_ADDRESS)ip, 0, sizeof(name) / sizeof(name[0]), &len, name, &displacement );

    if( FAILED( hr ) )
        return false;

    name[ len ] = 0;
    symbol += W2A(name);
    return true;
} //GetMethodName



ResoveStackM g_managedStackResolver;

So far tested only with some smaller piece of code, only 64-bit (doubt that 32-bit works at all - I don't have call stack determination yet for it).

到目前为止,只测试了一些更小的代码,只有64位(32位的工作完全不确定——我还没有调用堆栈决定)。

It's possible that this code contains bugs, but I'll try to haunt them down and fix them.

这段代码可能包含错误,但我将设法使它们陷入困境并修复它们。

I harvested so much code that please mark this answer as useful. :-)

我收集了太多的代码,请将这个答案标记为有用的。:-)

#2


0  

Alternative 1 / IDebugClient / GetNameByOffset is not usable for managed stack trace, it can be used for native code only - as for native call stack I have demo code snipet above already. Not sure whether IDebugClient provides something more than SymGetLineFromAddr64 / SymFromAddr does not provide - not sure.

备选方案1 / IDebugClient / GetNameByOffset不能用于托管堆栈跟踪,它只能用于本地代码——就像我上面已经有演示代码snipet的本地调用堆栈一样。不确定IDebugClient是否提供了超过SymGetLineFromAddr64 / SymFromAddr所不能提供的东西——不确定。

#3


0  

Here is an answer from Jan Kotas on this:

以下是简·考塔斯的回答:

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.

What I have briefly tried out - this requires Visual Studio 2015 / C# 6.0.

我简单尝试过的-这需要Visual Studio 2015 / c# 6.0。

Also this technique is unusable. Like .net StackTrace / StackFrame are resolving call stack and symbol information right away - and I need to resolve symbol information afterwards (after stack trace capturing).

而且这种技术也不能用。就像。net StackTrace / StackFrame正在解析调用堆栈和符号信息一样,我需要稍后解析符号信息(在堆栈跟踪捕获之后)。

#1


5  

After walking through huge amount of code samples and interfaces, I've understood that there aren't any simple to use API interface. Code and API's developed for native C++ works only with native C++, and code and API's developed for managed code works only with managed code.

在浏览了大量的代码示例和接口之后,我了解到使用API接口并不简单。为本机c++开发的代码和API只适用于本机c++,为托管代码开发的代码和API只适用于托管代码。

There is additionally problem of resolving stack trace afterwards might not work. You see - developer can generate code dynamically on fly using Jit engine / IL Generator, and dispose it as well - so after you have "void*" / instruction address - you should resolve symbolic information right away, not afterwards. But I'll leave this for time being, will assume that developer is not too fancy coder and not generating and disposing new code all the times, and FreeLibrary will not be called without need. (May be I can address this later on if I'll hook FreeLibrary / Jit components.)

另外还有解决堆栈跟踪的问题,以后可能无法工作。开发人员可以动态地使用Jit引擎/ IL生成器生成代码,并对其进行处理——因此,在拥有“void*”/指令地址之后,您应该立即解析符号信息,而不是之后。但我暂时把这个问题留给大家,假设开发人员不太喜欢编码器,而且不会一直生成和处理新代码,而FreeLibrary将不会在不需要的情况下被调用。(也许我以后可以解决这个问题,如果我用的是FreeLibrary / Jit组件。)

Resolving function name was quite trivial, through IXCLRDataProcess with little bit of magic and luck - I was able to get function names, however - I want to expand it deeper - into exact source code path and source code line where code were executing, and this turned to be quite complex functionality to reach.

解决函数名很琐碎,通过与一些IXCLRDataProcess魔法和幸运,我能够得到函数名,然而-我想扩大它深入到完全源代码路径和源代码,代码被执行,这将非常复杂的功能。

Finally I've hit upon source code where such thing were performed - and it was done here:

最后,我找到了执行这类操作的源代码——在这里完成了:

https://github.com/dotnet/coreclr/blob/master/src/ToolBox/SOS/Strike/util.cpp

https://github.com/dotnet/coreclr/blob/master/src/ToolBox/SOS/Strike/util.cpp

GetLineByOffset is function name in that file.

GetLineByOffset是该文件中的函数名。

I've analyzed, retuned and made my own solution from that source code, which I'm now attaching here now:

我分析过,重新调整过,并从那个源代码中得到了我自己的解决方案,现在我把它附在这里:

Updated code can be found from here: https://sourceforge.net/projects/diagnostic/

更新后的代码可以在这里找到:https://sourceforge.net/projects/diagnostics /

But here is just a snapshot of same code taken at some point of time:

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

ResolveStackM.h:

ResolveStackM.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;

ResolveStackM.cpp:

ResolveStackM.cpp:

#include "ResolveStackM.h"
#include <Psapi.h>                      //EnumProcessModules
#include <string>                       //to_string
#pragma comment( lib, "dbgeng.lib" )


class CLRDataTarget : public ICLRDataTarget
{
public:
    ULONG refCount;
    bool bIsWow64;
    HANDLE hProcess;

    CLRDataTarget( HANDLE _hProcess, bool _bIsWow64 ) :
        refCount(1), 
        bIsWow64(_bIsWow64),
        hProcess(_hProcess)
    {
    }

    HRESULT STDMETHODCALLTYPE QueryInterface( REFIID riid, PVOID* ppvObject)
    {
        if ( IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, __uuidof(ICLRDataTarget)) )
        {
            AddRef();
            *ppvObject = this;
            return S_OK;
        }

        *ppvObject = NULL;
        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef( void)
    {
        return ++refCount;
    }

    ULONG STDMETHODCALLTYPE Release( void)
    {
        refCount--;

        if( refCount == 0 )
            delete this;

        return refCount;
    }

    virtual HRESULT STDMETHODCALLTYPE GetMachineType( ULONG32 *machineType )
    {
        #ifdef _WIN64
            if (!bIsWow64)
                *machineType = IMAGE_FILE_MACHINE_AMD64;
            else
                *machineType = IMAGE_FILE_MACHINE_I386;
        #else
            *machineType = IMAGE_FILE_MACHINE_I386;
        #endif

        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE GetPointerSize( ULONG32* pointerSize )
    {
#ifdef _WIN64
    if (!bIsWow64)
#endif
        *pointerSize = sizeof(PVOID);
#ifdef _WIN64
    else
        *pointerSize = sizeof(ULONG);
#endif
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE GetImageBase( LPCWSTR imagePath, CLRDATA_ADDRESS *baseAddress )
    {
        HMODULE dlls[1024] = { 0 };
        DWORD nItems = 0;
        wchar_t path[ MAX_PATH ];
        DWORD whatToList = LIST_MODULES_ALL;

        if( bIsWow64 )
            whatToList = LIST_MODULES_32BIT;

        if( !EnumProcessModulesEx( hProcess, dlls, sizeof(dlls), &nItems, whatToList ) )
        {
            DWORD err = GetLastError();
            return HRESULT_FROM_WIN32(err);
        }

        nItems /= sizeof(HMODULE);
        for( unsigned int i = 0; i < nItems; i++ )
        {
            path[0] = 0;
            if( GetModuleFileNameEx(hProcess, dlls[i], path, sizeof(path) / sizeof(path[0])) )
            {
                wchar_t* pDll = wcsrchr( path, L'\\');
                if (pDll) pDll++;

                if (_wcsicmp(imagePath, path) == 0 || _wcsicmp(imagePath, pDll) == 0)
                {
                    *baseAddress = (CLRDATA_ADDRESS) dlls[i];
                    return S_OK;
                }
            }
        }
        return E_FAIL;
    }

    virtual HRESULT STDMETHODCALLTYPE ReadVirtual( CLRDATA_ADDRESS address, BYTE *buffer, ULONG32 bytesRequested, ULONG32 *bytesRead )
    {
        SIZE_T readed;

        if( !ReadProcessMemory(hProcess, (void*)address, buffer, bytesRequested, &readed) )
            return HRESULT_FROM_WIN32( GetLastError() );

        *bytesRead = (ULONG32) readed;
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE WriteVirtual( CLRDATA_ADDRESS address, BYTE *buffer, ULONG32 bytesRequested, ULONG32 *bytesWritten )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE GetTLSValue( ULONG32 threadID, ULONG32 index, CLRDATA_ADDRESS *value )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE SetTLSValue( ULONG32 threadID, ULONG32 index, CLRDATA_ADDRESS value )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE GetCurrentThreadID( ULONG32 *threadID )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE GetThreadContext( ULONG32 threadID, ULONG32 contextFlags, ULONG32 contextSize, BYTE *context )
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE SetThreadContext( ULONG32 threadID, ULONG32 contextSize, BYTE *context)
    {
        return E_NOTIMPL;
    }

    virtual HRESULT STDMETHODCALLTYPE Request( ULONG32 reqCode, ULONG32 inBufferSize, BYTE *inBuffer, ULONG32 outBufferSize, BYTE *outBuffer)
    {
        return E_NOTIMPL;
    }
}; //CLRDataTarget





ResoveStackM::ResoveStackM() :
    mscordacwks_dll(0)
{

}

ResoveStackM::~ResoveStackM()
{
    Close();
}

void ResoveStackM::Close( void )
{
    clrDataProcess.Release();
    target.Release();
    debugClient.Release();

    if( mscordacwks_dll != 0 )
    {
        FreeLibrary(mscordacwks_dll);
        mscordacwks_dll = 0;
    }
}

bool ResoveStackM::InitSymbolResolver(HANDLE hProcess, CString& lastError)
{
    wchar_t path[ MAX_PATH ] = { 0 };

    // According to process hacker - mscoree.dll must be loaded before loading mscordacwks.dll.
    // It's enough if base application is managed.

    if( GetWindowsDirectoryW(path, sizeof(path)/sizeof(wchar_t) ) == 0 )
        return false;   //Unlikely to fail.

#ifdef _WIN64
    wcscat(path, L"\\Microsoft.NET\\Framework64\\v4.0.30319\\mscordacwks.dll");
#else
    wcscat(path, L"\\Microsoft.NET\\Framework\\v4.0.30319\\mscordacwks.dll");
#endif

    mscordacwks_dll = LoadLibraryW(path);
    PFN_CLRDataCreateInstance pCLRCreateInstance = 0;

    if( mscordacwks_dll != 0 )
        pCLRCreateInstance = (PFN_CLRDataCreateInstance) GetProcAddress(mscordacwks_dll, "CLRDataCreateInstance");

    if( mscordacwks_dll == 0 || pCLRCreateInstance == 0)
    {
        lastError.Format(L"Required dll mscordacwks.dll from .NET4 installation was not found (%s)", path);
        Close();
        return false;
    }

    BOOL isWow64 = FALSE;
    IsWow64Process(hProcess, &isWow64);
    target.Attach( new CLRDataTarget(hProcess, isWow64 != FALSE) );

    HRESULT hr = pCLRCreateInstance(__uuidof(IXCLRDataProcess), target, (void**)&clrDataProcess );

    if( FAILED(hr) )
    {
        lastError.Format(L"Failed to initialize mscordacwks.dll for symbol resolving (%08X)", hr);
        Close();
        return false;
    }

    hr = DebugCreate(__uuidof(IDebugClient), (void**)&debugClient);
    if (FAILED(hr))
    {
        lastError.Format(_T("Could retrieve symbolic debug information using dbgeng.dll (Error code: 0x%08X)"), hr);
        return false;
    }

    DWORD processId = GetProcessId(hProcess);
    const ULONG64 LOCAL_SERVER = 0;
    int flags = DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND;

    hr = debugClient->AttachProcess(LOCAL_SERVER, processId, flags);
    if (hr != S_OK)
    {
        lastError.Format(_T("Could attach to process 0x%X (Error code: 0x%08X)"), processId, hr);
        Close();
        return false;
    }

    debugControl = debugClient;

    hr = debugControl->SetExecutionStatus(DEBUG_STATUS_GO);
    if ((hr = debugControl->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE)) != S_OK)
    {
        return false;
    }

    debugSymbols3 = debugClient;
    debugSymbols  = debugClient;
    // if debugSymbols3 == NULL - GetManagedFileLineInfo will not work
    return true;
} //Init

struct ImageInfo
{
    ULONG64 modBase;
};

// Based on a native offset, passed in the first argument this function
// identifies the corresponding source file name and line number.
bool ResoveStackM::GetManagedFileLineInfo( void* ip, CStringA& lineInfo )
{
    ULONG lineN = 0;
    char path[MAX_PATH];
    ULONG64 dispacement = 0;

    CComPtr<IXCLRDataMethodInstance> method;
    if (!debugSymbols || !debugSymbols3)
        return false;

    // Get managed method by address
    CLRDATA_ENUM methEnum;
    HRESULT hr = clrDataProcess->StartEnumMethodInstancesByAddress((ULONG64)ip, NULL, &methEnum);
    if( hr == S_OK )
    {
        hr = clrDataProcess->EnumMethodInstanceByAddress(&methEnum, &method);
        clrDataProcess->EndEnumMethodInstancesByAddress(methEnum);
    }

    if (!method)
        goto lDefaultFallback;

    ULONG32 ilOffsets = 0;
    hr = method->GetILOffsetsByAddress((CLRDATA_ADDRESS)ip, 1, NULL, &ilOffsets);

    switch( (long)ilOffsets )
    {
        case CLRDATA_IL_OFFSET_NO_MAPPING:
            goto lDefaultFallback;

        case CLRDATA_IL_OFFSET_PROLOG:
            // Treat all of the prologue as part of the first source line.
            ilOffsets = 0;
            break;

        case CLRDATA_IL_OFFSET_EPILOG:
        {
            // Back up until we find the last real IL offset.
            CLRDATA_IL_ADDRESS_MAP mapLocal[16];
            CLRDATA_IL_ADDRESS_MAP* map = mapLocal;
            ULONG32 count = _countof(mapLocal);
            ULONG32 needed = 0;

            for( ; ; )
            {
                hr = method->GetILAddressMap(count, &needed, map);

                if ( needed <= count || map != mapLocal)
                    break;

                map = new CLRDATA_IL_ADDRESS_MAP[ needed ];
            }

            ULONG32 highestOffset = 0;
            for (unsigned i = 0; i < needed; i++)
            {
                long l = (long) map[i].ilOffset;

                if (l == CLRDATA_IL_OFFSET_NO_MAPPING || l == CLRDATA_IL_OFFSET_PROLOG || l == CLRDATA_IL_OFFSET_EPILOG )
                    continue;

                if (map[i].ilOffset > highestOffset )
                    highestOffset = map[i].ilOffset;
            } //for

            if( map != mapLocal )
                delete[] map;

            ilOffsets = highestOffset;
        }
        break;
    } //switch

    mdMethodDef methodToken;
    void* moduleBase = 0;
    {
        CComPtr<IXCLRDataModule> module;

        hr = method->GetTokenAndScope(&methodToken, &module);
        if( !module )
            goto lDefaultFallback;

        //
        // Retrieve ImageInfo associated with the IXCLRDataModule instance passed in. First look for NGENed module, second for IL modules.
        //
        for (int extentType = CLRDATA_MODULE_PREJIT_FILE; extentType >= CLRDATA_MODULE_PE_FILE; extentType--)
        {
            CLRDATA_ENUM enumExtents;
            if (module->StartEnumExtents(&enumExtents) != S_OK )
                continue;

            CLRDATA_MODULE_EXTENT extent;
            while (module->EnumExtent(&enumExtents, &extent) == S_OK)
            {
                if (extentType != extent.type )
                    continue;

                ULONG startIndex = 0;
                ULONG64 modBase = 0;

                hr = debugSymbols->GetModuleByOffset((ULONG64) extent.base, 0, &startIndex, &modBase);
                if( FAILED(hr) )
                    continue;

                moduleBase = (void*)modBase;

                if (moduleBase )
                    break;
            }
            module->EndEnumExtents(enumExtents);

            if( moduleBase != 0 )
                break;
        } //for
    } //module scope

    DEBUG_MODULE_AND_ID id;
    DEBUG_SYMBOL_ENTRY symInfo;
    hr = debugSymbols3->GetSymbolEntryByToken((ULONG64)moduleBase, methodToken, &id);
    if( FAILED(hr) )
        goto lDefaultFallback;

    hr = debugSymbols3->GetSymbolEntryInformation(&id, &symInfo);
    if (FAILED(hr))
        goto lDefaultFallback;

    char* IlOffset = (char*)symInfo.Offset + ilOffsets;

    //
    // Source maps for managed code can end up with special 0xFEEFEE markers that
    // indicate don't-stop points.  Try and filter those out.
    //
    for (ULONG SkipCount = 64; SkipCount > 0; SkipCount--)
    {
        hr = debugSymbols3->GetLineByOffset((ULONG64)IlOffset, &lineN, path, sizeof(path), NULL, &dispacement );
        if( FAILED( hr ) )
            break;

        if (lineN == 0xfeefee)
            IlOffset++;
        else
            goto lCollectInfoAndReturn;
    }

    if( !FAILED(hr) )
        // Fall into the regular translation as a last-ditch effort.
        ip = IlOffset;

lDefaultFallback:
    hr = debugSymbols3->GetLineByOffset((ULONG64) ip, &lineN, path, sizeof(path), NULL, &dispacement);

    if( FAILED(hr) )
        return false;

lCollectInfoAndReturn:
    lineInfo += path;
    lineInfo += "(";
    lineInfo += std::to_string((_ULonglong) lineN).c_str();
    lineInfo += "): ";
    return true;
}


bool ResoveStackM::GetMethodName(void* ip, CStringA& symbol)
{
    symbol.Empty();

    GetManagedFileLineInfo(ip, symbol);

    USES_CONVERSION;
    CLRDATA_ADDRESS displacement = 0;
    ULONG32 len = 0;
    wchar_t name[1024];
    if (!clrDataProcess )
        return false;

    HRESULT hr = clrDataProcess->GetRuntimeNameByAddress( (CLRDATA_ADDRESS)ip, 0, sizeof(name) / sizeof(name[0]), &len, name, &displacement );

    if( FAILED( hr ) )
        return false;

    name[ len ] = 0;
    symbol += W2A(name);
    return true;
} //GetMethodName



ResoveStackM g_managedStackResolver;

So far tested only with some smaller piece of code, only 64-bit (doubt that 32-bit works at all - I don't have call stack determination yet for it).

到目前为止,只测试了一些更小的代码,只有64位(32位的工作完全不确定——我还没有调用堆栈决定)。

It's possible that this code contains bugs, but I'll try to haunt them down and fix them.

这段代码可能包含错误,但我将设法使它们陷入困境并修复它们。

I harvested so much code that please mark this answer as useful. :-)

我收集了太多的代码,请将这个答案标记为有用的。:-)

#2


0  

Alternative 1 / IDebugClient / GetNameByOffset is not usable for managed stack trace, it can be used for native code only - as for native call stack I have demo code snipet above already. Not sure whether IDebugClient provides something more than SymGetLineFromAddr64 / SymFromAddr does not provide - not sure.

备选方案1 / IDebugClient / GetNameByOffset不能用于托管堆栈跟踪,它只能用于本地代码——就像我上面已经有演示代码snipet的本地调用堆栈一样。不确定IDebugClient是否提供了超过SymGetLineFromAddr64 / SymFromAddr所不能提供的东西——不确定。

#3


0  

Here is an answer from Jan Kotas on this:

以下是简·考塔斯的回答:

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.

What I have briefly tried out - this requires Visual Studio 2015 / C# 6.0.

我简单尝试过的-这需要Visual Studio 2015 / c# 6.0。

Also this technique is unusable. Like .net StackTrace / StackFrame are resolving call stack and symbol information right away - and I need to resolve symbol information afterwards (after stack trace capturing).

而且这种技术也不能用。就像。net StackTrace / StackFrame正在解析调用堆栈和符号信息一样,我需要稍后解析符号信息(在堆栈跟踪捕获之后)。