第一个问题在这里:Fast capture stack trace on windows / 64-bit / mixed mode
现在我已经解决了大量的堆栈跟踪,现在想知道如何解析托管堆栈帧的符号信息.
对于原生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,&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 / ???
(*)文章末尾提到.
方法3可能需要对分析API进行深入分析.
我还发现了一些关于这些API的内容 – 在这里:
· 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.
这句话我不太明白.这些接口是否已经死亡或被替换或者发生了什么?
所以我想基于我的简要分析方法2只是好/活的API接口值得使用?您是否遇到过与这些api相关的任何问题.
此外还有解决堆栈跟踪的问题可能无法解决.你看 – 开发人员可以使用Jit引擎/ IL生成器动态生成代码,并将其配置 – 所以在你有“void *”/指令地址后 – 你应该立即解决符号信息,而不是之后.但是我暂时不会这样做,会认为开发人员不是太花哨的编码器而且不会一直生成和处理新的代码,FreeLibrary也不会在没有需要的情况下被调用. (如果我挂钩FreeLibrary / Jit组件,我可以稍后解决这个问题.)
解析函数名称非常简单,通过IXCLRDataProcess带来一点魔力和运气 – 我能够获得函数名称,但是 – 我想将它更深入地扩展到确切的源代码路径和代码执行的源代码行,这个变成了相当复杂的功能.
最后,我找到了执行此类操作的源代码 – 这是在这里完成的:
https://github.com/dotnet/coreclr/blob/master/src/ToolBox/SOS/Strike/util.cpp
我已经分析,重新调整并从源代码中创建了我自己的解决方案,我现在正在这里附加:
可在此处找到更新的代码:
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,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,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,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)"),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,&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,&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,&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,sizeof(path),&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,&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,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;