1328 lines
39 KiB
C
1328 lines
39 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "dn.h"
|
|
#include "clretw.h"
|
|
#include <evntcons.h>
|
|
#include <uxtheme.h>
|
|
|
|
#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;
|
|
}
|