Process Hacker
anawait.c
Go to the documentation of this file.
1 /*
2  * Process Hacker -
3  * thread wait analysis
4  *
5  * Copyright (C) 2010-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 /*
24  * There are two ways of seeing what a thread is waiting on. The first method
25  * is to walk the stack of a thread and read the arguments to whatever system
26  * call it is blocking on; this only works on x86 because on x64 the arguments
27  * are passed in registers (at least the first four are). The second method
28  * involves using the ThreadLastSystemCall info class for NtQueryInformationThread
29  * to retrieve the first argument to the system call the thread is blocking on.
30  * This is obviously only useful for NtWaitForSingleObject.
31  *
32  * There are other methods for specific scenarios, like USER messages and ALPC
33  * calls.
34  */
35 
36 #include <phapp.h>
37 #include <symprv.h>
38 
39 typedef HWND (WINAPI *_GetSendMessageReceiver)(
40  _In_ HANDLE ThreadId
41  );
42 
43 typedef NTSTATUS (NTAPI *_NtAlpcQueryInformation)(
44  _In_ HANDLE PortHandle,
45  _In_ ALPC_PORT_INFORMATION_CLASS PortInformationClass,
46  _Out_writes_bytes_(Length) PVOID PortInformation,
47  _In_ ULONG Length,
48  _Out_opt_ PULONG ReturnLength
49  );
50 
51 typedef struct _ANALYZE_WAIT_CONTEXT
52 {
53  BOOLEAN Found;
54  BOOLEAN IsWow64;
55  HANDLE ProcessId;
56  HANDLE ThreadId;
57  HANDLE ProcessHandle;
58 
59  PPH_SYMBOL_PROVIDER SymbolProvider;
60  PH_STRING_BUILDER StringBuilder;
61 
62  PVOID PrevParams[4];
64 
66  _In_ HWND hWnd,
67  _In_ HANDLE ProcessId,
68  _In_ HANDLE ThreadId
69  );
70 
72  _In_ PPH_THREAD_STACK_FRAME StackFrame,
73  _In_opt_ PVOID Context
74  );
75 
77  _In_ PANALYZE_WAIT_CONTEXT Context
78  );
79 
81  VOID
82  );
83 
85  _In_ HANDLE ProcessHandle,
86  _In_ HANDLE Handle
87  );
88 
90  _In_ HANDLE ProcessHandle,
91  _In_ BOOLEAN IsWow64,
92  _In_ ULONG NumberOfHandles,
93  _In_ PHANDLE AddressOfHandles,
94  _In_ WAIT_TYPE WaitType,
95  _In_ BOOLEAN Alertable,
96  _Inout_ PPH_STRING_BUILDER StringBuilder
97  );
98 
100  _In_ HANDLE ThreadId
101  );
102 
104  _In_ HANDLE ThreadId
105  );
106 
107 static PH_INITONCE ServiceNumbersInitOnce = PH_INITONCE_INIT;
108 static USHORT NumberForWfso = -1;
109 static USHORT NumberForWfmo = -1;
110 static USHORT NumberForRf = -1;
111 
113  _In_ HWND hWnd,
114  _In_ HANDLE ProcessId,
115  _In_ HANDLE ThreadId,
116  _In_ PPH_SYMBOL_PROVIDER SymbolProvider
117  )
118 {
119  NTSTATUS status;
120  HANDLE threadHandle;
121 #ifdef _WIN64
122  HANDLE processHandle;
123  BOOLEAN isWow64;
124 #endif
125  CLIENT_ID clientId;
126  ANALYZE_WAIT_CONTEXT context;
127 
128 #ifdef _WIN64
129  // Determine if the process is WOW64. If not, we use the passive method.
130 
131  if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, ProcessQueryAccess, ProcessId)))
132  {
133  PhShowStatus(hWnd, L"Unable to open the process", status, 0);
134  return;
135  }
136 
137  if (!NT_SUCCESS(status = PhGetProcessIsWow64(processHandle, &isWow64)) || !isWow64)
138  {
139  PhpAnalyzeWaitPassive(hWnd, ProcessId, ThreadId);
140  return;
141  }
142 
143  NtClose(processHandle);
144 #endif
145 
146  if (!NT_SUCCESS(status = PhOpenThread(
147  &threadHandle,
148  ThreadQueryAccess | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME,
149  ThreadId
150  )))
151  {
152  PhShowStatus(hWnd, L"Unable to open the thread", status, 0);
153  return;
154  }
155 
156  context.ProcessId = ProcessId;
157  context.ThreadId = ThreadId;
158 
159  context.ProcessHandle = SymbolProvider->ProcessHandle;
160  context.SymbolProvider = SymbolProvider;
161  PhInitializeStringBuilder(&context.StringBuilder, 100);
162 
163  clientId.UniqueProcess = ProcessId;
164  clientId.UniqueThread = ThreadId;
165 
167  threadHandle,
168  SymbolProvider->ProcessHandle,
169  &clientId,
170  SymbolProvider,
173  &context
174  );
175  NtClose(threadHandle);
176 
177  PhpAnalyzeWaitFallbacks(&context);
178 
179  if (context.Found)
180  {
181  PhShowInformationDialog(hWnd, context.StringBuilder.String->Buffer);
182  }
183  else
184  {
185  PhShowInformation(hWnd, L"The thread does not appear to be waiting.");
186  }
187 
188  PhDeleteStringBuilder(&context.StringBuilder);
189 }
190 
192  _In_ HWND hWnd,
193  _In_ HANDLE ProcessId,
194  _In_ HANDLE ThreadId
195  )
196 {
197  NTSTATUS status;
198  HANDLE processHandle;
199  HANDLE threadHandle;
200  THREAD_LAST_SYSCALL_INFORMATION lastSystemCall;
201  PH_STRING_BUILDER stringBuilder;
202  PPH_STRING string;
203 
205 
206  if (!NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_GET_CONTEXT, ThreadId)))
207  {
208  PhShowStatus(hWnd, L"Unable to open the thread", status, 0);
209  return;
210  }
211 
212  if (!NT_SUCCESS(status = NtQueryInformationThread(
213  threadHandle,
214  ThreadLastSystemCall,
215  &lastSystemCall,
217  NULL
218  )))
219  {
220  NtClose(threadHandle);
221  PhShowInformation(hWnd, L"Unable to determine whether the thread is waiting.");
222  return;
223  }
224 
225  if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_DUP_HANDLE, ProcessId)))
226  {
227  NtClose(threadHandle);
228  PhShowStatus(hWnd, L"Unable to open the process", status, 0);
229  return;
230  }
231 
232  PhInitializeStringBuilder(&stringBuilder, 100);
233 
234  if (lastSystemCall.SystemCallNumber == NumberForWfso)
235  {
236  string = PhpaGetHandleString(processHandle, lastSystemCall.FirstArgument);
237 
238  PhAppendFormatStringBuilder(&stringBuilder, L"Thread is waiting for:\r\n");
239  PhAppendStringBuilder(&stringBuilder, &string->sr);
240  }
241  else if (lastSystemCall.SystemCallNumber == NumberForWfmo)
242  {
243  PhAppendFormatStringBuilder(&stringBuilder, L"Thread is waiting for multiple (%u) objects.", (ULONG)lastSystemCall.FirstArgument);
244  }
245  else if (lastSystemCall.SystemCallNumber == NumberForRf)
246  {
247  string = PhpaGetHandleString(processHandle, lastSystemCall.FirstArgument);
248 
249  PhAppendFormatStringBuilder(&stringBuilder, L"Thread is waiting for file I/O:\r\n");
250  PhAppendStringBuilder(&stringBuilder, &string->sr);
251  }
252  else
253  {
254  string = PhpaGetSendMessageReceiver(ThreadId);
255 
256  if (string)
257  {
258  PhAppendStringBuilder2(&stringBuilder, L"Thread is sending a USER message:\r\n");
259  PhAppendStringBuilder(&stringBuilder, &string->sr);
260  }
261  else
262  {
263  string = PhpaGetAlpcInformation(ThreadId);
264 
265  if (string)
266  {
267  PhAppendStringBuilder2(&stringBuilder, L"Thread is waiting for an ALPC port:\r\n");
268  PhAppendStringBuilder(&stringBuilder, &string->sr);
269  }
270  }
271  }
272 
273  if (stringBuilder.String->Length == 0)
274  PhAppendStringBuilder2(&stringBuilder, L"Unable to determine why the thread is waiting.");
275 
276  PhShowInformationDialog(hWnd, stringBuilder.String->Buffer);
277 
278  PhDeleteStringBuilder(&stringBuilder);
279  NtClose(processHandle);
280  NtClose(threadHandle);
281 }
282 
284  _In_ PPH_THREAD_STACK_FRAME StackFrame,
285  _In_opt_ PVOID Context
286  )
287 {
288  PANALYZE_WAIT_CONTEXT context = (PANALYZE_WAIT_CONTEXT)Context;
289  PPH_STRING name;
290 
291  name = PhGetSymbolFromAddress(
292  context->SymbolProvider,
293  (ULONG64)StackFrame->PcAddress,
294  NULL,
295  NULL,
296  NULL,
297  NULL
298  );
299 
300  if (!name)
301  return TRUE;
302 
303  context->Found = TRUE;
304 
305 #define FUNC_MATCH(Name) PhStartsWithString2(name, L##Name, TRUE)
306 #define NT_FUNC_MATCH(Name) ( \
307  PhStartsWithString2(name, L"ntdll.dll!Nt" L##Name, TRUE) || \
308  PhStartsWithString2(name, L"ntdll.dll!Zw" L##Name, TRUE) \
309  )
310 
311  if (!name)
312  {
313  // Dummy
314  }
315  else if (FUNC_MATCH("kernel32.dll!Sleep"))
316  {
318  &context->StringBuilder,
319  L"Thread is sleeping. Timeout: %u milliseconds.",
320  (ULONG)StackFrame->Params[0]
321  );
322  }
323  else if (NT_FUNC_MATCH("DelayExecution"))
324  {
325  BOOLEAN alertable = !!StackFrame->Params[0];
326  PVOID timeoutAddress = StackFrame->Params[1];
327  LARGE_INTEGER timeout;
328 
330  context->ProcessHandle,
331  timeoutAddress,
332  &timeout,
333  sizeof(LARGE_INTEGER),
334  NULL
335  )))
336  {
337  if (timeout.QuadPart < 0)
338  {
340  &context->StringBuilder,
341  L"Thread is sleeping. Timeout: %I64u milliseconds.",
342  -timeout.QuadPart / PH_TIMEOUT_MS
343  );
344  }
345  else
346  {
347  // TODO
348  }
349  }
350  else
351  {
353  &context->StringBuilder,
354  L"Thread is sleeping."
355  );
356  }
357  }
358  else if (NT_FUNC_MATCH("DeviceIoControlFile"))
359  {
360  HANDLE handle = (HANDLE)StackFrame->Params[0];
361 
363  &context->StringBuilder,
364  L"Thread is waiting for an I/O control request:\r\n"
365  );
367  &context->StringBuilder,
368  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
369  );
370  }
371  else if (NT_FUNC_MATCH("FsControlFile"))
372  {
373  HANDLE handle = StackFrame->Params[0];
374 
376  &context->StringBuilder,
377  L"Thread is waiting for a FS control request:\r\n"
378  );
380  &context->StringBuilder,
381  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
382  );
383  }
384  else if (NT_FUNC_MATCH("QueryObject"))
385  {
386  HANDLE handle = StackFrame->Params[0];
387 
388  // Use the KiFastSystemCall args if the handle we have is wrong.
389  if ((ULONG_PTR)handle % 4 != 0 || !handle)
390  handle = context->PrevParams[1];
391 
393  &context->StringBuilder,
394  L"Thread is querying an object:\r\n"
395  );
397  &context->StringBuilder,
398  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
399  );
400  }
401  else if (NT_FUNC_MATCH("ReadFile") || NT_FUNC_MATCH("WriteFile"))
402  {
403  HANDLE handle = StackFrame->Params[0];
404 
406  &context->StringBuilder,
407  L"Thread is waiting for file I/O:\r\n"
408  );
410  &context->StringBuilder,
411  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
412  );
413  }
414  else if (NT_FUNC_MATCH("RemoveIoCompletion"))
415  {
416  HANDLE handle = StackFrame->Params[0];
417 
419  &context->StringBuilder,
420  L"Thread is waiting for an I/O completion port:\r\n"
421  );
423  &context->StringBuilder,
424  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
425  );
426  }
427  else if (
428  NT_FUNC_MATCH("ReplyWaitReceivePort") ||
429  NT_FUNC_MATCH("RequestWaitReplyPort") ||
430  NT_FUNC_MATCH("AlpcSendWaitReceivePort")
431  )
432  {
433  HANDLE handle = StackFrame->Params[0];
434  PPH_STRING alpcInfo;
435 
437  &context->StringBuilder,
438  WindowsVersion >= WINDOWS_VISTA ? L"Thread is waiting for an ALPC port:\r\n" : L"Thread is waiting for a LPC port:\r\n"
439  );
441  &context->StringBuilder,
442  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
443  );
444 
445  if (alpcInfo = PhpaGetAlpcInformation(context->ThreadId))
446  {
448  &context->StringBuilder,
449  L"\r\n"
450  );
452  &context->StringBuilder,
453  &alpcInfo->sr
454  );
455  }
456  }
457  else if (
458  NT_FUNC_MATCH("SetHighWaitLowEventPair") ||
459  NT_FUNC_MATCH("SetLowWaitHighEventPair") ||
460  NT_FUNC_MATCH("WaitHighEventPair") ||
461  NT_FUNC_MATCH("WaitLowEventPair")
462  )
463  {
464  HANDLE handle = StackFrame->Params[0];
465 
466  if ((ULONG_PTR)handle % 4 != 0 || !handle)
467  handle = context->PrevParams[1];
468 
470  &context->StringBuilder,
471  L"Thread is waiting (%s) for an event pair:\r\n",
472  name->Buffer
473  );
475  &context->StringBuilder,
476  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
477  );
478  }
479  else if (
480  FUNC_MATCH("user32.dll!NtUserGetMessage") ||
481  FUNC_MATCH("user32.dll!NtUserWaitMessage")
482  )
483  {
485  &context->StringBuilder,
486  L"Thread is waiting for a USER message.\r\n"
487  );
488  }
489  else if (FUNC_MATCH("user32.dll!NtUserMessageCall"))
490  {
491  PPH_STRING receiverString;
492 
494  &context->StringBuilder,
495  L"Thread is sending a USER message:\r\n"
496  );
497 
498  receiverString = PhpaGetSendMessageReceiver(context->ThreadId);
499 
500  if (receiverString)
501  {
502  PhAppendStringBuilder(&context->StringBuilder, &receiverString->sr);
503  PhAppendStringBuilder2(&context->StringBuilder, L"\r\n");
504  }
505  else
506  {
507  PhAppendStringBuilder2(&context->StringBuilder, L"Unknown.\r\n");
508  }
509  }
510  else if (NT_FUNC_MATCH("WaitForDebugEvent"))
511  {
512  HANDLE handle = StackFrame->Params[0];
513 
515  &context->StringBuilder,
516  L"Thread is waiting for a debug event:\r\n"
517  );
519  &context->StringBuilder,
520  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
521  );
522  }
523  else if (
524  NT_FUNC_MATCH("WaitForKeyedEvent") ||
525  NT_FUNC_MATCH("ReleaseKeyedEvent")
526  )
527  {
528  HANDLE handle = StackFrame->Params[0];
529  PVOID key = StackFrame->Params[1];
530 
532  &context->StringBuilder,
533  L"Thread is waiting (%s) for a keyed event (key 0x%Ix):\r\n",
534  name->Buffer,
535  key
536  );
538  &context->StringBuilder,
539  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
540  );
541  }
542  else if (
543  NT_FUNC_MATCH("WaitForMultipleObjects") ||
544  FUNC_MATCH("kernel32.dll!WaitForMultipleObjects")
545  )
546  {
547  ULONG numberOfHandles = (ULONG)StackFrame->Params[0];
548  PVOID addressOfHandles = StackFrame->Params[1];
549  WAIT_TYPE waitType = (WAIT_TYPE)StackFrame->Params[2];
550  BOOLEAN alertable = !!StackFrame->Params[3];
551 
552  if (numberOfHandles > MAXIMUM_WAIT_OBJECTS)
553  {
554  numberOfHandles = (ULONG)context->PrevParams[1];
555  addressOfHandles = context->PrevParams[2];
556  waitType = (WAIT_TYPE)context->PrevParams[3];
557  alertable = FALSE;
558  }
559 
561  context->ProcessHandle,
562  TRUE, // on x64 this function is only called for WOW64 processes
563  numberOfHandles,
564  addressOfHandles,
565  waitType,
566  alertable,
567  &context->StringBuilder
568  );
569  }
570  else if (
571  NT_FUNC_MATCH("WaitForSingleObject") ||
572  FUNC_MATCH("kernel32.dll!WaitForSingleObject")
573  )
574  {
575  HANDLE handle = StackFrame->Params[0];
576  BOOLEAN alertable = !!StackFrame->Params[1];
577 
578  if ((ULONG_PTR)handle % 4 != 0 || !handle)
579  {
580  handle = context->PrevParams[1];
581  alertable = !!context->PrevParams[2];
582  }
583 
585  &context->StringBuilder,
586  L"Thread is waiting (%s) for:\r\n",
587  alertable ? L"alertable" : L"non-alertable"
588  );
590  &context->StringBuilder,
591  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
592  );
593  }
594  else if (NT_FUNC_MATCH("WaitForWorkViaWorkerFactory"))
595  {
596  HANDLE handle = StackFrame->Params[0];
597 
599  &context->StringBuilder,
600  L"Thread is waiting for work from a worker factory:\r\n"
601  );
603  &context->StringBuilder,
604  &PhpaGetHandleString(context->ProcessHandle, handle)->sr
605  );
606  }
607  else
608  {
609  context->Found = FALSE;
610  }
611 
612  PhDereferenceObject(name);
613  memcpy(&context->PrevParams, StackFrame->Params, sizeof(StackFrame->Params));
614 
615  return !context->Found;
616 }
617 
619  _In_ PANALYZE_WAIT_CONTEXT Context
620  )
621 {
622  PPH_STRING info;
623 
624  // We didn't detect NtUserMessageCall, but this may still apply due to another
625  // win32k system call (e.g. from EnableWindow).
626  if (!Context->Found && (info = PhpaGetSendMessageReceiver(Context->ThreadId)))
627  {
629  &Context->StringBuilder,
630  L"Thread is sending a USER message:\r\n"
631  );
632  PhAppendStringBuilder(&Context->StringBuilder, &info->sr);
633  PhAppendStringBuilder2(&Context->StringBuilder, L"\r\n");
634 
635  Context->Found = TRUE;
636  }
637 
638  // Nt(Alpc)ConnectPort doesn't get detected anywhere else.
639  if (!Context->Found && (info = PhpaGetAlpcInformation(Context->ThreadId)))
640  {
642  &Context->StringBuilder,
643  L"Thread is waiting for an ALPC port:\r\n"
644  );
645  PhAppendStringBuilder(&Context->StringBuilder, &info->sr);
646  PhAppendStringBuilder2(&Context->StringBuilder, L"\r\n");
647 
648  Context->Found = TRUE;
649  }
650 }
651 
652 static BOOLEAN PhpWaitUntilThreadIsWaiting(
653  _In_ HANDLE ThreadHandle
654  )
655 {
656  ULONG attempts;
657  BOOLEAN isWaiting = FALSE;
658  THREAD_BASIC_INFORMATION basicInfo;
659 
660  if (!NT_SUCCESS(PhGetThreadBasicInformation(ThreadHandle, &basicInfo)))
661  return FALSE;
662 
663  for (attempts = 0; attempts < 5; attempts++)
664  {
665  PVOID processes;
666  PSYSTEM_PROCESS_INFORMATION processInfo;
667  ULONG i;
668  LARGE_INTEGER interval;
669 
670  interval.QuadPart = -100 * PH_TIMEOUT_MS;
671  NtDelayExecution(FALSE, &interval);
672 
673  if (!NT_SUCCESS(PhEnumProcesses(&processes)))
674  break;
675 
676  processInfo = PhFindProcessInformation(processes, basicInfo.ClientId.UniqueProcess);
677 
678  if (processInfo)
679  {
680  for (i = 0; i < processInfo->NumberOfThreads; i++)
681  {
682  if (
683  processInfo->Threads[i].ClientId.UniqueThread == basicInfo.ClientId.UniqueThread &&
684  processInfo->Threads[i].ThreadState == Waiting &&
685  (processInfo->Threads[i].WaitReason == UserRequest ||
686  processInfo->Threads[i].WaitReason == Executive)
687  )
688  {
689  isWaiting = TRUE;
690  break;
691  }
692  }
693  }
694 
695  PhFree(processes);
696 
697  if (isWaiting)
698  break;
699 
700  interval.QuadPart = -500 * PH_TIMEOUT_MS;
701  NtDelayExecution(FALSE, &interval);
702  }
703 
704  return isWaiting;
705 }
706 
707 static VOID PhpGetThreadLastSystemCallNumber(
708  _In_ HANDLE ThreadHandle,
709  _Out_ PUSHORT LastSystemCallNumber
710  )
711 {
712  THREAD_LAST_SYSCALL_INFORMATION lastSystemCall;
713 
714  if (NT_SUCCESS(NtQueryInformationThread(
715  ThreadHandle,
716  ThreadLastSystemCall,
717  &lastSystemCall,
719  NULL
720  )))
721  {
722  *LastSystemCallNumber = lastSystemCall.SystemCallNumber;
723  }
724 }
725 
726 static NTSTATUS PhpWfsoThreadStart(
727  _In_ PVOID Parameter
728  )
729 {
730  HANDLE eventHandle;
731  LARGE_INTEGER timeout;
732 
733  eventHandle = Parameter;
734 
735  timeout.QuadPart = -5 * PH_TIMEOUT_SEC;
736  NtWaitForSingleObject(eventHandle, FALSE, &timeout);
737 
738  return STATUS_SUCCESS;
739 }
740 
741 static NTSTATUS PhpWfmoThreadStart(
742  _In_ PVOID Parameter
743  )
744 {
745  HANDLE eventHandle;
746  LARGE_INTEGER timeout;
747 
748  eventHandle = Parameter;
749 
750  timeout.QuadPart = -5 * PH_TIMEOUT_SEC;
751  NtWaitForMultipleObjects(1, &eventHandle, WaitAll, FALSE, &timeout);
752 
753  return STATUS_SUCCESS;
754 }
755 
756 static NTSTATUS PhpRfThreadStart(
757  _In_ PVOID Parameter
758  )
759 {
760  HANDLE fileHandle;
761  IO_STATUS_BLOCK isb;
762  ULONG data;
763 
764  fileHandle = Parameter;
765 
766  NtReadFile(fileHandle, NULL, NULL, NULL, &isb, &data, sizeof(ULONG), NULL, NULL);
767 
768  return STATUS_SUCCESS;
769 }
770 
772  VOID
773  )
774 {
775  if (PhBeginInitOnce(&ServiceNumbersInitOnce))
776  {
777  NTSTATUS status;
778  HANDLE eventHandle;
779  HANDLE threadHandle;
780  HANDLE pipeReadHandle;
781  HANDLE pipeWriteHandle;
782 
783  // The ThreadLastSystemCall info class only works when the thread is in the Waiting
784  // state. We'll create a thread which blocks on an event object we create, then wait
785  // until it is in the Waiting state. Only then can we query the thread using
786  // ThreadLastSystemCall.
787 
788  // NtWaitForSingleObject
789 
790  status = NtCreateEvent(&eventHandle, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE);
791 
792  if (NT_SUCCESS(status))
793  {
794  if (threadHandle = PhCreateThread(0, PhpWfsoThreadStart, eventHandle))
795  {
796  if (PhpWaitUntilThreadIsWaiting(threadHandle))
797  {
798  PhpGetThreadLastSystemCallNumber(threadHandle, &NumberForWfso);
799  }
800 
801  // Allow the thread to exit.
802  NtSetEvent(eventHandle, NULL);
803  NtClose(threadHandle);
804  }
805 
806  NtClose(eventHandle);
807  }
808 
809  // NtWaitForMultipleObjects
810 
811  status = NtCreateEvent(&eventHandle, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE);
812 
813  if (NT_SUCCESS(status))
814  {
815  if (threadHandle = PhCreateThread(0, PhpWfmoThreadStart, eventHandle))
816  {
817  if (PhpWaitUntilThreadIsWaiting(threadHandle))
818  {
819  PhpGetThreadLastSystemCallNumber(threadHandle, &NumberForWfmo);
820  }
821 
822  NtSetEvent(eventHandle, NULL);
823  NtClose(threadHandle);
824  }
825 
826  NtClose(eventHandle);
827  }
828 
829  // NtReadFile
830 
831  if (CreatePipe(&pipeReadHandle, &pipeWriteHandle, NULL, 0))
832  {
833  if (threadHandle = PhCreateThread(0, PhpRfThreadStart, pipeReadHandle))
834  {
835  ULONG data = 0;
836  IO_STATUS_BLOCK isb;
837 
838  if (PhpWaitUntilThreadIsWaiting(threadHandle))
839  {
840  PhpGetThreadLastSystemCallNumber(threadHandle, &NumberForRf);
841  }
842 
843  NtWriteFile(pipeWriteHandle, NULL, NULL, NULL, &isb, &data, sizeof(data), NULL, NULL);
844  NtClose(threadHandle);
845  }
846 
847  NtClose(pipeReadHandle);
848  NtClose(pipeWriteHandle);
849  }
850 
851  PhEndInitOnce(&ServiceNumbersInitOnce);
852  }
853 }
854 
856  _In_ HANDLE ProcessHandle,
857  _In_ HANDLE Handle
858  )
859 {
860  PPH_STRING typeName = NULL;
861  PPH_STRING name = NULL;
862  PPH_STRING result;
863 
865  ProcessHandle,
866  Handle,
867  -1,
868  NULL,
869  &typeName,
870  NULL,
871  &name
872  );
873 
874  if (typeName && name)
875  {
876  result = PhaFormatString(
877  L"Handle 0x%Ix (%s): %s",
878  Handle,
879  typeName->Buffer,
880  !PhIsNullOrEmptyString(name) ? name->Buffer : L"(unnamed object)"
881  );
882  }
883  else
884  {
885  result = PhaFormatString(
886  L"Handle 0x%Ix: (error querying handle)",
887  Handle
888  );
889  }
890 
891  if (typeName)
892  PhDereferenceObject(typeName);
893  if (name)
894  PhDereferenceObject(name);
895 
896  return result;
897 }
898 
900  _In_ HANDLE ProcessHandle,
901  _In_ BOOLEAN IsWow64,
902  _In_ ULONG NumberOfHandles,
903  _In_ PHANDLE AddressOfHandles,
904  _In_ WAIT_TYPE WaitType,
905  _In_ BOOLEAN Alertable,
906  _Inout_ PPH_STRING_BUILDER StringBuilder
907  )
908 {
909  NTSTATUS status;
910  HANDLE handles[MAXIMUM_WAIT_OBJECTS];
911  ULONG i;
912 
913  status = STATUS_SUCCESS;
914 
915  if (NumberOfHandles <= MAXIMUM_WAIT_OBJECTS)
916  {
917 #ifdef _WIN64
918  if (IsWow64)
919  {
920  ULONG handles32[MAXIMUM_WAIT_OBJECTS];
921 
922  if (NT_SUCCESS(status = PhReadVirtualMemory(
923  ProcessHandle,
924  AddressOfHandles,
925  handles32,
926  NumberOfHandles * sizeof(ULONG),
927  NULL
928  )))
929  {
930  for (i = 0; i < NumberOfHandles; i++)
931  handles[i] = UlongToHandle(handles32[i]);
932  }
933  }
934  else
935  {
936 #endif
937  status = PhReadVirtualMemory(
938  ProcessHandle,
939  AddressOfHandles,
940  handles,
941  NumberOfHandles * sizeof(HANDLE),
942  NULL
943  );
944 #ifdef _WIN64
945  }
946 #endif
947 
948  if (NT_SUCCESS(status))
949  {
951  StringBuilder,
952  L"Thread is waiting (%s, %s) for:\r\n",
953  Alertable ? L"alertable" : L"non-alertable",
954  WaitType == WaitAll ? L"wait all" : L"wait any"
955  );
956 
957  for (i = 0; i < NumberOfHandles; i++)
958  {
960  StringBuilder,
961  &PhpaGetHandleString(ProcessHandle, handles[i])->sr
962  );
964  StringBuilder,
965  L"\r\n"
966  );
967  }
968  }
969  }
970 
971  if (!NT_SUCCESS(status) || NumberOfHandles > MAXIMUM_WAIT_OBJECTS)
972  {
974  StringBuilder,
975  L"Thread is waiting for multiple objects."
976  );
977  }
978 }
979 
981  _In_ HANDLE ThreadId
982  )
983 {
984  static _GetSendMessageReceiver GetSendMessageReceiver_I;
985 
986  HWND windowHandle;
987  ULONG threadId;
988  ULONG processId;
989  CLIENT_ID clientId;
990  PPH_STRING clientIdName;
991  WCHAR windowClass[64];
992  PPH_STRING windowText;
993 
994  // GetSendMessageReceiver is an undocumented function exported by
995  // user32.dll. It retrieves the handle of the window which a thread
996  // is sending a message to.
997 
998  if (!GetSendMessageReceiver_I)
999  GetSendMessageReceiver_I = PhGetModuleProcAddress(L"user32.dll", "GetSendMessageReceiver");
1000 
1001  if (!GetSendMessageReceiver_I)
1002  return NULL;
1003 
1004  windowHandle = GetSendMessageReceiver_I(ThreadId);
1005 
1006  if (!windowHandle)
1007  return NULL;
1008 
1009  threadId = GetWindowThreadProcessId(windowHandle, &processId);
1010 
1011  clientId.UniqueProcess = UlongToHandle(processId);
1012  clientId.UniqueThread = UlongToHandle(threadId);
1013  clientIdName = PhAutoDereferenceObject(PhGetClientIdName(&clientId));
1014 
1015  if (!GetClassName(windowHandle, windowClass, sizeof(windowClass) / sizeof(WCHAR)))
1016  windowClass[0] = 0;
1017 
1018  windowText = PhAutoDereferenceObject(PhGetWindowText(windowHandle));
1019 
1020  return PhaFormatString(L"Window 0x%Ix (%s): %s \"%s\"", windowHandle, clientIdName->Buffer, windowClass, PhGetStringOrEmpty(windowText));
1021 }
1022 
1024  _In_ HANDLE ThreadId
1025  )
1026 {
1027  static _NtAlpcQueryInformation NtAlpcQueryInformation_I;
1028 
1029  NTSTATUS status;
1030  PPH_STRING string = NULL;
1031  HANDLE threadHandle;
1032  PALPC_SERVER_INFORMATION serverInfo;
1033  ULONG bufferLength;
1034 
1035  if (!NtAlpcQueryInformation_I)
1036  NtAlpcQueryInformation_I = PhGetModuleProcAddress(L"ntdll.dll", "NtAlpcQueryInformation");
1037 
1038  if (!NtAlpcQueryInformation_I)
1039  return NULL;
1040 
1041  if (!NT_SUCCESS(PhOpenThread(&threadHandle, THREAD_QUERY_INFORMATION, ThreadId)))
1042  return NULL;
1043 
1044  bufferLength = 0x110;
1045  serverInfo = PhAllocate(bufferLength);
1046  serverInfo->In.ThreadHandle = threadHandle;
1047 
1048  status = NtAlpcQueryInformation_I(NULL, AlpcServerInformation, serverInfo, bufferLength, &bufferLength);
1049 
1050  if (status == STATUS_INFO_LENGTH_MISMATCH)
1051  {
1052  PhFree(serverInfo);
1053  serverInfo = PhAllocate(bufferLength);
1054  serverInfo->In.ThreadHandle = threadHandle;
1055 
1056  status = NtAlpcQueryInformation_I(NULL, AlpcServerInformation, serverInfo, bufferLength, &bufferLength);
1057  }
1058 
1059  if (NT_SUCCESS(status) && serverInfo->Out.ThreadBlocked)
1060  {
1061  CLIENT_ID clientId;
1062  PPH_STRING clientIdName;
1063 
1064  clientId.UniqueProcess = serverInfo->Out.ConnectedProcessId;
1065  clientId.UniqueThread = NULL;
1066  clientIdName = PhAutoDereferenceObject(PhGetClientIdName(&clientId));
1067 
1068  string = PhaFormatString(L"ALPC Port: %.*s (%s)", serverInfo->Out.ConnectionPortName.Length / 2, serverInfo->Out.ConnectionPortName.Buffer, clientIdName->Buffer);
1069  }
1070 
1071  PhFree(serverInfo);
1072  NtClose(threadHandle);
1073 
1074  return string;
1075 }