gpt4 book ai didi

windows - 解决托管和 native 堆栈跟踪 - 使用哪个 API?

转载 作者:可可西里 更新时间:2023-11-01 14:02:50 30 4
gpt4 key购买 nike

这是我之前问题的延续 - 可以说是第 2 阶段。

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

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

对于 native 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);
}

然后解析 native symbol ,有点像这样:

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;

}

这看起来像工作。

但现在还管理栈帧。

我找到了两个接口(interface):

  1. IDebugClient/GetNameByOffset

提到:

使用者:

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

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

  • 由 process hacker 使用(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: All of these header files have been removed. They are all the native mode COM interface to .NET.

这句话我没有完全理解。这些接口(interface)是死了还是被替换了,或者它们发生了什么?

所以我猜根据我的简要分析方法 2 是唯一值得使用的好/有效 API 接口(interface)吗?您是否遇到过与这些 API 相关的任何问题。

最佳答案

在浏览了大量的代码示例和接口(interface)之后,我了解到没有任何易于使用的 API 接口(interface)。为 native C++ 开发的代码和 API 仅适用于 native 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 位是否工作 - 我还没有确定调用堆栈)。

此代码可能包含错误,但我会设法找出并修复它们。

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

关于windows - 解决托管和 native 堆栈跟踪 - 使用哪个 API?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34733155/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com