Process Hacker
thrdstk.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * thread stack viewer
4  *
5  * Copyright (C) 2010-2015 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 <phapp.h>
24 #include <kphuser.h>
25 #include <symprv.h>
26 #include <settings.h>
27 #include <phplug.h>
28 
29 #define WM_PH_COMPLETED (WM_APP + 301)
30 #define WM_PH_STATUS_UPDATE (WM_APP + 302)
31 
32 typedef struct _THREAD_STACK_CONTEXT
33 {
34  HANDLE ProcessId;
35  HANDLE ThreadId;
36  HANDLE ThreadHandle;
37  HWND ListViewHandle;
38  PPH_THREAD_PROVIDER ThreadProvider;
39  PPH_SYMBOL_PROVIDER SymbolProvider;
40  BOOLEAN CustomWalk;
41 
42  BOOLEAN StopWalk;
43  PPH_LIST List;
44  PPH_LIST NewList;
45  HWND ProgressWindowHandle;
46  NTSTATUS WalkStatus;
47  PPH_STRING StatusMessage;
48  PH_QUEUED_LOCK StatusLock;
50 
51 typedef struct _THREAD_STACK_ITEM
52 {
53  PH_THREAD_STACK_FRAME StackFrame;
54  ULONG Index;
55  PPH_STRING Symbol;
57 
58 INT_PTR CALLBACK PhpThreadStackDlgProc(
59  _In_ HWND hwndDlg,
60  _In_ UINT uMsg,
61  _In_ WPARAM wParam,
62  _In_ LPARAM lParam
63  );
64 
66  _In_ PTHREAD_STACK_ITEM StackItem
67  );
68 
69 NTSTATUS PhpRefreshThreadStack(
70  _In_ HWND hwnd,
71  _In_ PTHREAD_STACK_CONTEXT ThreadStackContext
72  );
73 
74 INT_PTR CALLBACK PhpThreadStackProgressDlgProc(
75  _In_ HWND hwndDlg,
76  _In_ UINT uMsg,
77  _In_ WPARAM wParam,
78  _In_ LPARAM lParam
79  );
80 
81 static RECT MinimumSize = { -1, -1, -1, -1 };
82 
84  _In_ HWND ParentWindowHandle,
85  _In_ HANDLE ProcessId,
86  _In_ HANDLE ThreadId,
87  _In_ PPH_THREAD_PROVIDER ThreadProvider
88  )
89 {
90  NTSTATUS status;
91  THREAD_STACK_CONTEXT threadStackContext;
92  HANDLE threadHandle = NULL;
93 
94  // If the user is trying to view a system thread stack
95  // but KProcessHacker is not loaded, show an error message.
96  if (ProcessId == SYSTEM_PROCESS_ID && !KphIsConnected())
97  {
98  PhShowError(ParentWindowHandle, KPH_ERROR_MESSAGE);
99  return;
100  }
101 
102  memset(&threadStackContext, 0, sizeof(THREAD_STACK_CONTEXT));
103  threadStackContext.ProcessId = ProcessId;
104  threadStackContext.ThreadId = ThreadId;
105  threadStackContext.ThreadProvider = ThreadProvider;
106  threadStackContext.SymbolProvider = ThreadProvider->SymbolProvider;
107 
108  if (!NT_SUCCESS(status = PhOpenThread(
109  &threadHandle,
110  ThreadQueryAccess | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME,
111  ThreadId
112  )))
113  {
114  if (KphIsConnected())
115  {
116  status = PhOpenThread(
117  &threadHandle,
119  ThreadId
120  );
121  }
122  }
123 
124  if (!NT_SUCCESS(status))
125  {
126  PhShowStatus(ParentWindowHandle, L"Unable to open the thread", status, 0);
127  return;
128  }
129 
130  threadStackContext.ThreadHandle = threadHandle;
131  threadStackContext.List = PhCreateList(10);
132  threadStackContext.NewList = PhCreateList(10);
133  PhInitializeQueuedLock(&threadStackContext.StatusLock);
134 
135  DialogBoxParam(
137  MAKEINTRESOURCE(IDD_THRDSTACK),
138  ParentWindowHandle,
140  (LPARAM)&threadStackContext
141  );
142 
143  PhClearReference(&threadStackContext.StatusMessage);
144  PhDereferenceObject(threadStackContext.NewList);
145  PhDereferenceObject(threadStackContext.List);
146 
147  if (threadStackContext.ThreadHandle)
148  NtClose(threadStackContext.ThreadHandle);
149 }
150 
151 static INT_PTR CALLBACK PhpThreadStackDlgProc(
152  _In_ HWND hwndDlg,
153  _In_ UINT uMsg,
154  _In_ WPARAM wParam,
155  _In_ LPARAM lParam
156  )
157 {
158  switch (uMsg)
159  {
160  case WM_INITDIALOG:
161  {
162  NTSTATUS status;
163  PTHREAD_STACK_CONTEXT threadStackContext;
164  PPH_STRING title;
165  HWND lvHandle;
166  PPH_LAYOUT_MANAGER layoutManager;
167 
168  threadStackContext = (PTHREAD_STACK_CONTEXT)lParam;
169  SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)threadStackContext);
170 
171  title = PhFormatString(L"Stack - thread %u", (ULONG)threadStackContext->ThreadId);
172  SetWindowText(hwndDlg, title->Buffer);
173  PhDereferenceObject(title);
174 
175  lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
176  PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 30, L" ");
177  PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 300, L"Name");
178  PhSetListViewStyle(lvHandle, FALSE, TRUE);
179  PhSetControlTheme(lvHandle, L"explorer");
180  PhLoadListViewColumnsFromSetting(L"ThreadStackListViewColumns", lvHandle);
181 
182  threadStackContext->ListViewHandle = lvHandle;
183 
184  layoutManager = PhAllocate(sizeof(PH_LAYOUT_MANAGER));
185  PhInitializeLayoutManager(layoutManager, hwndDlg);
186  SetProp(hwndDlg, L"LayoutManager", (HANDLE)layoutManager);
187 
188  PhAddLayoutItem(layoutManager, lvHandle, NULL,
189  PH_ANCHOR_ALL);
190  PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDC_COPY),
192  PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDC_REFRESH),
194  PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDOK),
196 
197  if (MinimumSize.left == -1)
198  {
199  RECT rect;
200 
201  rect.left = 0;
202  rect.top = 0;
203  rect.right = 190;
204  rect.bottom = 120;
205  MapDialogRect(hwndDlg, &rect);
206  MinimumSize = rect;
207  MinimumSize.left = 0;
208  }
209 
210  PhLoadWindowPlacementFromSetting(NULL, L"ThreadStackWindowSize", hwndDlg);
211  PhCenterWindow(hwndDlg, GetParent(hwndDlg));
212 
213  if (PhPluginsEnabled)
214  {
216 
218  control.UniqueKey = threadStackContext;
219  control.u.Initializing.ProcessId = threadStackContext->ProcessId;
220  control.u.Initializing.ThreadId = threadStackContext->ThreadId;
221  control.u.Initializing.ThreadHandle = threadStackContext->ThreadHandle;
222  control.u.Initializing.SymbolProvider = threadStackContext->SymbolProvider;
223  control.u.Initializing.CustomWalk = FALSE;
225 
226  threadStackContext->CustomWalk = control.u.Initializing.CustomWalk;
227  }
228 
229  status = PhpRefreshThreadStack(hwndDlg, threadStackContext);
230 
231  if (status == STATUS_ABANDONED)
232  EndDialog(hwndDlg, IDCANCEL);
233  else if (!NT_SUCCESS(status))
234  PhShowStatus(hwndDlg, L"Unable to load the stack", status, 0);
235  }
236  break;
237  case WM_DESTROY:
238  {
239  PPH_LAYOUT_MANAGER layoutManager;
240  PTHREAD_STACK_CONTEXT threadStackContext;
241  ULONG i;
242 
243  layoutManager = (PPH_LAYOUT_MANAGER)GetProp(hwndDlg, L"LayoutManager");
244  PhDeleteLayoutManager(layoutManager);
245  PhFree(layoutManager);
246 
247  threadStackContext = (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
248 
249  if (PhPluginsEnabled)
250  {
252 
254  control.UniqueKey = threadStackContext;
256  }
257 
258  for (i = 0; i < threadStackContext->List->Count; i++)
259  PhpFreeThreadStackItem(threadStackContext->List->Items[i]);
260 
261  PhSaveListViewColumnsToSetting(L"ThreadStackListViewColumns", GetDlgItem(hwndDlg, IDC_LIST));
262  PhSaveWindowPlacementToSetting(NULL, L"ThreadStackWindowSize", hwndDlg);
263 
264  RemoveProp(hwndDlg, PhMakeContextAtom());
265  RemoveProp(hwndDlg, L"LayoutManager");
266  }
267  break;
268  case WM_COMMAND:
269  {
270  INT id = LOWORD(wParam);
271 
272  switch (id)
273  {
274  case IDCANCEL: // Esc and X button to close
275  case IDOK:
276  EndDialog(hwndDlg, IDOK);
277  break;
278  case IDC_REFRESH:
279  {
280  NTSTATUS status;
281 
282  if (!NT_SUCCESS(status = PhpRefreshThreadStack(
283  hwndDlg,
284  (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom())
285  )))
286  {
287  PhShowStatus(hwndDlg, L"Unable to load the stack", status, 0);
288  }
289  }
290  break;
291  case IDC_COPY:
292  {
293  HWND lvHandle;
294 
295  lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
296 
297  if (ListView_GetSelectedCount(lvHandle) == 0)
298  PhSetStateAllListViewItems(lvHandle, LVIS_SELECTED, LVIS_SELECTED);
299 
300  PhCopyListView(lvHandle);
301  SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)lvHandle, TRUE);
302  }
303  break;
304  }
305  }
306  break;
307  case WM_NOTIFY:
308  {
309  LPNMHDR header = (LPNMHDR)lParam;
310 
311  switch (header->code)
312  {
313  case LVN_GETINFOTIP:
314  {
315  LPNMLVGETINFOTIP getInfoTip = (LPNMLVGETINFOTIP)header;
316  HWND lvHandle;
317  PTHREAD_STACK_CONTEXT threadStackContext;
318 
319  lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
320  threadStackContext = (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
321 
322  if (header->hwndFrom == lvHandle)
323  {
324  PTHREAD_STACK_ITEM stackItem;
325  PPH_THREAD_STACK_FRAME stackFrame;
326 
327  if (PhGetListViewItemParam(lvHandle, getInfoTip->iItem, &stackItem))
328  {
329  PH_STRING_BUILDER stringBuilder;
330  PPH_STRING fileName;
332 
333  stackFrame = &stackItem->StackFrame;
334  PhInitializeStringBuilder(&stringBuilder, 40);
335 
337  &stringBuilder,
338  L"Stack: 0x%Ix, Frame: 0x%Ix\n",
339  stackFrame->StackAddress,
340  stackFrame->FrameAddress
341  );
342 
343  // There are no params for kernel-mode stack traces.
344  if ((ULONG_PTR)stackFrame->PcAddress <= PhSystemBasicInformation.MaximumUserModeAddress)
345  {
347  &stringBuilder,
348  L"Parameters: 0x%Ix, 0x%Ix, 0x%Ix, 0x%Ix\n",
349  stackFrame->Params[0],
350  stackFrame->Params[1],
351  stackFrame->Params[2],
352  stackFrame->Params[3]
353  );
354  }
355 
357  threadStackContext->SymbolProvider,
358  (ULONG64)stackFrame->PcAddress,
359  &fileName,
360  NULL,
361  &lineInfo
362  ))
363  {
365  &stringBuilder,
366  L"File: %s: line %u\n",
367  fileName->Buffer,
368  lineInfo.LineNumber
369  );
370  PhDereferenceObject(fileName);
371  }
372 
373  if (stringBuilder.String->Length != 0)
374  PhRemoveEndStringBuilder(&stringBuilder, 1);
375 
376  if (PhPluginsEnabled)
377  {
379 
381  control.UniqueKey = threadStackContext;
382  control.u.GetTooltip.StackFrame = stackFrame;
383  control.u.GetTooltip.StringBuilder = &stringBuilder;
385  }
386 
387  PhCopyListViewInfoTip(getInfoTip, &stringBuilder.String->sr);
388  PhDeleteStringBuilder(&stringBuilder);
389  }
390  }
391  }
392  break;
393  }
394  }
395  break;
396  case WM_SIZE:
397  {
398  PPH_LAYOUT_MANAGER layoutManager;
399 
400  layoutManager = (PPH_LAYOUT_MANAGER)GetProp(hwndDlg, L"LayoutManager");
401  PhLayoutManagerLayout(layoutManager);
402  }
403  break;
404  case WM_SIZING:
405  {
406  PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom);
407  }
408  break;
409  }
410 
411  return FALSE;
412 }
413 
415  _In_ PTHREAD_STACK_ITEM StackItem
416  )
417 {
418  PhClearReference(&StackItem->Symbol);
419  PhFree(StackItem);
420 }
421 
422 static NTSTATUS PhpRefreshThreadStack(
423  _In_ HWND hwnd,
424  _In_ PTHREAD_STACK_CONTEXT ThreadStackContext
425  )
426 {
427  ULONG i;
428 
429  ThreadStackContext->StopWalk = FALSE;
430  PhMoveReference(&ThreadStackContext->StatusMessage, PhCreateString(L"Loading stack..."));
431 
432  DialogBoxParam(
434  MAKEINTRESOURCE(IDD_PROGRESS),
435  hwnd,
437  (LPARAM)ThreadStackContext
438  );
439 
440  if (!ThreadStackContext->StopWalk && NT_SUCCESS(ThreadStackContext->WalkStatus))
441  {
442  for (i = 0; i < ThreadStackContext->List->Count; i++)
443  PhpFreeThreadStackItem(ThreadStackContext->List->Items[i]);
444 
445  PhDereferenceObject(ThreadStackContext->List);
446  ThreadStackContext->List = ThreadStackContext->NewList;
447  ThreadStackContext->NewList = PhCreateList(10);
448 
449  ListView_DeleteAllItems(ThreadStackContext->ListViewHandle);
450  SendMessage(ThreadStackContext->ListViewHandle, WM_SETREDRAW, FALSE, 0);
451 
452  for (i = 0; i < ThreadStackContext->List->Count; i++)
453  {
454  PTHREAD_STACK_ITEM item = ThreadStackContext->List->Items[i];
455  INT lvItemIndex;
456  WCHAR integerString[PH_INT32_STR_LEN_1];
457 
458  PhPrintUInt32(integerString, item->Index);
459  lvItemIndex = PhAddListViewItem(ThreadStackContext->ListViewHandle, MAXINT, integerString, item);
460  PhSetListViewSubItem(ThreadStackContext->ListViewHandle, lvItemIndex, 1, PhGetStringOrDefault(item->Symbol, L"???"));
461  }
462 
463  SendMessage(ThreadStackContext->ListViewHandle, WM_SETREDRAW, TRUE, 0);
464  InvalidateRect(ThreadStackContext->ListViewHandle, NULL, FALSE);
465  }
466  else
467  {
468  for (i = 0; i < ThreadStackContext->NewList->Count; i++)
469  PhpFreeThreadStackItem(ThreadStackContext->NewList->Items[i]);
470 
471  PhClearList(ThreadStackContext->NewList);
472  }
473 
474  if (ThreadStackContext->StopWalk)
475  return STATUS_ABANDONED;
476 
477  return ThreadStackContext->WalkStatus;
478 }
479 
480 static BOOLEAN NTAPI PhpWalkThreadStackCallback(
481  _In_ PPH_THREAD_STACK_FRAME StackFrame,
482  _In_opt_ PVOID Context
483  )
484 {
485  PTHREAD_STACK_CONTEXT threadStackContext = (PTHREAD_STACK_CONTEXT)Context;
486  PPH_STRING symbol;
487  PTHREAD_STACK_ITEM item;
488 
489  if (threadStackContext->StopWalk)
490  return FALSE;
491 
492  PhAcquireQueuedLockExclusive(&threadStackContext->StatusLock);
493  PhMoveReference(&threadStackContext->StatusMessage,
494  PhFormatString(L"Processing frame %u...", threadStackContext->NewList->Count));
495  PhReleaseQueuedLockExclusive(&threadStackContext->StatusLock);
496  PostMessage(threadStackContext->ProgressWindowHandle, WM_PH_STATUS_UPDATE, 0, 0);
497 
498  symbol = PhGetSymbolFromAddress(
499  threadStackContext->SymbolProvider,
500  (ULONG64)StackFrame->PcAddress,
501  NULL,
502  NULL,
503  NULL,
504  NULL
505  );
506 
507  if (symbol &&
508  (StackFrame->Flags & PH_THREAD_STACK_FRAME_I386) &&
509  !(StackFrame->Flags & PH_THREAD_STACK_FRAME_FPO_DATA_PRESENT))
510  {
511  PhMoveReference(&symbol, PhConcatStrings2(symbol->Buffer, L" (No unwind info)"));
512  }
513 
514  item = PhAllocate(sizeof(THREAD_STACK_ITEM));
515  item->StackFrame = *StackFrame;
516  item->Index = threadStackContext->NewList->Count;
517 
518  if (PhPluginsEnabled)
519  {
521 
523  control.UniqueKey = threadStackContext;
524  control.u.ResolveSymbol.StackFrame = StackFrame;
525  control.u.ResolveSymbol.Symbol = symbol;
527 
528  symbol = control.u.ResolveSymbol.Symbol;
529  }
530 
531  item->Symbol = symbol;
532  PhAddItemList(threadStackContext->NewList, item);
533 
534  return TRUE;
535 }
536 
537 static NTSTATUS PhpRefreshThreadStackThreadStart(
538  _In_ PVOID Parameter
539  )
540 {
541  NTSTATUS status;
542  PTHREAD_STACK_CONTEXT threadStackContext = Parameter;
543  CLIENT_ID clientId;
544  BOOLEAN defaultWalk;
545 
546  PhLoadSymbolsThreadProvider(threadStackContext->ThreadProvider);
547 
548  clientId.UniqueProcess = threadStackContext->ProcessId;
549  clientId.UniqueThread = threadStackContext->ThreadId;
550  defaultWalk = TRUE;
551 
552  if (threadStackContext->CustomWalk)
553  {
555 
557  control.UniqueKey = threadStackContext;
558  control.u.WalkStack.Status = STATUS_UNSUCCESSFUL;
559  control.u.WalkStack.ThreadHandle = threadStackContext->ThreadHandle;
560  control.u.WalkStack.ProcessHandle = threadStackContext->SymbolProvider->ProcessHandle;
561  control.u.WalkStack.ClientId = &clientId;
563  control.u.WalkStack.Callback = PhpWalkThreadStackCallback;
564  control.u.WalkStack.CallbackContext = threadStackContext;
566  status = control.u.WalkStack.Status;
567 
568  if (NT_SUCCESS(status))
569  defaultWalk = FALSE;
570  }
571 
572  if (defaultWalk)
573  {
575 
576  control.UniqueKey = threadStackContext;
577 
578  if (PhPluginsEnabled)
579  {
582  }
583 
584  status = PhWalkThreadStack(
585  threadStackContext->ThreadHandle,
586  threadStackContext->SymbolProvider->ProcessHandle,
587  &clientId,
588  threadStackContext->SymbolProvider,
590  PhpWalkThreadStackCallback,
591  threadStackContext
592  );
593 
594  if (PhPluginsEnabled)
595  {
598  }
599  }
600 
601  if (threadStackContext->NewList->Count != 0)
602  status = STATUS_SUCCESS;
603 
604  threadStackContext->WalkStatus = status;
605  PostMessage(threadStackContext->ProgressWindowHandle, WM_PH_COMPLETED, 0, 0);
606 
607  return STATUS_SUCCESS;
608 }
609 
610 static INT_PTR CALLBACK PhpThreadStackProgressDlgProc(
611  _In_ HWND hwndDlg,
612  _In_ UINT uMsg,
613  _In_ WPARAM wParam,
614  _In_ LPARAM lParam
615  )
616 {
617  switch (uMsg)
618  {
619  case WM_INITDIALOG:
620  {
621  PTHREAD_STACK_CONTEXT threadStackContext;
622  HANDLE threadHandle;
623 
624  threadStackContext = (PTHREAD_STACK_CONTEXT)lParam;
625  SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)threadStackContext);
626  threadStackContext->ProgressWindowHandle = hwndDlg;
627 
628  if (threadHandle = PhCreateThread(0, PhpRefreshThreadStackThreadStart, threadStackContext))
629  {
630  NtClose(threadHandle);
631  }
632  else
633  {
634  threadStackContext->WalkStatus = STATUS_UNSUCCESSFUL;
635  EndDialog(hwndDlg, IDOK);
636  break;
637  }
638 
639  PhCenterWindow(hwndDlg, GetParent(hwndDlg));
640 
641  PhSetWindowStyle(GetDlgItem(hwndDlg, IDC_PROGRESS), PBS_MARQUEE, PBS_MARQUEE);
642  SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS), PBM_SETMARQUEE, TRUE, 75);
643  SetWindowText(hwndDlg, L"Loading stack...");
644  }
645  break;
646  case WM_DESTROY:
647  {
648  RemoveProp(hwndDlg, PhMakeContextAtom());
649  }
650  break;
651  case WM_COMMAND:
652  {
653  switch (LOWORD(wParam))
654  {
655  case IDCANCEL:
656  {
657  PTHREAD_STACK_CONTEXT threadStackContext = (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
658 
659  EnableWindow(GetDlgItem(hwndDlg, IDCANCEL), FALSE);
660  threadStackContext->StopWalk = TRUE;
661  }
662  break;
663  }
664  }
665  break;
666  case WM_PH_COMPLETED:
667  {
668  EndDialog(hwndDlg, IDOK);
669  }
670  break;
671  case WM_PH_STATUS_UPDATE:
672  {
673  PTHREAD_STACK_CONTEXT threadStackContext = (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom());
674  PPH_STRING message;
675 
676  PhAcquireQueuedLockExclusive(&threadStackContext->StatusLock);
677  message = threadStackContext->StatusMessage;
678  PhReferenceObject(message);
679  PhReleaseQueuedLockExclusive(&threadStackContext->StatusLock);
680 
681  SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, message->Buffer);
682  PhDereferenceObject(message);
683  }
684  break;
685  }
686 
687  return 0;
688 }