564 lines
15 KiB
C++
564 lines
15 KiB
C++
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// GCLog.cpp - GC diagnosis extension support
|
||
|
//
|
||
|
// Copyright Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#include "singx86.h"
|
||
|
#include "diagnose.h"
|
||
|
|
||
|
static char szSymbol[512];
|
||
|
|
||
|
class GCTracing : public EventingEnumerator
|
||
|
{
|
||
|
// High level diagnosis enumeration. Each override will skip walking through actuall entries
|
||
|
|
||
|
public:
|
||
|
|
||
|
GCTracing ();
|
||
|
|
||
|
// Override callouts
|
||
|
|
||
|
bool virtual TypeCallout(EventTypeEntry * entryDescriptor);
|
||
|
bool virtual ControllerCallout(ControllerObject *controller, bool finished);
|
||
|
bool virtual EntryCallout(EntryHeader *header, EventTypeEntry * entryDescriptor);
|
||
|
bool virtual MedataCallout(ControllerObject *controller);
|
||
|
void virtual SourcesCallout(SourceEntry * source);
|
||
|
bool virtual StorageCallout(UINT64 storageAddress, bool finished);
|
||
|
|
||
|
private:
|
||
|
void InitFile();
|
||
|
|
||
|
FILE * f;
|
||
|
|
||
|
ControllerObject * controller;
|
||
|
|
||
|
char fname[128];
|
||
|
|
||
|
UINT64 RootsHandle;
|
||
|
UINT64 GenerationsHandle;
|
||
|
UINT64 ObjectHandle;
|
||
|
UINT64 StackHandle;
|
||
|
UINT64 TypeHandle;
|
||
|
UINT64 IntervalHandle;
|
||
|
UINT64 AllocationHandle;
|
||
|
UINT64 FunctionHandle;
|
||
|
|
||
|
UINT64 EventStorageHandle;
|
||
|
UINT64 TypeStorageHandle;
|
||
|
bool ParsingMetadata;
|
||
|
|
||
|
};
|
||
|
|
||
|
#define MAX_PAGE_SIZE 4096
|
||
|
|
||
|
char PageBuffer[MAX_PAGE_SIZE];
|
||
|
|
||
|
|
||
|
void DumpMemory(PCSTR fname)
|
||
|
{
|
||
|
HRESULT status;
|
||
|
|
||
|
ULONG64 pageTable;
|
||
|
ULONG64 pageCount;
|
||
|
ULONG64 pageSize = MAX_PAGE_SIZE;
|
||
|
|
||
|
EXT_CHECK(FindBound("FlatPages::pageTable", &pageTable));
|
||
|
EXT_CHECK(FindBound("FlatPages::pageCount", &pageCount));
|
||
|
// EXT_CHECK(FindBound("nt!MemoryManager::PageSize", &pageSize)); not available
|
||
|
|
||
|
ExtOut("%p %p %p", pageTable, pageCount, pageSize);
|
||
|
|
||
|
FILE * f;
|
||
|
f = fopen( fname, "wb" );
|
||
|
|
||
|
if (f != NULL)
|
||
|
{
|
||
|
|
||
|
for (ULONG64 i = 0; i < pageCount; i++) {
|
||
|
|
||
|
ULONG Descriptor;
|
||
|
|
||
|
if ((i % 1000) == 0) {
|
||
|
ExtOut(".", pageSize * i, Descriptor);
|
||
|
}
|
||
|
|
||
|
if (g_ExtData->ReadVirtual(pageTable + i * sizeof(Descriptor),
|
||
|
&Descriptor, sizeof(Descriptor), NULL) != S_OK)
|
||
|
{
|
||
|
ExtOut("Error reading descriptor\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (g_ExtData->ReadVirtual(pageSize * i, PageBuffer, (ULONG)pageSize, NULL) == S_OK)
|
||
|
{
|
||
|
// ExtOut("%p %lx\n", pageSize * i, Descriptor);
|
||
|
fwrite(PageBuffer, (ULONG)pageSize, 1, f);
|
||
|
} else {
|
||
|
|
||
|
memset(PageBuffer, 0, (ULONG)pageSize);
|
||
|
fwrite(PageBuffer, (ULONG)pageSize, 1, f);
|
||
|
ExtOut("%p %lx error\n", pageSize * i, Descriptor);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
fclose(f);
|
||
|
}
|
||
|
return;
|
||
|
|
||
|
Exit:
|
||
|
// Error path
|
||
|
|
||
|
ExtOut("Invalid symbols information\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void WriteFileRange (int Base, int Size, PCSTR fname) {
|
||
|
FILE * f;
|
||
|
f = fopen( fname, "wb" );
|
||
|
|
||
|
if (f != NULL)
|
||
|
{
|
||
|
char buff[2048];
|
||
|
|
||
|
while (Size)
|
||
|
{
|
||
|
int readsize = Size;
|
||
|
if (readsize > sizeof(buff))
|
||
|
{
|
||
|
readsize = sizeof(buff);
|
||
|
}
|
||
|
|
||
|
if (g_ExtData->ReadVirtual(Base, buff, readsize, NULL) == S_OK)
|
||
|
{
|
||
|
fwrite(buff, readsize, 1, f);
|
||
|
}
|
||
|
Size -= readsize;
|
||
|
Base += readsize;
|
||
|
}
|
||
|
|
||
|
fclose(f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
EnableKernelGCProfiler(ULONG64 MemorySize, bool breakOnRecycle)
|
||
|
{
|
||
|
HRESULT status;
|
||
|
ULONG64 ExistingKernelBufferSize;
|
||
|
ULONG64 KernelBufferAddress;
|
||
|
BOOL OKSymbols = FALSE;
|
||
|
|
||
|
EXT_CHECK(FindBound("Class_Microsoft_Singularity_GCProfilerLogger::c_StorageHandle",
|
||
|
&KernelBufferAddress));
|
||
|
|
||
|
if (KernelBufferAddress != 0)
|
||
|
{
|
||
|
ULONG64 isProfiling;
|
||
|
EXT_CHECK(FindBound("Microsoft_Singularity_V1_Services_DiagnosisService::KernelBufferSize", &ExistingKernelBufferSize));
|
||
|
OKSymbols = TRUE;
|
||
|
ExtOut("GC Profiling is already in use for kernel (%ld MBytes buffer)\n",
|
||
|
ExistingKernelBufferSize/ 1024 / 1024);
|
||
|
ExtOut("The default value cannot be changed dynamically without rebooting the system\n");
|
||
|
EXT_CHECK(FindBound("System_GC::isProfiling", &isProfiling));
|
||
|
|
||
|
if (isProfiling == 0)
|
||
|
{
|
||
|
EXT_CHECK(SetBound("System_GC::isProfiling", 1));
|
||
|
ExtOut("Re-enabling with the existing settings\n");
|
||
|
}
|
||
|
|
||
|
goto Exit;
|
||
|
}
|
||
|
|
||
|
ExtOut("Enabling kernel GC profiler with %ld MBytes buffer size and %sbreak on recycle\n",
|
||
|
(long)(MemorySize/ 1024 / 1024),
|
||
|
(breakOnRecycle ? "" : "NO "));
|
||
|
EXT_CHECK(SetBound("Microsoft_Singularity_V1_Services_DiagnosisService::DeferedCommand", 1));
|
||
|
EXT_CHECK(SetBound("Microsoft_Singularity_V1_Services_DiagnosisService::KernelBufferSize", MemorySize));
|
||
|
EXT_CHECK(SetBound("Microsoft_Singularity_V1_Services_DiagnosisService::KernelProfileOptions", breakOnRecycle));
|
||
|
OKSymbols = TRUE;
|
||
|
|
||
|
Exit:
|
||
|
|
||
|
if (!OKSymbols)
|
||
|
{
|
||
|
ExtOut("Error accessing variables. Please check symbols for kernel\n");
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
DisableKernelGCProfiler()
|
||
|
{
|
||
|
HRESULT status;
|
||
|
ULONG64 KernelBufferAddress;
|
||
|
BOOL OKSymbols = FALSE;
|
||
|
|
||
|
EXT_CHECK(FindBound("gKernelBufferStart", &KernelBufferAddress));
|
||
|
OKSymbols = TRUE;
|
||
|
|
||
|
if (KernelBufferAddress == 0)
|
||
|
{
|
||
|
ExtOut("GC profiling is not enabled\n");
|
||
|
|
||
|
goto Exit;
|
||
|
}
|
||
|
|
||
|
EXT_CHECK(SetBound("System_GC::isProfiling", 0));
|
||
|
OKSymbols = TRUE;
|
||
|
ExtOut("Kernel GC profiling disabled\n");
|
||
|
|
||
|
Exit:
|
||
|
|
||
|
if (!OKSymbols)
|
||
|
{
|
||
|
ExtOut("Error accessing variables. Please check symbols for kernel\n");
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
void
|
||
|
EnableSIPGCProfiler(ULONG64 MemorySize, bool breakOnRecycle)
|
||
|
{
|
||
|
HRESULT status;
|
||
|
BOOL OKSymbols = FALSE;
|
||
|
|
||
|
EXT_CHECK(SetBound("Microsoft_Singularity_V1_Services_DiagnosisService::SIPBufferSize", MemorySize));
|
||
|
EXT_CHECK(SetBound("Microsoft_Singularity_V1_Services_DiagnosisService::SIPProfileOptions", breakOnRecycle));
|
||
|
|
||
|
if (MemorySize == 0)
|
||
|
{
|
||
|
ExtOut("Disabling SIP GC profiler for new processes\n");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ExtOut("Enabling SIP GC profiler with %ld MBytes buffer size and %sbreak on recycle\n",
|
||
|
(long)(MemorySize/ 1024 / 1024),
|
||
|
(breakOnRecycle ? "" : "NO "));
|
||
|
ExtOut("New processes starting from now on will have GC profiler enabled\n");
|
||
|
}
|
||
|
|
||
|
OKSymbols = TRUE;
|
||
|
|
||
|
Exit:
|
||
|
|
||
|
if (!OKSymbols)
|
||
|
{
|
||
|
ExtOut("Error accessing variables. Please check symbols for kernel\n");
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// GC CLR writter
|
||
|
//
|
||
|
|
||
|
PCSTR prefixName;
|
||
|
|
||
|
GCTracing::GCTracing ()
|
||
|
{
|
||
|
f = NULL;
|
||
|
EventStorageHandle = 0;
|
||
|
TypeStorageHandle = 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
void GCTracing::InitFile()
|
||
|
{
|
||
|
if (f == NULL) {
|
||
|
char fname[128];
|
||
|
|
||
|
char * controllerName = controller->GetControllerName();
|
||
|
|
||
|
_snprintf(fname, sizeof(fname), "%s_%s.LOG", prefixName, controllerName);
|
||
|
|
||
|
free(controllerName);
|
||
|
f = fopen( fname, "wt" );
|
||
|
}
|
||
|
}
|
||
|
bool GCTracing::TypeCallout(EventTypeEntry * entryDescriptor)
|
||
|
{
|
||
|
if (!EventingEnumerator::TypeCallout(entryDescriptor)) {
|
||
|
|
||
|
if (!EventingEnumerator::TypeCallout(SystemHeaderType)) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_stricmp(entryDescriptor->Name, "System.GC_ROOTS") == 0) {
|
||
|
|
||
|
RootsHandle = (UINT64)entryDescriptor->Key;
|
||
|
|
||
|
} else if (_stricmp(entryDescriptor->Name, "System.GC_GENERATIONS") == 0) {
|
||
|
|
||
|
GenerationsHandle = (UINT64)entryDescriptor->Key;
|
||
|
|
||
|
} else if (_stricmp(entryDescriptor->Name, "System.GC_OBJECT") == 0) {
|
||
|
|
||
|
ObjectHandle = (UINT64)entryDescriptor->Key;
|
||
|
|
||
|
} else if (_stricmp(entryDescriptor->Name, "System.GC_STACK") == 0) {
|
||
|
|
||
|
StackHandle = (UINT64)entryDescriptor->Key;
|
||
|
|
||
|
} else if (_stricmp(entryDescriptor->Name, "System.GC_TYPE") == 0) {
|
||
|
|
||
|
TypeHandle = (UINT64)entryDescriptor->Key;
|
||
|
|
||
|
} else if (_stricmp(entryDescriptor->Name, "System.GC_INTERVAL") == 0) {
|
||
|
|
||
|
IntervalHandle = (UINT64)entryDescriptor->Key;
|
||
|
|
||
|
} else if (_stricmp(entryDescriptor->Name, "System.GC_ALLOCATION") == 0) {
|
||
|
|
||
|
AllocationHandle = (UINT64)entryDescriptor->Key;
|
||
|
|
||
|
} else if (_stricmp(entryDescriptor->Name, "System.GC_FUNCTION") == 0) {
|
||
|
|
||
|
FunctionHandle = (UINT64)entryDescriptor->Key;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
}
|
||
|
|
||
|
bool GCTracing::ControllerCallout( ControllerObject *ctrl, bool finished)
|
||
|
{
|
||
|
if (finished) {
|
||
|
controller = NULL;
|
||
|
} else {
|
||
|
controller = ctrl;
|
||
|
}
|
||
|
if (f != NULL) fclose(f);
|
||
|
f = NULL;
|
||
|
EventStorageHandle = 0;
|
||
|
TypeStorageHandle = 0;
|
||
|
ParsingMetadata = TRUE;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
bool GCTracing::MedataCallout(ControllerObject *controller)
|
||
|
{
|
||
|
ParsingMetadata = FALSE;
|
||
|
if (TypeStorageHandle == 0) return FALSE;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
bool GCTracing::StorageCallout(UINT64 storageAddress, bool finished)
|
||
|
{
|
||
|
if (ParsingMetadata) return TRUE;
|
||
|
|
||
|
if (EventStorageHandle == storageAddress) {
|
||
|
|
||
|
ExtOut("Walking event storage %p\n", storageAddress);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
if (TypeStorageHandle == storageAddress) {
|
||
|
|
||
|
ExtOut("Walking type storage %p\n", storageAddress);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void GCTracing::SourcesCallout(SourceEntry * source)
|
||
|
{
|
||
|
if (MatchPatternString(exact, source->Name, "GC.Events")) {
|
||
|
EventStorageHandle = source->StorageHandle;
|
||
|
ExtOut("GC source: %s: (Key=%p, Storage=%p, ControlFlags=%lx)\n",
|
||
|
source->Name,
|
||
|
(UINT64)source->Key,
|
||
|
source->StorageHandle,
|
||
|
source->ControlFlags);
|
||
|
} else if (MatchPatternString(exact, source->Name, "GC.TypeDefinitions")) {
|
||
|
TypeStorageHandle = source->StorageHandle;
|
||
|
ExtOut("GC source: %s: (Key=%p, Storage=%p, ControlFlags=%lx)\n",
|
||
|
source->Name,
|
||
|
(UINT64)source->Key,
|
||
|
source->StorageHandle,
|
||
|
source->ControlFlags);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool GCTracing::EntryCallout(EntryHeader *header, EventTypeEntry * entryDescriptor)
|
||
|
{
|
||
|
HRESULT status;
|
||
|
|
||
|
if (entryDescriptor == NULL) {
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (entryDescriptor->Key == TypeHandle) {
|
||
|
|
||
|
InitFile();
|
||
|
PCHAR str = entryDescriptor->GetExtendedString(1);
|
||
|
|
||
|
fprintf(f, "t 0x%p 0 %s\n", (ULONG)entryDescriptor->GetField("TypeId")->GetFieldNumericValue(), str);
|
||
|
|
||
|
} else if (entryDescriptor->Key == FunctionHandle) {
|
||
|
InitFile();
|
||
|
|
||
|
ULONG64 displacement = 0;
|
||
|
ULONG64 funcPtr = entryDescriptor->GetField("IP")->GetFieldNumericValue();
|
||
|
status = g_ExtSymbols->GetNameByOffset(funcPtr,
|
||
|
szSymbol,
|
||
|
arrayof(szSymbol),
|
||
|
NULL,
|
||
|
&displacement);
|
||
|
if (status == S_OK) {
|
||
|
fprintf(f, "f 0x%p %s+0x%I64x_0x%p (UNKNOWN ARGUMENTS) 0 0\n",
|
||
|
(ULONG)entryDescriptor->GetField("FnIdx")->GetFieldNumericValue(),
|
||
|
szSymbol,
|
||
|
displacement,
|
||
|
(ULONG)funcPtr);
|
||
|
} else {
|
||
|
|
||
|
fprintf(f, "f 0x%p FUNCTION_EIP 0x%p (UNKNOWN ARGUMENTS) 0 0\n",
|
||
|
(ULONG)entryDescriptor->GetField("FnIdx")->GetFieldNumericValue(),
|
||
|
(ULONG)funcPtr);
|
||
|
}
|
||
|
} else if (entryDescriptor->Key == IntervalHandle) {
|
||
|
|
||
|
InitFile();
|
||
|
fprintf(f, "i 0x%I64x\n", entryDescriptor->GetField("Delta")->GetFieldNumericValue());
|
||
|
|
||
|
} else if (entryDescriptor->Key == AllocationHandle) {
|
||
|
|
||
|
InitFile();
|
||
|
|
||
|
ULONG tid;
|
||
|
ULONG_PTR ctx = SWITCH_TO_METADATA_CURSOR();
|
||
|
|
||
|
FieldEntry * field = SystemHeaderType->GetField("TID");
|
||
|
tid = (ULONG)field->GetFieldNumericValue();
|
||
|
|
||
|
RESTORE_CURSOR(ctx);
|
||
|
|
||
|
fprintf(f, "! 0x%p 0x%p 0x%p\n",
|
||
|
tid,
|
||
|
(ULONG)entryDescriptor->GetField("Object")->GetFieldNumericValue(),
|
||
|
(ULONG)entryDescriptor->GetField("StkNo")->GetFieldNumericValue());
|
||
|
|
||
|
} else if (entryDescriptor->Key == StackHandle) {
|
||
|
|
||
|
InitFile();
|
||
|
fprintf(f, "n 0x%lx 1 0x%lx 0x%lx",
|
||
|
(ULONG)entryDescriptor->GetField("StkNo")->GetFieldNumericValue(),
|
||
|
(ULONG)entryDescriptor->GetField("TypeId")->GetFieldNumericValue(),
|
||
|
(ULONG)entryDescriptor->GetField("Size")->GetFieldNumericValue());
|
||
|
FieldEntry * array = entryDescriptor->GetField("fncList");
|
||
|
void * buffer = entryDescriptor->GetFieldArray((int)array->GetFieldNumericValue());
|
||
|
|
||
|
int count = array->GetArraySize(buffer);
|
||
|
|
||
|
if (count >= 2) {
|
||
|
|
||
|
for (int i = 0; i < count - 2; i++) {
|
||
|
ULONG fnc = (ULONG)array->GetFieldNumericValue(buffer, i);
|
||
|
fprintf(f, " 0x%lx", fnc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fprintf(f, "\n");
|
||
|
} else if (entryDescriptor->Key == RootsHandle) {
|
||
|
|
||
|
InitFile();
|
||
|
fprintf(f, "r");
|
||
|
|
||
|
FieldEntry * array = entryDescriptor->GetField("Roots");
|
||
|
void * buffer = entryDescriptor->GetFieldArray((int)array->GetFieldNumericValue());
|
||
|
|
||
|
int count = array->GetArraySize(buffer);
|
||
|
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
|
||
|
ULONG Address = (ULONG)array->GetFieldNumericValue(buffer, i);
|
||
|
fprintf(f, " 0x%lx", Address);
|
||
|
}
|
||
|
|
||
|
fprintf(f, "\n");
|
||
|
} else if (entryDescriptor->Key == GenerationsHandle) {
|
||
|
|
||
|
InitFile();
|
||
|
fprintf(f, "g");
|
||
|
|
||
|
FieldEntry * array = entryDescriptor->GetField("Genrations");
|
||
|
void * buffer = entryDescriptor->GetFieldArray((int)array->GetFieldNumericValue());
|
||
|
|
||
|
int count = array->GetArraySize(buffer);
|
||
|
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
|
||
|
ULONG Address = (ULONG)array->GetFieldNumericValue(buffer, i);
|
||
|
fprintf(f, " %ld", Address);
|
||
|
}
|
||
|
fprintf(f, "\n");
|
||
|
|
||
|
} else if (entryDescriptor->Key == ObjectHandle) {
|
||
|
|
||
|
InitFile();
|
||
|
fprintf(f, "o");
|
||
|
|
||
|
FieldEntry * array = entryDescriptor->GetField("ParameterList");
|
||
|
void * buffer = entryDescriptor->GetFieldArray((int)array->GetFieldNumericValue());
|
||
|
|
||
|
int count = array->GetArraySize(buffer);
|
||
|
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
|
||
|
ULONG Address = (ULONG)array->GetFieldNumericValue(buffer, i);
|
||
|
fprintf(f, " 0x%lx", Address);
|
||
|
}
|
||
|
|
||
|
fprintf(f, "\n");
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
WriteCLRProfileFile(PCSTR fname, bool Kernel)
|
||
|
{
|
||
|
prefixName = fname;
|
||
|
|
||
|
TypeEntryCollector * collector = new TypeEntryCollector(0, "Timestamp");
|
||
|
if (collector) {
|
||
|
|
||
|
GCTracing * dump = new GCTracing();
|
||
|
|
||
|
if (dump) {
|
||
|
|
||
|
collector->TypeHandle = 0;
|
||
|
collector->GroupByController = TRUE;
|
||
|
collector->cascadeEnumerator = dump;
|
||
|
|
||
|
WalkTracingDatabase(collector, FALSE);
|
||
|
|
||
|
delete dump;
|
||
|
}
|
||
|
|
||
|
delete collector;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|