572 lines
15 KiB
C
572 lines
15 KiB
C
/*
|
|
* Process Hacker Extended Tools -
|
|
* working set watch
|
|
*
|
|
* Copyright (C) 2011 wj32
|
|
*
|
|
* 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 "exttools.h"
|
|
#include <workqueue.h>
|
|
#include <symprv.h>
|
|
|
|
typedef struct _WS_WATCH_CONTEXT
|
|
{
|
|
LONG RefCount;
|
|
|
|
PPH_PROCESS_ITEM ProcessItem;
|
|
HWND WindowHandle;
|
|
HWND ListViewHandle;
|
|
BOOLEAN Enabled;
|
|
BOOLEAN Destroying;
|
|
PPH_HASHTABLE Hashtable;
|
|
HANDLE ProcessHandle;
|
|
PVOID Buffer;
|
|
ULONG BufferSize;
|
|
|
|
PPH_SYMBOL_PROVIDER SymbolProvider;
|
|
HANDLE LoadingSymbolsForProcessId;
|
|
SINGLE_LIST_ENTRY ResultListHead;
|
|
PH_QUEUED_LOCK ResultListLock;
|
|
} WS_WATCH_CONTEXT, *PWS_WATCH_CONTEXT;
|
|
|
|
typedef struct _SYMBOL_LOOKUP_RESULT
|
|
{
|
|
SINGLE_LIST_ENTRY ListEntry;
|
|
PWS_WATCH_CONTEXT Context;
|
|
PVOID Address;
|
|
PPH_STRING Symbol;
|
|
} SYMBOL_LOOKUP_RESULT, *PSYMBOL_LOOKUP_RESULT;
|
|
|
|
VOID EtpReferenceWsWatchContext(
|
|
_Inout_ PWS_WATCH_CONTEXT Context
|
|
);
|
|
|
|
VOID EtpDereferenceWsWatchContext(
|
|
_Inout_ PWS_WATCH_CONTEXT Context
|
|
);
|
|
|
|
INT_PTR CALLBACK EtpWsWatchDlgProc(
|
|
_In_ HWND hwndDlg,
|
|
_In_ UINT uMsg,
|
|
_In_ WPARAM wParam,
|
|
_In_ LPARAM lParam
|
|
);
|
|
|
|
VOID EtShowWsWatchDialog(
|
|
_In_ HWND ParentWindowHandle,
|
|
_In_ PPH_PROCESS_ITEM ProcessItem
|
|
)
|
|
{
|
|
PWS_WATCH_CONTEXT context;
|
|
|
|
context = PhAllocate(sizeof(WS_WATCH_CONTEXT));
|
|
memset(context, 0, sizeof(WS_WATCH_CONTEXT));
|
|
context->RefCount = 1;
|
|
context->ProcessItem = ProcessItem;
|
|
|
|
DialogBoxParam(
|
|
PluginInstance->DllBase,
|
|
MAKEINTRESOURCE(IDD_WSWATCH),
|
|
ParentWindowHandle,
|
|
EtpWsWatchDlgProc,
|
|
(LPARAM)context
|
|
);
|
|
EtpDereferenceWsWatchContext(context);
|
|
}
|
|
|
|
static VOID EtpReferenceWsWatchContext(
|
|
_Inout_ PWS_WATCH_CONTEXT Context
|
|
)
|
|
{
|
|
_InterlockedIncrement(&Context->RefCount);
|
|
}
|
|
|
|
static VOID EtpDereferenceWsWatchContext(
|
|
_Inout_ PWS_WATCH_CONTEXT Context
|
|
)
|
|
{
|
|
if (_InterlockedDecrement(&Context->RefCount) == 0)
|
|
{
|
|
PSINGLE_LIST_ENTRY listEntry;
|
|
|
|
if (Context->SymbolProvider)
|
|
PhDereferenceObject(Context->SymbolProvider);
|
|
|
|
// Free all unused results.
|
|
|
|
PhAcquireQueuedLockExclusive(&Context->ResultListLock);
|
|
|
|
listEntry = Context->ResultListHead.Next;
|
|
|
|
while (listEntry)
|
|
{
|
|
PSYMBOL_LOOKUP_RESULT result;
|
|
|
|
result = CONTAINING_RECORD(listEntry, SYMBOL_LOOKUP_RESULT, ListEntry);
|
|
listEntry = listEntry->Next;
|
|
PhDereferenceObject(result->Symbol);
|
|
PhFree(result);
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&Context->ResultListLock);
|
|
|
|
PhFree(Context);
|
|
}
|
|
}
|
|
|
|
static NTSTATUS EtpSymbolLookupFunction(
|
|
_In_ PVOID Parameter
|
|
)
|
|
{
|
|
PSYMBOL_LOOKUP_RESULT result;
|
|
|
|
result = Parameter;
|
|
|
|
// Don't bother looking up the symbol if the window has already closed.
|
|
if (result->Context->Destroying)
|
|
{
|
|
EtpDereferenceWsWatchContext(result->Context);
|
|
PhFree(result);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
result->Symbol = PhGetSymbolFromAddress(
|
|
result->Context->SymbolProvider,
|
|
(ULONG64)result->Address,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
// Fail if we don't have a symbol.
|
|
if (!result->Symbol)
|
|
{
|
|
EtpDereferenceWsWatchContext(result->Context);
|
|
PhFree(result);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
PhAcquireQueuedLockExclusive(&result->Context->ResultListLock);
|
|
PushEntryList(&result->Context->ResultListHead, &result->ListEntry);
|
|
PhReleaseQueuedLockExclusive(&result->Context->ResultListLock);
|
|
EtpDereferenceWsWatchContext(result->Context);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static VOID EtpQueueSymbolLookup(
|
|
_In_ PWS_WATCH_CONTEXT Context,
|
|
_In_ PVOID Address
|
|
)
|
|
{
|
|
PSYMBOL_LOOKUP_RESULT result;
|
|
|
|
result = PhAllocate(sizeof(SYMBOL_LOOKUP_RESULT));
|
|
result->Context = Context;
|
|
result->Address = Address;
|
|
EtpReferenceWsWatchContext(Context);
|
|
|
|
PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), EtpSymbolLookupFunction, result);
|
|
}
|
|
|
|
static PPH_STRING EtpGetBasicSymbol(
|
|
_In_ PPH_SYMBOL_PROVIDER SymbolProvider,
|
|
_In_ ULONG64 Address
|
|
)
|
|
{
|
|
ULONG64 modBase;
|
|
PPH_STRING fileName = NULL;
|
|
PPH_STRING baseName = NULL;
|
|
PPH_STRING symbol;
|
|
|
|
modBase = PhGetModuleFromAddress(SymbolProvider, Address, &fileName);
|
|
|
|
if (!fileName)
|
|
{
|
|
symbol = PhCreateStringEx(NULL, PH_PTR_STR_LEN * 2);
|
|
PhPrintPointer(symbol->Buffer, (PVOID)Address);
|
|
PhTrimToNullTerminatorString(symbol);
|
|
}
|
|
else
|
|
{
|
|
PH_FORMAT format[3];
|
|
|
|
baseName = PhGetBaseName(fileName);
|
|
|
|
PhInitFormatSR(&format[0], baseName->sr);
|
|
PhInitFormatS(&format[1], L"+0x");
|
|
PhInitFormatIX(&format[2], (ULONG_PTR)(Address - modBase));
|
|
|
|
symbol = PhFormat(format, 3, baseName->Length + 6 + 32);
|
|
}
|
|
|
|
if (fileName)
|
|
PhDereferenceObject(fileName);
|
|
if (baseName)
|
|
PhDereferenceObject(baseName);
|
|
|
|
return symbol;
|
|
}
|
|
|
|
static VOID EtpProcessSymbolLookupResults(
|
|
_In_ HWND hwndDlg,
|
|
_In_ PWS_WATCH_CONTEXT Context
|
|
)
|
|
{
|
|
PSINGLE_LIST_ENTRY listEntry;
|
|
|
|
// Remove all results.
|
|
PhAcquireQueuedLockExclusive(&Context->ResultListLock);
|
|
listEntry = Context->ResultListHead.Next;
|
|
Context->ResultListHead.Next = NULL;
|
|
PhReleaseQueuedLockExclusive(&Context->ResultListLock);
|
|
|
|
// Update the list view with the results.
|
|
while (listEntry)
|
|
{
|
|
PSYMBOL_LOOKUP_RESULT result;
|
|
|
|
result = CONTAINING_RECORD(listEntry, SYMBOL_LOOKUP_RESULT, ListEntry);
|
|
listEntry = listEntry->Next;
|
|
|
|
PhSetListViewSubItem(
|
|
Context->ListViewHandle,
|
|
PhFindListViewItemByParam(Context->ListViewHandle, -1, result->Address),
|
|
0,
|
|
result->Symbol->Buffer
|
|
);
|
|
|
|
PhDereferenceObject(result->Symbol);
|
|
PhFree(result);
|
|
}
|
|
}
|
|
|
|
static BOOLEAN EtpUpdateWsWatch(
|
|
_In_ HWND hwndDlg,
|
|
_In_ PWS_WATCH_CONTEXT Context
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
BOOLEAN result;
|
|
ULONG returnLength;
|
|
PPROCESS_WS_WATCH_INFORMATION_EX wsWatchInfo;
|
|
|
|
// Query WS watch information.
|
|
|
|
if (!Context->Buffer)
|
|
return FALSE;
|
|
|
|
status = NtQueryInformationProcess(
|
|
Context->ProcessHandle,
|
|
ProcessWorkingSetWatchEx,
|
|
Context->Buffer,
|
|
Context->BufferSize,
|
|
&returnLength
|
|
);
|
|
|
|
if (status == STATUS_UNSUCCESSFUL)
|
|
{
|
|
// WS Watch is not enabled.
|
|
return FALSE;
|
|
}
|
|
|
|
if (status == STATUS_NO_MORE_ENTRIES)
|
|
{
|
|
// There were no new faults, but we still need to process symbol lookup results.
|
|
result = TRUE;
|
|
goto SkipBuffer;
|
|
}
|
|
|
|
if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(Context->Buffer);
|
|
Context->Buffer = PhAllocate(returnLength);
|
|
Context->BufferSize = returnLength;
|
|
|
|
status = NtQueryInformationProcess(
|
|
Context->ProcessHandle,
|
|
ProcessWorkingSetWatchEx,
|
|
Context->Buffer,
|
|
Context->BufferSize,
|
|
&returnLength
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
// Error related to the buffer size. Try again later.
|
|
result = FALSE;
|
|
goto SkipBuffer;
|
|
}
|
|
|
|
// Update the hashtable and list view.
|
|
|
|
ExtendedListView_SetRedraw(Context->ListViewHandle, FALSE);
|
|
|
|
wsWatchInfo = Context->Buffer;
|
|
|
|
while (wsWatchInfo->BasicInfo.FaultingPc)
|
|
{
|
|
PVOID *entry;
|
|
WCHAR buffer[PH_INT32_STR_LEN_1];
|
|
INT lvItemIndex;
|
|
ULONG newCount;
|
|
|
|
// Update the count in the entry for this instruction pointer, or add a new entry if it doesn't exist.
|
|
|
|
entry = PhFindItemSimpleHashtable(Context->Hashtable, wsWatchInfo->BasicInfo.FaultingPc);
|
|
|
|
if (entry)
|
|
{
|
|
newCount = PtrToUlong(*entry) + 1;
|
|
*entry = UlongToPtr(newCount);
|
|
lvItemIndex = PhFindListViewItemByParam(Context->ListViewHandle, -1, wsWatchInfo->BasicInfo.FaultingPc);
|
|
}
|
|
else
|
|
{
|
|
PPH_STRING basicSymbol;
|
|
|
|
newCount = 1;
|
|
PhAddItemSimpleHashtable(Context->Hashtable, wsWatchInfo->BasicInfo.FaultingPc, UlongToPtr(1));
|
|
|
|
// Get a basic symbol name (module+offset).
|
|
basicSymbol = EtpGetBasicSymbol(Context->SymbolProvider, (ULONG64)wsWatchInfo->BasicInfo.FaultingPc);
|
|
|
|
lvItemIndex = PhAddListViewItem(Context->ListViewHandle, MAXINT, basicSymbol->Buffer, wsWatchInfo->BasicInfo.FaultingPc);
|
|
PhDereferenceObject(basicSymbol);
|
|
|
|
// Queue a full symbol lookup.
|
|
EtpQueueSymbolLookup(Context, wsWatchInfo->BasicInfo.FaultingPc);
|
|
}
|
|
|
|
// Update the count in the list view item.
|
|
PhPrintUInt32(buffer, newCount);
|
|
PhSetListViewSubItem(
|
|
Context->ListViewHandle,
|
|
lvItemIndex,
|
|
1,
|
|
buffer
|
|
);
|
|
|
|
wsWatchInfo++;
|
|
}
|
|
|
|
ExtendedListView_SetRedraw(Context->ListViewHandle, TRUE);
|
|
result = TRUE;
|
|
|
|
SkipBuffer:
|
|
EtpProcessSymbolLookupResults(hwndDlg, Context);
|
|
ExtendedListView_SortItems(Context->ListViewHandle);
|
|
|
|
return result;
|
|
}
|
|
|
|
static BOOLEAN NTAPI EnumGenericModulesCallback(
|
|
_In_ PPH_MODULE_INFO Module,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PWS_WATCH_CONTEXT context = Context;
|
|
|
|
// If we're loading kernel module symbols for a process other than
|
|
// System, ignore modules which are in user space. This may happen
|
|
// in Windows 7.
|
|
if (
|
|
context->LoadingSymbolsForProcessId == SYSTEM_PROCESS_ID &&
|
|
(ULONG_PTR)Module->BaseAddress <= PhSystemBasicInformation.MaximumUserModeAddress
|
|
)
|
|
return TRUE;
|
|
|
|
PhLoadModuleSymbolProvider(context->SymbolProvider, Module->FileName->Buffer,
|
|
(ULONG64)Module->BaseAddress, Module->Size);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
INT_PTR CALLBACK EtpWsWatchDlgProc(
|
|
_In_ HWND hwndDlg,
|
|
_In_ UINT uMsg,
|
|
_In_ WPARAM wParam,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
PWS_WATCH_CONTEXT context;
|
|
|
|
if (uMsg == WM_INITDIALOG)
|
|
{
|
|
context = (PWS_WATCH_CONTEXT)lParam;
|
|
SetProp(hwndDlg, L"Context", (HANDLE)context);
|
|
}
|
|
else
|
|
{
|
|
context = (PWS_WATCH_CONTEXT)GetProp(hwndDlg, L"Context");
|
|
|
|
if (uMsg == WM_DESTROY)
|
|
RemoveProp(hwndDlg, L"Context");
|
|
}
|
|
|
|
if (!context)
|
|
return FALSE;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
HWND lvHandle;
|
|
|
|
PhCenterWindow(hwndDlg, GetParent(hwndDlg));
|
|
|
|
context->WindowHandle = hwndDlg;
|
|
context->ListViewHandle = lvHandle = GetDlgItem(hwndDlg, IDC_LIST);
|
|
|
|
PhSetListViewStyle(lvHandle, FALSE, TRUE);
|
|
PhSetControlTheme(lvHandle, L"explorer");
|
|
PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 340, L"Instruction");
|
|
PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 80, L"Count");
|
|
PhSetExtendedListView(lvHandle);
|
|
ExtendedListView_SetSort(lvHandle, 1, DescendingSortOrder);
|
|
|
|
context->Hashtable = PhCreateSimpleHashtable(64);
|
|
context->BufferSize = 0x2000;
|
|
context->Buffer = PhAllocate(context->BufferSize);
|
|
|
|
PhInitializeQueuedLock(&context->ResultListLock);
|
|
context->SymbolProvider = PhCreateSymbolProvider(context->ProcessItem->ProcessId);
|
|
PhLoadSymbolProviderOptions(context->SymbolProvider);
|
|
|
|
if (!context->SymbolProvider || !context->SymbolProvider->IsRealHandle)
|
|
{
|
|
PhShowError(hwndDlg, L"Unable to open the process.");
|
|
EndDialog(hwndDlg, IDCANCEL);
|
|
break;
|
|
}
|
|
|
|
context->ProcessHandle = context->SymbolProvider->ProcessHandle;
|
|
|
|
// Load symbols for both process and kernel modules.
|
|
context->LoadingSymbolsForProcessId = context->ProcessItem->ProcessId;
|
|
PhEnumGenericModules(
|
|
NULL,
|
|
context->ProcessHandle,
|
|
0,
|
|
EnumGenericModulesCallback,
|
|
context
|
|
);
|
|
context->LoadingSymbolsForProcessId = SYSTEM_PROCESS_ID;
|
|
PhEnumGenericModules(
|
|
SYSTEM_PROCESS_ID,
|
|
NULL,
|
|
0,
|
|
EnumGenericModulesCallback,
|
|
context
|
|
);
|
|
|
|
context->Enabled = EtpUpdateWsWatch(hwndDlg, context);
|
|
|
|
if (context->Enabled)
|
|
{
|
|
// WS Watch is already enabled for the process. Enable updating.
|
|
EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLE), FALSE);
|
|
ShowWindow(GetDlgItem(hwndDlg, IDC_WSWATCHENABLED), SW_SHOW);
|
|
SetTimer(hwndDlg, 1, 1000, NULL);
|
|
}
|
|
else
|
|
{
|
|
// WS Watch has not yet been enabled for the process.
|
|
}
|
|
}
|
|
break;
|
|
case WM_DESTROY:
|
|
{
|
|
context->Destroying = TRUE;
|
|
|
|
PhDereferenceObject(context->Hashtable);
|
|
|
|
if (context->Buffer)
|
|
{
|
|
PhFree(context->Buffer);
|
|
context->Buffer = NULL;
|
|
}
|
|
}
|
|
break;
|
|
case WM_COMMAND:
|
|
{
|
|
switch (LOWORD(wParam))
|
|
{
|
|
case IDCANCEL:
|
|
case IDOK:
|
|
EndDialog(hwndDlg, IDOK);
|
|
break;
|
|
case IDC_ENABLE:
|
|
{
|
|
NTSTATUS status;
|
|
HANDLE processHandle;
|
|
|
|
if (NT_SUCCESS(status = PhOpenProcess(
|
|
&processHandle,
|
|
PROCESS_SET_INFORMATION,
|
|
context->ProcessItem->ProcessId
|
|
)))
|
|
{
|
|
status = NtSetInformationProcess(
|
|
processHandle,
|
|
ProcessWorkingSetWatchEx,
|
|
NULL,
|
|
0
|
|
);
|
|
NtClose(processHandle);
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLE), FALSE);
|
|
ShowWindow(GetDlgItem(hwndDlg, IDC_WSWATCHENABLED), SW_SHOW);
|
|
SetTimer(hwndDlg, 1, 1000, NULL);
|
|
}
|
|
else
|
|
{
|
|
PhShowStatus(hwndDlg, L"Unable to enable WS watch", status, 0);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case WM_NOTIFY:
|
|
{
|
|
PhHandleListViewNotifyForCopy(lParam, context->ListViewHandle);
|
|
}
|
|
break;
|
|
case WM_TIMER:
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case 1:
|
|
{
|
|
EtpUpdateWsWatch(hwndDlg, context);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|