/* * 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 . */ #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); }