【问题标题】:Resolve managed and native stack trace - which API to use?解决托管和本机堆栈跟踪 - 使用哪个 API?
【发布时间】:2016-04-16 10:44:58
【问题描述】:

这是我上一个问题的延续 - 可以说是第 2 阶段。

第一个问题在这里:Fast capture stack trace on windows / 64-bit / mixed mode

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

对于原生 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();

并初始化符号加载:

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

然后解析原生符号,就像这样:

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;

}

这看起来可行。

但现在也托管堆栈帧。

我找到了两个接口:

  1. IDebugClient / GetNameByOffset

提到:

使用者:

  • https://github.com/okigan/CrashInsight(代码4年没碰过)
  • 混合模式 stackwalk 文章提供了很好的示例。

    1. IXCLRDATAProcess / GetRuntimeNameByAddress
  • 在上面的两个链接中也提到过。

  • 由进程黑客使用(GPL 许可,C 风格)

实现似乎驻留在此处:

在 (*) 文章末尾提到。

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

方法 3 可能需要深入分析分析 API。 我还发现了一个关于这些 API 的提及 - 在这里:

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: 所有这些头文件 已被删除。它们都是 .NET 的原生模式 COM 接口。

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

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

【问题讨论】:

  • 这是我在研究中发现的混合代码调试的最佳文本。很好。在我的情况下,混合代码在 WOW 上以 32 位运行。有时我有非托管调用托管,如果发生崩溃堆栈跟踪丢失。这个问题让我创建了单独的模块来调用运行时的相同模块。
  • 谢谢。如果您有兴趣,这里应该更新代码:sourceforge.net/projects/diagnostic

标签: windows stack-trace mixed-mode


【解决方案1】:

这是 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.

我已经简单地尝试过 - 这需要 Visual Studio 2015 / C# 6.0。

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

【讨论】:

    【解决方案2】:

    Alternative 1 / IDebugClient / GetNameByOffset 不可用于托管堆栈跟踪,它只能用于本机代码 - 至于本机调用堆栈,我已经有上面的演示代码片段。不确定 IDebugClient 是否提供了超出 SymGetLineFromAddr64 / SymFromAddr 不提供的东西 - 不确定。

    【讨论】:

      【解决方案3】:

      在浏览了大量的代码示例和接口之后,我了解到没有任何简单易用的 API 接口。为本机 C++ 开发的代码和 API 仅适用于本机 C++,而为托管代码开发的代码和 API 仅适用于托管代码。

      另外还有一个问题,事后解决堆栈跟踪可能不起作用。您会看到 - 开发人员可以使用 Jit 引擎/IL 生成器动态生成代码,并将其处理掉 - 因此在您拥有“void*”/指令地址之后 - 您应该立即解析符号信息,而不是之后。但我将暂时离开这个,假设开发人员不是太花哨的编码器,并且不会一直生成和处理新代码,并且不会在不需要的情况下调用 FreeLibrary。 (如果我要挂钩 FreeLibrary / Jit 组件,也许我可以稍后解决这个问题。)

      解析函数名称非常简单,通过 IXCLRDataProcess 有点魔法和运气 - 我能够获得函数名称,但是 - 我想更深入地扩展它 - 到确切的源代码路径和代码正在执行的源代码行,而这变成了一个相当复杂的功能。

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

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

      GetLineByOffset 是该文件中的函数名。

      我已经分析、重新调整并根据该源代码制作了自己的解决方案,现在我将其附在此处:

      可以从这里找到更新的代码: https://sourceforge.net/projects/diagnostic/

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

      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:

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

      到目前为止,仅使用一些较小的代码进行了测试,仅 64 位(怀疑 32 位是否有效 - 我还没有确定调用堆栈)。

      这段代码可能包含错误,但我会尝试解决它们并修复它们。

      我收获了很多代码,请将此答案标记为有用。 :-)

      【讨论】:

        猜你喜欢
        • 2019-03-31
        • 2012-07-25
        • 1970-01-01
        • 1970-01-01
        • 2013-01-14
        • 1970-01-01
        • 2021-08-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多