解决托pipe和本地堆栈跟踪 – 要使用哪个API?

这是我前一个问题的继续 – 第二阶段可以这么说。

第一个问题是: 在Windows / 64位/混合模式下快速捕获堆栈跟踪

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

对于本机C ++方面来说,它相对简单 –

首先你要指定从哪里取符号的过程:

HANDLE g_hProcess = GetCurrentProcess(); 

在这里你可以使用代码snipet在运行时replace进程:

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

这看起来像工作。

但是现在也pipe理栈帧。

有两个接口,我find了:

  1. 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文章提供了很好的例子。

    1. IXCLRDATAProcess / GetRuntimeNameByAddress
  • 上面还提到了两个链接。

  • 过程黑客使用(GPL许可证,C风格)

实现似乎驻留在这里:

  • https://github.com/dotnet/coreclr/blob/master/src/debug/daccess/daccess.cpp (基于提交这个代码是相当活着)

    1. ICorProfiler / ???

(*)文章末尾提及。

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

方法3可能需要对分析API进行深入分析。 还有一个提到我已经find关于这些API的 – 在这里:

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接口。

这句话我不完全明白。 那些接口是死的还是被replace的?或者他们发生了什么?

所以我想根据我的简要分析方法2是只有好的/活着的API接口,值得使用? 你有没有遇到任何有关这些API的问题。

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

另外还有解决堆栈跟踪的问题可能不起作用。 你看 – 开发人员可以使用Jit engine / IL Generator动态生成代码并进行处理 – 所以在你有了“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> //EnumProcessmodulees #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( !EnumProcessmoduleesEx( 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( GetmoduleeFileNameEx(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<IXCLRDatamodulee> module; hr = method->GetTokenAndScope(&methodToken, &module); if( !module ) goto lDefaultFallback; // // Retrieve ImageInfo associated with the IXCLRDatamodulee 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->GetmoduleeByOffset((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位工作 – 我还没有调用堆栈确定)。

这段代码可能包含错误,但是我会试着去纠缠它们并修复它们。

我收获了太多的代码,请标记这个答案是有用的。 🙂

替代方案1 / IDebugClient / GetNameByOffset不可用于托管堆栈跟踪,它只能用于本机代码 – 就本机调用堆栈而言,上面已经有演示代码snipet。 不知道是否IDebugClient提供的东西比SymGetLineFromAddr64 / SymFromAddr不提供 – 不知道。

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正在解决调用堆栈和符号信息 – 我需要解决符号信息后(堆栈跟踪捕获后)。