问题描述
可以这么说,这是我之前的问题的延续-第二阶段.
This is continuation to my previous question - phase 2 so to say.
第一个问题在这里:快速捕获堆栈跟踪在Windows/64位/混合模式下
现在我已经解决了很多堆栈跟踪,现在想知道如何解析托管堆栈帧的符号信息.
Now I have resolved a huge amount of stack traces and now wondering how to resolve symbol information of managed stack frames.
对于本机C ++而言,它相对简单-
For native C++ side it's relatively simple -
首先,您指定从何处获取符号的过程:
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:
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);
}
然后解析本机符号,就像这样:
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;
}
这看起来很正常.
但现在也管理堆栈帧.
我已经找到了两个接口:
There are two interfaces which I've located:
- IDebugClient/GetNameByOffset
提到:
- http://www .codeproject.com/Articles/371137/A-Mixed-Mode-Stackwalk-with-the-IDebugClient-Inter (*)(包括示例代码)
- http://blog.steveniemitz. com/building-a-mixed-mode-stack-walker-part-1/
- http://www.codeproject.com/Articles/371137/A-Mixed-Mode-Stackwalk-with-the-IDebugClient-Inter(*) (Includes sample code)
- http://blog.steveniemitz.com/building-a-mixed-mode-stack-walker-part-1/
使用者:
- https://github.com/okigan/CrashInsight (四年未修改代码)
-
混合模式stackwalk文章提供了很好的例子.
- https://github.com/okigan/CrashInsight (Code not touched for 4 years)
Mixed mode stackwalk article provides good example.
- IXCLRDATAProcess/GetRuntimeNameByAddress
在上面的两个链接中也提到了这一点.
Mentioned also in two links above.
实施似乎驻留在这里:
https://github.com/dotnet/coreclr/blob/master/src/debug/daccess/daccess.cpp(Based on commits this code is quite alive)
- ICorProfiler/???
在(*)文章末尾提及.
Mentioned at the end of (*) article.
方法1似乎过时了,文章(*)也提到了周围的一些问题.
Approach 1 seems to be quite old fashioned, also article (*) mentions some problems around it.
方法3可能需要对概要分析API进行深入分析.我还发现了关于这些API的一个提及-在这里:
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:
我不完全理解这句话.这些接口是死了还是被替换了,或者发生了什么?
This sentence I don't fully understand. Are those interfaces dead or replaced or what happened to them ?
因此,基于我的简要分析方法2,我认为值得使用的是良好/有效的API接口?您是否遇到过与这些api相关的任何问题.
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.
推荐答案
在遍历了大量代码示例和接口之后,我了解到没有简单易用的API接口.专为本机C ++开发的代码和API仅可用于本机C ++,而专为托管代码开发的代码和API仅可用于托管代码.
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.
还有一个额外的问题,那就是以后无法解决堆栈跟踪问题.您会看到-开发人员可以使用Jit引擎/IL Generator动态生成代码,并对其进行处理-因此,在拥有"void *"/指令地址后-您应该立即解析符号信息,而不是之后.但是我暂时将其保留下来,假设开发人员不是太花哨的编码器,也不总是一直在生成和配置新的代码,并且FreeLibrary不会在不需要时被调用. (如果我要钩住FreeLibrary/Jit组件,也许以后可以解决这个问题.)
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.)
通过IXCLRDataProcess解析函数名非常简单,我只有一点点魔术和运气-我能够获得函数名,但是-我想更深入地扩展-进入执行代码的确切源代码路径和源代码行,这变成了相当复杂的功能.
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.
最后,我碰到了执行此操作的源代码-并在此处完成:
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
GetLineByOffset是该文件中的函数名称.
GetLineByOffset is function name in that file.
我已经从该源代码分析,重新调整并制定了自己的解决方案,现在我将其附加在这里:
I've analyzed, retuned and made my own solution from that source code, which I'm now attaching here now:
更新的代码可以在这里找到: https://sourceforge.net/projects/diagnostic/
Updated code can be found from here:https://sourceforge.net/projects/diagnostic/
但这只是在某些时间点拍摄的相同代码的快照:
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;
到目前为止,仅使用一些较小的代码进行了测试,只有64位(怀疑32位完全可以工作-我还没有确定调用堆栈的方法).
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).
此代码可能包含错误,但我将尽力解决这些错误并加以修复.
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. :-)
这篇关于解决托管和本机堆栈跟踪-使用哪个API?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!