Process Hacker
wswatch.c
Go to the documentation of this file.
1 /*
2  * Process Hacker Extended Tools -
3  * working set watch
4  *
5  * Copyright (C) 2011 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 "exttools.h"
24 #include "resource.h"
25 #include <symprv.h>
26 
27 typedef struct _WS_WATCH_CONTEXT
28 {
29  LONG RefCount;
30 
31  PPH_PROCESS_ITEM ProcessItem;
32  HWND WindowHandle;
33  HWND ListViewHandle;
34  BOOLEAN Enabled;
35  BOOLEAN Destroying;
36  PPH_HASHTABLE Hashtable;
37  HANDLE ProcessHandle;
38  PVOID Buffer;
39  ULONG BufferSize;
40 
41  PPH_SYMBOL_PROVIDER SymbolProvider;
42  HANDLE LoadingSymbolsForProcessId;
43  SINGLE_LIST_ENTRY ResultListHead;
44  PH_QUEUED_LOCK ResultListLock;
46 
47 typedef struct _SYMBOL_LOOKUP_RESULT
48 {
49  SINGLE_LIST_ENTRY ListEntry;
50  PWS_WATCH_CONTEXT Context;
51  PVOID Address;
52  PPH_STRING Symbol;
54 
56  _Inout_ PWS_WATCH_CONTEXT Context
57  );
58 
60  _Inout_ PWS_WATCH_CONTEXT Context
61  );
62 
63 INT_PTR CALLBACK EtpWsWatchDlgProc(
64  _In_ HWND hwndDlg,
65  _In_ UINT uMsg,
66  _In_ WPARAM wParam,
67  _In_ LPARAM lParam
68  );
69 
71  _In_ HWND ParentWindowHandle,
72  _In_ PPH_PROCESS_ITEM ProcessItem
73  )
74 {
75  PWS_WATCH_CONTEXT context;
76 
77  context = PhAllocate(sizeof(WS_WATCH_CONTEXT));
78  memset(context, 0, sizeof(WS_WATCH_CONTEXT));
79  context->RefCount = 1;
80  context->ProcessItem = ProcessItem;
81 
82  DialogBoxParam(
84  MAKEINTRESOURCE(IDD_WSWATCH),
85  ParentWindowHandle,
87  (LPARAM)context
88  );
90 }
91 
93  _Inout_ PWS_WATCH_CONTEXT Context
94  )
95 {
96  _InterlockedIncrement(&Context->RefCount);
97 }
98 
100  _Inout_ PWS_WATCH_CONTEXT Context
101  )
102 {
103  if (_InterlockedDecrement(&Context->RefCount) == 0)
104  {
105  PSINGLE_LIST_ENTRY listEntry;
106 
107  if (Context->SymbolProvider)
108  PhDereferenceObject(Context->SymbolProvider);
109 
110  // Free all unused results.
111 
112  PhAcquireQueuedLockExclusive(&Context->ResultListLock);
113 
114  listEntry = Context->ResultListHead.Next;
115 
116  while (listEntry)
117  {
118  PSYMBOL_LOOKUP_RESULT result;
119 
120  result = CONTAINING_RECORD(listEntry, SYMBOL_LOOKUP_RESULT, ListEntry);
121  listEntry = listEntry->Next;
122  PhDereferenceObject(result->Symbol);
123  PhFree(result);
124  }
125 
126  PhReleaseQueuedLockExclusive(&Context->ResultListLock);
127 
128  PhFree(Context);
129  }
130 }
131 
132 static NTSTATUS EtpSymbolLookupFunction(
133  _In_ PVOID Parameter
134  )
135 {
136  PSYMBOL_LOOKUP_RESULT result;
137 
138  result = Parameter;
139 
140  // Don't bother looking up the symbol if the window has already closed.
141  if (result->Context->Destroying)
142  {
143  EtpDereferenceWsWatchContext(result->Context);
144  PhFree(result);
145  return STATUS_SUCCESS;
146  }
147 
148  result->Symbol = PhGetSymbolFromAddress(
149  result->Context->SymbolProvider,
150  (ULONG64)result->Address,
151  NULL,
152  NULL,
153  NULL,
154  NULL
155  );
156 
157  // Fail if we don't have a symbol.
158  if (!result->Symbol)
159  {
160  EtpDereferenceWsWatchContext(result->Context);
161  PhFree(result);
162  return STATUS_SUCCESS;
163  }
164 
165  PhAcquireQueuedLockExclusive(&result->Context->ResultListLock);
166  PushEntryList(&result->Context->ResultListHead, &result->ListEntry);
167  PhReleaseQueuedLockExclusive(&result->Context->ResultListLock);
168  EtpDereferenceWsWatchContext(result->Context);
169 
170  return STATUS_SUCCESS;
171 }
172 
173 static VOID EtpQueueSymbolLookup(
174  _In_ PWS_WATCH_CONTEXT Context,
175  _In_ PVOID Address
176  )
177 {
178  PSYMBOL_LOOKUP_RESULT result;
179 
180  result = PhAllocate(sizeof(SYMBOL_LOOKUP_RESULT));
181  result->Context = Context;
182  result->Address = Address;
184 
185  PhQueueItemGlobalWorkQueue(EtpSymbolLookupFunction, result);
186 }
187 
188 static PPH_STRING EtpGetBasicSymbol(
189  _In_ PPH_SYMBOL_PROVIDER SymbolProvider,
190  _In_ ULONG64 Address
191  )
192 {
193  ULONG64 modBase;
194  PPH_STRING fileName = NULL;
195  PPH_STRING baseName = NULL;
196  PPH_STRING symbol;
197 
198  modBase = PhGetModuleFromAddress(SymbolProvider, Address, &fileName);
199 
200  if (!fileName)
201  {
202  symbol = PhCreateStringEx(NULL, PH_PTR_STR_LEN * 2);
203  PhPrintPointer(symbol->Buffer, (PVOID)Address);
205  }
206  else
207  {
208  PH_FORMAT format[3];
209 
210  baseName = PhGetBaseName(fileName);
211 
212  PhInitFormatSR(&format[0], baseName->sr);
213  PhInitFormatS(&format[1], L"+0x");
214  PhInitFormatIX(&format[2], (ULONG_PTR)(Address - modBase));
215 
216  symbol = PhFormat(format, 3, baseName->Length + 6 + 32);
217  }
218 
219  if (fileName)
220  PhDereferenceObject(fileName);
221  if (baseName)
222  PhDereferenceObject(baseName);
223 
224  return symbol;
225 }
226 
227 static VOID EtpProcessSymbolLookupResults(
228  _In_ HWND hwndDlg,
229  _In_ PWS_WATCH_CONTEXT Context
230  )
231 {
232  PSINGLE_LIST_ENTRY listEntry;
233 
234  // Remove all results.
235  PhAcquireQueuedLockExclusive(&Context->ResultListLock);
236  listEntry = Context->ResultListHead.Next;
237  Context->ResultListHead.Next = NULL;
238  PhReleaseQueuedLockExclusive(&Context->ResultListLock);
239 
240  // Update the list view with the results.
241  while (listEntry)
242  {
243  PSYMBOL_LOOKUP_RESULT result;
244 
245  result = CONTAINING_RECORD(listEntry, SYMBOL_LOOKUP_RESULT, ListEntry);
246  listEntry = listEntry->Next;
247 
249  Context->ListViewHandle,
250  PhFindListViewItemByParam(Context->ListViewHandle, -1, result->Address),
251  0,
252  result->Symbol->Buffer
253  );
254 
255  PhDereferenceObject(result->Symbol);
256  PhFree(result);
257  }
258 }
259 
260 static BOOLEAN EtpUpdateWsWatch(
261  _In_ HWND hwndDlg,
262  _In_ PWS_WATCH_CONTEXT Context
263  )
264 {
265  NTSTATUS status;
266  BOOLEAN result;
267  ULONG returnLength;
269 
270  // Query WS watch information.
271 
272  if (!Context->Buffer)
273  return FALSE;
274 
275  status = NtQueryInformationProcess(
276  Context->ProcessHandle,
277  ProcessWorkingSetWatchEx,
278  Context->Buffer,
279  Context->BufferSize,
280  &returnLength
281  );
282 
283  if (status == STATUS_UNSUCCESSFUL)
284  {
285  // WS Watch is not enabled.
286  return FALSE;
287  }
288 
289  if (status == STATUS_NO_MORE_ENTRIES)
290  {
291  // There were no new faults, but we still need to process symbol lookup results.
292  result = TRUE;
293  goto SkipBuffer;
294  }
295 
296  if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH)
297  {
298  PhFree(Context->Buffer);
299  Context->Buffer = PhAllocate(returnLength);
300  Context->BufferSize = returnLength;
301 
302  status = NtQueryInformationProcess(
303  Context->ProcessHandle,
304  ProcessWorkingSetWatchEx,
305  Context->Buffer,
306  Context->BufferSize,
307  &returnLength
308  );
309  }
310 
311  if (!NT_SUCCESS(status))
312  {
313  // Error related to the buffer size. Try again later.
314  result = FALSE;
315  goto SkipBuffer;
316  }
317 
318  // Update the hashtable and list view.
319 
320  ExtendedListView_SetRedraw(Context->ListViewHandle, FALSE);
321 
322  wsWatchInfo = Context->Buffer;
323 
324  while (wsWatchInfo->BasicInfo.FaultingPc)
325  {
326  PVOID *entry;
327  WCHAR buffer[PH_INT32_STR_LEN_1];
328  INT lvItemIndex;
329  ULONG newCount;
330 
331  // Update the count in the entry for this instruction pointer, or add a new entry if it doesn't exist.
332 
333  entry = PhFindItemSimpleHashtable(Context->Hashtable, wsWatchInfo->BasicInfo.FaultingPc);
334 
335  if (entry)
336  {
337  newCount = PtrToUlong(*entry) + 1;
338  *entry = UlongToPtr(newCount);
339  lvItemIndex = PhFindListViewItemByParam(Context->ListViewHandle, -1, wsWatchInfo->BasicInfo.FaultingPc);
340  }
341  else
342  {
343  PPH_STRING basicSymbol;
344 
345  newCount = 1;
346  PhAddItemSimpleHashtable(Context->Hashtable, wsWatchInfo->BasicInfo.FaultingPc, UlongToPtr(1));
347 
348  // Get a basic symbol name (module+offset).
349  basicSymbol = EtpGetBasicSymbol(Context->SymbolProvider, (ULONG64)wsWatchInfo->BasicInfo.FaultingPc);
350 
351  lvItemIndex = PhAddListViewItem(Context->ListViewHandle, MAXINT, basicSymbol->Buffer, wsWatchInfo->BasicInfo.FaultingPc);
352  PhDereferenceObject(basicSymbol);
353 
354  // Queue a full symbol lookup.
355  EtpQueueSymbolLookup(Context, wsWatchInfo->BasicInfo.FaultingPc);
356  }
357 
358  // Update the count in the list view item.
359  PhPrintUInt32(buffer, newCount);
361  Context->ListViewHandle,
362  lvItemIndex,
363  1,
364  buffer
365  );
366 
367  wsWatchInfo++;
368  }
369 
370  ExtendedListView_SetRedraw(Context->ListViewHandle, TRUE);
371  result = TRUE;
372 
373 SkipBuffer:
374  EtpProcessSymbolLookupResults(hwndDlg, Context);
375  ExtendedListView_SortItems(Context->ListViewHandle);
376 
377  return result;
378 }
379 
380 static BOOLEAN NTAPI EnumGenericModulesCallback(
381  _In_ PPH_MODULE_INFO Module,
382  _In_opt_ PVOID Context
383  )
384 {
385  PWS_WATCH_CONTEXT context = Context;
386 
387  // If we're loading kernel module symbols for a process other than
388  // System, ignore modules which are in user space. This may happen
389  // in Windows 7.
390  if (
391  context->LoadingSymbolsForProcessId == SYSTEM_PROCESS_ID &&
392  (ULONG_PTR)Module->BaseAddress <= PhSystemBasicInformation.MaximumUserModeAddress
393  )
394  return TRUE;
395 
396  PhLoadModuleSymbolProvider(context->SymbolProvider, Module->FileName->Buffer,
397  (ULONG64)Module->BaseAddress, Module->Size);
398 
399  return TRUE;
400 }
401 
402 INT_PTR CALLBACK EtpWsWatchDlgProc(
403  _In_ HWND hwndDlg,
404  _In_ UINT uMsg,
405  _In_ WPARAM wParam,
406  _In_ LPARAM lParam
407  )
408 {
409  PWS_WATCH_CONTEXT context;
410 
411  if (uMsg == WM_INITDIALOG)
412  {
413  context = (PWS_WATCH_CONTEXT)lParam;
414  SetProp(hwndDlg, L"Context", (HANDLE)context);
415  }
416  else
417  {
418  context = (PWS_WATCH_CONTEXT)GetProp(hwndDlg, L"Context");
419 
420  if (uMsg == WM_DESTROY)
421  RemoveProp(hwndDlg, L"Context");
422  }
423 
424  if (!context)
425  return FALSE;
426 
427  switch (uMsg)
428  {
429  case WM_INITDIALOG:
430  {
431  HWND lvHandle;
432 
433  PhCenterWindow(hwndDlg, GetParent(hwndDlg));
434 
435  context->WindowHandle = hwndDlg;
436  context->ListViewHandle = lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
437 
438  PhSetListViewStyle(lvHandle, FALSE, TRUE);
439  PhSetControlTheme(lvHandle, L"explorer");
440  PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 340, L"Instruction");
441  PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 80, L"Count");
442  PhSetExtendedListView(lvHandle);
444 
445  context->Hashtable = PhCreateSimpleHashtable(64);
446  context->BufferSize = 0x2000;
447  context->Buffer = PhAllocate(context->BufferSize);
448 
449  PhInitializeQueuedLock(&context->ResultListLock);
450  context->SymbolProvider = PhCreateSymbolProvider(context->ProcessItem->ProcessId);
451  PhLoadSymbolProviderOptions(context->SymbolProvider);
452 
453  if (!context->SymbolProvider || !context->SymbolProvider->IsRealHandle)
454  {
455  PhShowError(hwndDlg, L"Unable to open the process.");
456  EndDialog(hwndDlg, IDCANCEL);
457  break;
458  }
459 
460  context->ProcessHandle = context->SymbolProvider->ProcessHandle;
461 
462  // Load symbols for both process and kernel modules.
463  context->LoadingSymbolsForProcessId = context->ProcessItem->ProcessId;
465  NULL,
466  context->ProcessHandle,
467  0,
468  EnumGenericModulesCallback,
469  context
470  );
471  context->LoadingSymbolsForProcessId = SYSTEM_PROCESS_ID;
474  NULL,
475  0,
476  EnumGenericModulesCallback,
477  context
478  );
479 
480  context->Enabled = EtpUpdateWsWatch(hwndDlg, context);
481 
482  if (context->Enabled)
483  {
484  // WS Watch is already enabled for the process. Enable updating.
485  EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLE), FALSE);
486  ShowWindow(GetDlgItem(hwndDlg, IDC_WSWATCHENABLED), SW_SHOW);
487  SetTimer(hwndDlg, 1, 1000, NULL);
488  }
489  else
490  {
491  // WS Watch has not yet been enabled for the process.
492  }
493  }
494  break;
495  case WM_DESTROY:
496  {
497  context->Destroying = TRUE;
498 
499  PhDereferenceObject(context->Hashtable);
500 
501  if (context->Buffer)
502  {
503  PhFree(context->Buffer);
504  context->Buffer = NULL;
505  }
506  }
507  break;
508  case WM_COMMAND:
509  {
510  switch (LOWORD(wParam))
511  {
512  case IDCANCEL:
513  case IDOK:
514  EndDialog(hwndDlg, IDOK);
515  break;
516  case IDC_ENABLE:
517  {
518  NTSTATUS status;
519  HANDLE processHandle;
520 
521  if (NT_SUCCESS(status = PhOpenProcess(
522  &processHandle,
524  context->ProcessItem->ProcessId
525  )))
526  {
527  status = NtSetInformationProcess(
528  processHandle,
529  ProcessWorkingSetWatchEx,
530  NULL,
531  0
532  );
533  NtClose(processHandle);
534  }
535 
536  if (NT_SUCCESS(status))
537  {
538  EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLE), FALSE);
539  ShowWindow(GetDlgItem(hwndDlg, IDC_WSWATCHENABLED), SW_SHOW);
540  SetTimer(hwndDlg, 1, 1000, NULL);
541  }
542  else
543  {
544  PhShowStatus(hwndDlg, L"Unable to enable WS watch", status, 0);
545  }
546  }
547  break;
548  }
549  }
550  break;
551  case WM_NOTIFY:
552  {
553  PhHandleListViewNotifyForCopy(lParam, context->ListViewHandle);
554  }
555  break;
556  case WM_TIMER:
557  {
558  switch (wParam)
559  {
560  case 1:
561  {
562  EtpUpdateWsWatch(hwndDlg, context);
563  }
564  break;
565  }
566  }
567  break;
568  }
569 
570  return FALSE;
571 }