From a77130fe0d5ea9280ee22a092fbd1a267aa6cdc3 Mon Sep 17 00:00:00 2001 From: wj32 Date: Sat, 20 Nov 2010 00:45:54 +0000 Subject: [PATCH] added wait analysis for x64 git-svn-id: svn://svn.code.sf.net/p/processhacker/code@3851 21ef857c-d57f-4fe0-8362-d861dc6d29cd --- 2.x/trunk/CHANGELOG.txt | 1 + 2.x/trunk/ProcessHacker/anawait.c | 450 ++++++++++++++++++++++++++--- 2.x/trunk/ProcessHacker/procprp.c | 6 - 2.x/trunk/phlib/include/ntlpcapi.h | 2 +- 2.x/trunk/phlib/symprv.c | 37 ++- 5 files changed, 434 insertions(+), 62 deletions(-) diff --git a/2.x/trunk/CHANGELOG.txt b/2.x/trunk/CHANGELOG.txt index a80efa424..85972c114 100644 --- a/2.x/trunk/CHANGELOG.txt +++ b/2.x/trunk/CHANGELOG.txt @@ -3,6 +3,7 @@ Process Hacker 2.9 * NEW/IMPROVED: * Added column selection for modules list + * Added wait analysis for 64-bit systems * Environment variables and current directory are now correctly shown for WOW64 processes * FIXED: diff --git a/2.x/trunk/ProcessHacker/anawait.c b/2.x/trunk/ProcessHacker/anawait.c index 0672d378a..c2dd2ae3e 100644 --- a/2.x/trunk/ProcessHacker/anawait.c +++ b/2.x/trunk/ProcessHacker/anawait.c @@ -20,23 +20,75 @@ * along with Process Hacker. If not, see . */ +/* + * There are two ways of seeing what a thread is waiting on. The first method + * is to walk the stack of a thread and read the arguments to whatever system + * call it is blocking on; this only works on x86 because on x64 the arguments + * are passed in registers (at least the first four are). The second method + * involves using the ThreadLastSystemCall info class for NtQueryInformationThread + * to retrieve the first argument to the system call the thread is blocking on. + * This is obviously only useful for NtWaitForSingleObject. + * + * There are other methods for specific scenarios, like USER messages. + */ + #include +typedef HWND (WINAPI *_GetSendMessageReceiver)( + __in HANDLE ThreadId + ); + +typedef NTSTATUS (NTAPI *_NtAlpcQueryInformation)( + __in HANDLE PortHandle, + __in ALPC_PORT_INFORMATION_CLASS PortInformationClass, + __out_bcount(PortInformationLength) PVOID PortInformation, + __in ULONG PortInformationLength, + __out_opt PULONG ReturnLength + ); + typedef struct _ANALYZE_WAIT_CONTEXT { BOOLEAN Found; + BOOLEAN IsWow64; + HANDLE ProcessId; + HANDLE ThreadId; HANDLE ProcessHandle; + PPH_SYMBOL_PROVIDER SymbolProvider; PH_STRING_BUILDER StringBuilder; PVOID PrevParams[4]; } ANALYZE_WAIT_CONTEXT, *PANALYZE_WAIT_CONTEXT; +VOID PhpAnalyzeWaitPassive( + __in HWND hWnd, + __in HANDLE ProcessId, + __in HANDLE ThreadId + ); + BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( __in PPH_THREAD_STACK_FRAME StackFrame, __in_opt PVOID Context ); +VOID PhpInitializeWfsoNumber(); + +PPH_STRING PhapGetHandleString( + __in HANDLE ProcessHandle, + __in HANDLE Handle + ); + +PPH_STRING PhapGetSendMessageReceiver( + __in HANDLE ThreadId + ); + +PPH_STRING PhapGetAlpcInformation( + __in HANDLE ThreadId + ); + +static PH_INITONCE WfsoInitOnce = PH_INITONCE_INIT; +static USHORT WfsoNumber = -1; + VOID PhUiAnalyzeWaitThread( __in HWND hWnd, __in HANDLE ProcessId, @@ -46,8 +98,30 @@ VOID PhUiAnalyzeWaitThread( { NTSTATUS status; HANDLE threadHandle; +#ifdef _M_X64 + HANDLE processHandle; + BOOLEAN isWow64; +#endif ANALYZE_WAIT_CONTEXT context; +#ifdef _M_X64 + // Determine if the process is WOW64. If not, we use the passive method. + + if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, ProcessQueryAccess, ProcessId))) + { + PhShowStatus(hWnd, L"Unable to open the process", status, 0); + return; + } + + if (!NT_SUCCESS(status = PhGetProcessIsWow64(processHandle, &isWow64)) || !isWow64) + { + PhpAnalyzeWaitPassive(hWnd, ProcessId, ThreadId); + return; + } + + NtClose(processHandle); +#endif + if (!NT_SUCCESS(status = PhOpenThread( &threadHandle, THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, @@ -58,9 +132,12 @@ VOID PhUiAnalyzeWaitThread( return; } + context.ProcessId = ProcessId; + context.ThreadId = ThreadId; + context.ProcessHandle = SymbolProvider->ProcessHandle; context.SymbolProvider = SymbolProvider; - PhInitializeStringBuilder(&context.StringBuilder, 10); + PhInitializeStringBuilder(&context.StringBuilder, 100); PhWalkThreadStack( threadHandle, @@ -83,48 +160,85 @@ VOID PhUiAnalyzeWaitThread( PhDeleteStringBuilder(&context.StringBuilder); } -static PPH_STRING PhapGetHandleString( - __in HANDLE ProcessHandle, - __in HANDLE Handle +VOID PhpAnalyzeWaitPassive( + __in HWND hWnd, + __in HANDLE ProcessId, + __in HANDLE ThreadId ) { - PPH_STRING typeName = NULL; - PPH_STRING name = NULL; - PPH_STRING result; + NTSTATUS status; + HANDLE processHandle; + HANDLE threadHandle; + THREAD_LAST_SYSTEM_CALL lastSystemCall; + PH_STRING_BUILDER stringBuilder; + PPH_STRING string; - PhGetHandleInformation( - ProcessHandle, - Handle, - -1, - NULL, - &typeName, - NULL, - &name - ); + PhpInitializeWfsoNumber(); - if (typeName && name) + if (!NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_GET_CONTEXT, ThreadId))) { - result = PhaFormatString( - L"Handle 0x%Ix (%s): %s", - Handle, - typeName->Buffer, - !PhIsNullOrEmptyString(name) ? name->Buffer : L"(unnamed object)" - ); + PhShowStatus(hWnd, L"Unable to open the thread", status, 0); + return; + } + + if (!NT_SUCCESS(status = NtQueryInformationThread( + threadHandle, + ThreadLastSystemCall, + &lastSystemCall, + sizeof(THREAD_LAST_SYSTEM_CALL), + NULL + ))) + { + NtClose(threadHandle); + PhShowInformation(hWnd, L"Unable to determine whether the thread is waiting."); + return; + } + + NtClose(threadHandle); + + if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_DUP_HANDLE, ProcessId))) + { + PhShowStatus(hWnd, L"Unable to open the process", status, 0); + return; + } + + PhInitializeStringBuilder(&stringBuilder, 100); + + if (lastSystemCall.SystemCallNumber == WfsoNumber) + { + string = PhapGetHandleString(processHandle, lastSystemCall.FirstArgument); + + PhAppendFormatStringBuilder(&stringBuilder, L"Thread is waiting for:\r\n"); + PhAppendStringBuilder(&stringBuilder, string); } else { - result = PhaFormatString( - L"Handle 0x%Ix: (error querying handle)", - Handle - ); + string = PhapGetSendMessageReceiver(ThreadId); + + if (string) + { + PhAppendStringBuilder2(&stringBuilder, L"Thread is sending a USER message:\r\n"); + PhAppendStringBuilder(&stringBuilder, string); + } + else + { + string = PhapGetAlpcInformation(ThreadId); + + if (string) + { + PhAppendStringBuilder2(&stringBuilder, L"Thread is waiting for an ALPC port:\r\n"); + PhAppendStringBuilder(&stringBuilder, string); + } + } } - if (typeName) - PhDereferenceObject(typeName); - if (name) - PhDereferenceObject(name); + if (stringBuilder.String->Length == 0) + PhAppendStringBuilder2(&stringBuilder, L"Unable to determine why the thread is waiting."); - return result; + PhShowInformationDialog(hWnd, stringBuilder.String->Buffer); + + PhDeleteStringBuilder(&stringBuilder); + NtClose(processHandle); } static BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( @@ -320,6 +434,27 @@ static BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( L"Thread is waiting for a USER message.\r\n" ); } + else if (FUNC_MATCH("user32.dll!NtUserMessageCall")) + { + PPH_STRING receiverString; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is sending a USER message:\r\n" + ); + + receiverString = PhapGetSendMessageReceiver(context->ThreadId); + + if (receiverString) + { + PhAppendStringBuilder(&context->StringBuilder, receiverString); + PhAppendStringBuilder2(&context->StringBuilder, L"\r\n"); + } + else + { + PhAppendStringBuilder2(&context->StringBuilder, L"Unknown.\r\n"); + } + } else if (NT_FUNC_MATCH("WaitForDebugEvent")) { HANDLE handle = StackFrame->Params[0]; @@ -361,7 +496,7 @@ static BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( PVOID addressOfHandles = StackFrame->Params[1]; WAIT_TYPE waitType = (WAIT_TYPE)StackFrame->Params[2]; BOOLEAN alertable = !!StackFrame->Params[3]; - HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + ULONG handles[MAXIMUM_WAIT_OBJECTS]; // must be ULONG, because on x64 this function will only be called on WOW64 processes ULONG i; if (numberOfHandles > MAXIMUM_WAIT_OBJECTS) @@ -377,7 +512,7 @@ static BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( context->ProcessHandle, addressOfHandles, handles, - numberOfHandles * sizeof(HANDLE), + numberOfHandles * sizeof(ULONG), NULL ))) { @@ -392,7 +527,7 @@ static BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( { PhAppendStringBuilder( &context->StringBuilder, - PhapGetHandleString(context->ProcessHandle, handles[i]) + PhapGetHandleString(context->ProcessHandle, UlongToHandle(handles[i])) ); PhAppendStringBuilder2( &context->StringBuilder, @@ -462,3 +597,248 @@ static BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( return !context->Found; } + +static NTSTATUS PhpWfsoThreadStart( + __in PVOID Parameter + ) +{ + HANDLE eventHandle; + LARGE_INTEGER timeout; + + eventHandle = Parameter; + + timeout.QuadPart = -5 * PH_TIMEOUT_SEC; + NtWaitForSingleObject(eventHandle, FALSE, &timeout); + + return STATUS_SUCCESS; +} + +static BOOLEAN PhpWaitUntilThreadIsWaiting( + __in HANDLE ThreadHandle + ) +{ + ULONG attempts; + BOOLEAN isWaiting = FALSE; + THREAD_BASIC_INFORMATION basicInfo; + + if (!NT_SUCCESS(PhGetThreadBasicInformation(ThreadHandle, &basicInfo))) + return FALSE; + + for (attempts = 0; attempts < 5; attempts++) + { + PVOID processes; + PSYSTEM_PROCESS_INFORMATION processInfo; + ULONG i; + + Sleep(100); + + if (!NT_SUCCESS(PhEnumProcesses(&processes))) + break; + + processInfo = PhFindProcessInformation(processes, NtCurrentProcessId()); + + for (i = 0; i < processInfo->NumberOfThreads; i++) + { + if ( + processInfo->Threads[i].ClientId.UniqueThread == basicInfo.ClientId.UniqueThread && + processInfo->Threads[i].ThreadState == Waiting && + processInfo->Threads[i].WaitReason == WrUserRequest + ) + { + isWaiting = TRUE; + break; + } + } + + PhFree(processes); + + if (isWaiting) + break; + + Sleep(500); + } + + return isWaiting; +} + +static VOID PhpInitializeWfsoNumber() +{ + if (PhBeginInitOnce(&WfsoInitOnce)) + { + NTSTATUS status; + HANDLE eventHandle; + HANDLE threadHandle; + + // The ThreadLastSystemCall info class only works when the thread is in the Waiting + // state. We'll create a thread which blocks on an event object we create, then wait + // until it is in the Waiting state. Only then can we query the thread using + // ThreadLastSystemCall. + + status = NtCreateEvent(&eventHandle, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE); + + if (NT_SUCCESS(status)) + { + if (threadHandle = PhCreateThread(0, PhpWfsoThreadStart, eventHandle)) + { + if (PhpWaitUntilThreadIsWaiting(threadHandle)) + { + THREAD_LAST_SYSTEM_CALL lastSystemCall; + + if (NT_SUCCESS(NtQueryInformationThread( + threadHandle, + ThreadLastSystemCall, + &lastSystemCall, + sizeof(THREAD_LAST_SYSTEM_CALL), + NULL + ))) + { + WfsoNumber = lastSystemCall.SystemCallNumber; + } + } + + // Allow the thread to exit. + NtSetEvent(eventHandle, NULL); + + NtClose(threadHandle); + } + + NtClose(eventHandle); + } + + PhEndInitOnce(&WfsoInitOnce); + } +} + +static PPH_STRING PhapGetHandleString( + __in HANDLE ProcessHandle, + __in HANDLE Handle + ) +{ + PPH_STRING typeName = NULL; + PPH_STRING name = NULL; + PPH_STRING result; + + PhGetHandleInformation( + ProcessHandle, + Handle, + -1, + NULL, + &typeName, + NULL, + &name + ); + + if (typeName && name) + { + result = PhaFormatString( + L"Handle 0x%Ix (%s): %s", + Handle, + typeName->Buffer, + !PhIsNullOrEmptyString(name) ? name->Buffer : L"(unnamed object)" + ); + } + else + { + result = PhaFormatString( + L"Handle 0x%Ix: (error querying handle)", + Handle + ); + } + + if (typeName) + PhDereferenceObject(typeName); + if (name) + PhDereferenceObject(name); + + return result; +} + +static PPH_STRING PhapGetSendMessageReceiver( + __in HANDLE ThreadId + ) +{ + static _GetSendMessageReceiver GetSendMessageReceiver_I; + + HWND windowHandle; + ULONG threadId; + ULONG processId; + CLIENT_ID clientId; + PPH_STRING clientIdName; + + // GetSendMessageReceiver is an undocumented function exported by + // user32.dll. It retrieves the handle of the window which a thread + // is sending a message to. + + if (!GetSendMessageReceiver_I) + GetSendMessageReceiver_I = PhGetProcAddress(L"user32.dll", "GetSendMessageReceiver"); + + if (!GetSendMessageReceiver_I) + return NULL; + + windowHandle = GetSendMessageReceiver_I(ThreadId); + + if (!windowHandle) + return NULL; + + threadId = GetWindowThreadProcessId(windowHandle, &processId); + + clientId.UniqueProcess = UlongToPtr(processId); + clientId.UniqueThread = UlongToPtr(threadId); + clientIdName = PHA_DEREFERENCE(PhGetClientIdName(&clientId)); + + return PhaFormatString(L"Window 0x%Ix (%s)", windowHandle, clientIdName->Buffer); +} + +static PPH_STRING PhapGetAlpcInformation( + __in HANDLE ThreadId + ) +{ + static _NtAlpcQueryInformation NtAlpcQueryInformation_I; + + NTSTATUS status; + PPH_STRING string = NULL; + HANDLE threadHandle; + PALPC_PORT_SERVER_INFORMATION serverInfo; + ULONG bufferLength; + + if (!NtAlpcQueryInformation_I) + NtAlpcQueryInformation_I = PhGetProcAddress(L"ntdll.dll", "NtAlpcQueryInformation"); + + if (!NtAlpcQueryInformation_I) + return NULL; + + if (!NT_SUCCESS(PhOpenThread(&threadHandle, THREAD_QUERY_INFORMATION, ThreadId))) + return NULL; + + bufferLength = 0x110; + serverInfo = PhAllocate(bufferLength); + serverInfo->ThreadHandle = threadHandle; + + status = NtAlpcQueryInformation_I(NULL, AlpcPortServerInformation, serverInfo, bufferLength, &bufferLength); + + if (status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(serverInfo); + serverInfo = PhAllocate(bufferLength); + serverInfo->ThreadHandle = threadHandle; + + status = NtAlpcQueryInformation_I(NULL, AlpcPortServerInformation, serverInfo, bufferLength, &bufferLength); + } + + if (NT_SUCCESS(status) && serverInfo->MessageFound) + { + CLIENT_ID clientId; + PPH_STRING clientIdName; + + clientId.UniqueProcess = serverInfo->ServerProcessId; + clientId.UniqueThread = NULL; + clientIdName = PHA_DEREFERENCE(PhGetClientIdName(&clientId)); + + string = PhaFormatString(L"ALPC Port: %.*s (%s)", serverInfo->PortName.Length / 2, serverInfo->PortName.Buffer, clientIdName->Buffer); + } + + PhFree(serverInfo); + NtClose(threadHandle); + + return string; +} diff --git a/2.x/trunk/ProcessHacker/procprp.c b/2.x/trunk/ProcessHacker/procprp.c index a81a066d8..74f737a05 100644 --- a/2.x/trunk/ProcessHacker/procprp.c +++ b/2.x/trunk/ProcessHacker/procprp.c @@ -1856,12 +1856,6 @@ VOID PhpInitializeThreadMenu( PhDestroyEMenuItem(item); } -#ifndef _M_IX86 - // Remove Analyze. - if (item = PhFindEMenuItem(Menu, 0, L"Analyze", 0)) - PhDestroyEMenuItem(item); -#endif - if (!PhKphHandle) { // Remove Force Terminate. diff --git a/2.x/trunk/phlib/include/ntlpcapi.h b/2.x/trunk/phlib/include/ntlpcapi.h index 31c4f3064..c1345a987 100644 --- a/2.x/trunk/phlib/include/ntlpcapi.h +++ b/2.x/trunk/phlib/include/ntlpcapi.h @@ -463,7 +463,7 @@ typedef struct _ALPC_PORT_SERVER_INFORMATION union { __in HANDLE ThreadHandle; - __out BOOLEAN PortNamePresent; + __out BOOLEAN MessageFound; }; __out HANDLE ServerProcessId; __out UNICODE_STRING PortName; diff --git a/2.x/trunk/phlib/symprv.c b/2.x/trunk/phlib/symprv.c index 4eb979892..dda254942 100644 --- a/2.x/trunk/phlib/symprv.c +++ b/2.x/trunk/phlib/symprv.c @@ -158,30 +158,27 @@ PPH_SYMBOL_PROVIDER PhCreateSymbolProvider( if (ProcessId) { - symbolProvider->IsRealHandle = TRUE; + static ACCESS_MASK accesses[] = + { + STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xfff, // pre-Vista full access + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + MAXIMUM_ALLOWED + }; + + ULONG i; + + symbolProvider->IsRealHandle = FALSE; // Try to open the process with many different accesses. - // This handle will be re-used when walking stacks. - if (!NT_SUCCESS(PhOpenProcess( - &symbolProvider->ProcessHandle, - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - ProcessId - ))) + // This handle will be re-used when walking stacks, and doing + // various other things. + for (i = 0; i < sizeof(accesses) / sizeof(ACCESS_MASK); i++) { - if (!NT_SUCCESS(PhOpenProcess( - &symbolProvider->ProcessHandle, - ProcessQueryAccess | PROCESS_VM_READ, - ProcessId - ))) + if (NT_SUCCESS(PhOpenProcess(&symbolProvider->ProcessHandle, accesses[i], ProcessId))) { - if (!NT_SUCCESS(PhOpenProcess( - &symbolProvider->ProcessHandle, - ProcessQueryAccess, - ProcessId - ))) - { - symbolProvider->IsRealHandle = FALSE; - } + symbolProvider->IsRealHandle = TRUE; + break; } } }