2025-05-13 19:49:49 +03:00

694 lines
22 KiB
C

/*
* Process Hacker Extra Plugins -
* Debug View Plugin
*
* Copyright (C) 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"
static HANDLE DbgDialogThreadHandle = NULL;
static HWND DbgDialogHandle = NULL;
static PH_EVENT InitializedEvent = PH_EVENT_INIT;
VOID NTAPI DbgLoggedEventCallback(
_In_opt_ PVOID Parameter,
_In_opt_ PVOID Context
)
{
PPH_DBGEVENTS_CONTEXT context = (PPH_DBGEVENTS_CONTEXT)Context;
if (context->DialogHandle)
{
PostMessage(context->DialogHandle, WM_DEBUG_LOG_UPDATED, 0, 0);
}
}
VOID DbgUpdateLogList(
_Inout_ PPH_DBGEVENTS_CONTEXT Context
)
{
Context->ListViewCount = Context->LogMessageList->Count;
ListView_SetItemCountEx(Context->ListViewHandle, Context->ListViewCount, LVSICF_NOSCROLL);
if (Context->ListViewCount >= 2 && Button_GetCheck(Context->AutoScrollHandle) == BST_CHECKED)
{
if (ListView_IsItemVisible(Context->ListViewHandle, Context->ListViewCount - 2))
{
ListView_EnsureVisible(Context->ListViewHandle, Context->ListViewCount - 1, FALSE);
}
}
}
PPH_STRING DbgGetStringForSelectedLogEntries(
_Inout_ PPH_DBGEVENTS_CONTEXT Context,
_In_ BOOLEAN All
)
{
PH_STRING_BUILDER stringBuilder;
ULONG i;
if (Context->ListViewCount == 0)
return PhReferenceEmptyString();
PhInitializeStringBuilder(&stringBuilder, 0x100);
i = Context->ListViewCount - 1;
while (TRUE)
{
PDEBUG_LOG_ENTRY entry;
SYSTEMTIME systemTime;
PPH_STRING temp;
if (!All)
{
if (!(ListView_GetItemState(Context->ListViewHandle, i, LVIS_SELECTED) & LVIS_SELECTED))
{
goto ContinueLoop;
}
}
entry = Context->LogMessageList->Items[i];
if (!entry)
goto ContinueLoop;
PhLargeIntegerToLocalSystemTime(&systemTime, &entry->Time);
temp = PhFormatDateTime(&systemTime);
PhAppendStringBuilder(&stringBuilder, &temp->sr);
PhDereferenceObject(temp);
PhAppendStringBuilder2(&stringBuilder, L": ");
temp = PhFormatString(
L"%s (%lu): %s",
entry->ProcessName->Buffer, // entry->FilePath->Buffer;
HandleToUlong(entry->ProcessId),
entry->Message->Buffer
);
PhAppendStringBuilder(&stringBuilder, &temp->sr);
PhDereferenceObject(temp);
PhAppendStringBuilder2(&stringBuilder, L"\r\n");
ContinueLoop:
if (i == 0)
break;
i--;
}
return PhFinalStringBuilderString(&stringBuilder);
}
VOID ShowListViewMenu(
_Inout_ PPH_DBGEVENTS_CONTEXT Context
)
{
INT index;
PDEBUG_LOG_ENTRY entry;
index = PhFindListViewItemByFlags(Context->ListViewHandle, -1, LVNI_SELECTED);
if (index == -1)
return;
entry = Context->LogMessageList->Items[index];
if (entry)
{
POINT cursorPos;
PPH_EMENU menu;
PPH_EMENU_ITEM selectedItem;
GetCursorPos(&cursorPos);
menu = PhCreateEMenu();
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, 1, L"View Message", NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, 2, L"Go to Owning &Process", NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1);
PhInsertEMenuItem(menu, selectedItem = PhCreateEMenuItem(0, 0, L"E&xclude Process", NULL, NULL), -1);
PhInsertEMenuItem(selectedItem, PhCreateEMenuItem(0, 3, L"By PID", NULL, NULL), -1);
PhInsertEMenuItem(selectedItem, PhCreateEMenuItem(0, 4, L"By Name", NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, 5, L"&Copy", NULL, NULL), -1);
selectedItem = PhShowEMenu(
menu,
Context->ListViewHandle,
PH_EMENU_SHOW_LEFTRIGHT,
PH_ALIGN_LEFT | PH_ALIGN_TOP,
cursorPos.x,
cursorPos.y
);
if (selectedItem && selectedItem->Id != -1)
{
switch (selectedItem->Id)
{
case 1:
{
DialogBoxParam(
PluginInstance->DllBase,
MAKEINTRESOURCE(IDD_MESSAGE_DIALOG),
Context->DialogHandle,
DbgPropDlgProc,
(LPARAM)entry
);
}
break;
case 2:
{
PPH_PROCESS_NODE processNode;
if (processNode = PhFindProcessNode(entry->ProcessId))
{
ProcessHacker_ToggleVisible(PhMainWndHandle, TRUE);
ProcessHacker_SelectTabPage(PhMainWndHandle, 0);
ProcessHacker_SelectProcessNode(PhMainWndHandle, processNode);
}
}
break;
case 3:
{
AddFilterType(Context, FilterByPid, entry->ProcessId, entry->ProcessName);
DbgUpdateLogList(Context);
}
break;
case 4:
{
AddFilterType(Context, FilterByName, entry->ProcessId, entry->ProcessName);
DbgUpdateLogList(Context);
}
break;
case 5:
{
PPH_STRING string;
string = DbgGetStringForSelectedLogEntries(Context, FALSE);
PhSetClipboardString(Context->DialogHandle, &string->sr);
PhDereferenceObject(string);
SetFocus(Context->ListViewHandle);
}
break;
}
}
PhDestroyEMenu(menu);
}
}
VOID ShowDropdownMenu(
_Inout_ PPH_DBGEVENTS_CONTEXT Context
)
{
RECT rect;
PPH_EMENU menu = NULL;
PPH_EMENU_ITEM selectedItem = NULL;
PPH_EMENU_ITEM resetMenuItem = NULL;
PPH_EMENU_ITEM captureMenuItem = NULL;
PPH_EMENU_ITEM captureGlobalMenuItem = NULL;
GetWindowRect(Context->OptionsHandle, &rect);
menu = PhCreateEMenu();
PhInsertEMenuItem(menu, captureMenuItem = PhCreateEMenuItem(0, ID_CAPTURE_WIN32, L"Capture Win32", NULL, NULL), -1);
PhInsertEMenuItem(menu, captureGlobalMenuItem = PhCreateEMenuItem(0, ID_CAPTURE_WIN32_GLOBAL, L"Capture Global Win32", NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1);
PhInsertEMenuItem(menu, resetMenuItem = PhCreateEMenuItem(PH_EMENU_DISABLED, ID_RESET_FILTERS, L"Reset Filters", NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, ID_CLEAR_EVENTS, L"Clear", NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1);
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, ID_SAVE_EVENTS, L"Save", NULL, NULL), -1);
if (Context->ExcludeList->Count > 0)
{
resetMenuItem->Text = PhaFormatString(L"Reset Filters [%lu]", Context->ExcludeList->Count)->Buffer;
resetMenuItem->Flags &= ~PH_EMENU_DISABLED;
}
if (Context->CaptureLocalEnabled)
{
captureMenuItem->Flags |= PH_EMENU_CHECKED;
}
if (Context->CaptureGlobalEnabled)
{
captureGlobalMenuItem->Flags |= PH_EMENU_CHECKED;
}
selectedItem = PhShowEMenu(
menu,
Context->DialogHandle,
PH_EMENU_SHOW_LEFTRIGHT,
PH_ALIGN_LEFT | PH_ALIGN_TOP,
rect.left,
rect.bottom
);
if (selectedItem && selectedItem->Id != -1)
{
switch (selectedItem->Id)
{
case ID_CAPTURE_WIN32:
{
if (!Context->CaptureLocalEnabled)
DbgEventsCreate(Context, FALSE);
else
DbgEventsCleanup(Context, FALSE);
}
break;
case ID_CAPTURE_WIN32_GLOBAL:
{
if (!PhGetOwnTokenAttributes().Elevated)
{
PhShowInformation(Context->DialogHandle, L"This option requires elevation.");
break;
}
if (!Context->CaptureGlobalEnabled)
DbgEventsCreate(Context, TRUE);
else
DbgEventsCleanup(Context, TRUE);
}
break;
case ID_RESET_FILTERS:
{
ResetFilters(Context);
}
break;
case ID_CLEAR_EVENTS:
{
DbgClearLogEntries(Context);
DbgUpdateLogList(Context);
}
break;
case ID_SAVE_EVENTS:
{
static PH_FILETYPE_FILTER filters[] =
{
{ L"Text files (*.txt)", L"*.txt" },
{ L"All files (*.*)", L"*.*" }
};
PVOID fileDialog = PhCreateSaveFileDialog();
PhSetFileDialogFilter(fileDialog, filters, ARRAYSIZE(filters));
PhSetFileDialogFileName(fileDialog, L"DbgView.txt");
if (PhShowFileDialog(Context->DialogHandle, fileDialog))
{
NTSTATUS status;
PPH_STRING fileName;
PPH_FILE_STREAM fileStream;
PPH_STRING string;
fileName = PhGetFileDialogFileName(fileDialog);
PhAutoDereferenceObject(fileName);
if (NT_SUCCESS(status = PhCreateFileStream(
&fileStream,
fileName->Buffer,
FILE_GENERIC_WRITE,
FILE_SHARE_READ,
FILE_OVERWRITE_IF,
0
)))
{
PhWritePhTextHeader(fileStream);
string = DbgGetStringForSelectedLogEntries(Context, TRUE);
PhWriteStringAsUtf8FileStreamEx(fileStream, string->Buffer, string->Length);
PhDereferenceObject(string);
PhDereferenceObject(fileStream);
}
if (!NT_SUCCESS(status))
PhShowStatus(PhMainWndHandle, L"Unable to create the file", status, 0);
}
PhFreeFileDialog(fileDialog);
}
break;
}
}
PhDestroyEMenu(menu);
}
INT_PTR CALLBACK DbgViewDlgProc(
_In_ HWND hwndDlg,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
PPH_DBGEVENTS_CONTEXT context;
if (uMsg == WM_INITDIALOG)
{
context = (PPH_DBGEVENTS_CONTEXT)PhAllocate(sizeof(PH_DBGEVENTS_CONTEXT));
memset(context, 0, sizeof(PH_DBGEVENTS_CONTEXT));
SetProp(hwndDlg, L"Context", (HANDLE)context);
}
else
{
context = (PPH_DBGEVENTS_CONTEXT)GetProp(hwndDlg, L"Context");
if (uMsg == WM_NCDESTROY)
{
RemoveProp(hwndDlg, L"Context");
PhFree(context);
}
}
if (!context)
return FALSE;
switch (uMsg)
{
case WM_INITDIALOG:
{
PhCenterWindow(hwndDlg, PhMainWndHandle);
context->DialogHandle = hwndDlg;
context->ListViewHandle = GetDlgItem(hwndDlg, IDC_DEBUGLISTVIEW);
context->AutoScrollHandle = GetDlgItem(hwndDlg, IDC_AUTOSCROLL);
context->OptionsHandle = GetDlgItem(hwndDlg, IDC_OPTIONS);
context->LogMessageList = PhCreateList(1);
context->ExcludeList = PhCreateList(1);
PhRegisterDialog(hwndDlg);
PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE);
PhSetControlTheme(context->ListViewHandle, L"explorer");
PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 140, L"Process");
PhAddListViewColumn(context->ListViewHandle, 1, 1, 1, LVCFMT_LEFT, 100, L"Timestamp");
PhAddListViewColumn(context->ListViewHandle, 2, 2, 2, LVCFMT_LEFT, 220, L"Message");
//PhSetExtendedListView(context->ListViewHandle);
if (context->ListViewImageList = ImageList_Create(19, 19, ILC_COLOR32 | ILC_MASK, 0, 40))
{
HICON defaultIcon = PhGetFileShellIcon(NULL, L".exe", TRUE);
ListView_SetImageList(context->ListViewHandle, context->ListViewImageList, LVSIL_SMALL);
ImageList_AddIcon(context->ListViewImageList, defaultIcon);
DestroyIcon(defaultIcon);
}
PhInitializeLayoutManager(&context->LayoutManager, hwndDlg);
PhAddLayoutItem(&context->LayoutManager, context->ListViewHandle, NULL, PH_ANCHOR_ALL);
PhAddLayoutItem(&context->LayoutManager, context->OptionsHandle, NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT);
PhAddLayoutItem(&context->LayoutManager, context->AutoScrollHandle, NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT);
PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_ALWAYSONTOP), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT);
PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDCLOSE), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT);
PhLoadWindowPlacementFromSetting(SETTING_NAME_WINDOW_POSITION, SETTING_NAME_WINDOW_SIZE, hwndDlg);
PhLoadListViewColumnsFromSetting(SETTING_NAME_COLUMNS, context->ListViewHandle);
if (PhGetIntegerSetting(SETTING_NAME_ALWAYSONTOP))
{
context->AlwaysOnTop = TRUE;
SetFocus(hwndDlg);
SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE);
Button_SetCheck(GetDlgItem(hwndDlg, IDC_ALWAYSONTOP), BST_CHECKED);
}
if (PhGetIntegerSetting(SETTING_NAME_AUTOSCROLL))
{
context->AutoScroll = TRUE;
Button_SetCheck(context->AutoScrollHandle, BST_CHECKED);
}
DbgCreateSecurityAttributes(context);
DbgUpdateLogList(context);
PhRegisterCallback(&DbgLoggedCallback, DbgLoggedEventCallback, context, &context->DebugLoggedRegistration);
}
return TRUE;
case WM_DESTROY:
{
DbgEventsCleanup(context, FALSE);
DbgEventsCleanup(context, TRUE);
DbgCleanupSecurityAttributes(context);
if (context->ListViewImageList)
ImageList_Destroy(context->ListViewImageList);
if (context->ExcludeList)
{
ResetFilters(context);
PhDereferenceObject(context->ExcludeList);
}
if (context->LogMessageList)
{
DbgClearLogEntries(context);
PhDereferenceObject(context->LogMessageList);
}
PhSaveWindowPlacementToSetting(SETTING_NAME_WINDOW_POSITION, SETTING_NAME_WINDOW_SIZE, hwndDlg);
PhSaveListViewColumnsToSetting(SETTING_NAME_COLUMNS, context->ListViewHandle);
PhDeleteLayoutManager(&context->LayoutManager);
PhUnregisterCallback(&DbgLoggedCallback, &context->DebugLoggedRegistration);
PhUnregisterDialog(hwndDlg);
PostQuitMessage(0);
}
break;
case WM_SIZE:
PhLayoutManagerLayout(&context->LayoutManager);
break;
case WM_SHOWDIALOG:
{
if (IsMinimized(hwndDlg))
ShowWindow(hwndDlg, SW_RESTORE);
else
ShowWindow(hwndDlg, SW_SHOW);
SetForegroundWindow(hwndDlg);
}
break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_OPTIONS:
ShowDropdownMenu(context);
break;
case IDCLOSE:
case IDCANCEL:
DestroyWindow(hwndDlg);
break;
case IDC_AUTOSCROLL:
{
context->AutoScroll = !context->AutoScroll;
PhSetIntegerSetting(SETTING_NAME_AUTOSCROLL, context->AutoScroll);
Button_SetCheck(context->AutoScrollHandle, context->AutoScroll ? BST_CHECKED : BST_UNCHECKED);
}
break;
case IDC_ALWAYSONTOP:
{
context->AlwaysOnTop = !context->AlwaysOnTop;
SetWindowPos(hwndDlg, context->AlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
PhSetIntegerSetting(SETTING_NAME_ALWAYSONTOP, context->AlwaysOnTop);
}
break;
}
}
break;
case WM_NOTIFY:
{
LPNMHDR hdr = (LPNMHDR)lParam;
switch (hdr->code)
{
case NM_RCLICK:
{
if (hdr->hwndFrom == context->ListViewHandle)
{
ShowListViewMenu(context);
}
}
break;
case NM_DBLCLK:
{
PDEBUG_LOG_ENTRY entry;
INT index;
index = PhFindListViewItemByFlags(context->ListViewHandle, -1, LVNI_SELECTED);
if (index == -1)
break;
entry = context->LogMessageList->Items[index];
DialogBoxParam(
PluginInstance->DllBase,
MAKEINTRESOURCE(IDD_MESSAGE_DIALOG),
hwndDlg,
DbgPropDlgProc,
(LPARAM)entry
);
}
break;
case LVN_GETDISPINFO:
{
NMLVDISPINFO* dispInfo = (NMLVDISPINFO*)hdr;
PDEBUG_LOG_ENTRY entry;
entry = context->LogMessageList->Items[dispInfo->item.iItem];
if (dispInfo->item.mask & LVIF_IMAGE)
{
dispInfo->item.iImage = entry->ImageIndex;
}
if (dispInfo->item.iSubItem == 0)
{
if (dispInfo->item.mask & LVIF_TEXT)
{
wcsncpy_s(
dispInfo->item.pszText,
dispInfo->item.cchTextMax,
entry->ProcessName->Buffer,
_TRUNCATE
);
}
}
else if (dispInfo->item.iSubItem == 1)
{
if (dispInfo->item.mask & LVIF_TEXT)
{
SYSTEMTIME systemTime;
PPH_STRING dateTime;
PPH_STRING dateString;
PhLargeIntegerToLocalSystemTime(&systemTime, &entry->Time);
dateTime = PhFormatTime(&systemTime, L"hh:mm:ss");
dateString = PhFormatString(
L"%s.%u",
dateTime->Buffer,
systemTime.wMilliseconds
);
wcsncpy_s(
dispInfo->item.pszText,
dispInfo->item.cchTextMax,
dateString->Buffer,
_TRUNCATE
);
PhDereferenceObject(dateString);
PhDereferenceObject(dateTime);
}
}
else if (dispInfo->item.iSubItem == 2)
{
if (dispInfo->item.mask & LVIF_TEXT)
{
wcsncpy_s(
dispInfo->item.pszText,
dispInfo->item.cchTextMax,
entry->Message->Buffer,
_TRUNCATE
);
}
}
}
break;
}
}
break;
case WM_DEBUG_LOG_UPDATED:
DbgUpdateLogList(context);
break;
}
return FALSE;
}
NTSTATUS DbgViewDialogThread(
_In_ PVOID Parameter
)
{
BOOL result;
MSG message;
PH_AUTO_POOL autoPool;
PhInitializeAutoPool(&autoPool);
DbgDialogHandle = CreateDialog(
PluginInstance->DllBase,
MAKEINTRESOURCE(IDD_DBGVIEW_DIALOG),
NULL,
DbgViewDlgProc
);
PhSetEvent(&InitializedEvent);
while (result = GetMessage(&message, NULL, 0, 0))
{
if (result == -1)
break;
if (!IsDialogMessage(DbgDialogHandle, &message))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
PhDrainAutoPool(&autoPool);
}
PhDeleteAutoPool(&autoPool);
PhResetEvent(&InitializedEvent);
if (DbgDialogThreadHandle)
{
NtClose(DbgDialogThreadHandle);
DbgDialogThreadHandle = NULL;
}
return STATUS_SUCCESS;
}
VOID ShowDebugEventsDialog(
VOID
)
{
if (!DbgDialogThreadHandle)
{
if (!(DbgDialogThreadHandle = PhCreateThread(0, DbgViewDialogThread, NULL)))
{
PhShowStatus(PhMainWndHandle, L"Unable to create the window.", 0, GetLastError());
return;
}
PhWaitForEvent(&InitializedEvent, NULL);
}
PostMessage(DbgDialogHandle, WM_SHOWDIALOG, 0, 0);
}