Process Hacker
asmpage.c
Go to the documentation of this file.
1 /*
2  * Process Hacker .NET Tools -
3  * .NET Assemblies property page
4  *
5  * Copyright (C) 2011-2013 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "dn.h"
24 #include "resource.h"
25 #include <windowsx.h>
26 #include <evntcons.h>
27 #include "clretw.h"
28 
29 #define DNATNC_STRUCTURE 0
30 #define DNATNC_ID 1
31 #define DNATNC_FLAGS 2
32 #define DNATNC_PATH 3
33 #define DNATNC_NATIVEPATH 4
34 #define DNATNC_MAXIMUM 5
35 
36 #define DNA_TYPE_CLR 1
37 #define DNA_TYPE_APPDOMAIN 2
38 #define DNA_TYPE_ASSEMBLY 3
39 
40 typedef struct _DNA_NODE
41 {
42  PH_TREENEW_NODE Node;
43 
44  struct _DNA_NODE *Parent;
45  PPH_LIST Children;
46 
47  PH_STRINGREF TextCache[DNATNC_MAXIMUM];
48 
49  ULONG Type;
50  BOOLEAN IsFakeClr;
51 
52  union
53  {
54  struct
55  {
56  USHORT ClrInstanceID;
57  PPH_STRING DisplayName;
58  } Clr;
59  struct
60  {
61  ULONG64 AppDomainID;
62  PPH_STRING DisplayName;
63  } AppDomain;
64  struct
65  {
66  ULONG64 AssemblyID;
67  PPH_STRING FullyQualifiedAssemblyName;
68  } Assembly;
69  } u;
70 
71  PH_STRINGREF StructureText;
72  PPH_STRING IdText;
73  PPH_STRING FlagsText;
74  PPH_STRING PathText;
75  PPH_STRING NativePathText;
77 
78 typedef struct _ASMPAGE_CONTEXT
79 {
80  HWND WindowHandle;
81  PPH_PROCESS_ITEM ProcessItem;
82  ULONG ClrVersions;
83  PDNA_NODE ClrV2Node;
84 
85  BOOLEAN TraceClrV2;
86  ULONG TraceResult;
87  LONG TraceHandleActive;
88  TRACEHANDLE TraceHandle;
89 
90  HWND TnHandle;
91  PPH_LIST NodeList;
92  PPH_LIST NodeRootList;
94 
95 typedef struct _FLAG_DEFINITION
96 {
97  PWSTR Name;
98  ULONG Flag;
100 
101 typedef ULONG (__stdcall *_EnableTraceEx)(
102  _In_ LPCGUID ProviderId,
103  _In_opt_ LPCGUID SourceId,
104  _In_ TRACEHANDLE TraceHandle,
105  _In_ ULONG IsEnabled,
106  _In_ UCHAR Level,
107  _In_ ULONGLONG MatchAnyKeyword,
108  _In_ ULONGLONG MatchAllKeyword,
109  _In_ ULONG EnableProperty,
110  _In_opt_ PEVENT_FILTER_DESCRIPTOR EnableFilterDesc
111  );
112 
113 INT_PTR CALLBACK DotNetAsmPageDlgProc(
114  _In_ HWND hwndDlg,
115  _In_ UINT uMsg,
116  _In_ WPARAM wParam,
117  _In_ LPARAM lParam
118  );
119 
120 static UNICODE_STRING DotNetLoggerName = RTL_CONSTANT_STRING(L"PhDnLogger");
121 static GUID ClrRuntimeProviderGuid = { 0xe13c0d23, 0xccbc, 0x4e12, { 0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4 } };
122 static GUID ClrRundownProviderGuid = { 0xa669021c, 0xc450, 0x4609, { 0xa0, 0x35, 0x5a, 0xf5, 0x9a, 0xf4, 0xdf, 0x18 } };
123 
124 static FLAG_DEFINITION AppDomainFlagsMap[] =
125 {
126  { L"Default", 0x1 },
127  { L"Executable", 0x2 },
128  { L"Shared", 0x4 }
129 };
130 
131 static FLAG_DEFINITION AssemblyFlagsMap[] =
132 {
133  { L"DomainNeutral", 0x1 },
134  { L"Dynamic", 0x2 },
135  { L"Native", 0x4 },
136  { L"Collectible", 0x8 }
137 };
138 
139 static FLAG_DEFINITION ModuleFlagsMap[] =
140 {
141  { L"DomainNeutral", 0x1 },
142  { L"Native", 0x2 },
143  { L"Dynamic", 0x4 },
144  { L"Manifest", 0x8 }
145 };
146 
147 static FLAG_DEFINITION StartupModeMap[] =
148 {
149  { L"ManagedExe", 0x1 },
150  { L"HostedCLR", 0x2 },
151  { L"IjwDll", 0x4 },
152  { L"ComActivated", 0x8 },
153  { L"Other", 0x10 }
154 };
155 
156 static FLAG_DEFINITION StartupFlagsMap[] =
157 {
158  { L"CONCURRENT_GC", 0x1 },
159  { L"LOADER_OPTIMIZATION_SINGLE_DOMAIN", 0x2 },
160  { L"LOADER_OPTIMIZATION_MULTI_DOMAIN", 0x4 },
161  { L"LOADER_SAFEMODE", 0x10 },
162  { L"LOADER_SETPREFERENCE", 0x100 },
163  { L"SERVER_GC", 0x1000 },
164  { L"HOARD_GC_VM", 0x2000 },
165  { L"SINGLE_VERSION_HOSTING_INTERFACE", 0x4000 },
166  { L"LEGACY_IMPERSONATION", 0x10000 },
167  { L"DISABLE_COMMITTHREADSTACK", 0x20000 },
168  { L"ALWAYSFLOW_IMPERSONATION", 0x40000 },
169  { L"TRIM_GC_COMMIT", 0x80000 },
170  { L"ETW", 0x100000 },
171  { L"SERVER_BUILD", 0x200000 },
172  { L"ARM", 0x400000 }
173 };
174 
176  _In_ PPH_PLUGIN_PROCESS_PROPCONTEXT PropContext
177  )
178 {
180  PropContext->PropContext,
182  );
183 }
184 
186  _In_ ULONG Flags,
187  _In_ PFLAG_DEFINITION Map,
188  _In_ ULONG SizeOfMap
189  )
190 {
192  ULONG i;
193 
194  PhInitializeStringBuilder(&sb, 100);
195 
196  for (i = 0; i < SizeOfMap / sizeof(FLAG_DEFINITION); i++)
197  {
198  if (Flags & Map[i].Flag)
199  {
200  PhAppendStringBuilder2(&sb, Map[i].Name);
201  PhAppendStringBuilder2(&sb, L", ");
202  }
203  }
204 
205  if (sb.String->Length != 0)
206  PhRemoveEndStringBuilder(&sb, 2);
207 
208  return PhFinalStringBuilderString(&sb);
209 }
210 
212  _Inout_ PASMPAGE_CONTEXT Context
213  )
214 {
215  PDNA_NODE node;
216 
217  node = PhAllocate(sizeof(DNA_NODE));
218  memset(node, 0, sizeof(DNA_NODE));
219  PhInitializeTreeNewNode(&node->Node);
220 
221  memset(node->TextCache, 0, sizeof(PH_STRINGREF) * DNATNC_MAXIMUM);
222  node->Node.TextCache = node->TextCache;
223  node->Node.TextCacheSize = DNATNC_MAXIMUM;
224 
225  node->Children = PhCreateList(1);
226 
227  PhAddItemList(Context->NodeList, node);
228 
229  return node;
230 }
231 
233  _In_ PDNA_NODE Node
234  )
235 {
236  PhDereferenceObject(Node->Children);
237 
238  if (Node->Type == DNA_TYPE_CLR)
239  {
240  if (Node->u.Clr.DisplayName) PhDereferenceObject(Node->u.Clr.DisplayName);
241  }
242  else if (Node->Type == DNA_TYPE_APPDOMAIN)
243  {
244  if (Node->u.AppDomain.DisplayName) PhDereferenceObject(Node->u.AppDomain.DisplayName);
245  }
246  else if (Node->Type == DNA_TYPE_ASSEMBLY)
247  {
248  if (Node->u.Assembly.FullyQualifiedAssemblyName) PhDereferenceObject(Node->u.Assembly.FullyQualifiedAssemblyName);
249  }
250 
251  if (Node->IdText) PhDereferenceObject(Node->IdText);
252  if (Node->FlagsText) PhDereferenceObject(Node->FlagsText);
253  if (Node->PathText) PhDereferenceObject(Node->PathText);
254  if (Node->NativePathText) PhDereferenceObject(Node->NativePathText);
255 
256  PhFree(Node);
257 }
258 
260  _In_ PASMPAGE_CONTEXT Context,
261  _In_ PWSTR DisplayName
262  )
263 {
264  PDNA_NODE node;
265 
266  node = AddNode(Context);
267  node->Type = DNA_TYPE_CLR;
268  node->IsFakeClr = TRUE;
269  node->u.Clr.ClrInstanceID = 0;
270  node->u.Clr.DisplayName = NULL;
271  PhInitializeStringRef(&node->StructureText, DisplayName);
272 
273  PhAddItemList(Context->NodeRootList, node);
274 
275  return node;
276 }
277 
279  _In_ PASMPAGE_CONTEXT Context,
280  _In_ USHORT ClrInstanceID
281  )
282 {
283  ULONG i;
284 
285  for (i = 0; i < Context->NodeRootList->Count; i++)
286  {
287  PDNA_NODE node = Context->NodeRootList->Items[i];
288 
289  if (!node->IsFakeClr && node->u.Clr.ClrInstanceID == ClrInstanceID)
290  return node;
291  }
292 
293  return NULL;
294 }
295 
297  _In_ PDNA_NODE ClrNode,
298  _In_ ULONG64 AppDomainID
299  )
300 {
301  ULONG i;
302 
303  for (i = 0; i < ClrNode->Children->Count; i++)
304  {
305  PDNA_NODE node = ClrNode->Children->Items[i];
306 
307  if (node->u.AppDomain.AppDomainID == AppDomainID)
308  return node;
309  }
310 
311  return NULL;
312 }
313 
315  _In_ PDNA_NODE AppDomainNode,
316  _In_ ULONG64 AssemblyID
317  )
318 {
319  ULONG i;
320 
321  for (i = 0; i < AppDomainNode->Children->Count; i++)
322  {
323  PDNA_NODE node = AppDomainNode->Children->Items[i];
324 
325  if (node->u.Assembly.AssemblyID == AssemblyID)
326  return node;
327  }
328 
329  return NULL;
330 }
331 
333  _In_ PDNA_NODE ClrNode,
334  _In_ ULONG64 AssemblyID
335  )
336 {
337  ULONG i;
338  ULONG j;
339 
340  for (i = 0; i < ClrNode->Children->Count; i++)
341  {
342  PDNA_NODE appDomainNode = ClrNode->Children->Items[i];
343 
344  for (j = 0; j < appDomainNode->Children->Count; j++)
345  {
346  PDNA_NODE assemblyNode = appDomainNode->Children->Items[j];
347 
348  if (assemblyNode->u.Assembly.AssemblyID == AssemblyID)
349  return assemblyNode;
350  }
351  }
352 
353  return NULL;
354 }
355 
356 static int __cdecl AssemblyNodeNameCompareFunction(
357  _In_ const void *elem1,
358  _In_ const void *elem2
359  )
360 {
361  PDNA_NODE node1 = *(PDNA_NODE *)elem1;
362  PDNA_NODE node2 = *(PDNA_NODE *)elem2;
363 
364  return PhCompareStringRef(&node1->StructureText, &node2->StructureText, TRUE);
365 }
366 
368  _In_ HWND hwnd,
369  _In_ PH_TREENEW_MESSAGE Message,
370  _In_opt_ PVOID Parameter1,
371  _In_opt_ PVOID Parameter2,
372  _In_opt_ PVOID Context
373  )
374 {
375  PASMPAGE_CONTEXT context;
376  PDNA_NODE node;
377 
378  context = Context;
379 
380  switch (Message)
381  {
382  case TreeNewGetChildren:
383  {
384  PPH_TREENEW_GET_CHILDREN getChildren = Parameter1;
385 
386  node = (PDNA_NODE)getChildren->Node;
387 
388  if (!node)
389  {
390  getChildren->Children = (PPH_TREENEW_NODE *)context->NodeRootList->Items;
391  getChildren->NumberOfChildren = context->NodeRootList->Count;
392  }
393  else
394  {
395  if (node->Type == DNA_TYPE_APPDOMAIN || node == context->ClrV2Node)
396  {
397  // Sort the assemblies.
398  qsort(node->Children->Items, node->Children->Count, sizeof(PVOID), AssemblyNodeNameCompareFunction);
399  }
400 
401  getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items;
402  getChildren->NumberOfChildren = node->Children->Count;
403  }
404  }
405  return TRUE;
406  case TreeNewIsLeaf:
407  {
408  PPH_TREENEW_IS_LEAF isLeaf = Parameter1;
409 
410  node = (PDNA_NODE)isLeaf->Node;
411 
412  isLeaf->IsLeaf = node->Children->Count == 0;
413  }
414  return TRUE;
415  case TreeNewGetCellText:
416  {
417  PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1;
418 
419  node = (PDNA_NODE)getCellText->Node;
420 
421  switch (getCellText->Id)
422  {
423  case DNATNC_STRUCTURE:
424  getCellText->Text = node->StructureText;
425  break;
426  case DNATNC_ID:
427  getCellText->Text = PhGetStringRef(node->IdText);
428  break;
429  case DNATNC_FLAGS:
430  getCellText->Text = PhGetStringRef(node->FlagsText);
431  break;
432  case DNATNC_PATH:
433  getCellText->Text = PhGetStringRef(node->PathText);
434  break;
435  case DNATNC_NATIVEPATH:
436  getCellText->Text = PhGetStringRef(node->NativePathText);
437  break;
438  default:
439  return FALSE;
440  }
441 
442  getCellText->Flags = TN_CACHE;
443  }
444  return TRUE;
446  {
447  PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1;
448 
449  node = (PDNA_NODE)getCellTooltip->Node;
450 
451  if (getCellTooltip->Column->Id != 0 || node->Type != DNA_TYPE_ASSEMBLY)
452  return FALSE;
453 
454  if (!PhIsNullOrEmptyString(node->u.Assembly.FullyQualifiedAssemblyName))
455  {
456  getCellTooltip->Text = node->u.Assembly.FullyQualifiedAssemblyName->sr;
457  getCellTooltip->Unfolding = FALSE;
458  }
459  else
460  {
461  return FALSE;
462  }
463  }
464  return TRUE;
465  case TreeNewKeyDown:
466  {
467  PPH_TREENEW_KEY_EVENT keyEvent = Parameter1;
468 
469  switch (keyEvent->VirtualKey)
470  {
471  case 'C':
472  if (GetKeyState(VK_CONTROL) < 0)
473  SendMessage(context->WindowHandle, WM_COMMAND, ID_COPY, 0);
474  break;
475  }
476  }
477  return TRUE;
478  }
479 
480  return FALSE;
481 }
482 
484  _In_ PASMPAGE_CONTEXT Context,
485  _Out_ PTRACEHANDLE SessionHandle,
486  _Out_ PEVENT_TRACE_PROPERTIES *Properties
487  )
488 {
489  ULONG result;
490  ULONG bufferSize;
491  PEVENT_TRACE_PROPERTIES properties;
492  TRACEHANDLE sessionHandle;
493 
494  bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + DotNetLoggerName.Length + sizeof(WCHAR);
495  properties = PhAllocate(bufferSize);
496  memset(properties, 0, sizeof(EVENT_TRACE_PROPERTIES));
497 
498  properties->Wnode.BufferSize = bufferSize;
499  properties->Wnode.ClientContext = 2; // System time clock resolution
500  properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
501  properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_USE_PAGED_MEMORY;
502  properties->LogFileNameOffset = 0;
503  properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
504 
505  result = StartTrace(&sessionHandle, DotNetLoggerName.Buffer, properties);
506 
507  if (result == 0)
508  {
509  *SessionHandle = sessionHandle;
510  *Properties = properties;
511 
512  return 0;
513  }
514  else if (result == ERROR_ALREADY_EXISTS)
515  {
516  // Session already exists, so use that. Get the existing session handle.
517 
518  result = ControlTrace(0, DotNetLoggerName.Buffer, properties, EVENT_TRACE_CONTROL_QUERY);
519 
520  if (result != 0)
521  {
522  PhFree(properties);
523  return result;
524  }
525 
526  *SessionHandle = properties->Wnode.HistoricalContext;
527  *Properties = properties;
528 
529  return 0;
530  }
531  else
532  {
533  PhFree(properties);
534 
535  return result;
536  }
537 }
538 
540  _In_ PEVENT_TRACE_LOGFILE Buffer
541  )
542 {
543  return TRUE;
544 }
545 
547  _In_ PEVENT_RECORD EventRecord
548  )
549 {
550  PASMPAGE_CONTEXT context = EventRecord->UserContext;
551  PEVENT_HEADER eventHeader = &EventRecord->EventHeader;
552  PEVENT_DESCRIPTOR eventDescriptor = &eventHeader->EventDescriptor;
553 
554  if (UlongToHandle(eventHeader->ProcessId) == context->ProcessItem->ProcessId)
555  {
556  // .NET 4.0+
557 
558  switch (eventDescriptor->Id)
559  {
561  {
562  PRuntimeInformationRundown data = EventRecord->UserData;
563  PDNA_NODE node;
564  PPH_STRING startupFlagsString;
565  PPH_STRING startupModeString;
566 
567  // Check for duplicates.
568  if (FindClrNode(context, data->ClrInstanceID))
569  break;
570 
571  node = AddNode(context);
572  node->Type = DNA_TYPE_CLR;
573  node->u.Clr.ClrInstanceID = data->ClrInstanceID;
574  node->u.Clr.DisplayName = PhFormatString(L"CLR v%u.%u.%u.%u", data->VMMajorVersion, data->VMMinorVersion, data->VMBuildNumber, data->VMQfeNumber);
575  node->StructureText = node->u.Clr.DisplayName->sr;
576  node->IdText = PhFormatString(L"%u", data->ClrInstanceID);
577 
578  startupFlagsString = FlagsToString(data->StartupFlags, StartupFlagsMap, sizeof(StartupFlagsMap));
579  startupModeString = FlagsToString(data->StartupMode, StartupModeMap, sizeof(StartupModeMap));
580 
581  if (startupFlagsString->Length != 0 && startupModeString->Length != 0)
582  {
583  node->FlagsText = PhConcatStrings(3, startupFlagsString->Buffer, L", ", startupModeString->Buffer);
584  PhDereferenceObject(startupFlagsString);
585  PhDereferenceObject(startupModeString);
586  }
587  else if (startupFlagsString->Length != 0)
588  {
589  node->FlagsText = startupFlagsString;
590  PhDereferenceObject(startupModeString);
591  }
592  else if (startupModeString->Length != 0)
593  {
594  node->FlagsText = startupModeString;
595  PhDereferenceObject(startupFlagsString);
596  }
597 
598  if (data->CommandLine[0])
599  node->PathText = PhCreateString(data->CommandLine);
600 
601  PhAddItemList(context->NodeRootList, node);
602  }
603  break;
604  case AppDomainDCStart_V1:
605  {
606  PAppDomainLoadUnloadRundown_V1 data = EventRecord->UserData;
607  SIZE_T appDomainNameLength;
608  USHORT clrInstanceID;
609  PDNA_NODE parentNode;
610  PDNA_NODE node;
611 
612  appDomainNameLength = PhCountStringZ(data->AppDomainName) * sizeof(WCHAR);
613  clrInstanceID = *(PUSHORT)((PCHAR)data + FIELD_OFFSET(AppDomainLoadUnloadRundown_V1, AppDomainName) + appDomainNameLength + sizeof(WCHAR) + sizeof(ULONG));
614 
615  // Find the CLR node to add the AppDomain node to.
616  parentNode = FindClrNode(context, clrInstanceID);
617 
618  if (parentNode)
619  {
620  // Check for duplicates.
621  if (FindAppDomainNode(parentNode, data->AppDomainID))
622  break;
623 
624  node = AddNode(context);
625  node->Type = DNA_TYPE_APPDOMAIN;
626  node->u.AppDomain.AppDomainID = data->AppDomainID;
627  node->u.AppDomain.DisplayName = PhConcatStrings2(L"AppDomain: ", data->AppDomainName);
628  node->StructureText = node->u.AppDomain.DisplayName->sr;
629  node->IdText = PhFormatString(L"%I64u", data->AppDomainID);
630  node->FlagsText = FlagsToString(data->AppDomainFlags, AppDomainFlagsMap, sizeof(AppDomainFlagsMap));
631 
632  PhAddItemList(parentNode->Children, node);
633  }
634  }
635  break;
636  case AssemblyDCStart_V1:
637  {
638  PAssemblyLoadUnloadRundown_V1 data = EventRecord->UserData;
639  SIZE_T fullyQualifiedAssemblyNameLength;
640  USHORT clrInstanceID;
641  PDNA_NODE parentNode;
642  PDNA_NODE node;
643  PH_STRINGREF remainingPart;
644 
645  fullyQualifiedAssemblyNameLength = PhCountStringZ(data->FullyQualifiedAssemblyName) * sizeof(WCHAR);
646  clrInstanceID = *(PUSHORT)((PCHAR)data + FIELD_OFFSET(AssemblyLoadUnloadRundown_V1, FullyQualifiedAssemblyName) + fullyQualifiedAssemblyNameLength + sizeof(WCHAR));
647 
648  // Find the AppDomain node to add the Assembly node to.
649 
650  parentNode = FindClrNode(context, clrInstanceID);
651 
652  if (parentNode)
653  parentNode = FindAppDomainNode(parentNode, data->AppDomainID);
654 
655  if (parentNode)
656  {
657  // Check for duplicates.
658  if (FindAssemblyNode(parentNode, data->AssemblyID))
659  break;
660 
661  node = AddNode(context);
662  node->Type = DNA_TYPE_ASSEMBLY;
663  node->u.Assembly.AssemblyID = data->AssemblyID;
664  node->u.Assembly.FullyQualifiedAssemblyName = PhCreateStringEx(data->FullyQualifiedAssemblyName, fullyQualifiedAssemblyNameLength);
665 
666  // Display only the assembly name, not the whole fully qualified name.
667  if (!PhSplitStringRefAtChar(&node->u.Assembly.FullyQualifiedAssemblyName->sr, ',', &node->StructureText, &remainingPart))
668  node->StructureText = node->u.Assembly.FullyQualifiedAssemblyName->sr;
669 
670  node->IdText = PhFormatString(L"%I64u", data->AssemblyID);
671  node->FlagsText = FlagsToString(data->AssemblyFlags, AssemblyFlagsMap, sizeof(AssemblyFlagsMap));
672 
673  PhAddItemList(parentNode->Children, node);
674  }
675  }
676  break;
677  case ModuleDCStart_V1:
678  {
679  PModuleLoadUnloadRundown_V1 data = EventRecord->UserData;
680  PWSTR moduleILPath;
681  SIZE_T moduleILPathLength;
682  PWSTR moduleNativePath;
683  SIZE_T moduleNativePathLength;
684  USHORT clrInstanceID;
685  PDNA_NODE node;
686 
687  moduleILPath = data->ModuleILPath;
688  moduleILPathLength = PhCountStringZ(moduleILPath) * sizeof(WCHAR);
689  moduleNativePath = (PWSTR)((PCHAR)moduleILPath + moduleILPathLength + sizeof(WCHAR));
690  moduleNativePathLength = PhCountStringZ(moduleNativePath) * sizeof(WCHAR);
691  clrInstanceID = *(PUSHORT)((PCHAR)moduleNativePath + moduleNativePathLength + sizeof(WCHAR));
692 
693  // Find the Assembly node to set the path on.
694 
695  node = FindClrNode(context, clrInstanceID);
696 
697  if (node)
698  node = FindAssemblyNode2(node, data->AssemblyID);
699 
700  if (node)
701  {
702  PhMoveReference(&node->PathText, PhCreateStringEx(moduleILPath, moduleILPathLength));
703 
704  if (moduleNativePathLength != 0)
705  PhMoveReference(&node->NativePathText, PhCreateStringEx(moduleNativePath, moduleNativePathLength));
706  }
707  }
708  break;
709  case DCStartComplete_V1:
710  {
711  if (_InterlockedExchange(&context->TraceHandleActive, 0) == 1)
712  {
713  CloseTrace(context->TraceHandle);
714  }
715  }
716  break;
717  }
718 
719  // .NET 2.0
720 
721  if (eventDescriptor->Id == 0)
722  {
723  switch (eventDescriptor->Opcode)
724  {
726  {
727  PModuleLoadUnloadRundown_V1 data = EventRecord->UserData;
728  PWSTR moduleILPath;
729  SIZE_T moduleILPathLength;
730  PWSTR moduleNativePath;
731  SIZE_T moduleNativePathLength;
732  PDNA_NODE node;
733  ULONG_PTR indexOfBackslash;
734  ULONG_PTR indexOfLastDot;
735 
736  moduleILPath = data->ModuleILPath;
737  moduleILPathLength = PhCountStringZ(moduleILPath) * sizeof(WCHAR);
738  moduleNativePath = (PWSTR)((PCHAR)moduleILPath + moduleILPathLength + sizeof(WCHAR));
739  moduleNativePathLength = PhCountStringZ(moduleNativePath) * sizeof(WCHAR);
740 
741  if (context->ClrV2Node && (moduleILPathLength != 0 || moduleNativePathLength != 0))
742  {
743  node = AddNode(context);
744  node->Type = DNA_TYPE_ASSEMBLY;
745  node->FlagsText = FlagsToString(data->ModuleFlags, ModuleFlagsMap, sizeof(ModuleFlagsMap));
746  node->PathText = PhCreateStringEx(moduleILPath, moduleILPathLength);
747 
748  if (moduleNativePathLength != 0)
749  node->NativePathText = PhCreateStringEx(moduleNativePath, moduleNativePathLength);
750 
751  // Use the name between the last backslash and the last dot for the structure column text.
752  // (E.g. C:\...\AcmeSoft.BigLib.dll -> AcmeSoft.BigLib)
753 
754  indexOfBackslash = PhFindLastCharInString(node->PathText, 0, '\\');
755  indexOfLastDot = PhFindLastCharInString(node->PathText, 0, '.');
756 
757  if (indexOfBackslash != -1)
758  {
759  node->StructureText.Buffer = node->PathText->Buffer + indexOfBackslash + 1;
760 
761  if (indexOfLastDot != -1 && indexOfLastDot > indexOfBackslash)
762  {
763  node->StructureText.Length = (indexOfLastDot - indexOfBackslash - 1) * sizeof(WCHAR);
764  }
765  else
766  {
767  node->StructureText.Length = node->PathText->Length - indexOfBackslash * sizeof(WCHAR) - sizeof(WCHAR);
768  }
769  }
770  else
771  {
772  node->StructureText = node->PathText->sr;
773  }
774 
775  PhAddItemList(context->ClrV2Node->Children, node);
776  }
777  }
778  break;
780  {
781  if (_InterlockedExchange(&context->TraceHandleActive, 0) == 1)
782  {
783  CloseTrace(context->TraceHandle);
784  }
785  }
786  break;
787  }
788  }
789  }
790 }
791 
793  _In_ PASMPAGE_CONTEXT Context
794  )
795 {
796  ULONG result;
797  TRACEHANDLE traceHandle;
798  EVENT_TRACE_LOGFILE logFile;
799 
800  memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE));
801  logFile.LoggerName = DotNetLoggerName.Buffer;
802  logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
803  logFile.BufferCallback = DotNetBufferCallback;
804  logFile.EventRecordCallback = DotNetEventCallback;
805  logFile.Context = Context;
806 
807  traceHandle = OpenTrace(&logFile);
808 
809  if (traceHandle == INVALID_PROCESSTRACE_HANDLE)
810  return GetLastError();
811 
812  Context->TraceHandleActive = 1;
813  Context->TraceHandle = traceHandle;
814  result = ProcessTrace(&traceHandle, 1, NULL, NULL);
815 
816  if (_InterlockedExchange(&Context->TraceHandleActive, 0) == 1)
817  {
818  CloseTrace(traceHandle);
819  }
820 
821  return result;
822 }
823 
825  _In_ PASMPAGE_CONTEXT Context,
826  _In_ BOOLEAN ClrV2
827  )
828 {
829  static _EnableTraceEx EnableTraceEx_I = NULL;
830 
831  ULONG result;
832  TRACEHANDLE sessionHandle;
833  PEVENT_TRACE_PROPERTIES properties;
834  PGUID guidToEnable;
835 
836  if (!EnableTraceEx_I)
837  EnableTraceEx_I = (_EnableTraceEx)PhGetModuleProcAddress(L"advapi32.dll", "EnableTraceEx");
838  if (!EnableTraceEx_I)
839  return ERROR_NOT_SUPPORTED;
840 
841  result = StartDotNetTrace(Context, &sessionHandle, &properties);
842 
843  if (result != 0)
844  return result;
845 
846  if (!ClrV2)
847  guidToEnable = &ClrRundownProviderGuid;
848  else
849  guidToEnable = &ClrRuntimeProviderGuid;
850 
851  EnableTraceEx_I(
852  guidToEnable,
853  NULL,
854  sessionHandle,
855  1,
856  TRACE_LEVEL_INFORMATION,
858  0,
859  0,
860  NULL
861  );
862 
863  result = ProcessDotNetTrace(Context);
864 
865  ControlTrace(sessionHandle, NULL, properties, EVENT_TRACE_CONTROL_STOP);
866  PhFree(properties);
867 
868  return result;
869 }
870 
872  _In_ PVOID Parameter
873  )
874 {
875  PASMPAGE_CONTEXT context = Parameter;
876 
877  context->TraceResult = UpdateDotNetTraceInfo(context, context->TraceClrV2);
878 
879  return STATUS_SUCCESS;
880 }
881 
883  _In_ PASMPAGE_CONTEXT Context,
884  _In_ BOOLEAN ClrV2,
885  _In_opt_ PLARGE_INTEGER Timeout
886  )
887 {
888  HANDLE threadHandle;
889  BOOLEAN timeout = FALSE;
890 
891  // ProcessDotNetTrace is not guaranteed to complete within any period of time, because
892  // the target process might terminate before it writes the DCStartComplete_V1 event.
893  // If the timeout is reached, the trace handle is closed, forcing ProcessTrace to stop
894  // processing.
895 
896  Context->TraceClrV2 = ClrV2;
897  Context->TraceResult = 0;
898  Context->TraceHandleActive = 0;
899  Context->TraceHandle = 0;
900 
901  threadHandle = PhCreateThread(0, UpdateDotNetTraceInfoThreadStart, Context);
902 
903  if (NtWaitForSingleObject(threadHandle, FALSE, Timeout) != STATUS_WAIT_0)
904  {
905  // Timeout has expired. Stop the trace processing if it's still active.
906  // BUG: This assumes that the thread is in ProcessTrace. It might still be
907  // setting up though!
908  if (_InterlockedExchange(&Context->TraceHandleActive, 0) == 1)
909  {
910  CloseTrace(Context->TraceHandle);
911  timeout = TRUE;
912  }
913 
914  NtWaitForSingleObject(threadHandle, FALSE, NULL);
915  }
916 
917  NtClose(threadHandle);
918 
919  if (timeout)
920  return ERROR_TIMEOUT;
921 
922  return Context->TraceResult;
923 }
924 
926  _In_ HANDLE ProcessId
927  )
928 {
929  PVOID processes;
931 
932  if (NT_SUCCESS(PhEnumProcesses(&processes)))
933  {
934  if (process = PhFindProcessInformation(processes, ProcessId))
935  return PhGetProcessIsSuspended(process);
936 
937  PhFree(processes);
938  }
939 
940  return FALSE;
941 }
942 
943 INT_PTR CALLBACK DotNetAsmPageDlgProc(
944  _In_ HWND hwndDlg,
945  _In_ UINT uMsg,
946  _In_ WPARAM wParam,
947  _In_ LPARAM lParam
948  )
949 {
950  LPPROPSHEETPAGE propSheetPage;
951  PPH_PROCESS_PROPPAGECONTEXT propPageContext;
952  PPH_PROCESS_ITEM processItem;
953  PASMPAGE_CONTEXT context;
954 
955  if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem))
956  {
957  context = propPageContext->Context;
958  }
959  else
960  {
961  return FALSE;
962  }
963 
964  switch (uMsg)
965  {
966  case WM_INITDIALOG:
967  {
968  ULONG result = 0;
969  PPH_STRING settings;
970  LARGE_INTEGER timeout;
971  HWND tnHandle;
972 
973  context = PhAllocate(sizeof(ASMPAGE_CONTEXT));
974  memset(context, 0, sizeof(ASMPAGE_CONTEXT));
975  propPageContext->Context = context;
976  context->WindowHandle = hwndDlg;
977  context->ProcessItem = processItem;
978 
979  context->ClrVersions = 0;
980  PhGetProcessIsDotNetEx(processItem->ProcessId, NULL, 0, NULL, &context->ClrVersions);
981 
982  context->NodeList = PhCreateList(64);
983  context->NodeRootList = PhCreateList(2);
984 
985  tnHandle = GetDlgItem(hwndDlg, IDC_LIST);
986  context->TnHandle = tnHandle;
987 
988  TreeNew_SetRedraw(tnHandle, FALSE);
989 
990  TreeNew_SetCallback(tnHandle, DotNetAsmTreeNewCallback, context);
992  PhSetControlTheme(tnHandle, L"explorer");
993  SendMessage(TreeNew_GetTooltips(tnHandle), TTM_SETMAXTIPWIDTH, 0, MAXSHORT);
994  PhAddTreeNewColumn(tnHandle, DNATNC_STRUCTURE, TRUE, L"Structure", 240, PH_ALIGN_LEFT, -2, 0);
995  PhAddTreeNewColumn(tnHandle, DNATNC_ID, TRUE, L"ID", 50, PH_ALIGN_RIGHT, 0, DT_RIGHT);
996  PhAddTreeNewColumn(tnHandle, DNATNC_FLAGS, TRUE, L"Flags", 120, PH_ALIGN_LEFT, 1, 0);
997  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
998  PhAddTreeNewColumn(tnHandle, DNATNC_NATIVEPATH, TRUE, L"Native Image Path", 600, PH_ALIGN_LEFT, 3, 0);
999 
1001  PhCmLoadSettings(tnHandle, &settings->sr);
1002  PhDereferenceObject(settings);
1003 
1004  SetCursor(LoadCursor(NULL, IDC_WAIT));
1005 
1006  if (
1007  !IsProcessSuspended(processItem->ProcessId) ||
1008  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
1009  )
1010  {
1011  BOOLEAN timeoutReached = FALSE;
1012  BOOLEAN nonClrNode = FALSE;
1013  ULONG i;
1014 
1015  if (context->ClrVersions & PH_CLR_VERSION_1_0)
1016  {
1017  AddFakeClrNode(context, L"CLR v1.0.3705"); // what PE displays
1018  }
1019 
1020  if (context->ClrVersions & PH_CLR_VERSION_1_1)
1021  {
1022  AddFakeClrNode(context, L"CLR v1.1.4322");
1023  }
1024 
1025  timeout.QuadPart = -10 * PH_TIMEOUT_SEC;
1026 
1027  if (context->ClrVersions & PH_CLR_VERSION_2_0)
1028  {
1029  context->ClrV2Node = AddFakeClrNode(context, L"CLR v2.0.50727");
1030  result = UpdateDotNetTraceInfoWithTimeout(context, TRUE, &timeout);
1031 
1032  if (result == ERROR_TIMEOUT)
1033  {
1034  timeoutReached = TRUE;
1035  result = ERROR_SUCCESS;
1036  }
1037  }
1038 
1039  if (context->ClrVersions & PH_CLR_VERSION_4_ABOVE)
1040  {
1041  result = UpdateDotNetTraceInfoWithTimeout(context, FALSE, &timeout);
1042 
1043  if (result == ERROR_TIMEOUT)
1044  {
1045  timeoutReached = TRUE;
1046  result = ERROR_SUCCESS;
1047  }
1048  }
1049 
1050  TreeNew_NodesStructured(tnHandle);
1051 
1052  // If we reached the timeout, check whether we got any data back.
1053  if (timeoutReached)
1054  {
1055  for (i = 0; i < context->NodeList->Count; i++)
1056  {
1057  PDNA_NODE node = context->NodeList->Items[i];
1058 
1059  if (node->Type != DNA_TYPE_CLR)
1060  {
1061  nonClrNode = TRUE;
1062  break;
1063  }
1064  }
1065 
1066  if (!nonClrNode)
1067  result = ERROR_TIMEOUT;
1068  }
1069  }
1070  else
1071  {
1072  result = ERROR_INSTALL_SUSPEND;
1073  }
1074 
1075  TreeNew_SetRedraw(tnHandle, TRUE);
1076  SetCursor(LoadCursor(NULL, IDC_ARROW));
1077 
1078  if (result != 0)
1079  {
1080  ShowWindow(tnHandle, SW_HIDE);
1081  ShowWindow(GetDlgItem(hwndDlg, IDC_ERROR), SW_SHOW);
1082 
1083  if (result == ERROR_ACCESS_DENIED)
1084  {
1085  SetDlgItemText(hwndDlg, IDC_ERROR, L"Unable to start the event tracing session. Make sure Process Hacker is running with administrative privileges.");
1086  }
1087  else if (result == ERROR_INSTALL_SUSPEND)
1088  {
1089  SetDlgItemText(hwndDlg, IDC_ERROR, L"Unable to start the event tracing session because the process is suspended.");
1090  }
1091  else if (result == ERROR_TIMEOUT)
1092  {
1093  SetDlgItemText(hwndDlg, IDC_ERROR, L"The event tracing session timed out.");
1094  }
1095  else
1096  {
1097  SetDlgItemText(hwndDlg, IDC_ERROR,
1098  PhaConcatStrings2(L"Unable to start the event tracing session: %s", PhGetStringOrDefault(PhGetWin32Message(result), L"Unknown error"))->Buffer);
1099  }
1100  }
1101  }
1102  break;
1103  case WM_DESTROY:
1104  {
1105  PPH_STRING settings;
1106  ULONG i;
1107 
1108  settings = PhCmSaveSettings(context->TnHandle);
1110  PhDereferenceObject(settings);
1111 
1112  for (i = 0; i < context->NodeList->Count; i++)
1113  DestroyNode(context->NodeList->Items[i]);
1114 
1115  PhDereferenceObject(context->NodeList);
1116  PhDereferenceObject(context->NodeRootList);
1117  PhFree(context);
1118 
1119  PhPropPageDlgProcDestroy(hwndDlg);
1120  }
1121  break;
1122  case WM_SHOWWINDOW:
1123  {
1124  PPH_LAYOUT_ITEM dialogItem;
1125 
1126  if (dialogItem = PhBeginPropPageLayout(hwndDlg, propPageContext))
1127  {
1128  PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), dialogItem, PH_ANCHOR_ALL);
1130  PhEndPropPageLayout(hwndDlg, propPageContext);
1131  }
1132  }
1133  break;
1134  case WM_COMMAND:
1135  {
1136  switch (LOWORD(wParam))
1137  {
1138  case ID_COPY:
1139  {
1140  PPH_STRING text;
1141 
1142  text = PhGetTreeNewText(context->TnHandle, 0);
1143  PhSetClipboardString(context->TnHandle, &text->sr);
1144  PhDereferenceObject(text);
1145  }
1146  break;
1147  }
1148  }
1149  break;
1150  }
1151 
1152  return FALSE;
1153 }