这是我前一个问题的延续 - 第二阶段就是这样说的.
第一个问题是:在Windows/64位/混合模式下快速捕获堆栈跟踪
现在我已经解决了大量的堆栈跟踪,现在想知道如何解析托管堆栈帧的符号信息.
对于原生C++方面,它相对简单 -
首先,您指定从哪里获取符号的过程:
HANDLE g_hProcess = GetCurrentProcess();
您可以使用代码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();
并初始化符号加载:
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; }
这看起来像工作.
但现在也管理堆栈帧.
我找到了两个接口:
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/
使用者:
https://github.com/okigan/CrashInsight(代码未触及4年)
混合模式stackwalk文章提供了很好的例子.
IXCLRDATAProcess/GetRuntimeNameByAddress
上面提到的两个链接也提到了.
由进程黑客使用(GPL许可证,C风格)
实现似乎存在于此:
https://github.com/dotnet/coreclr/blob/master/src/debug/daccess/daccess.cpp (基于提交此代码非常活跃)
ICorProfiler/???
(*)文章末尾提到.
方法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相关的任何问题.
在浏览了大量代码示例和接口之后,我了解到没有任何简单易用的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#pragma warning (disable: 4091) //dbghelp.h(1544): warning C4091: 'typedef ': ignored on left of '' when no variable is declared #include //xclrdata.h requires this #include "xclrdata.h" //IXCLRDataProcess #include //CComPtr #include //CString #include //TCONTEXT #include //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 clrDataProcess; CComPtr target; CComPtr debugClient; CComQIPtr debugControl; CComQIPtr debugSymbols; CComQIPtr 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//EnumProcessModules #include //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 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 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位工作原理 - 我还没有调用堆栈确定).
这段代码可能包含错误,但我会试着困扰它们并修复它们.
我收集了很多代码,请将此答案标记为有用.:-)