532 lines
17 KiB
C
532 lines
17 KiB
C
/*
|
|
* Process Hacker Extra Plugins -
|
|
* Wait Chain Traversal (WCT) Plugin
|
|
*
|
|
* Copyright (C) 2013-2015 dmex
|
|
*
|
|
* This file is part of Process Hacker.
|
|
*
|
|
* Process Hacker is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Process Hacker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "main.h"
|
|
|
|
// Wait Chain Traversal Documentation:
|
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms681622.aspx
|
|
|
|
static PPH_PLUGIN PluginInstance;
|
|
static PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration;
|
|
static PH_CALLBACK_REGISTRATION ProcessMenuInitializingCallbackRegistration;
|
|
static PH_CALLBACK_REGISTRATION ThreadMenuInitializingCallbackRegistration;
|
|
|
|
BOOLEAN WaitChainRegisterCallbacks(
|
|
_Inout_ PWCT_CONTEXT Context
|
|
)
|
|
{
|
|
PCOGETCALLSTATE coGetCallStateCallback = NULL;
|
|
PCOGETACTIVATIONSTATE coGetActivationStateCallback = NULL;
|
|
|
|
if (!(Context->Ole32ModuleHandle = LoadLibrary(L"ole32.dll")))
|
|
return FALSE;
|
|
|
|
if (!(coGetCallStateCallback = PhGetProcedureAddress(Context->Ole32ModuleHandle, "CoGetCallState", 0)))
|
|
return FALSE;
|
|
|
|
if (!(coGetActivationStateCallback = PhGetProcedureAddress(Context->Ole32ModuleHandle, "CoGetActivationState", 0)))
|
|
return FALSE;
|
|
|
|
RegisterWaitChainCOMCallback(coGetCallStateCallback, coGetActivationStateCallback);
|
|
return TRUE;
|
|
}
|
|
|
|
VOID WaitChainCheckThread(
|
|
_Inout_ PWCT_CONTEXT Context,
|
|
_In_ HANDLE ThreadId
|
|
)
|
|
{
|
|
BOOL isDeadLocked = FALSE;
|
|
ULONG nodeInfoLength = WCT_MAX_NODE_COUNT;
|
|
WAITCHAIN_NODE_INFO nodeInfoArray[WCT_MAX_NODE_COUNT];
|
|
PWCT_ROOT_NODE rootNode = NULL;
|
|
|
|
memset(nodeInfoArray, 0, sizeof(nodeInfoArray));
|
|
|
|
// Retrieve the thread wait chain.
|
|
if (!GetThreadWaitChain(
|
|
Context->WctSessionHandle,
|
|
0,
|
|
WCT_GETINFO_ALL_FLAGS,
|
|
HandleToUlong(ThreadId),
|
|
&nodeInfoLength,
|
|
nodeInfoArray,
|
|
&isDeadLocked
|
|
))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if the wait chain is too big for the array we passed in.
|
|
if (nodeInfoLength > WCT_MAX_NODE_COUNT)
|
|
nodeInfoLength = WCT_MAX_NODE_COUNT;
|
|
|
|
|
|
for (ULONG i = 0; i < nodeInfoLength; i++)
|
|
{
|
|
PWAITCHAIN_NODE_INFO wctNode = &nodeInfoArray[i];
|
|
|
|
if (wctNode->ObjectType == WctThreadType)
|
|
{
|
|
rootNode = WeAddWindowNode(&Context->TreeContext);
|
|
|
|
rootNode->ObjectType = wctNode->ObjectType;
|
|
rootNode->ObjectStatus = wctNode->ObjectStatus;
|
|
rootNode->Alertable = wctNode->LockObject.Alertable;
|
|
rootNode->ThreadId = UlongToHandle(wctNode->ThreadObject.ThreadId);
|
|
rootNode->ProcessId = UlongToHandle(wctNode->ThreadObject.ProcessId);
|
|
rootNode->ThreadIdString = PhFormatString(L"%lu", wctNode->ThreadObject.ThreadId);
|
|
rootNode->ProcessIdString = PhFormatString(L"%lu", wctNode->ThreadObject.ProcessId);
|
|
rootNode->WaitTimeString = PhFormatString(L"%lu", wctNode->ThreadObject.WaitTime);
|
|
rootNode->ContextSwitchesString = PhFormatString(L"%lu", wctNode->ThreadObject.ContextSwitches);
|
|
rootNode->TimeoutString = PhFormatString(L"%I64d", wctNode->LockObject.Timeout.QuadPart);
|
|
|
|
if (wctNode->LockObject.ObjectName[0] != '\0')
|
|
{
|
|
// -- ProcessID --
|
|
//wctNode->LockObject.ObjectName[0]
|
|
// -- ThreadID --
|
|
//wctNode->LockObject.ObjectName[2]
|
|
// -- Unknown --
|
|
//wctNode->LockObject.ObjectName[4]
|
|
// -- ContextSwitches --
|
|
//wctNode->LockObject.ObjectName[6]
|
|
|
|
if (PhIsDigitCharacter(wctNode->LockObject.ObjectName[0]))
|
|
{
|
|
rootNode->ObjectNameString = PhFormatString(L"%s", wctNode->LockObject.ObjectName);
|
|
}
|
|
//else
|
|
//{
|
|
// rootNode->ObjectNameString = PhFormatString(L"[%lu, %lu]",
|
|
// wctNode.LockObject.ObjectName[0],
|
|
// wctNode.LockObject.ObjectName[2]
|
|
// );
|
|
//}
|
|
}
|
|
|
|
rootNode->Node.Expanded = TRUE;
|
|
rootNode->HasChildren = TRUE;
|
|
|
|
// This is a root node.
|
|
PhAddItemList(Context->TreeContext.NodeRootList, rootNode);
|
|
}
|
|
else
|
|
{
|
|
WctAddChildWindowNode(&Context->TreeContext, rootNode, wctNode, isDeadLocked);
|
|
}
|
|
}
|
|
}
|
|
|
|
NTSTATUS WaitChainCallbackThread(
|
|
_In_ PVOID Parameter
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PWCT_CONTEXT context = (PWCT_CONTEXT)Parameter;
|
|
|
|
if (!WaitChainRegisterCallbacks(context))
|
|
return NTSTATUS_FROM_WIN32(GetLastError());
|
|
|
|
// Synchronous WCT session
|
|
if (!(context->WctSessionHandle = OpenThreadWaitChainSession(0, NULL)))
|
|
return NTSTATUS_FROM_WIN32(GetLastError());
|
|
|
|
//TreeNew_SetRedraw(context->TreeNewHandle, FALSE);
|
|
|
|
if (context->IsProcessItem)
|
|
{
|
|
NTSTATUS status;
|
|
HANDLE threadHandle;
|
|
HANDLE newThreadHandle;
|
|
THREAD_BASIC_INFORMATION basicInfo;
|
|
|
|
status = NtGetNextThread(
|
|
context->ProcessItem->QueryHandle,
|
|
NULL,
|
|
ThreadQueryAccess,
|
|
0,
|
|
0,
|
|
&threadHandle
|
|
);
|
|
|
|
while (NT_SUCCESS(status))
|
|
{
|
|
if (NT_SUCCESS(PhGetThreadBasicInformation(threadHandle, &basicInfo)))
|
|
{
|
|
WaitChainCheckThread(context, basicInfo.ClientId.UniqueThread);
|
|
}
|
|
|
|
status = NtGetNextThread(
|
|
context->ProcessItem->QueryHandle,
|
|
threadHandle,
|
|
ThreadQueryAccess,
|
|
0,
|
|
0,
|
|
&newThreadHandle
|
|
);
|
|
|
|
NtClose(threadHandle);
|
|
|
|
threadHandle = newThreadHandle;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WaitChainCheckThread(context, context->ThreadItem->ThreadId);
|
|
}
|
|
|
|
if (context->WctSessionHandle)
|
|
{
|
|
CloseThreadWaitChainSession(context->WctSessionHandle);
|
|
}
|
|
|
|
if (context->Ole32ModuleHandle)
|
|
{
|
|
FreeLibrary(context->Ole32ModuleHandle);
|
|
}
|
|
|
|
//TreeNew_SetRedraw(context->TreeNewHandle, TRUE);
|
|
TreeNew_NodesStructured(context->TreeNewHandle);
|
|
|
|
return status;
|
|
}
|
|
|
|
INT_PTR CALLBACK WaitChainDlgProc(
|
|
_In_ HWND hwndDlg,
|
|
_In_ UINT uMsg,
|
|
_In_ WPARAM wParam,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
PWCT_CONTEXT context = NULL;
|
|
|
|
if (uMsg == WM_INITDIALOG)
|
|
{
|
|
context = (PWCT_CONTEXT)lParam;
|
|
SetProp(hwndDlg, L"Context", (HANDLE)context);
|
|
}
|
|
else
|
|
{
|
|
context = (PWCT_CONTEXT)GetProp(hwndDlg, L"Context");
|
|
|
|
if (uMsg == WM_DESTROY)
|
|
{
|
|
PhUnregisterDialog(hwndDlg);
|
|
PhSaveWindowPlacementToSetting(SETTING_NAME_WINDOW_POSITION, SETTING_NAME_WINDOW_SIZE, hwndDlg);
|
|
PhDeleteLayoutManager(&context->LayoutManager);
|
|
WtcDeleteWindowTree(&context->TreeContext);
|
|
|
|
RemoveProp(hwndDlg, L"Context");
|
|
PhFree(context);
|
|
}
|
|
}
|
|
|
|
if (context == NULL)
|
|
return FALSE;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
HANDLE threadHandle = NULL;
|
|
|
|
context->TreeNewHandle = GetDlgItem(hwndDlg, IDC_CUSTOM1);
|
|
|
|
PhRegisterDialog(hwndDlg);
|
|
WtcInitializeWindowTree(hwndDlg, context->TreeNewHandle, &context->TreeContext);
|
|
PhInitializeLayoutManager(&context->LayoutManager, hwndDlg);
|
|
PhAddLayoutItem(&context->LayoutManager, context->TreeNewHandle, NULL, PH_ANCHOR_ALL);
|
|
PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT);
|
|
PhLoadWindowPlacementFromSetting(SETTING_NAME_WINDOW_POSITION, SETTING_NAME_WINDOW_SIZE, hwndDlg);
|
|
|
|
if (threadHandle = PhCreateThread(0, WaitChainCallbackThread, (PVOID)context))
|
|
NtClose(threadHandle);
|
|
}
|
|
break;
|
|
case WM_SIZE:
|
|
PhLayoutManagerLayout(&context->LayoutManager);
|
|
break;
|
|
case WM_COMMAND:
|
|
{
|
|
switch (LOWORD(wParam))
|
|
{
|
|
case IDCANCEL:
|
|
case IDOK:
|
|
EndDialog(hwndDlg, IDOK);
|
|
break;
|
|
case ID_WCTSHOWCONTEXTMENU:
|
|
{
|
|
POINT point;
|
|
PPH_EMENU menu;
|
|
PWCT_ROOT_NODE selectedNode;
|
|
|
|
point.x = (SHORT)LOWORD(lParam);
|
|
point.y = (SHORT)HIWORD(lParam);
|
|
|
|
if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext))
|
|
{
|
|
menu = PhCreateEMenu();
|
|
PhLoadResourceEMenuItem(menu, PluginInstance->DllBase, MAKEINTRESOURCE(IDR_MAIN_MENU), 0);
|
|
PhSetFlagsEMenuItem(menu, ID_MENU_PROPERTIES, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT);
|
|
|
|
if (selectedNode->ThreadId > 0)
|
|
{
|
|
PhSetFlagsEMenuItem(menu, ID_MENU_GOTOTHREAD, PH_EMENU_DISABLED, 0);
|
|
PhSetFlagsEMenuItem(menu, ID_MENU_GOTOPROCESS, PH_EMENU_DISABLED, 0);
|
|
}
|
|
else
|
|
{
|
|
PhSetFlagsEMenuItem(menu, ID_MENU_GOTOTHREAD, PH_EMENU_DISABLED, PH_EMENU_DISABLED);
|
|
PhSetFlagsEMenuItem(menu, ID_MENU_GOTOPROCESS, PH_EMENU_DISABLED, PH_EMENU_DISABLED);
|
|
}
|
|
|
|
PhShowEMenu(menu, hwndDlg, PH_EMENU_SHOW_SEND_COMMAND | PH_EMENU_SHOW_LEFTRIGHT, PH_ALIGN_LEFT | PH_ALIGN_TOP, point.x, point.y);
|
|
PhDestroyEMenu(menu);
|
|
}
|
|
}
|
|
break;
|
|
case ID_MENU_GOTOPROCESS:
|
|
{
|
|
PWCT_ROOT_NODE selectedNode = NULL;
|
|
PPH_PROCESS_NODE processNode = NULL;
|
|
|
|
if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext))
|
|
{
|
|
if (processNode = PhFindProcessNode(selectedNode->ProcessId))
|
|
{
|
|
ProcessHacker_SelectTabPage(PhMainWndHandle, 0);
|
|
PhSelectAndEnsureVisibleProcessNode(processNode);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ID_MENU_GOTOTHREAD:
|
|
{
|
|
PWCT_ROOT_NODE selectedNode = NULL;
|
|
PPH_PROCESS_ITEM processItem = NULL;
|
|
PPH_PROCESS_PROPCONTEXT propContext = NULL;
|
|
|
|
if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext))
|
|
{
|
|
if (processItem = PhReferenceProcessItem(selectedNode->ProcessId))
|
|
{
|
|
if (propContext = PhCreateProcessPropContext(NULL, processItem))
|
|
{
|
|
if (selectedNode->ThreadId)
|
|
{
|
|
PhSetSelectThreadIdProcessPropContext(propContext, selectedNode->ThreadId);
|
|
}
|
|
|
|
PhShowProcessProperties(propContext);
|
|
PhDereferenceObject(propContext);
|
|
}
|
|
|
|
PhDereferenceObject(processItem);
|
|
}
|
|
else
|
|
{
|
|
PhShowError(hwndDlg, L"The process does not exist.");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ID_MENU_COPY:
|
|
{
|
|
PPH_STRING text;
|
|
|
|
text = PhGetTreeNewText(context->TreeNewHandle, 0);
|
|
PhSetClipboardString(hwndDlg, &text->sr);
|
|
PhDereferenceObject(text);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID NTAPI MenuItemCallback(
|
|
_In_opt_ PVOID Parameter,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PPH_PLUGIN_MENU_ITEM menuItem = (PPH_PLUGIN_MENU_ITEM)Parameter;
|
|
|
|
switch (menuItem->Id)
|
|
{
|
|
case IDD_WCT_MENUITEM:
|
|
{
|
|
DialogBoxParam(
|
|
PluginInstance->DllBase,
|
|
MAKEINTRESOURCE(IDD_WCT_DIALOG),
|
|
NULL,
|
|
WaitChainDlgProc,
|
|
(LPARAM)menuItem->Context
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
VOID NTAPI ProcessMenuInitializingCallback(
|
|
_In_opt_ PVOID Parameter,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
ULONG insertIndex = 0;
|
|
PWCT_CONTEXT context = NULL;
|
|
PPH_PLUGIN_MENU_INFORMATION menuInfo = NULL;
|
|
PPH_PROCESS_ITEM processItem = NULL;
|
|
PPH_EMENU_ITEM menuItem = NULL;
|
|
PPH_EMENU_ITEM miscMenuItem = NULL;
|
|
PPH_EMENU_ITEM wsMenuItem = NULL;
|
|
|
|
menuInfo = (PPH_PLUGIN_MENU_INFORMATION)Parameter;
|
|
|
|
if (menuInfo->u.Process.NumberOfProcesses == 1)
|
|
processItem = menuInfo->u.Process.Processes[0];
|
|
else
|
|
{
|
|
processItem = NULL;
|
|
}
|
|
|
|
if (processItem == NULL)
|
|
return;
|
|
|
|
context = (PWCT_CONTEXT)PhAllocate(sizeof(WCT_CONTEXT));
|
|
memset(context, 0, sizeof(WCT_CONTEXT));
|
|
|
|
context->IsProcessItem = TRUE;
|
|
context->ProcessItem = processItem;
|
|
|
|
if (miscMenuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"Miscellaneous", 0))
|
|
{
|
|
PhInsertEMenuItem(miscMenuItem, menuItem = PhPluginCreateEMenuItem(PluginInstance, 0, IDD_WCT_MENUITEM, L"Wait Chain Traversal", context), -1);
|
|
|
|
if (!processItem || !processItem->QueryHandle || processItem->ProcessId == NtCurrentProcessId())
|
|
menuItem->Flags |= PH_EMENU_DISABLED;
|
|
|
|
if (!PhGetOwnTokenAttributes().Elevated)
|
|
menuItem->Flags |= PH_EMENU_DISABLED;
|
|
}
|
|
else
|
|
{
|
|
PhFree(context);
|
|
}
|
|
}
|
|
|
|
VOID NTAPI ThreadMenuInitializingCallback(
|
|
_In_opt_ PVOID Parameter,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PWCT_CONTEXT context = NULL;
|
|
PPH_PLUGIN_MENU_INFORMATION menuInfo = NULL;
|
|
PPH_THREAD_ITEM threadItem = NULL;
|
|
PPH_EMENU_ITEM menuItem = NULL;
|
|
PPH_EMENU_ITEM miscMenuItem = NULL;
|
|
|
|
menuInfo = (PPH_PLUGIN_MENU_INFORMATION)Parameter;
|
|
|
|
if (menuInfo->u.Thread.NumberOfThreads == 1)
|
|
threadItem = menuInfo->u.Thread.Threads[0];
|
|
else
|
|
threadItem = NULL;
|
|
|
|
context = (PWCT_CONTEXT)PhAllocate(sizeof(WCT_CONTEXT));
|
|
memset(context, 0, sizeof(WCT_CONTEXT));
|
|
|
|
context->IsProcessItem = FALSE;
|
|
context->ThreadItem = threadItem;
|
|
|
|
miscMenuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"Analyze", 0);
|
|
if (miscMenuItem)
|
|
{
|
|
menuItem = PhPluginCreateEMenuItem(PluginInstance, 0, IDD_WCT_MENUITEM, L"Wait Chain Traversal", context);
|
|
PhInsertEMenuItem(miscMenuItem, menuItem, -1);
|
|
|
|
// Disable menu if current process selected.
|
|
if (threadItem == NULL || menuInfo->u.Thread.ProcessId == NtCurrentProcessId())
|
|
menuItem->Flags |= PH_EMENU_DISABLED;
|
|
}
|
|
}
|
|
|
|
LOGICAL DllMain(
|
|
_In_ HINSTANCE Instance,
|
|
_In_ ULONG Reason,
|
|
_Reserved_ PVOID Reserved
|
|
)
|
|
{
|
|
switch (Reason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
{
|
|
PPH_PLUGIN_INFORMATION info;
|
|
|
|
PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info);
|
|
|
|
if (!PluginInstance)
|
|
return FALSE;
|
|
|
|
info->DisplayName = L"Wait Chain Traversal";
|
|
info->Author = L"dmex";
|
|
info->Description = L"Plugin for Wait Chain analysis via right-click Miscellaneous > Wait Chain Traversal or individual threads via Process properties > Threads tab > Analyze > Wait Chain Traversal.";
|
|
info->HasOptions = FALSE;
|
|
|
|
PhRegisterCallback(
|
|
PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem),
|
|
MenuItemCallback,
|
|
NULL,
|
|
&PluginMenuItemCallbackRegistration
|
|
);
|
|
PhRegisterCallback(
|
|
PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing),
|
|
ProcessMenuInitializingCallback,
|
|
NULL,
|
|
&ProcessMenuInitializingCallbackRegistration
|
|
);
|
|
PhRegisterCallback(
|
|
PhGetGeneralCallback(GeneralCallbackThreadMenuInitializing),
|
|
ThreadMenuInitializingCallback,
|
|
NULL,
|
|
&ThreadMenuInitializingCallbackRegistration
|
|
);
|
|
|
|
{
|
|
PH_SETTING_CREATE settings[] =
|
|
{
|
|
{ StringSettingType, SETTING_NAME_TREE_LIST_COLUMNS, L"" },
|
|
{ IntegerPairSettingType, SETTING_NAME_WINDOW_POSITION, L"100,100" },
|
|
{ ScalableIntegerPairSettingType, SETTING_NAME_WINDOW_SIZE, L"@96|690,540" }
|
|
};
|
|
|
|
PhAddSettings(settings, ARRAYSIZE(settings));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
} |