/* * Process Hacker .NET Tools - * .NET Assemblies property page * * Copyright (C) 2011-2015 wj32 * Copyright (C) 2016 dmex * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include "dn.h" #include "clretw.h" #include #include #define DNATNC_STRUCTURE 0 #define DNATNC_ID 1 #define DNATNC_FLAGS 2 #define DNATNC_PATH 3 #define DNATNC_NATIVEPATH 4 #define DNATNC_MAXIMUM 5 #define DNA_TYPE_CLR 1 #define DNA_TYPE_APPDOMAIN 2 #define DNA_TYPE_ASSEMBLY 3 #define UPDATE_MSG (WM_APP + 1) typedef struct _DNA_NODE { PH_TREENEW_NODE Node; struct _DNA_NODE *Parent; PPH_LIST Children; PH_STRINGREF TextCache[DNATNC_MAXIMUM]; ULONG Type; BOOLEAN IsFakeClr; union { struct { USHORT ClrInstanceID; PPH_STRING DisplayName; } Clr; struct { ULONG64 AppDomainID; PPH_STRING DisplayName; } AppDomain; struct { ULONG64 AssemblyID; PPH_STRING FullyQualifiedAssemblyName; } Assembly; } u; PH_STRINGREF StructureText; PPH_STRING IdText; PPH_STRING FlagsText; PPH_STRING PathText; PPH_STRING NativePathText; } DNA_NODE, *PDNA_NODE; typedef struct _ASMPAGE_CONTEXT { HWND WindowHandle; PPH_PROCESS_ITEM ProcessItem; ULONG ClrVersions; PDNA_NODE ClrV2Node; HWND TnHandle; PPH_STRING TnErrorMessage; PPH_LIST NodeList; PPH_LIST NodeRootList; } ASMPAGE_CONTEXT, *PASMPAGE_CONTEXT; typedef struct _ASMPAGE_QUERY_CONTEXT { HANDLE WindowHandle; HANDLE ProcessId; ULONG ClrVersions; PDNA_NODE ClrV2Node; BOOLEAN TraceClrV2; ULONG TraceResult; LONG TraceHandleActive; TRACEHANDLE TraceHandle; PPH_LIST NodeList; PPH_LIST NodeRootList; } ASMPAGE_QUERY_CONTEXT, *PASMPAGE_QUERY_CONTEXT; typedef struct _FLAG_DEFINITION { PWSTR Name; ULONG Flag; } FLAG_DEFINITION, *PFLAG_DEFINITION; typedef ULONG (__stdcall *_EnableTraceEx)( _In_ LPCGUID ProviderId, _In_opt_ LPCGUID SourceId, _In_ TRACEHANDLE TraceHandle, _In_ ULONG IsEnabled, _In_ UCHAR Level, _In_ ULONGLONG MatchAnyKeyword, _In_ ULONGLONG MatchAllKeyword, _In_ ULONG EnableProperty, _In_opt_ PEVENT_FILTER_DESCRIPTOR EnableFilterDesc ); VOID DestroyDotNetTraceQuery( _In_ PASMPAGE_QUERY_CONTEXT Context ); INT_PTR CALLBACK DotNetAsmPageDlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ); static UNICODE_STRING DotNetLoggerName = RTL_CONSTANT_STRING(L"PhDnLogger"); static GUID ClrRuntimeProviderGuid = { 0xe13c0d23, 0xccbc, 0x4e12, { 0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4 } }; static GUID ClrRundownProviderGuid = { 0xa669021c, 0xc450, 0x4609, { 0xa0, 0x35, 0x5a, 0xf5, 0x9a, 0xf4, 0xdf, 0x18 } }; static FLAG_DEFINITION AppDomainFlagsMap[] = { { L"Default", 0x1 }, { L"Executable", 0x2 }, { L"Shared", 0x4 } }; static FLAG_DEFINITION AssemblyFlagsMap[] = { { L"DomainNeutral", 0x1 }, { L"Dynamic", 0x2 }, { L"Native", 0x4 }, { L"Collectible", 0x8 } }; static FLAG_DEFINITION ModuleFlagsMap[] = { { L"DomainNeutral", 0x1 }, { L"Native", 0x2 }, { L"Dynamic", 0x4 }, { L"Manifest", 0x8 } }; static FLAG_DEFINITION StartupModeMap[] = { { L"ManagedExe", 0x1 }, { L"HostedCLR", 0x2 }, { L"IjwDll", 0x4 }, { L"ComActivated", 0x8 }, { L"Other", 0x10 } }; static FLAG_DEFINITION StartupFlagsMap[] = { { L"CONCURRENT_GC", 0x1 }, { L"LOADER_OPTIMIZATION_SINGLE_DOMAIN", 0x2 }, { L"LOADER_OPTIMIZATION_MULTI_DOMAIN", 0x4 }, { L"LOADER_SAFEMODE", 0x10 }, { L"LOADER_SETPREFERENCE", 0x100 }, { L"SERVER_GC", 0x1000 }, { L"HOARD_GC_VM", 0x2000 }, { L"SINGLE_VERSION_HOSTING_INTERFACE", 0x4000 }, { L"LEGACY_IMPERSONATION", 0x10000 }, { L"DISABLE_COMMITTHREADSTACK", 0x20000 }, { L"ALWAYSFLOW_IMPERSONATION", 0x40000 }, { L"TRIM_GC_COMMIT", 0x80000 }, { L"ETW", 0x100000 }, { L"SERVER_BUILD", 0x200000 }, { L"ARM", 0x400000 } }; VOID AddAsmPageToPropContext( _In_ PPH_PLUGIN_PROCESS_PROPCONTEXT PropContext ) { PhAddProcessPropPage( PropContext->PropContext, PhCreateProcessPropPageContextEx(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_PROCDOTNETASM), DotNetAsmPageDlgProc, NULL) ); } PPH_STRING FlagsToString( _In_ ULONG Flags, _In_ PFLAG_DEFINITION Map, _In_ ULONG SizeOfMap ) { PH_STRING_BUILDER sb; ULONG i; PhInitializeStringBuilder(&sb, 100); for (i = 0; i < SizeOfMap / sizeof(FLAG_DEFINITION); i++) { if (Flags & Map[i].Flag) { PhAppendStringBuilder2(&sb, Map[i].Name); PhAppendStringBuilder2(&sb, L", "); } } if (sb.String->Length != 0) PhRemoveEndStringBuilder(&sb, 2); return PhFinalStringBuilderString(&sb); } PDNA_NODE AddNode( _Inout_ PASMPAGE_QUERY_CONTEXT Context ) { PDNA_NODE node; node = PhAllocate(sizeof(DNA_NODE)); memset(node, 0, sizeof(DNA_NODE)); PhInitializeTreeNewNode(&node->Node); memset(node->TextCache, 0, sizeof(PH_STRINGREF) * DNATNC_MAXIMUM); node->Node.TextCache = node->TextCache; node->Node.TextCacheSize = DNATNC_MAXIMUM; node->Children = PhCreateList(1); PhAddItemList(Context->NodeList, node); return node; } VOID DestroyNode( _In_ PDNA_NODE Node ) { PhDereferenceObject(Node->Children); if (Node->Type == DNA_TYPE_CLR) { if (Node->u.Clr.DisplayName) PhDereferenceObject(Node->u.Clr.DisplayName); } else if (Node->Type == DNA_TYPE_APPDOMAIN) { if (Node->u.AppDomain.DisplayName) PhDereferenceObject(Node->u.AppDomain.DisplayName); } else if (Node->Type == DNA_TYPE_ASSEMBLY) { if (Node->u.Assembly.FullyQualifiedAssemblyName) PhDereferenceObject(Node->u.Assembly.FullyQualifiedAssemblyName); } if (Node->IdText) PhDereferenceObject(Node->IdText); if (Node->FlagsText) PhDereferenceObject(Node->FlagsText); if (Node->PathText) PhDereferenceObject(Node->PathText); if (Node->NativePathText) PhDereferenceObject(Node->NativePathText); PhFree(Node); } PDNA_NODE AddFakeClrNode( _In_ PASMPAGE_QUERY_CONTEXT Context, _In_ PWSTR DisplayName ) { PDNA_NODE node; node = AddNode(Context); node->Type = DNA_TYPE_CLR; node->IsFakeClr = TRUE; node->u.Clr.ClrInstanceID = 0; node->u.Clr.DisplayName = NULL; PhInitializeStringRef(&node->StructureText, DisplayName); PhAddItemList(Context->NodeRootList, node); return node; } PDNA_NODE FindClrNode( _In_ PASMPAGE_QUERY_CONTEXT Context, _In_ USHORT ClrInstanceID ) { ULONG i; for (i = 0; i < Context->NodeRootList->Count; i++) { PDNA_NODE node = Context->NodeRootList->Items[i]; if (!node->IsFakeClr && node->u.Clr.ClrInstanceID == ClrInstanceID) return node; } return NULL; } PDNA_NODE FindAppDomainNode( _In_ PDNA_NODE ClrNode, _In_ ULONG64 AppDomainID ) { ULONG i; for (i = 0; i < ClrNode->Children->Count; i++) { PDNA_NODE node = ClrNode->Children->Items[i]; if (node->u.AppDomain.AppDomainID == AppDomainID) return node; } return NULL; } PDNA_NODE FindAssemblyNode( _In_ PDNA_NODE AppDomainNode, _In_ ULONG64 AssemblyID ) { ULONG i; for (i = 0; i < AppDomainNode->Children->Count; i++) { PDNA_NODE node = AppDomainNode->Children->Items[i]; if (node->u.Assembly.AssemblyID == AssemblyID) return node; } return NULL; } PDNA_NODE FindAssemblyNode2( _In_ PDNA_NODE ClrNode, _In_ ULONG64 AssemblyID ) { ULONG i; ULONG j; for (i = 0; i < ClrNode->Children->Count; i++) { PDNA_NODE appDomainNode = ClrNode->Children->Items[i]; for (j = 0; j < appDomainNode->Children->Count; j++) { PDNA_NODE assemblyNode = appDomainNode->Children->Items[j]; if (assemblyNode->u.Assembly.AssemblyID == AssemblyID) return assemblyNode; } } return NULL; } static int __cdecl AssemblyNodeNameCompareFunction( _In_ const void *elem1, _In_ const void *elem2 ) { PDNA_NODE node1 = *(PDNA_NODE *)elem1; PDNA_NODE node2 = *(PDNA_NODE *)elem2; return PhCompareStringRef(&node1->StructureText, &node2->StructureText, TRUE); } PDNA_NODE DotNetAsmGetSelectedEntry( _In_ PASMPAGE_CONTEXT Context ) { if (Context->NodeList) { for (ULONG i = 0; i < Context->NodeList->Count; i++) { PDNA_NODE node = Context->NodeList->Items[i]; if (node->Node.Selected) { return node; } } } return NULL; } VOID DotNetAsmShowContextMenu( _In_ PASMPAGE_CONTEXT Context, _In_ POINT Location ) { PDNA_NODE node; PPH_EMENU menu; PPH_EMENU_ITEM selectedItem; if (!(node = DotNetAsmGetSelectedEntry(Context))) return; menu = PhCreateEMenu(); PhLoadResourceEMenuItem(menu, PluginInstance->DllBase, MAKEINTRESOURCE(IDR_ASSEMBLY_MENU), 0); if (PhIsNullOrEmptyString(node->PathText) || !RtlDoesFileExists_U(node->PathText->Buffer)) { PhSetFlagsEMenuItem(menu, ID_CLR_OPENFILELOCATION, PH_EMENU_DISABLED, PH_EMENU_DISABLED); } selectedItem = PhShowEMenu( menu, Context->WindowHandle, PH_EMENU_SHOW_LEFTRIGHT, PH_ALIGN_LEFT | PH_ALIGN_TOP, Location.x, Location.y ); if (selectedItem && selectedItem->Id != -1) { switch (selectedItem->Id) { case ID_CLR_OPENFILELOCATION: { if (!PhIsNullOrEmptyString(node->PathText) && RtlDoesFileExists_U(node->PathText->Buffer)) { PhShellExploreFile(Context->WindowHandle, node->PathText->Buffer); } } break; case ID_CLR_COPY: { PPH_STRING text; text = PhGetTreeNewText(Context->TnHandle, 0); PhSetClipboardString(Context->TnHandle, &text->sr); PhDereferenceObject(text); } break; } } PhDestroyEMenu(menu); } BOOLEAN NTAPI DotNetAsmTreeNewCallback( _In_ HWND hwnd, _In_ PH_TREENEW_MESSAGE Message, _In_opt_ PVOID Parameter1, _In_opt_ PVOID Parameter2, _In_opt_ PVOID Context ) { PASMPAGE_CONTEXT context; PDNA_NODE node; context = Context; switch (Message) { case TreeNewGetChildren: { PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; node = (PDNA_NODE)getChildren->Node; if (!node) { getChildren->Children = (PPH_TREENEW_NODE *)context->NodeRootList->Items; getChildren->NumberOfChildren = context->NodeRootList->Count; } else { if (node->Type == DNA_TYPE_APPDOMAIN || node == context->ClrV2Node) { // Sort the assemblies. qsort(node->Children->Items, node->Children->Count, sizeof(PVOID), AssemblyNodeNameCompareFunction); } getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items; getChildren->NumberOfChildren = node->Children->Count; } } return TRUE; case TreeNewIsLeaf: { PPH_TREENEW_IS_LEAF isLeaf = Parameter1; node = (PDNA_NODE)isLeaf->Node; isLeaf->IsLeaf = node->Children->Count == 0; } return TRUE; case TreeNewGetCellText: { PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; node = (PDNA_NODE)getCellText->Node; switch (getCellText->Id) { case DNATNC_STRUCTURE: getCellText->Text = node->StructureText; break; case DNATNC_ID: getCellText->Text = PhGetStringRef(node->IdText); break; case DNATNC_FLAGS: getCellText->Text = PhGetStringRef(node->FlagsText); break; case DNATNC_PATH: getCellText->Text = PhGetStringRef(node->PathText); break; case DNATNC_NATIVEPATH: getCellText->Text = PhGetStringRef(node->NativePathText); break; default: return FALSE; } getCellText->Flags = TN_CACHE; } return TRUE; case TreeNewGetCellTooltip: { PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1; node = (PDNA_NODE)getCellTooltip->Node; if (getCellTooltip->Column->Id != 0 || node->Type != DNA_TYPE_ASSEMBLY) return FALSE; if (!PhIsNullOrEmptyString(node->u.Assembly.FullyQualifiedAssemblyName)) { getCellTooltip->Text = node->u.Assembly.FullyQualifiedAssemblyName->sr; getCellTooltip->Unfolding = FALSE; } else { return FALSE; } } return TRUE; case TreeNewKeyDown: { PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; switch (keyEvent->VirtualKey) { case 'C': if (GetKeyState(VK_CONTROL) < 0) SendMessage(context->WindowHandle, WM_COMMAND, ID_COPY, 0); break; } } return TRUE; case TreeNewContextMenu: { PPH_TREENEW_MOUSE_EVENT mouseEvent = Parameter1; DotNetAsmShowContextMenu(context, mouseEvent->Location); } return TRUE; } return FALSE; } ULONG StartDotNetTrace( _Out_ PTRACEHANDLE SessionHandle, _Out_ PEVENT_TRACE_PROPERTIES *Properties ) { ULONG result; ULONG bufferSize; PEVENT_TRACE_PROPERTIES properties; TRACEHANDLE sessionHandle; bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + DotNetLoggerName.Length + sizeof(WCHAR); properties = PhAllocate(bufferSize); memset(properties, 0, sizeof(EVENT_TRACE_PROPERTIES)); properties->Wnode.BufferSize = bufferSize; properties->Wnode.ClientContext = 2; // System time clock resolution properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_USE_PAGED_MEMORY; properties->LogFileNameOffset = 0; properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); result = StartTrace(&sessionHandle, DotNetLoggerName.Buffer, properties); if (result == ERROR_SUCCESS) { *SessionHandle = sessionHandle; *Properties = properties; return ERROR_SUCCESS; } else if (result == ERROR_ALREADY_EXISTS) { // Session already exists, so use that. Get the existing session handle. result = ControlTrace(0, DotNetLoggerName.Buffer, properties, EVENT_TRACE_CONTROL_QUERY); if (result != ERROR_SUCCESS) { PhFree(properties); return result; } *SessionHandle = properties->Wnode.HistoricalContext; *Properties = properties; return ERROR_SUCCESS; } else { PhFree(properties); return result; } } ULONG NTAPI DotNetBufferCallback( _In_ PEVENT_TRACE_LOGFILE Buffer ) { return TRUE; } VOID NTAPI DotNetEventCallback( _In_ PEVENT_RECORD EventRecord ) { PASMPAGE_QUERY_CONTEXT context = EventRecord->UserContext; PEVENT_HEADER eventHeader = &EventRecord->EventHeader; PEVENT_DESCRIPTOR eventDescriptor = &eventHeader->EventDescriptor; if (UlongToHandle(eventHeader->ProcessId) == context->ProcessId) { // .NET 4.0+ switch (eventDescriptor->Id) { case RuntimeInformationDCStart: { PRuntimeInformationRundown data = EventRecord->UserData; PDNA_NODE node; PPH_STRING startupFlagsString; PPH_STRING startupModeString; // Check for duplicates. if (FindClrNode(context, data->ClrInstanceID)) break; node = AddNode(context); node->Type = DNA_TYPE_CLR; node->u.Clr.ClrInstanceID = data->ClrInstanceID; node->u.Clr.DisplayName = PhFormatString(L"CLR v%u.%u.%u.%u", data->VMMajorVersion, data->VMMinorVersion, data->VMBuildNumber, data->VMQfeNumber); node->StructureText = node->u.Clr.DisplayName->sr; node->IdText = PhFormatString(L"%u", data->ClrInstanceID); startupFlagsString = FlagsToString(data->StartupFlags, StartupFlagsMap, sizeof(StartupFlagsMap)); startupModeString = FlagsToString(data->StartupMode, StartupModeMap, sizeof(StartupModeMap)); if (startupFlagsString->Length != 0 && startupModeString->Length != 0) { node->FlagsText = PhConcatStrings(3, startupFlagsString->Buffer, L", ", startupModeString->Buffer); PhDereferenceObject(startupFlagsString); PhDereferenceObject(startupModeString); } else if (startupFlagsString->Length != 0) { node->FlagsText = startupFlagsString; PhDereferenceObject(startupModeString); } else if (startupModeString->Length != 0) { node->FlagsText = startupModeString; PhDereferenceObject(startupFlagsString); } if (data->CommandLine[0]) node->PathText = PhCreateString(data->CommandLine); PhAddItemList(context->NodeRootList, node); } break; case AppDomainDCStart_V1: { PAppDomainLoadUnloadRundown_V1 data = EventRecord->UserData; SIZE_T appDomainNameLength; USHORT clrInstanceID; PDNA_NODE parentNode; PDNA_NODE node; appDomainNameLength = PhCountStringZ(data->AppDomainName) * sizeof(WCHAR); clrInstanceID = *(PUSHORT)((PCHAR)data + FIELD_OFFSET(AppDomainLoadUnloadRundown_V1, AppDomainName) + appDomainNameLength + sizeof(WCHAR) + sizeof(ULONG)); // Find the CLR node to add the AppDomain node to. parentNode = FindClrNode(context, clrInstanceID); if (parentNode) { // Check for duplicates. if (FindAppDomainNode(parentNode, data->AppDomainID)) break; node = AddNode(context); node->Type = DNA_TYPE_APPDOMAIN; node->u.AppDomain.AppDomainID = data->AppDomainID; node->u.AppDomain.DisplayName = PhConcatStrings2(L"AppDomain: ", data->AppDomainName); node->StructureText = node->u.AppDomain.DisplayName->sr; node->IdText = PhFormatString(L"%I64u", data->AppDomainID); node->FlagsText = FlagsToString(data->AppDomainFlags, AppDomainFlagsMap, sizeof(AppDomainFlagsMap)); PhAddItemList(parentNode->Children, node); } } break; case AssemblyDCStart_V1: { PAssemblyLoadUnloadRundown_V1 data = EventRecord->UserData; SIZE_T fullyQualifiedAssemblyNameLength; USHORT clrInstanceID; PDNA_NODE parentNode; PDNA_NODE node; PH_STRINGREF remainingPart; fullyQualifiedAssemblyNameLength = PhCountStringZ(data->FullyQualifiedAssemblyName) * sizeof(WCHAR); clrInstanceID = *(PUSHORT)((PCHAR)data + FIELD_OFFSET(AssemblyLoadUnloadRundown_V1, FullyQualifiedAssemblyName) + fullyQualifiedAssemblyNameLength + sizeof(WCHAR)); // Find the AppDomain node to add the Assembly node to. parentNode = FindClrNode(context, clrInstanceID); if (parentNode) parentNode = FindAppDomainNode(parentNode, data->AppDomainID); if (parentNode) { // Check for duplicates. if (FindAssemblyNode(parentNode, data->AssemblyID)) break; node = AddNode(context); node->Type = DNA_TYPE_ASSEMBLY; node->u.Assembly.AssemblyID = data->AssemblyID; node->u.Assembly.FullyQualifiedAssemblyName = PhCreateStringEx(data->FullyQualifiedAssemblyName, fullyQualifiedAssemblyNameLength); // Display only the assembly name, not the whole fully qualified name. if (!PhSplitStringRefAtChar(&node->u.Assembly.FullyQualifiedAssemblyName->sr, ',', &node->StructureText, &remainingPart)) node->StructureText = node->u.Assembly.FullyQualifiedAssemblyName->sr; node->IdText = PhFormatString(L"%I64u", data->AssemblyID); node->FlagsText = FlagsToString(data->AssemblyFlags, AssemblyFlagsMap, sizeof(AssemblyFlagsMap)); PhAddItemList(parentNode->Children, node); } } break; case ModuleDCStart_V1: { PModuleLoadUnloadRundown_V1 data = EventRecord->UserData; PWSTR moduleILPath; SIZE_T moduleILPathLength; PWSTR moduleNativePath; SIZE_T moduleNativePathLength; USHORT clrInstanceID; PDNA_NODE node; moduleILPath = data->ModuleILPath; moduleILPathLength = PhCountStringZ(moduleILPath) * sizeof(WCHAR); moduleNativePath = (PWSTR)((PCHAR)moduleILPath + moduleILPathLength + sizeof(WCHAR)); moduleNativePathLength = PhCountStringZ(moduleNativePath) * sizeof(WCHAR); clrInstanceID = *(PUSHORT)((PCHAR)moduleNativePath + moduleNativePathLength + sizeof(WCHAR)); // Find the Assembly node to set the path on. node = FindClrNode(context, clrInstanceID); if (node) node = FindAssemblyNode2(node, data->AssemblyID); if (node) { PhMoveReference(&node->PathText, PhCreateStringEx(moduleILPath, moduleILPathLength)); if (moduleNativePathLength != 0) PhMoveReference(&node->NativePathText, PhCreateStringEx(moduleNativePath, moduleNativePathLength)); } } break; case DCStartComplete_V1: { if (_InterlockedExchange(&context->TraceHandleActive, 0) == 1) { CloseTrace(context->TraceHandle); } } break; } // .NET 2.0 if (eventDescriptor->Id == 0) { switch (eventDescriptor->Opcode) { case CLR_MODULEDCSTART_OPCODE: { PModuleLoadUnloadRundown_V1 data = EventRecord->UserData; PWSTR moduleILPath; SIZE_T moduleILPathLength; PWSTR moduleNativePath; SIZE_T moduleNativePathLength; PDNA_NODE node; ULONG_PTR indexOfBackslash; ULONG_PTR indexOfLastDot; moduleILPath = data->ModuleILPath; moduleILPathLength = PhCountStringZ(moduleILPath) * sizeof(WCHAR); moduleNativePath = (PWSTR)((PCHAR)moduleILPath + moduleILPathLength + sizeof(WCHAR)); moduleNativePathLength = PhCountStringZ(moduleNativePath) * sizeof(WCHAR); if (context->ClrV2Node && (moduleILPathLength != 0 || moduleNativePathLength != 0)) { node = AddNode(context); node->Type = DNA_TYPE_ASSEMBLY; node->FlagsText = FlagsToString(data->ModuleFlags, ModuleFlagsMap, sizeof(ModuleFlagsMap)); node->PathText = PhCreateStringEx(moduleILPath, moduleILPathLength); if (moduleNativePathLength != 0) node->NativePathText = PhCreateStringEx(moduleNativePath, moduleNativePathLength); // Use the name between the last backslash and the last dot for the structure column text. // (E.g. C:\...\AcmeSoft.BigLib.dll -> AcmeSoft.BigLib) indexOfBackslash = PhFindLastCharInString(node->PathText, 0, '\\'); indexOfLastDot = PhFindLastCharInString(node->PathText, 0, '.'); if (indexOfBackslash != -1) { node->StructureText.Buffer = node->PathText->Buffer + indexOfBackslash + 1; if (indexOfLastDot != -1 && indexOfLastDot > indexOfBackslash) { node->StructureText.Length = (indexOfLastDot - indexOfBackslash - 1) * sizeof(WCHAR); } else { node->StructureText.Length = node->PathText->Length - indexOfBackslash * sizeof(WCHAR) - sizeof(WCHAR); } } else { node->StructureText = node->PathText->sr; } PhAddItemList(context->ClrV2Node->Children, node); } } break; case CLR_METHODDC_DCSTARTCOMPLETE_OPCODE: { if (_InterlockedExchange(&context->TraceHandleActive, 0) == 1) { CloseTrace(context->TraceHandle); } } break; } } } } ULONG ProcessDotNetTrace( _In_ PASMPAGE_QUERY_CONTEXT Context ) { ULONG result; TRACEHANDLE traceHandle; EVENT_TRACE_LOGFILE logFile; memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE)); logFile.LoggerName = DotNetLoggerName.Buffer; logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; logFile.BufferCallback = DotNetBufferCallback; logFile.EventRecordCallback = DotNetEventCallback; logFile.Context = Context; traceHandle = OpenTrace(&logFile); if (traceHandle == INVALID_PROCESSTRACE_HANDLE) return GetLastError(); Context->TraceHandleActive = 1; Context->TraceHandle = traceHandle; result = ProcessTrace(&traceHandle, 1, NULL, NULL); if (_InterlockedExchange(&Context->TraceHandleActive, 0) == 1) { CloseTrace(traceHandle); } return result; } ULONG UpdateDotNetTraceInfo( _In_ PASMPAGE_QUERY_CONTEXT Context, _In_ BOOLEAN ClrV2 ) { static _EnableTraceEx EnableTraceEx_I = NULL; ULONG result; TRACEHANDLE sessionHandle; PEVENT_TRACE_PROPERTIES properties; PGUID guidToEnable; if (!EnableTraceEx_I) EnableTraceEx_I = PhGetModuleProcAddress(L"advapi32.dll", "EnableTraceEx"); if (!EnableTraceEx_I) return ERROR_NOT_SUPPORTED; result = StartDotNetTrace(&sessionHandle, &properties); if (result != 0) return result; if (!ClrV2) guidToEnable = &ClrRundownProviderGuid; else guidToEnable = &ClrRuntimeProviderGuid; EnableTraceEx_I( guidToEnable, NULL, sessionHandle, 1, TRACE_LEVEL_INFORMATION, CLR_LOADER_KEYWORD | CLR_STARTENUMERATION_KEYWORD, 0, 0, NULL ); result = ProcessDotNetTrace(Context); ControlTrace(sessionHandle, NULL, properties, EVENT_TRACE_CONTROL_STOP); PhFree(properties); return result; } NTSTATUS UpdateDotNetTraceInfoThreadStart( _In_ PVOID Parameter ) { PASMPAGE_QUERY_CONTEXT context = Parameter; context->TraceResult = UpdateDotNetTraceInfo(context, context->TraceClrV2); return STATUS_SUCCESS; } ULONG UpdateDotNetTraceInfoWithTimeout( _In_ PASMPAGE_QUERY_CONTEXT Context, _In_ BOOLEAN ClrV2, _In_opt_ PLARGE_INTEGER Timeout ) { HANDLE threadHandle; BOOLEAN timeout = FALSE; // ProcessDotNetTrace is not guaranteed to complete within any period of time, because // the target process might terminate before it writes the DCStartComplete_V1 event. // If the timeout is reached, the trace handle is closed, forcing ProcessTrace to stop // processing. Context->TraceClrV2 = ClrV2; Context->TraceResult = 0; Context->TraceHandleActive = 0; Context->TraceHandle = 0; threadHandle = PhCreateThread(0, UpdateDotNetTraceInfoThreadStart, Context); if (NtWaitForSingleObject(threadHandle, FALSE, Timeout) != STATUS_WAIT_0) { // Timeout has expired. Stop the trace processing if it's still active. // BUG: This assumes that the thread is in ProcessTrace. It might still be // setting up though! if (_InterlockedExchange(&Context->TraceHandleActive, 0) == 1) { CloseTrace(Context->TraceHandle); timeout = TRUE; } NtWaitForSingleObject(threadHandle, FALSE, NULL); } NtClose(threadHandle); if (timeout) return ERROR_TIMEOUT; return Context->TraceResult; } NTSTATUS DotNetTraceQueryThreadStart( _In_ PVOID Parameter ) { LARGE_INTEGER timeout; PASMPAGE_QUERY_CONTEXT context = Parameter; BOOLEAN timeoutReached = FALSE; BOOLEAN nonClrNode = FALSE; ULONG i; ULONG result = 0; if (context->ClrVersions & PH_CLR_VERSION_1_0) { AddFakeClrNode(context, L"CLR v1.0.3705"); // what PE displays } if (context->ClrVersions & PH_CLR_VERSION_1_1) { AddFakeClrNode(context, L"CLR v1.1.4322"); } timeout.QuadPart = -10 * PH_TIMEOUT_SEC; if (context->ClrVersions & PH_CLR_VERSION_2_0) { context->ClrV2Node = AddFakeClrNode(context, L"CLR v2.0.50727"); result = UpdateDotNetTraceInfoWithTimeout(context, TRUE, &timeout); if (result == ERROR_TIMEOUT) { timeoutReached = TRUE; result = ERROR_SUCCESS; } } if (context->ClrVersions & PH_CLR_VERSION_4_ABOVE) { result = UpdateDotNetTraceInfoWithTimeout(context, FALSE, &timeout); if (result == ERROR_TIMEOUT) { timeoutReached = TRUE; result = ERROR_SUCCESS; } } // If we reached the timeout, check whether we got any data back. if (timeoutReached) { for (i = 0; i < context->NodeList->Count; i++) { PDNA_NODE node = context->NodeList->Items[i]; if (node->Type != DNA_TYPE_CLR) { nonClrNode = TRUE; break; } } if (!nonClrNode) result = ERROR_TIMEOUT; } // If the process properties window has been closed, bail and cleanup. // IsWindow should be safe from being called on this thread: // https://blogs.msdn.microsoft.com/oldnewthing/20070717-00/?p=25983 if (IsWindow(context->WindowHandle)) { PostMessage(context->WindowHandle, UPDATE_MSG, result, (LPARAM)context); } else { DestroyDotNetTraceQuery(context); } return STATUS_SUCCESS; } VOID CreateDotNetTraceQueryThread( _In_ HWND WindowHandle, _In_ ULONG ClrVersions, _In_ HANDLE ProcessId ) { HANDLE threadHandle; PASMPAGE_QUERY_CONTEXT context; context = PhAllocate(sizeof(ASMPAGE_QUERY_CONTEXT)); memset(context, 0, sizeof(ASMPAGE_QUERY_CONTEXT)); context->WindowHandle = WindowHandle; context->ClrVersions = ClrVersions; context->ProcessId = ProcessId; context->NodeList = PhCreateList(64); context->NodeRootList = PhCreateList(2); if (threadHandle = PhCreateThread(0, DotNetTraceQueryThreadStart, context)) { NtClose(threadHandle); } else { DestroyDotNetTraceQuery(context); } } VOID DestroyDotNetTraceQuery( _In_ PASMPAGE_QUERY_CONTEXT Context ) { if (Context->NodeList) { PhClearReference(&Context->NodeList); } if (Context->NodeRootList) { PhClearReference(&Context->NodeRootList); } PhFree(Context); } BOOLEAN IsProcessSuspended( _In_ HANDLE ProcessId ) { PVOID processes; PSYSTEM_PROCESS_INFORMATION process; if (NT_SUCCESS(PhEnumProcesses(&processes))) { if (process = PhFindProcessInformation(processes, ProcessId)) return PhGetProcessIsSuspended(process); PhFree(processes); } return FALSE; } INT_PTR CALLBACK DotNetAsmPageDlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { LPPROPSHEETPAGE propSheetPage; PPH_PROCESS_PROPPAGECONTEXT propPageContext; PPH_PROCESS_ITEM processItem; PASMPAGE_CONTEXT context; if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem)) { context = propPageContext->Context; } else { return FALSE; } switch (uMsg) { case WM_INITDIALOG: { PPH_STRING settings; HWND tnHandle; context = PhAllocate(sizeof(ASMPAGE_CONTEXT)); memset(context, 0, sizeof(ASMPAGE_CONTEXT)); propPageContext->Context = context; context->WindowHandle = hwndDlg; context->ProcessItem = processItem; context->ClrVersions = 0; PhGetProcessIsDotNetEx(processItem->ProcessId, NULL, 0, NULL, &context->ClrVersions); tnHandle = GetDlgItem(hwndDlg, IDC_LIST); context->TnHandle = tnHandle; TreeNew_SetCallback(tnHandle, DotNetAsmTreeNewCallback, context); TreeNew_SetExtendedFlags(tnHandle, TN_FLAG_ITEM_DRAG_SELECT, TN_FLAG_ITEM_DRAG_SELECT); PhSetControlTheme(tnHandle, L"explorer"); SendMessage(TreeNew_GetTooltips(tnHandle), TTM_SETMAXTIPWIDTH, 0, MAXSHORT); PhAddTreeNewColumn(tnHandle, DNATNC_STRUCTURE, TRUE, L"Structure", 240, PH_ALIGN_LEFT, -2, 0); PhAddTreeNewColumn(tnHandle, DNATNC_ID, TRUE, L"ID", 50, PH_ALIGN_RIGHT, 0, DT_RIGHT); PhAddTreeNewColumn(tnHandle, DNATNC_FLAGS, TRUE, L"Flags", 120, PH_ALIGN_LEFT, 1, 0); PhAddTreeNewColumn(tnHandle, DNATNC_PATH, TRUE, L"Path", 600, PH_ALIGN_LEFT, 2, 0); // don't use path ellipsis - the user already has the base file name PhAddTreeNewColumn(tnHandle, DNATNC_NATIVEPATH, TRUE, L"Native image path", 600, PH_ALIGN_LEFT, 3, 0); settings = PhGetStringSetting(SETTING_NAME_ASM_TREE_LIST_COLUMNS); PhCmLoadSettings(tnHandle, &settings->sr); PhDereferenceObject(settings); PhSwapReference(&context->TnErrorMessage, PhCreateString(L"Loading .NET assemblies...")); TreeNew_SetEmptyText(tnHandle, &context->TnErrorMessage->sr, 0); if ( !IsProcessSuspended(processItem->ProcessId) || PhShowMessage(hwndDlg, MB_ICONWARNING | MB_YESNO, L".NET assembly enumeration may not work properly because the process is currently suspended. Do you want to continue?") == IDYES ) { CreateDotNetTraceQueryThread( hwndDlg, context->ClrVersions, processItem->ProcessId ); } else { PhSwapReference(&context->TnErrorMessage, PhCreateString(L"Unable to start the event tracing session because the process is suspended.") ); TreeNew_SetEmptyText(tnHandle, &context->TnErrorMessage->sr, 0); InvalidateRect(tnHandle, NULL, FALSE); } EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); } break; case WM_DESTROY: { PPH_STRING settings; ULONG i; settings = PhCmSaveSettings(context->TnHandle); PhSetStringSetting2(SETTING_NAME_ASM_TREE_LIST_COLUMNS, &settings->sr); PhDereferenceObject(settings); if (context->NodeList) { for (i = 0; i < context->NodeList->Count; i++) DestroyNode(context->NodeList->Items[i]); PhDereferenceObject(context->NodeList); } if (context->NodeRootList) PhDereferenceObject(context->NodeRootList); PhClearReference(&context->TnErrorMessage); PhFree(context); PhPropPageDlgProcDestroy(hwndDlg); } break; case WM_SHOWWINDOW: { PPH_LAYOUT_ITEM dialogItem; if (dialogItem = PhBeginPropPageLayout(hwndDlg, propPageContext)) { PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), dialogItem, PH_ANCHOR_ALL); PhEndPropPageLayout(hwndDlg, propPageContext); } } break; case WM_COMMAND: { switch (LOWORD(wParam)) { case ID_COPY: { PPH_STRING text; text = PhGetTreeNewText(context->TnHandle, 0); PhSetClipboardString(context->TnHandle, &text->sr); PhDereferenceObject(text); } break; } } break; case UPDATE_MSG: { ULONG result = (ULONG)wParam; PASMPAGE_QUERY_CONTEXT queryContext = (PASMPAGE_QUERY_CONTEXT)lParam; if (result == 0) { PhSwapReference(&context->NodeList, queryContext->NodeList); PhSwapReference(&context->NodeRootList, queryContext->NodeRootList); DestroyDotNetTraceQuery(queryContext); TreeNew_NodesStructured(context->TnHandle); } else { PhSwapReference(&context->TnErrorMessage, PhConcatStrings2(L"Unable to start the event tracing session: ", PhGetStringOrDefault(PhGetWin32Message(result), L"Unknown error")) ); TreeNew_SetEmptyText(context->TnHandle, &context->TnErrorMessage->sr, 0); InvalidateRect(context->TnHandle, NULL, FALSE); } } break; } return FALSE; }