/ / Verwaltete und native Stack-Ablaufverfolgung auflösen - welche API soll verwendet werden? - Windows, Stack-Trace, Mixed-Mode

Verwalten Sie den verwalteten und nativen Stack-Trace - welche API soll verwendet werden? - Windows, Stack-Trace, Mixed-Mode

Dies ist eine Fortsetzung meiner vorherigen Frage - sozusagen Phase 2.

Die erste Frage war hier: Schnelle Erfassung des Stack-Trace im Windows / 64-Bit / Mixed-Modus

Jetzt habe ich eine große Anzahl von Stapelspuren aufgelöst und mich gefragt, wie Symbolinformationen von verwalteten Stapelrahmen aufgelöst werden können.

Für native C ++ - Seite ist es relativ einfach -

Zuerst legen Sie fest, von welchem ​​Prozess Symbole verwendet werden sollen:

HANDLE g_hProcess = GetCurrentProcess();

Wo Sie den Prozess zur Laufzeit mit einem Code-Snipet wie folgt ersetzen können:

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();

Und initialisieren Sie das Laden von Symbolen:

void InitSymbolLoad()
{
SymInitialize(g_hProcess, NULL, TRUE);
DWORD dwFlags = SymGetOptions();
SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_NO_IMAGE_SEARCH);
}

Und danach lösen Sie das native Symbol irgendwie so auf:

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;

}

Das sieht nach Arbeit aus.

Aber jetzt auch gestapelte Stack-Frames.

Es gibt zwei Schnittstellen, die ich gefunden habe:

  1. IDebugClient / GetNameByOffset

Erwähnt in:

Benutzt von:

  • https://github.com/okigan/CrashInsight (Code seit 4 Jahren nicht mehr berührt)
  • Der Stackwalk-Artikel im gemischten Modus bietet ein gutes Beispiel.

    1. IXCLRDATAProcess / GetRuntimeNameByAddress
  • Erwähnt auch in zwei Links oben.

  • Wird vom Prozesshacker verwendet (GPL-Lizenz, C-Stil)

Die Implementierung scheint hier zu liegen:

Erwähnt am Ende des (*) Artikels.

Ansatz 1 scheint ziemlich altmodisch zu sein, auch Artikel (*) erwähnt einige Probleme damit.

Ansatz 3 erfordert wahrscheinlich eine eingehende Analyse der Profilierungs-APIs. Es gibt auch eine Erwähnung, die ich über diese APIs gefunden habe - hier:

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: Alle diese Header-Dateien wurden entfernt. Sie sind alle die COM-Schnittstelle im einheitlichen Modus zu .NET.

Diesen Satz verstehe ich nicht ganz. Sind diese Schnittstellen tot oder ersetzt oder was ist mit ihnen passiert?

Ich denke also, basierend auf meinem kurzen Analyseansatz 2 ist nur eine gute / lebendige API-Schnittstelle, die es wert ist, verwendet zu werden? Sind Sie auf Probleme im Zusammenhang mit diesen APIs gestoßen?

Antworten:

5 für die Antwort № 1

Nach dem Durchlaufen einer großen Anzahl von Codebeispielenund Schnittstellen habe ich verstanden, dass es keine einfach zu verwendende API-Schnittstelle gibt. Code und APIs, die für natives C ++ entwickelt wurden, funktionieren nur mit nativem C ++, und Code und APIs, die für verwalteten Code entwickelt wurden, funktionieren nur mit verwaltetem Code.

Es gibt zusätzlich ein Problem beim Auflösen des StapelsTrace danach funktioniert möglicherweise nicht. Sie sehen, Entwickler können Code mithilfe der Jit-Engine / des IL-Generators dynamisch im laufenden Betrieb generieren und ebenfalls entsorgen. Nachdem Sie also "void *" / Anweisungsadresse haben, sollten Sie symbolische Informationen sofort und nicht danach auflösen. Aber ich werde dies vorerst belassen, gehe davon aus, dass der Entwickler kein allzu ausgefallener Codierer ist und nicht ständig neuen Code generiert und entsorgt, und FreeLibrary wird nicht ohne Notwendigkeit aufgerufen. (Vielleicht kann ich dies später ansprechen, wenn ich "Ich werde FreeLibrary / Jit-Komponenten einbinden.)

Das Auflösen des Funktionsnamens war ziemlich trivial.durch IXCLRDataProcess mit ein wenig Magie und Glück - ich konnte Funktionsnamen erhalten, aber ich möchte es tiefer erweitern - in exakten Quellcodepfad und Quellcodezeile, in der Code ausgeführt wurde, und dies stellte sich als recht komplexe Funktionalität heraus erreichen.

Schließlich habe ich den Quellcode gefunden, in dem so etwas ausgeführt wurde - und das wurde hier getan:

https://github.com/dotnet/coreclr/blob/master/src/ToolBox/SOS/Strike/util.cpp

GetLineByOffset ist der Funktionsname in dieser Datei.

Ich habe diesen Quellcode, den ich jetzt hier anhänge, analysiert, neu abgestimmt und meine eigene Lösung erstellt:

Aktualisierten Code finden Sie hier: https://sourceforge.net/projects/diagnostic/

Aber hier ist nur eine Momentaufnahme des gleichen Codes, der zu einem bestimmten Zeitpunkt aufgenommen wurde:

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;

Bisher nur mit einem kleineren Code getestet, nur 64-Bit (Zweifel, dass 32-Bit überhaupt funktioniert - ich habe noch keine Call-Stack-Bestimmung dafür).

Es ist möglich, dass dieser Code Fehler enthält, aber ich werde versuchen, sie aufzuspüren und zu beheben.

Ich habe so viel Code geerntet, dass Sie diese Antwort bitte als nützlich markieren. :-)


0 für die Antwort № 2

Alternative 1 / IDebugClient / GetNameByOffset istNicht für die verwaltete Stapelverfolgung verwendbar, kann nur für nativen Code verwendet werden. Wie für den nativen Aufrufstapel habe ich oben bereits ein Demo-Code-Snipet. Nicht sicher, ob IDebugClient mehr bietet als SymGetLineFromAddr64 / SymFromAddr nicht - nicht sicher.


0 für die Antwort № 3

Hier ist eine Antwort von Jan Kotas dazu:

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.

Was ich kurz ausprobiert habe - dies erfordert Visual Studio 2015 / C # 6.0.

Auch diese Technik ist unbrauchbar. Mögen .net StackTrace / StackFrame lösen Aufrufstapel- und Symbolinformationen sofort auf - und ich muss die Symbolinformationen anschließend auflösen (nach der Stapelverfolgungserfassung).