From 898d996061aff9e8ffc8012d475e007a8b24d167 Mon Sep 17 00:00:00 2001
From: wnstngs <64602598+wnstngs@users.noreply.github.com>
Date: Mon, 26 May 2025 22:56:42 +0300
Subject: [PATCH 1/4] Initial support for CPU Sets
Related issue: #2289
Changes made:
- Process Default CPU Sets
- Get/Set (API)
- Get/Set (GUI)
- System CPU Sets
- Get (API)
---
SystemInformer/SystemInformer.rc | 39 +-
SystemInformer/SystemInformer.vcxproj | 1 +
SystemInformer/SystemInformer.vcxproj.filters | 3 +
SystemInformer/cpuset.c | 1256 +++++++++++++++++
SystemInformer/include/phapp.h | 7 +
SystemInformer/include/sysinfop.h | 4 -
SystemInformer/mainwnd.c | 12 +
SystemInformer/mwpgproc.c | 3 +
SystemInformer/resource.h | 59 +-
SystemInformer/syssccpu.c | 4 +-
phlib/include/cpuset.h | 55 +
phlib/phlib.vcxproj | 1 +
12 files changed, 1408 insertions(+), 36 deletions(-)
create mode 100644 SystemInformer/cpuset.c
create mode 100644 phlib/include/cpuset.h
diff --git a/SystemInformer/SystemInformer.rc b/SystemInformer/SystemInformer.rc
index 25249c591d09..343c6dc5e378 100644
--- a/SystemInformer/SystemInformer.rc
+++ b/SystemInformer/SystemInformer.rc
@@ -26,18 +26,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// TEXTINCLUDE
//
-1 TEXTINCLUDE
+1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
-2 TEXTINCLUDE
+2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"#include ""include/phappres.h""\0"
END
-3 TEXTINCLUDE
+3 TEXTINCLUDE
BEGIN
"\0"
END
@@ -611,6 +611,21 @@ BEGIN
COMBOBOX IDC_GROUPCPU,220,4,52,30,CBS_DROPDOWNLIST | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
END
+IDD_CPUSETS DIALOGEX 0, 0, 368, 244
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+CAPTION "CPU Sets"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "CPU Sets provide a way to declare CPU affinity in a soft manner that is compatible with OS power management, and to reaffinitize all background threads in the process to a subset of processors to avoid interference from OS threads within the process.",IDC_STATIC,6,6,339,23
+ CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,6,35,357,174
+ PUSHBUTTON "Cancel",IDCANCEL,261,219,50,14
+ DEFPUSHBUTTON "OK",IDOK,314,219,50,14
+ PUSHBUTTON "Select all",IDC_SELECTALL,6,219,50,14
+ PUSHBUTTON "Unselect all",IDC_DESELECTALL,59,219,50,14
+ CONTROL "When all are unselected, all CPU Sets may be used",IDC_STATIC,
+ "Static",SS_SIMPLE | SS_WORDELLIPSIS | WS_GROUP,8,234,154,8
+END
+
IDD_SYSINFO DIALOGEX 0, 0, 423, 247
STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
@@ -1799,6 +1814,11 @@ BEGIN
BOTTOMMARGIN, 220
END
+ IDD_CPUSETS, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 4
+ END
+
IDD_SYSINFO, DIALOG
BEGIN
END
@@ -2187,7 +2207,6 @@ BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 253
TOPMARGIN, 7
- BOTTOMMARGIN, 253
END
END
#endif // APSTUDIO_INVOKED
@@ -2234,6 +2253,7 @@ BEGIN
VK_F11, IDC_MAXSCREEN, VIRTKEY, NOINVERT
END
+
/////////////////////////////////////////////////////////////////////////////
//
// Bitmap
@@ -2243,6 +2263,17 @@ IDB_SEARCH_ACTIVE_BMP BITMAP "resources\\active_search.bmp"
IDB_SEARCH_INACTIVE_BMP BITMAP "resources\\inactive_search.bmp"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AFX_DIALOG_LAYOUT
+//
+
+IDD_CPUSETS AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
diff --git a/SystemInformer/SystemInformer.vcxproj b/SystemInformer/SystemInformer.vcxproj
index 1c28b7dafd53..364858f42f65 100644
--- a/SystemInformer/SystemInformer.vcxproj
+++ b/SystemInformer/SystemInformer.vcxproj
@@ -230,6 +230,7 @@
+
diff --git a/SystemInformer/SystemInformer.vcxproj.filters b/SystemInformer/SystemInformer.vcxproj.filters
index 35d8d0002268..d8c8a556f7ac 100644
--- a/SystemInformer/SystemInformer.vcxproj.filters
+++ b/SystemInformer/SystemInformer.vcxproj.filters
@@ -345,6 +345,9 @@
System Informer
+
+ System Informer
+
diff --git a/SystemInformer/cpuset.c b/SystemInformer/cpuset.c
new file mode 100644
index 000000000000..517d3ab79b24
--- /dev/null
+++ b/SystemInformer/cpuset.c
@@ -0,0 +1,1256 @@
+/*
+ * Copyright (c) 2025 Winsider Seminars & Solutions, Inc. All rights reserved.
+ *
+ * This file is part of System Informer ("SI").
+ *
+ * Description:
+ *
+ * This module contains code to support CPU Sets in SI.
+ *
+ * Authors:
+ *
+ * wnstngs 2025
+ *
+ */
+
+//
+// The current version only supports modifying CPU Sets at the Process Default
+// level. TODO: Support the Thread Selected level.
+//
+
+//
+// ------------------------------------------------------------------- Includes
+//
+
+#include
+#include
+
+#include
+
+//
+// ---------------------------------------------------------------- Definitions
+//
+
+//
+// Each CPU Set ID is encoded as follows:
+// Bits [31:16]: Processor group index
+// Bits [15:8]: Processor index within the group
+// Bits [7:0]: CPUSET_BIAS
+//
+#define CPUSET_BIAS 0x100
+#define CPUSET_GROUP_SHIFT 16
+#define CPUSET_ID_MASK 0xFF
+
+/**
+ * Encodes a CPU Set ID from a group index and a logical processor index.
+ *
+ * @param GroupIndex
+ * The index of the processor group.
+ *
+ * @param CpuIndex
+ * The index of the logical processor within the group.
+ *
+ * @return The encoded CPU Set ID.
+ */
+#define ENCODE_CPU_SET_ID(GroupIndex, CpuIndex) \
+ (((GroupIndex) << CPUSET_GROUP_SHIFT) | \
+ ((CpuIndex) & CPUSET_ID_MASK) | \
+ CPUSET_BIAS)
+
+/**
+ * Decodes a CPU Set ID to extract the processor group index.
+ *
+ * @param CpuSetId
+ * The encoded CPU Set ID.
+ *
+ * @return The processor group index.
+ */
+#define DECODE_CPU_SET_GROUP(CpuSetId) \
+ (((CpuSetId) - CPUSET_BIAS) >> CPUSET_GROUP_SHIFT)
+
+/**
+ * A helper which iterates through a list of CPU Set information structures
+ * and calls the specified callback for each CPU Set.
+ *
+ * @param InfoStart
+ * Pointer to the start of the CPU Set information buffer.
+ *
+ * @param Context
+ * Optional context pointer to pass to the callback function.
+ *
+ * @param Callback
+ * Callback function to invoke. If it fails, the enumeration stops.
+ * TBD: Add a parameter to control this behavior (break vs continue).
+ */
+#define FOREACH_CPU_SET_INFO(InfoStart, Context, Callback) \
+ do { \
+ PSYSTEM_CPU_SET_INFORMATION _info = (InfoStart); \
+ while (_info->Size != 0 && \
+ RTL_CONTAINS_FIELD(_info, _info->Size, CpuSet)) { \
+ if (_info->Type == CpuSetInformation) { \
+ if (!Callback(_info, (PVOID)(Context))) { \
+ break; \
+ } \
+ } \
+ _info = PTR_ADD_OFFSET(_info, _info->Size); \
+ } \
+ } while (0)
+
+typedef enum PH_CPU_SET_TYPE {
+ PhCpuSetTypeSystem,
+ PhCpuSetTypeProcessDefault,
+ PhCpuSetTypeThreadSelected
+} PH_CPU_SET_TYPE;
+
+typedef struct PHP_CPU_SETS_DLG_CTX {
+ HWND WindowHandle;
+ HWND ListViewHandle;
+ PPH_PROCESS_ITEM ProcessItem;
+ PH_CPU_SET_TYPE CpuSetType;
+ PULONG CpuSetIds;
+ ULONG CpuSetIdCount;
+ ULONG Index;
+} PHP_CPU_SETS_DLG_CTX, *PPHP_CPU_SETS_DLG_CTX;
+
+//
+// ----------------------------------------------------------------- Prototypes
+//
+
+INT_PTR
+CALLBACK
+PhpProcessCpuSetsDlgProc(
+ _In_ HWND DlgHandle,
+ _In_ UINT Message,
+ _In_ WPARAM WParam,
+ _In_ LPARAM LParam
+ );
+
+//
+// ------------------------------------------------------------------ Functions
+//
+
+//
+// CPU Set ID Helpers
+//
+
+/**
+ * Converts an array of CPU Set IDs into processor group affinity masks.
+ *
+ * @param CpuSetIds
+ * An array of CPU Set IDs to convert.
+ *
+ * @param CpuSetIdCount
+ * The number of CPU Set IDs in the array.
+ *
+ * @param CpuSetMasks
+ * An output buffer for processor group affinity masks.
+ *
+ * @param GroupCount
+ * The number of processor groups.
+ *
+ * @return TRUE if successful, FALSE otherwise.
+ */
+_Success_(return != FALSE)
+static
+BOOLEAN
+PhpCpuSetIdsToMasks(
+ _In_reads_opt_(CpuSetIdCount) CONST ULONG *CpuSetIds,
+ _In_ ULONG CpuSetIdCount,
+ _Out_writes_(GroupCount) PKAFFINITY CpuSetMasks,
+ _In_ USHORT GroupCount
+ )
+{
+ ULONG i;
+ USHORT group;
+ UCHAR cpuIndex;
+
+ assert(CpuSetMasks != NULL);
+ assert(GroupCount > 0);
+
+ memset(CpuSetMasks, 0, GroupCount * sizeof(KAFFINITY));
+
+ for (i = 0; i < CpuSetIdCount; i += 1) {
+
+ //
+ // Decode group and processor index
+ //
+ cpuIndex = (UCHAR)CpuSetIds[i];
+ group = DECODE_CPU_SET_GROUP(CpuSetIds[i]);
+
+ assert(group < GroupCount);
+ assert(cpuIndex < MAXIMUM_PROC_PER_GROUP);
+
+ if (group >= GroupCount || cpuIndex >= MAXIMUM_PROC_PER_GROUP) {
+ return FALSE;
+ }
+
+ //
+ // Set the corresponding bit
+ //
+ CpuSetMasks[group] |= ((KAFFINITY)1 << cpuIndex);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Converts processor group affinity masks to CPU Set IDs.
+ *
+ * @param CpuSetMasks
+ * An array of processor group affinity masks to be converted.
+ *
+ * @param GroupCount
+ * The number of processor groups.
+ *
+ * @param IdList
+ * An optional output array where CPU Set IDs will be stored if provided.
+ *
+ * @param IdListSize
+ * The size of the IdList array.
+ *
+ * @param RequiredCount
+ * A pointer to a variable that will receive the number of IDs required to
+ * fully represent the input affinity masks.
+ *
+ * @return TRUE if successful, FALSE otherwise.
+ */
+_Success_(return != FALSE)
+static
+BOOLEAN
+PhpCpuSetMasksToIdList(
+ _In_reads_(GroupCount) CONST KAFFINITY *CpuSetMasks,
+ _In_ USHORT GroupCount,
+ _Out_writes_to_opt_(IdListSize, *RequiredCount) PULONG IdList,
+ _In_ ULONG IdListSize,
+ _Out_ PULONG RequiredCount
+ )
+{
+ ULONG total = 0;
+ ULONG bit;
+ USHORT group;
+ KAFFINITY mask;
+
+ assert(CpuSetMasks != NULL);
+ assert(GroupCount > 0);
+ assert(RequiredCount != NULL);
+
+ for (group = 0; group < GroupCount; group += 1) {
+ mask = CpuSetMasks[group];
+
+ while (mask) {
+ //
+ // Find the next set bit (logical processor)
+ //
+ BitScanForward64(&bit, mask);
+
+ //
+ // Clear the bit
+ //
+ mask &= ~(1ULL << bit);
+
+ //
+ // Encode the CPU Set ID if there's room
+ //
+ if (IdList && total < IdListSize) {
+ IdList[total] = ENCODE_CPU_SET_ID(group, (UCHAR)bit);
+ }
+
+ total += 1;
+ }
+ }
+
+ *RequiredCount = total;
+
+ if (total <= IdListSize) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+//
+// System CPU Sets
+//
+
+/**
+ * Internal helper function to query the available CPU Sets on the system.
+ *
+ * @param Information
+ * A pointer to a SYSTEM_CPU_SET_INFORMATION structure that receives
+ * the CPU Set data.
+ *
+ * @return An NTSTATUS error code.
+ */
+static
+NTSTATUS
+PhpQueryCpuSetInformation(
+ _Out_ PVOID *Information
+ )
+{
+ NTSTATUS status;
+ static ULONG initialBufferSize = 0;
+ HANDLE processHandle = NULL;
+ ULONG returnLength;
+ PSYSTEM_CPU_SET_INFORMATION cpuSetInfo;
+
+ *Information = NULL;
+
+ //
+ // MSDN: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_cpu_set_information
+ // This is a variable-sized structure designed for future expansion. When iterating over
+ // this structure, use the size field to determine the offset to the next structure.
+ //
+ // N.B. The kernel returns the size even in the last element, we over-allocate by the
+ // Size offset and check it to minimize instructions (jxy-s).
+ //
+
+ if (initialBufferSize) {
+ returnLength = initialBufferSize;
+ cpuSetInfo = PhAllocateZero(returnLength);
+
+ if (!cpuSetInfo) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+ else {
+ returnLength = 0;
+ cpuSetInfo = NULL;
+ }
+
+ status = NtQuerySystemInformationEx(SystemCpuSetInformation,
+ &processHandle,
+ sizeof(HANDLE),
+ cpuSetInfo,
+ returnLength,
+ &returnLength);
+
+ if (status == STATUS_BUFFER_TOO_SMALL) {
+ returnLength += RTL_SIZEOF_THROUGH_FIELD(SYSTEM_CPU_SET_INFORMATION,
+ Size);
+
+ PhFree(cpuSetInfo);
+ cpuSetInfo = PhAllocateZero(returnLength);
+
+ if (!cpuSetInfo) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ status = NtQuerySystemInformationEx(SystemCpuSetInformation,
+ &processHandle,
+ sizeof(HANDLE),
+ cpuSetInfo,
+ returnLength,
+ NULL);
+
+ if (NT_SUCCESS(status) && initialBufferSize <= 0x100000) {
+ initialBufferSize = returnLength;
+ }
+ }
+
+ if (!NT_SUCCESS(status)) {
+ if (cpuSetInfo) {
+ PhFree(cpuSetInfo);
+ cpuSetInfo = NULL;
+ }
+ }
+
+ *Information = cpuSetInfo;
+
+ return status;
+}
+
+/**
+ * Queries the available CPU Sets on the system.
+ *
+ * @param Information
+ * A pointer to a SYSTEM_CPU_SET_INFORMATION structure that receives
+ * the CPU Set data.
+ *
+ * @return An NTSTATUS error code.
+ */
+NTSTATUS
+PhQuerySystemCpuSetInformation(
+ _Out_ PVOID *Information
+ )
+{
+ return PhpQueryCpuSetInformation(Information);
+}
+
+/**
+ * Enumerates all available CPU Sets on the system and calls the callback
+ * function for each CPU Set found.
+ *
+ * @param Callback
+ * A pointer to a callback function that is called for each CPU Set.
+ * If the callback returns FALSE, enumeration stops.
+ *
+ * @param Context
+ * An optional context pointer that is passed to the callback.
+ *
+ * @return An NTSTATUS error code.
+ */
+NTSTATUS
+PhEnumerateSystemCpuSets(
+ _In_ PPH_CPU_SETS_ENUMERATION_CALLBACK Callback,
+ _In_opt_ PVOID Context
+ )
+{
+ NTSTATUS status;
+ PVOID cpuSetInfo = NULL;
+
+ if (!Callback) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ status = PhpQueryCpuSetInformation(&cpuSetInfo);
+
+ if (NT_SUCCESS(status)) {
+ FOREACH_CPU_SET_INFO(cpuSetInfo, Context, Callback);
+ PhFree(cpuSetInfo);
+ }
+
+ return status;
+}
+
+//
+// Process Default CPU Sets
+//
+
+/**
+ * Queries the Process Default CPU Sets assigned to the given process.
+ *
+ * @param ProcessHandle
+ * A handle to the target process. The handle must have
+ * QUERY_LIMITED_INFORMATION permissions.
+ *
+ * @param CpuSetIds
+ * A pointer to an array of CPU Set IDs.
+ *
+ * @param CpuSetIdCount
+ * The number of CPU Set IDs in the array.
+ *
+ * @return An NTSTATUS error code.
+ */
+NTSTATUS
+PhQueryCpuSetsProcess(
+ _In_ HANDLE ProcessHandle,
+ _Out_ PULONG *CpuSetIds,
+ _Out_ PULONG CpuSetIdCount
+ )
+{
+ NTSTATUS status;
+ PKAFFINITY masks;
+ ULONG bufferSize;
+ static USHORT groupCount;
+ ULONG requiredCount;
+
+ groupCount = USER_SHARED_DATA->ActiveGroupCount;
+
+ *CpuSetIds = NULL;
+ *CpuSetIdCount = 0;
+
+ bufferSize = groupCount * sizeof(KAFFINITY);
+ masks = PhAllocateZero(bufferSize);
+
+ if (!masks) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ status = NtQueryInformationProcess(ProcessHandle,
+ ProcessDefaultCpuSetsInformation,
+ masks,
+ bufferSize,
+ NULL);
+
+ if (!NT_SUCCESS(status)) {
+ PhFree(masks);
+ return status;
+ }
+
+ PhpCpuSetMasksToIdList(masks,
+ groupCount,
+ NULL,
+ 0,
+ &requiredCount);
+
+ if (requiredCount == 0) {
+ *CpuSetIds = NULL;
+ *CpuSetIdCount = 0;
+ PhFree(masks);
+ return STATUS_SUCCESS;
+ }
+
+ *CpuSetIds = PhAllocate(requiredCount * sizeof(**CpuSetIds));
+
+ if (!*CpuSetIds) {
+ PhFree(masks);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ status = PhpCpuSetMasksToIdList(masks,
+ groupCount,
+ *CpuSetIds,
+ requiredCount,
+ CpuSetIdCount)
+ ? STATUS_SUCCESS
+ : STATUS_BUFFER_TOO_SMALL;
+
+ if (!NT_SUCCESS(status)) {
+ PhFree(*CpuSetIds);
+ *CpuSetIds = NULL;
+ }
+
+ PhFree(masks);
+
+ return status;
+}
+
+/**
+ * Sets the Process Default CPU Sets for a process.
+ *
+ * @param ProcessHandle
+ * A handle to the process. The handle must have
+ * PROCESS_SET_LIMITED_INFORMATION access.
+ *
+ * @param CpuSetIds
+ * An optional array of CPU Set IDs to assign to the process. If NULL or
+ * CpuSetIdCount is 0, the process will be assigned to all available
+ * CPU Sets.
+ *
+ * @param CpuSetIdCount
+ * The number of CPU Set IDs in the array.
+ *
+ * @return An NTSTATUS error code.
+ */
+NTSTATUS
+PhSetCpuSetsProcess(
+ _In_ HANDLE ProcessHandle,
+ _In_reads_opt_(CpuSetIdCount) const ULONG *CpuSetIds,
+ _In_ ULONG CpuSetIdCount
+ )
+{
+ NTSTATUS status;
+ static USHORT groupCount;
+ PKAFFINITY masks;
+
+ groupCount = USER_SHARED_DATA->ActiveGroupCount;
+
+ masks = PhAllocateZero(groupCount * sizeof(KAFFINITY));
+ if (!masks) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (!PhpCpuSetIdsToMasks(CpuSetIds, CpuSetIdCount, masks, groupCount)) {
+ PhFree(masks);
+ return STATUS_CPU_SET_INVALID;
+ }
+
+ status = NtSetInformationProcess(ProcessHandle,
+ ProcessDefaultCpuSetsInformation,
+ masks,
+ groupCount * sizeof(KAFFINITY));
+
+ PhFree(masks);
+
+ return status;
+}
+
+//
+// Thread Selected CPU Sets
+// TODO: PhQueryCpuSetsThread()
+// TODO: PhSetCpuSetsThread()
+//
+
+//
+// CPU Sets Dialog
+//
+
+/**
+ * Shows the CPU Sets dialog for a process.
+ *
+ * @param ParentWindowHandle
+ * A handle to the parent window.
+ *
+ * @param ProcessItem
+ * A pointer to a process item. The dialog will display and allow
+ * modification of the CPU Sets for this process.
+ */
+VOID
+PhShowCpuSetsDialogProcess(
+ _In_ HWND ParentWindowHandle,
+ _In_ PPH_PROCESS_ITEM ProcessItem
+ )
+{
+ PPHP_CPU_SETS_DLG_CTX context;
+
+ context = PhAllocateZero(sizeof(PHP_CPU_SETS_DLG_CTX));
+ context->ProcessItem = ProcessItem;
+ context->CpuSetType = PhCpuSetTypeProcessDefault;
+
+ //
+ // We'll query the CPU Sets in WM_INITDIALOG
+ //
+ context->CpuSetIds = NULL;
+ context->CpuSetIdCount = 0;
+
+ PhDialogBox(PhInstanceHandle,
+ MAKEINTRESOURCE(IDD_CPUSETS),
+ ParentWindowHandle,
+ PhpProcessCpuSetsDlgProc,
+ context);
+
+ //
+ // Context is freed in WM_DESTROY
+ //
+}
+
+/**
+ * Callback function invoked for each System CPU Set from
+ * PhpPopulateListViewWithCpuSets().
+ * Adds detailed information about CPU Sets to a list view.
+ *
+ * @param Information
+ * A pointer to a structure representing a CPU Set.
+ *
+ * @param Context
+ * An optional pointer to a structure that provides the dialog context,
+ * including selected CPU Sets.
+ *
+ * @return TRUE if the operation was successful, FALSE otherwise.
+ */
+_Success_(return != FALSE)
+static
+BOOLEAN
+PhpPopulateCpuSetsListViewCallback(
+ _In_ PSYSTEM_CPU_SET_INFORMATION Information,
+ _In_opt_ PVOID Context
+ )
+{
+ PPHP_CPU_SETS_DLG_CTX context = (PPHP_CPU_SETS_DLG_CTX)Context;
+ INT lvItemIndex;
+ WCHAR number[PH_INT32_STR_LEN_1];
+ BOOLEAN isChecked = FALSE;
+ ULONG i;
+ ULONG low;
+ ULONG high;
+ ULONG mid;
+
+ assert(Information != NULL);
+
+ if (!context || !context->ListViewHandle) {
+ return FALSE;
+ }
+
+ if (context->CpuSetIds && context->CpuSetIdCount > 0) {
+#define BINARY_SEARCH_THRESHOLD 32
+ if (context->CpuSetIdCount > BINARY_SEARCH_THRESHOLD) {
+ low = 0;
+ high = context->CpuSetIdCount - 1;
+
+ while (low <= high) {
+ mid = low + (high - low) / 2;
+
+ if (context->CpuSetIds[mid] == Information->CpuSet.Id) {
+ isChecked = TRUE;
+ break;
+ }
+
+ if (context->CpuSetIds[mid] < Information->CpuSet.Id) {
+ low = mid + 1;
+ }
+ else {
+ high = mid - 1;
+ }
+ }
+ }
+ else {
+ for (i = 0; i < context->CpuSetIdCount; i += 1) {
+ if (context->CpuSetIds[i] == Information->CpuSet.Id) {
+ isChecked = TRUE;
+ break;
+ }
+ }
+ }
+ }
+
+ PhPrintUInt32(number, Information->CpuSet.Id);
+
+ //
+ // Index = MAXINT because:
+ // "If this value [Index] is greater than the number of items currently
+ // contained by the listview control, the new item will be appended to
+ // the end of the list and assigned the correct index"
+ //
+ lvItemIndex = PhAddListViewItem(context->ListViewHandle,
+ MAXINT,
+ number,
+ UlongToPtr(context->Index++));
+
+ if (isChecked) {
+ assert(lvItemIndex >= 0);
+ ListView_SetCheckState(context->ListViewHandle, lvItemIndex, TRUE)
+ }
+
+ //
+ // Node
+ //
+ PhPrintUInt32(number, Information->CpuSet.NumaNodeIndex);
+ PhSetListViewSubItem(context->ListViewHandle, lvItemIndex, 1, number);
+
+ //
+ // Group
+ //
+ PhPrintUInt32(number, Information->CpuSet.Group);
+ PhSetListViewSubItem(context->ListViewHandle, lvItemIndex, 2, number);
+
+ //
+ // Core
+ //
+ PhPrintUInt32(number, Information->CpuSet.CoreIndex);
+ PhSetListViewSubItem(context->ListViewHandle, lvItemIndex, 3, number);
+
+ //
+ // Processor
+ //
+ PhPrintUInt32(number, Information->CpuSet.LogicalProcessorIndex);
+ PhSetListViewSubItem(context->ListViewHandle, lvItemIndex, 4, number);
+
+ //
+ // Allocated
+ //
+ PhSetListViewSubItem(context->ListViewHandle,
+ lvItemIndex,
+ 5,
+ Information->CpuSet.Allocated ? L"Yes" : L"No");
+
+ return TRUE;
+}
+
+/**
+ * Adds CPU Set items to a list view control.
+ *
+ * @param ListViewHandle
+ * The handle to the list view control.
+ *
+ * @param DialogContext
+ * A pointer to a dialog context structure.
+ *
+ * @return STATUS_SUCCESS if no errors, or the specific error status code.
+ */
+static
+NTSTATUS
+PhpPopulateListViewWithCpuSets(
+ _In_ HWND ListViewHandle,
+ _In_ PPHP_CPU_SETS_DLG_CTX DialogContext
+ )
+{
+ if (!DialogContext || !DialogContext->ProcessItem) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ NTSTATUS status;
+ HANDLE processHandle;
+ PULONG cpuSetIds = NULL;
+ ULONG cpuSetIdCount = 0;
+ PVOID cpuSetsInfo = NULL;
+
+ status = PhOpenProcess(&processHandle,
+ PROCESS_QUERY_LIMITED_INFORMATION,
+ DialogContext->ProcessItem->ProcessId);
+
+ if (!NT_SUCCESS(status)) {
+ PhShowStatus(ListViewHandle,
+ L"Unable to query CPU Sets information",
+ status,
+ 0);
+ return status;
+ }
+
+ //
+ // Query Process Default CPU Sets
+ //
+ status = PhQueryCpuSetsProcess(processHandle,
+ &cpuSetIds,
+ &cpuSetIdCount);
+
+ NtClose(processHandle);
+
+ //
+ // Disable redraw while updating
+ //
+ ExtendedListView_SetRedraw(ListViewHandle, FALSE);
+
+ if (NT_SUCCESS(status) && cpuSetIdCount > 0) {
+ //
+ // On success with count > 0, we keep cpuSetIds and check those IDs
+ //
+ if (DialogContext->CpuSetIds) {
+ PhFree(DialogContext->CpuSetIds);
+ }
+ DialogContext->CpuSetIds = cpuSetIds;
+ DialogContext->CpuSetIdCount = cpuSetIdCount;
+
+ //
+ // Enumerate all system CPU Sets and check selected
+ //
+ DialogContext->Index = 0;
+ status = PhEnumerateSystemCpuSets(PhpPopulateCpuSetsListViewCallback,
+ DialogContext);
+ }
+ else {
+ //
+ // Otherwise, free the allocated buffer
+ //
+ if (cpuSetIds) {
+ PhFree(cpuSetIds);
+ }
+ DialogContext->CpuSetIds = NULL;
+ DialogContext->CpuSetIdCount = 0;
+
+ //
+ // No Process Default CPU Set, show all System CPU Sets as unchecked
+ //
+ status = PhQuerySystemCpuSetInformation(&cpuSetsInfo);
+ if (NT_SUCCESS(status) && cpuSetsInfo) {
+ DialogContext->Index = 0;
+ FOREACH_CPU_SET_INFO(cpuSetsInfo,
+ DialogContext,
+ PhpPopulateCpuSetsListViewCallback);
+ PhFree(cpuSetsInfo);
+ }
+
+ if (cpuSetIds) {
+ PhFree(cpuSetIds);
+ }
+ }
+
+ //
+ // Bring back redrawing
+ //
+ ExtendedListView_SetRedraw(ListViewHandle, TRUE);
+
+ return status;
+}
+
+/**
+ * Attempts to parse a CPU Set ID from a null-terminated wide string.
+ *
+ * @param CpuSetIdText
+ * The input string to parse.
+ *
+ * @param AsUlong
+ * Receives the parsed unsigned long on success.
+ *
+ * @returns
+ * TRUE if the entire string is a valid non-negative integer;
+ * FALSE otherwise.
+ *
+ * @remark
+ * We know that every ListView item's text is exactly PhPrintUInt32(...):
+ * pure digits, no sign, no extra spaces. We use a specialized parser that
+ * simply walks the WCHAR* buffer once and stops on the first non-digit.
+ */
+static
+BOOL
+PhpTryParseCpuId(
+ _In_ LPCWSTR CpuSetIdText,
+ _Out_ PULONG AsUlong
+)
+{
+ if (!CpuSetIdText ||
+ *CpuSetIdText < L'0' ||
+ *CpuSetIdText > L'9') {
+ return FALSE;
+ }
+
+ ULONG v = 0;
+ WCHAR c;
+ for (; *CpuSetIdText; CpuSetIdText += 1) {
+ c = *CpuSetIdText;
+ if (c < L'0' || c > L'9') {
+ return FALSE;
+ }
+ v = v * 10 + (c - L'0');
+ }
+ *AsUlong = v;
+
+ return TRUE;
+}
+
+/**
+ * Retrieves the selected CPU Set IDs from a ListView.
+ *
+ * @param ListViewHandle
+ * The handle to the ListView control.
+ *
+ * @param SelectedIds
+ * A pointer to store the dynamically allocated array of selected IDs.
+ * The caller must free this memory when it is no longer needed.
+ *
+ * @param SelectedCount
+ * Pointer to store the count of selected IDs.
+ */
+static
+NTSTATUS
+PhpGetSelectedCpuSetIds(
+ _In_ HWND ListViewHandle,
+ _Out_ PULONG *SelectedIds,
+ _Out_ PULONG SelectedCount
+)
+{
+ INT i;
+ INT itemCount = ListView_GetItemCount(ListViewHandle);
+ ULONG count;
+ WCHAR buffer[PH_INT32_STR_LEN_1];
+ LVITEMW lv = {0};
+ PULONG shrunk;
+
+ if (itemCount <= 0) {
+ *SelectedIds = NULL;
+ *SelectedCount = 0;
+ return STATUS_SUCCESS;
+ }
+
+ ULONG *ids = PhAllocate(sizeof(*ids) * itemCount);
+ if (!ids) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ lv.mask = LVIF_TEXT;
+ lv.iSubItem = 0;
+ lv.pszText = buffer;
+ lv.cchTextMax = ARRAYSIZE(buffer);
+
+ count = 0;
+ for (i = 0; i < itemCount; i += 1) {
+ if (!ListView_GetCheckState(ListViewHandle, i)) {
+ continue;
+ }
+
+ //
+ // Retrieve the item text
+ //
+ lv.iItem = i;
+ if (SendMessageW(ListViewHandle,
+ LVM_GETITEMTEXTW,
+ (WPARAM)i,
+ (LPARAM)&lv) <= 0) {
+ continue;
+ }
+
+ if (PhpTryParseCpuId(buffer, &ids[count])) {
+ count += 1;
+ }
+ }
+
+ if (count == 0) {
+ PhFree(ids);
+ *SelectedIds = NULL;
+ *SelectedCount = 0;
+ return STATUS_SUCCESS;
+ }
+
+ if (count < (ULONG)itemCount) {
+ shrunk = PhReAllocate(ids, sizeof(*ids) * count);
+ if (shrunk) {
+ ids = shrunk;
+ }
+ }
+
+ *SelectedIds = ids;
+ *SelectedCount = count;
+
+ return STATUS_SUCCESS;
+}
+
+INT_PTR
+CALLBACK
+PhpProcessCpuSetsDlgProc(
+ _In_ HWND DlgHandle,
+ _In_ UINT Message,
+ _In_ WPARAM WParam,
+ _In_ LPARAM LParam
+)
+{
+ PPHP_CPU_SETS_DLG_CTX context;
+
+ if (Message == WM_INITDIALOG) {
+ context = (PPHP_CPU_SETS_DLG_CTX)LParam;
+
+ if (!context) {
+ return FALSE;
+ }
+
+ context->WindowHandle = DlgHandle;
+
+ PhSetWindowContext(DlgHandle,
+ PH_WINDOW_CONTEXT_DEFAULT,
+ context);
+ }
+ else {
+ context = PhGetWindowContext(DlgHandle, PH_WINDOW_CONTEXT_DEFAULT);
+
+ if (!context && Message != WM_DESTROY) {
+ return FALSE;
+ }
+ }
+
+ switch (Message) {
+ case WM_INITDIALOG: {
+ NTSTATUS status;
+ PWSTR titleText;
+
+ context->WindowHandle = DlgHandle;
+ context->ListViewHandle = GetDlgItem(DlgHandle, IDC_LIST);
+ context->Index = 0;
+
+ PhSetApplicationWindowIcon(DlgHandle);
+
+ //
+ // Set dialog title with process name if available
+ //
+ // @formatter:off
+ if (context->ProcessItem) {
+ titleText = PhaFormatString(L"CPU Sets - %s (%lu)",
+ context->ProcessItem->ProcessName->Buffer,
+ HandleToUlong(context->ProcessItem->ProcessId))->Buffer;
+
+ SetWindowText(DlgHandle, titleText);
+ }
+
+ PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 60, L"ID");
+ PhAddListViewColumn(context->ListViewHandle, 1, 1, 1, LVCFMT_LEFT, 60, L"Node");
+ PhAddListViewColumn(context->ListViewHandle, 2, 2, 2, LVCFMT_LEFT, 60, L"Group");
+ PhAddListViewColumn(context->ListViewHandle, 3, 3, 3, LVCFMT_LEFT, 60, L"Core");
+ PhAddListViewColumn(context->ListViewHandle, 4, 4, 4, LVCFMT_LEFT, 60, L"Processor");
+ PhAddListViewColumn(context->ListViewHandle, 5, 5, 5, LVCFMT_LEFT, 60, L"Allocated");
+ // @formatter:on
+
+ PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE);
+
+ PhSetControlTheme(context->ListViewHandle, L"explorer");
+
+ PhSetExtendedListView(context->ListViewHandle);
+
+ ListView_SetExtendedListViewStyleEx(context->ListViewHandle,
+ LVS_EX_CHECKBOXES,
+ LVS_EX_CHECKBOXES);
+
+ ExtendedListView_SetRedraw(context->ListViewHandle, FALSE);
+
+ ListView_DeleteAllItems(context->ListViewHandle);
+
+ status = PhpPopulateListViewWithCpuSets(context->ListViewHandle,
+ context);
+
+ ExtendedListView_SetRedraw(context->ListViewHandle, TRUE);
+
+ if (!NT_SUCCESS(status)) {
+ //
+ // If we couldn't query the CPU Sets, close the dialog
+ //
+ EndDialog(DlgHandle, IDCANCEL);
+ break;
+ }
+
+ //
+ // Enable/disable the OK button based on whether we have a process
+ //
+ EnableWindow(GetDlgItem(DlgHandle, IDOK), context->ProcessItem != NULL);
+
+ PhSetDialogFocus(DlgHandle, GetDlgItem(DlgHandle, IDOK));
+
+ PhInitializeWindowTheme(DlgHandle, PhEnableThemeSupport);
+ }
+ break;
+ case WM_DESTROY: {
+ PPHP_CPU_SETS_DLG_CTX destroyContext =
+ PhGetWindowContext(DlgHandle, PH_WINDOW_CONTEXT_DEFAULT);
+
+ if (destroyContext) {
+ if (destroyContext->CpuSetIds) {
+ PhFree(destroyContext->CpuSetIds);
+ destroyContext->CpuSetIds = NULL;
+ destroyContext->CpuSetIdCount = 0;
+ }
+
+ PhRemoveWindowContext(DlgHandle, PH_WINDOW_CONTEXT_DEFAULT);
+ PhFree(destroyContext);
+ }
+
+ return FALSE;
+ }
+ case WM_COMMAND: {
+ switch (GET_WM_COMMAND_ID(WParam, LParam)) {
+ case IDCANCEL:
+ EndDialog(DlgHandle, IDCANCEL);
+ break;
+ case IDC_SELECTALL:
+ case IDC_DESELECTALL: {
+ BOOL state = (GET_WM_COMMAND_ID(WParam, LParam) == IDC_SELECTALL);
+ HWND listView = context->ListViewHandle;
+ for (INT i = 0; i < ListView_GetItemCount(listView); i += 1) {
+ ListView_SetCheckState(listView, i, state)
+ }
+ break;
+ }
+ case IDOK: {
+ NTSTATUS status;
+ HANDLE processHandle = NULL;
+ PULONG selectedIds = NULL;
+ ULONG selectedCount = 0;
+ BOOLEAN succeeded = FALSE;
+
+ if (!context || !context->ListViewHandle) {
+ break;
+ }
+
+ //
+ // Get selected CPU Sets from the list view
+ //
+ status = PhpGetSelectedCpuSetIds(context->ListViewHandle,
+ &selectedIds,
+ &selectedCount);
+
+ if (!NT_SUCCESS(status)) {
+ PhShowStatus(DlgHandle,
+ L"Failed to retrieve selected CPU Set IDs.",
+ status,
+ 0);
+ break;
+ }
+
+ if (!selectedIds && selectedCount > 0) {
+ selectedCount = 0;
+ }
+
+ //
+ // Apply the selected CPU Sets to the process if we have a process
+ //
+ if (context->ProcessItem && context->ProcessItem->ProcessId) {
+ status = PhOpenProcess(&processHandle,
+ PROCESS_SET_LIMITED_INFORMATION |
+ PROCESS_QUERY_LIMITED_INFORMATION,
+ context->ProcessItem->ProcessId);
+ if (NT_SUCCESS(status)) {
+ //
+ // Set the process CPU Sets
+ //
+ status = PhSetCpuSetsProcess(processHandle,
+ selectedIds,
+ selectedCount);
+
+ if (NT_SUCCESS(status)) {
+ //
+ // Verify the changes were actually applied
+ //
+ PULONG verifyIds = NULL;
+ ULONG verifyCount = 0;
+
+ //
+ // Query the process again to verify our changes
+ //
+ status = PhQueryCpuSetsProcess(processHandle,
+ &verifyIds,
+ &verifyCount);
+
+ if (NT_SUCCESS(status)) {
+ //
+ // If we selected CPU Sets but none were found on
+ // verification, something might have gone wrong
+ //
+ if (selectedCount > 0 && verifyCount == 0) {
+ succeeded = FALSE;
+ status = STATUS_UNSUCCESSFUL;
+ }
+ else {
+ succeeded = TRUE;
+ }
+
+ if (verifyIds) {
+ PhFree(verifyIds);
+ verifyIds = NULL;
+ }
+ }
+ else {
+ succeeded = FALSE;
+ }
+ }
+ else {
+ succeeded = FALSE;
+ }
+
+ NtClose(processHandle);
+ }
+
+ if (!succeeded) {
+ PhShowStatus(DlgHandle,
+ L"Unable to set CPU Sets for the process.",
+ status,
+ 0);
+
+ if (selectedIds) {
+ PhFree(selectedIds);
+ selectedIds = NULL;
+ }
+
+ break;
+ }
+ }
+ else {
+ //
+ // If we don't have a process to set CPU Set for,
+ // just consider it successful
+ //
+ succeeded = TRUE;
+ }
+
+ if (succeeded) {
+ //
+ // Free the previously selected IDs if any
+ //
+ if (context->CpuSetIds) {
+ PhFree(context->CpuSetIds);
+ }
+
+ //
+ // Update context with the newly selected IDs
+ //
+ context->CpuSetIds = selectedIds;
+ context->CpuSetIdCount = selectedCount;
+
+ EndDialog(DlgHandle, IDOK);
+ }
+ else {
+ //
+ // Clean up on failure
+ //
+ if (selectedIds) {
+ PhFree(selectedIds);
+ selectedIds = NULL;
+ }
+ }
+ }
+ break;
+ }
+ break;
+ case WM_CTLCOLORBTN:
+ return HANDLE_WM_CTLCOLORBTN(DlgHandle,
+ WParam,
+ LParam,
+ PhWindowThemeControlColor);
+ case WM_CTLCOLORDLG:
+ return HANDLE_WM_CTLCOLORDLG(DlgHandle,
+ WParam,
+ LParam,
+ PhWindowThemeControlColor);
+ case WM_CTLCOLORSTATIC:
+ return HANDLE_WM_CTLCOLORSTATIC(DlgHandle,
+ WParam,
+ LParam,
+ PhWindowThemeControlColor);
+ }
+ }
+
+ return FALSE;
+}
diff --git a/SystemInformer/include/phapp.h b/SystemInformer/include/phapp.h
index 173210906140..8087a56b1d34 100644
--- a/SystemInformer/include/phapp.h
+++ b/SystemInformer/include/phapp.h
@@ -324,6 +324,13 @@ VOID PhShowProcessAffinityDialog(
_In_opt_ PPH_THREAD_ITEM ThreadItem
);
+// CPU Sets
+
+VOID PhShowCpuSetsDialogProcess(
+ _In_ HWND ParentWindowHandle,
+ _In_ PPH_PROCESS_ITEM ProcessItem
+ );
+
// begin_phapppub
_Success_(return)
PHAPPAPI
diff --git a/SystemInformer/include/sysinfop.h b/SystemInformer/include/sysinfop.h
index 1f8c7073283e..8ad92825626e 100644
--- a/SystemInformer/include/sysinfop.h
+++ b/SystemInformer/include/sysinfop.h
@@ -346,10 +346,6 @@ NTSTATUS PhSipQueryProcessorPerformanceDistributionEx(
_Out_ PVOID* Buffer
);
-NTSTATUS PhSipQueryCpuSetInformation(
- _Out_ PVOID* Buffer
- );
-
PCPH_STRINGREF PhGetHybridProcessorType(
_In_ ULONG ProcessorIndex
);
diff --git a/SystemInformer/mainwnd.c b/SystemInformer/mainwnd.c
index 348e534bd025..2db7a009ea58 100644
--- a/SystemInformer/mainwnd.c
+++ b/SystemInformer/mainwnd.c
@@ -1664,6 +1664,18 @@ VOID PhMwpOnCommand(
}
}
break;
+ case ID_PROCESS_CPU_SETS:
+ {
+ PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem();
+
+ if (processItem)
+ {
+ PhReferenceObject(processItem);
+ PhShowCpuSetsDialogProcess(WindowHandle, processItem);
+ PhDereferenceObject(processItem);
+ }
+ }
+ break;
case ID_MISCELLANEOUS_ACTIVITY:
{
PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem();
diff --git a/SystemInformer/mwpgproc.c b/SystemInformer/mwpgproc.c
index 016cb1ca2f7c..660a737ddd58 100644
--- a/SystemInformer/mwpgproc.c
+++ b/SystemInformer/mwpgproc.c
@@ -909,6 +909,9 @@ PPH_EMENU PhpCreateProcessMenu(
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, ID_PROCESS_DEBUG, L"De&bug", NULL, NULL), ULONG_MAX);
PhInsertEMenuItem(menu, PhCreateEMenuSeparator(), ULONG_MAX);
PhInsertEMenuItem(menu, PhCreateEMenuItem(0, ID_PROCESS_AFFINITY, L"&Affinity", NULL, NULL), ULONG_MAX);
+ if (WindowsVersion >= WINDOWS_10) {
+ PhInsertEMenuItem(menu, PhCreateEMenuItem(0, ID_PROCESS_CPU_SETS, L"&CPU Sets", NULL, NULL), ULONG_MAX);
+ }
menuItem = PhCreateEMenuItem(0, ID_PROCESS_PRIORITY, L"&Priority", NULL, NULL);
PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, ID_PRIORITY_REALTIME, L"&Real time", NULL, NULL), ULONG_MAX);
diff --git a/SystemInformer/resource.h b/SystemInformer/resource.h
index 03a2111b6d39..832356b600b2 100644
--- a/SystemInformer/resource.h
+++ b/SystemInformer/resource.h
@@ -130,6 +130,7 @@
#define IDD_CHOOSENEW 275
#define IDD_MEMSTRINGSMINLEN 276
#define IDD_MEMSTRINGS 277
+#define IDD_CPUSETS 278
#define IDC_TERMINATE 1003
#define IDC_FILEICON 1005
#define IDC_FILE 1006
@@ -571,33 +572,36 @@
#define IDC_FONTMONOSPACE 1416
#define IDC_ONLYKERNELTHREADSTACKS 1417
#define IDC_HYPERVISORNONESSENTIAL 1418
+#define IDC_LISTBOX1 1418
#define IDC_INPUT 1419
-#define IDC_MINIDUMP_NORMAL 1420
-#define IDC_MINIDUMP_WITH_DATA_SEGS 1421
-#define IDC_MINIDUMP_WITH_FULL_MEM 1422
-#define IDC_MINIDUMP_WITH_HANDLE_DATA 1423
-#define IDC_MINIDUMP_FILTER_MEMORY 1424
-#define IDC_MINIDUMP_SCAN_MEMORY 1425
-#define IDC_MINIDUMP_WITH_UNLOADED_MODULES 1426
-#define IDC_MINIDUMP_WITH_INDIRECT_REFERENCED_MEM 1427
-#define IDC_MINIDUMP_FILTER_MODULE_PATHS 1428
-#define IDC_MINIDUMP_WITH_PROC_THRD_DATA 1429
-#define IDC_MINIDUMP_WITH_PRIVATE_RW_MEM 1430
-#define IDC_MINIDUMP_WITHOUT_OPTIONAL_DATA 1431
-#define IDC_MINIDUMP_WITH_FULL_MEM_INFO 1432
-#define IDC_MINIDUMP_WITH_THRD_INFO 1433
-#define IDC_MINIDUMP_WITH_CODE_SEGS 1434
-#define IDC_MINIDUMP_WITHOUT_AUXILIARY_STATE 1435
-#define IDC_MINIDUMP_WITH_FULL_AUXILIARY_STATE 1436
-#define IDC_MINIDUMP_WITH_PRIVATE_WRITE_COPY_MEM 1437
-#define IDC_MINIDUMP_IGNORE_INACCESSIBLE_MEM 1438
-#define IDC_MINIDUMP_WITH_TOKEN_INFO 1439
-#define IDC_MINIDUMP_WITH_MODULE_HEADERS 1440
-#define IDC_MINIDUMP_FILTER_TRIAGE 1441
-#define IDC_MINIDUMP_WITH_AVXX_STATE_CONTEXT 1442
-#define IDC_MINIDUMP_WITH_IPT_TRACE 1443
+#define IDC_MINIDUMP_NORMAL 1420
+#define IDC_MINIDUMP_WITH_DATA_SEGS 1421
+#define IDC_CHECK1 1421
+#define IDC_MINIDUMP_WITH_FULL_MEM 1422
+#define IDC_MINIDUMP_WITH_HANDLE_DATA 1423
+#define IDC_MINIDUMP_FILTER_MEMORY 1424
+#define IDC_BUTTON3 1424
+#define IDC_MINIDUMP_SCAN_MEMORY 1425
+#define IDC_MINIDUMP_WITH_UNLOADED_MODULES 1426
+#define IDC_MINIDUMP_WITH_INDIRECT_REFERENCED_MEM 1427
+#define IDC_MINIDUMP_FILTER_MODULE_PATHS 1428
+#define IDC_MINIDUMP_WITH_PROC_THRD_DATA 1429
+#define IDC_MINIDUMP_WITH_PRIVATE_RW_MEM 1430
+#define IDC_MINIDUMP_WITHOUT_OPTIONAL_DATA 1431
+#define IDC_MINIDUMP_WITH_FULL_MEM_INFO 1432
+#define IDC_MINIDUMP_WITH_THRD_INFO 1433
+#define IDC_MINIDUMP_WITH_CODE_SEGS 1434
+#define IDC_MINIDUMP_WITHOUT_AUXILIARY_STATE 1435
+#define IDC_MINIDUMP_WITH_FULL_AUXILIARY_STATE 1436
+#define IDC_MINIDUMP_WITH_PRIVATE_WRITE_COPY_MEM 1437
+#define IDC_MINIDUMP_IGNORE_INACCESSIBLE_MEM 1438
+#define IDC_MINIDUMP_WITH_TOKEN_INFO 1439
+#define IDC_MINIDUMP_WITH_MODULE_HEADERS 1440
+#define IDC_MINIDUMP_FILTER_TRIAGE 1441
+#define IDC_MINIDUMP_WITH_AVXX_STATE_CONTEXT 1442
+#define IDC_MINIDUMP_WITH_IPT_TRACE 1443
#define IDC_MINIDUMP_SCAN_INACCESSIBLE_PARTIAL_PAGES 1444
-#define IDC_MINIDUMP_FILTER_WRITE_COMBINE_MEM 1445
+#define IDC_MINIDUMP_FILTER_WRITE_COMBINE_MEM 1445
#define IDC_ZMEMSLOTS_V 1446
#define IDC_ZMEMFORMFACTOR_V 1447
#define IDC_ZMEMTYPE_V 1448
@@ -858,16 +862,17 @@
#define ID_NOTIFICATIONS_ENABLESINGLECLICKICONS 40312
#define ID_MISCELLANEOUS_EXECUTIONREQUIRED 40313
#define IDD_OBJAFDSOCKET 40314
+#define ID_PROCESS_CPU_SETS 40315
#define IDDYNAMIC 50000
#define IDPLUGINS 55000
// Next default values for new objects
-//
+//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 273
#define _APS_NEXT_COMMAND_VALUE 40298
-#define _APS_NEXT_CONTROL_VALUE 1418
+#define _APS_NEXT_CONTROL_VALUE 1425
#define _APS_NEXT_SYMED_VALUE 170
#endif
#endif
diff --git a/SystemInformer/syssccpu.c b/SystemInformer/syssccpu.c
index 8b3bdfc16f6f..f02017f6c8cc 100644
--- a/SystemInformer/syssccpu.c
+++ b/SystemInformer/syssccpu.c
@@ -2032,12 +2032,14 @@ PCPH_STRINGREF PhGetHybridProcessorType(
*
* This function determines whether a specific logical processor in the system
* is parked, meaning it is temporarily unavailable for the thread scheduler.
- * It utilizes CPU sets functionality available starting with Windows 10.
+ * It uses CPU Sets functionality available starting with Windows 10.
*
* \param[in] ProcessorIndex The index of the logical processor to check.
*
* \return TRUE if the specified logical processor is parked, FALSE otherwise or
* in case of failure.
+ *
+ * \todo Use PhEnumerateSystemCpuSets()
*/
BOOLEAN PhIsCoreParked(
_In_ ULONG ProcessorIndex
diff --git a/phlib/include/cpuset.h b/phlib/include/cpuset.h
new file mode 100644
index 000000000000..0b5c600c00d8
--- /dev/null
+++ b/phlib/include/cpuset.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2025 Winsider Seminars & Solutions, Inc. All rights reserved.
+ *
+ * This file is part of System Informer ("SI").
+ *
+ * Description:
+ *
+ * This module contains the public routine prototypes for CPU Sets in SI.
+ *
+ * Authors:
+ *
+ * wnstngs 2025
+ *
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef BOOLEAN
+(*PPH_CPU_SETS_ENUMERATION_CALLBACK)(
+ _In_ PSYSTEM_CPU_SET_INFORMATION Information,
+ _In_opt_ PVOID Context
+ );
+
+NTSTATUS
+PhQuerySystemCpuSetInformation(
+ _Out_ PVOID *Information
+ );
+
+NTSTATUS
+PhEnumerateSystemCpuSets(
+ _In_ PPH_CPU_SETS_ENUMERATION_CALLBACK Callback,
+ _In_opt_ PVOID Context
+ );
+
+NTSTATUS
+PhQueryCpuSetsProcess(
+ _In_ HANDLE ProcessHandle,
+ _Out_ PULONG *CpuSetIds,
+ _Out_ PULONG CpuSetIdCount
+ );
+
+NTSTATUS
+PhSetCpuSetsProcess(
+ _In_ HANDLE ProcessHandle,
+ _In_reads_opt_(CpuSetIdCount) const ULONG *CpuSetIds,
+ _In_ ULONG CpuSetIdCount
+ );
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/phlib/phlib.vcxproj b/phlib/phlib.vcxproj
index 8e888850a9cb..9145b52a6510 100644
--- a/phlib/phlib.vcxproj
+++ b/phlib/phlib.vcxproj
@@ -221,6 +221,7 @@
+
From 72e26a8b0279fce78ad91051348af460c11060e1 Mon Sep 17 00:00:00 2001
From: wnstngs <64602598+wnstngs@users.noreply.github.com>
Date: Thu, 5 Jun 2025 18:29:54 +0300
Subject: [PATCH 2/4] Address code review feedback
- Remove AFX_DIALOG_LAYOUT
- Update processor group retrieval to use PhSystemProcessorInformation.NumberOfProcessorGroups
- Replace slow text parsing in listview with ID passed via listview item lParam
- Add new helper PhGetSelectedListViewItemParams()
- Use NTAPI in cpuset.h for 32-bit compatibility
---
SystemInformer/SystemInformer.rc | 16 +---
SystemInformer/cpuset.c | 133 +++++++++----------------------
phlib/guisuplistview.cpp | 50 ++++++++++++
phlib/include/cpuset.h | 9 ++-
phlib/include/guisup.h | 9 +++
phlib/phlib.vcxproj.filters | 3 +
6 files changed, 106 insertions(+), 114 deletions(-)
diff --git a/SystemInformer/SystemInformer.rc b/SystemInformer/SystemInformer.rc
index 343c6dc5e378..49641584b673 100644
--- a/SystemInformer/SystemInformer.rc
+++ b/SystemInformer/SystemInformer.rc
@@ -2253,7 +2253,6 @@ BEGIN
VK_F11, IDC_MAXSCREEN, VIRTKEY, NOINVERT
END
-
/////////////////////////////////////////////////////////////////////////////
//
// Bitmap
@@ -2263,18 +2262,5 @@ IDB_SEARCH_ACTIVE_BMP BITMAP "resources\\active_search.bmp"
IDB_SEARCH_INACTIVE_BMP BITMAP "resources\\inactive_search.bmp"
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// AFX_DIALOG_LAYOUT
-//
-
-IDD_CPUSETS AFX_DIALOG_LAYOUT
-BEGIN
- 0
-END
-
#endif // English (United States) resources
-/////////////////////////////////////////////////////////////////////////////
-
-
+/////////////////////////////////////////////////////////////////////////////
\ No newline at end of file
diff --git a/SystemInformer/cpuset.c b/SystemInformer/cpuset.c
index 517d3ab79b24..2847ead529ca 100644
--- a/SystemInformer/cpuset.c
+++ b/SystemInformer/cpuset.c
@@ -109,7 +109,6 @@ typedef struct PHP_CPU_SETS_DLG_CTX {
PH_CPU_SET_TYPE CpuSetType;
PULONG CpuSetIds;
ULONG CpuSetIdCount;
- ULONG Index;
} PHP_CPU_SETS_DLG_CTX, *PPHP_CPU_SETS_DLG_CTX;
//
@@ -444,7 +443,7 @@ PhQueryCpuSetsProcess(
static USHORT groupCount;
ULONG requiredCount;
- groupCount = USER_SHARED_DATA->ActiveGroupCount;
+ groupCount = PhSystemProcessorInformation.NumberOfProcessorGroups;
*CpuSetIds = NULL;
*CpuSetIdCount = 0;
@@ -533,7 +532,7 @@ PhSetCpuSetsProcess(
static USHORT groupCount;
PKAFFINITY masks;
- groupCount = USER_SHARED_DATA->ActiveGroupCount;
+ groupCount = PhSystemProcessorInformation.NumberOfProcessorGroups;
masks = PhAllocateZero(groupCount * sizeof(KAFFINITY));
if (!masks) {
@@ -641,6 +640,10 @@ PhpPopulateCpuSetsListViewCallback(
return FALSE;
}
+ //
+ // Determine if this CPU Set should be marked as checked
+ // by searching for its ID in the provided ID array.
+ //
if (context->CpuSetIds && context->CpuSetIdCount > 0) {
#define BINARY_SEARCH_THRESHOLD 32
if (context->CpuSetIdCount > BINARY_SEARCH_THRESHOLD) {
@@ -681,11 +684,17 @@ PhpPopulateCpuSetsListViewCallback(
// contained by the listview control, the new item will be appended to
// the end of the list and assigned the correct index"
//
+ // Associate the CPU Set ID to the list item so that it can be retrieved
+ // in the selection handler
+ //
lvItemIndex = PhAddListViewItem(context->ListViewHandle,
MAXINT,
number,
- UlongToPtr(context->Index++));
+ UlongToPtr(Information->CpuSet.Id));
+ //
+ // Set the check state if this CPU Set is in the selection list
+ //
if (isChecked) {
assert(lvItemIndex >= 0);
ListView_SetCheckState(context->ListViewHandle, lvItemIndex, TRUE)
@@ -793,7 +802,6 @@ PhpPopulateListViewWithCpuSets(
//
// Enumerate all system CPU Sets and check selected
//
- DialogContext->Index = 0;
status = PhEnumerateSystemCpuSets(PhpPopulateCpuSetsListViewCallback,
DialogContext);
}
@@ -812,7 +820,6 @@ PhpPopulateListViewWithCpuSets(
//
status = PhQuerySystemCpuSetInformation(&cpuSetsInfo);
if (NT_SUCCESS(status) && cpuSetsInfo) {
- DialogContext->Index = 0;
FOREACH_CPU_SET_INFO(cpuSetsInfo,
DialogContext,
PhpPopulateCpuSetsListViewCallback);
@@ -832,51 +839,6 @@ PhpPopulateListViewWithCpuSets(
return status;
}
-/**
- * Attempts to parse a CPU Set ID from a null-terminated wide string.
- *
- * @param CpuSetIdText
- * The input string to parse.
- *
- * @param AsUlong
- * Receives the parsed unsigned long on success.
- *
- * @returns
- * TRUE if the entire string is a valid non-negative integer;
- * FALSE otherwise.
- *
- * @remark
- * We know that every ListView item's text is exactly PhPrintUInt32(...):
- * pure digits, no sign, no extra spaces. We use a specialized parser that
- * simply walks the WCHAR* buffer once and stops on the first non-digit.
- */
-static
-BOOL
-PhpTryParseCpuId(
- _In_ LPCWSTR CpuSetIdText,
- _Out_ PULONG AsUlong
-)
-{
- if (!CpuSetIdText ||
- *CpuSetIdText < L'0' ||
- *CpuSetIdText > L'9') {
- return FALSE;
- }
-
- ULONG v = 0;
- WCHAR c;
- for (; *CpuSetIdText; CpuSetIdText += 1) {
- c = *CpuSetIdText;
- if (c < L'0' || c > L'9') {
- return FALSE;
- }
- v = v * 10 + (c - L'0');
- }
- *AsUlong = v;
-
- return TRUE;
-}
-
/**
* Retrieves the selected CPU Set IDs from a ListView.
*
@@ -889,6 +851,8 @@ PhpTryParseCpuId(
*
* @param SelectedCount
* Pointer to store the count of selected IDs.
+ *
+ * @return STATUS_SUCCESS if no errors, or the specific error status code.
*/
static
NTSTATUS
@@ -898,65 +862,43 @@ PhpGetSelectedCpuSetIds(
_Out_ PULONG SelectedCount
)
{
- INT i;
- INT itemCount = ListView_GetItemCount(ListViewHandle);
+ PVOID *paramArray;
+ PULONG ids;
ULONG count;
- WCHAR buffer[PH_INT32_STR_LEN_1];
- LVITEMW lv = {0};
- PULONG shrunk;
+ ULONG i;
- if (itemCount <= 0) {
+ if (!PhGetCheckedListViewItemParams(ListViewHandle,
+ ¶mArray,
+ &count)) {
*SelectedIds = NULL;
*SelectedCount = 0;
- return STATUS_SUCCESS;
+ return STATUS_NOT_FOUND;
}
- ULONG *ids = PhAllocate(sizeof(*ids) * itemCount);
- if (!ids) {
- return STATUS_INSUFFICIENT_RESOURCES;
+ //
+ // If no rows were checked, clean up and return empty.
+ //
+ if (count == 0) {
+ *SelectedIds = NULL;
+ *SelectedCount = 0;
+ return STATUS_SUCCESS;
}
- lv.mask = LVIF_TEXT;
- lv.iSubItem = 0;
- lv.pszText = buffer;
- lv.cchTextMax = ARRAYSIZE(buffer);
+ ids = PhAllocate(sizeof(*ids) * count);
- count = 0;
- for (i = 0; i < itemCount; i += 1) {
- if (!ListView_GetCheckState(ListViewHandle, i)) {
- continue;
- }
-
- //
- // Retrieve the item text
- //
- lv.iItem = i;
- if (SendMessageW(ListViewHandle,
- LVM_GETITEMTEXTW,
- (WPARAM)i,
- (LPARAM)&lv) <= 0) {
- continue;
- }
-
- if (PhpTryParseCpuId(buffer, &ids[count])) {
- count += 1;
- }
- }
-
- if (count == 0) {
- PhFree(ids);
+ if (!ids) {
+ PhFree(paramArray);
*SelectedIds = NULL;
*SelectedCount = 0;
- return STATUS_SUCCESS;
+ return STATUS_INSUFFICIENT_RESOURCES;
}
- if (count < (ULONG)itemCount) {
- shrunk = PhReAllocate(ids, sizeof(*ids) * count);
- if (shrunk) {
- ids = shrunk;
- }
+ for (i = 0; i < count; i += 1) {
+ ids[i] = (ULONG)(ULONG_PTR)paramArray[i];
}
+ PhFree(paramArray);
+
*SelectedIds = ids;
*SelectedCount = count;
@@ -1002,7 +944,6 @@ PhpProcessCpuSetsDlgProc(
context->WindowHandle = DlgHandle;
context->ListViewHandle = GetDlgItem(DlgHandle, IDC_LIST);
- context->Index = 0;
PhSetApplicationWindowIcon(DlgHandle);
diff --git a/phlib/guisuplistview.cpp b/phlib/guisuplistview.cpp
index c0b6ed0efd25..a354c9c63d01 100644
--- a/phlib/guisuplistview.cpp
+++ b/phlib/guisuplistview.cpp
@@ -697,6 +697,56 @@ VOID PhGetSelectedIListViewItemParams(
*Items = static_cast(PhFinalArrayItems(&array));
}
+/*!
+ * Retrieves the lParam of each checked item in a checkbox-enabled ListView.
+ *
+ * @param ListViewHandle
+ * A handle to the ListView control (must have checkboxes enabled).
+ *
+ * @param Items
+ * Receives a pointer to an array of PVOID values, each corresponding
+ * to the lParam of a checked item.
+ *
+ * @param NumberOfItems
+ * Receives the number of entries in @p Items.
+ *
+ * @return TRUE if the operation succeeded, FALSE otherwise.
+ */
+_Success_(return != FALSE)
+BOOLEAN PhGetCheckedListViewItemParams(
+ _In_ HWND ListViewHandle,
+ _Out_ PVOID **Items,
+ _Out_ PULONG NumberOfItems
+ )
+{
+ PH_ARRAY array;
+ INT totalItems, i;
+ PVOID param;
+
+ if (!ListViewHandle || !Items || !NumberOfItems)
+ {
+ return FALSE;
+ }
+
+ PhInitializeArray(&array, sizeof(PVOID), 4);
+
+ totalItems = ListView_GetItemCount(ListViewHandle);
+ for (i = 0; i < totalItems; i += 1)
+ {
+ if (ListView_GetCheckState(ListViewHandle, i))
+ {
+ if (PhGetListViewItemParam(ListViewHandle, i, ¶m))
+ {
+ PhAddItemArray(&array, ¶m);
+ }
+ }
+ }
+
+ *NumberOfItems = static_cast(PhFinalArrayCount(&array));
+ *Items = static_cast(PhFinalArrayItems(&array));
+ return TRUE;
+}
+
BOOLEAN PhGetIListViewClientRect(
_In_ IListView* ListView,
_Inout_ PRECT ClientRect
diff --git a/phlib/include/cpuset.h b/phlib/include/cpuset.h
index 0b5c600c00d8..eb2330bb22ff 100644
--- a/phlib/include/cpuset.h
+++ b/phlib/include/cpuset.h
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright (c) 2025 Winsider Seminars & Solutions, Inc. All rights reserved.
*
* This file is part of System Informer ("SI").
@@ -19,24 +19,26 @@
extern "C" {
#endif
-typedef BOOLEAN
-(*PPH_CPU_SETS_ENUMERATION_CALLBACK)(
+typedef BOOLEAN (NTAPI *PPH_CPU_SETS_ENUMERATION_CALLBACK)(
_In_ PSYSTEM_CPU_SET_INFORMATION Information,
_In_opt_ PVOID Context
);
NTSTATUS
+NTAPI
PhQuerySystemCpuSetInformation(
_Out_ PVOID *Information
);
NTSTATUS
+NTAPI
PhEnumerateSystemCpuSets(
_In_ PPH_CPU_SETS_ENUMERATION_CALLBACK Callback,
_In_opt_ PVOID Context
);
NTSTATUS
+NTAPI
PhQueryCpuSetsProcess(
_In_ HANDLE ProcessHandle,
_Out_ PULONG *CpuSetIds,
@@ -44,6 +46,7 @@ PhQueryCpuSetsProcess(
);
NTSTATUS
+NTAPI
PhSetCpuSetsProcess(
_In_ HANDLE ProcessHandle,
_In_reads_opt_(CpuSetIdCount) const ULONG *CpuSetIds,
diff --git a/phlib/include/guisup.h b/phlib/include/guisup.h
index 040f032bf8b7..b2db87ee721d 100644
--- a/phlib/include/guisup.h
+++ b/phlib/include/guisup.h
@@ -959,6 +959,15 @@ PhGetSelectedIListViewItemParams(
_Out_ PULONG NumberOfItems
);
+PHLIBAPI
+BOOLEAN
+NTAPI
+PhGetCheckedListViewItemParams(
+ _In_ HWND ListViewHandle,
+ _Out_ PVOID **Items,
+ _Out_ PULONG NumberOfItems
+ );
+
PHLIBAPI
BOOLEAN
NTAPI
diff --git a/phlib/phlib.vcxproj.filters b/phlib/phlib.vcxproj.filters
index e5b141a9a20a..e4a6e1522bd8 100644
--- a/phlib/phlib.vcxproj.filters
+++ b/phlib/phlib.vcxproj.filters
@@ -439,5 +439,8 @@
Header Files
+
+ Header Files
+
\ No newline at end of file
From 83282552ecec741b755bbcb16982d0300da8a6e1 Mon Sep 17 00:00:00 2001
From: dmex
Date: Fri, 10 Apr 2026 00:00:24 +1000
Subject: [PATCH 3/4] Update resource.h
---
SystemInformer/resource.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/SystemInformer/resource.h b/SystemInformer/resource.h
index d02a22b1ae7c..758be96fc604 100644
--- a/SystemInformer/resource.h
+++ b/SystemInformer/resource.h
@@ -578,7 +578,6 @@
#define IDC_FONTMONOSPACE 1416
#define IDC_ONLYKERNELTHREADSTACKS 1417
#define IDC_HYPERVISORNONESSENTIAL 1418
-#define IDC_LISTBOX1 1418
#define IDC_INPUT 1419
#define IDC_MINIDUMP_NORMAL 1420
#define IDC_MINIDUMP_WITH_DATA_SEGS 1421
From 2e958f80f8ef3e2fed8ea514c8cb08071e14559d Mon Sep 17 00:00:00 2001
From: dmex
Date: Fri, 10 Apr 2026 00:58:45 +1000
Subject: [PATCH 4/4] Update resource.h
---
SystemInformer/resource.h | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/SystemInformer/resource.h b/SystemInformer/resource.h
index 758be96fc604..cd02d46ba82b 100644
--- a/SystemInformer/resource.h
+++ b/SystemInformer/resource.h
@@ -874,11 +874,11 @@
#define ID_MISCELLANEOUS_EXECUTIONREQUIRED 40313
#define IDD_OBJAFDSOCKET 40314
#define ID_PROCESS_CPU_SETS 40315
-#define ID_TOOLS_COM_ACCESS_PERMISSIONS 40315
-#define ID_TOOLS_COM_ACCESS_RESTRICTIONS 40316
-#define ID_TOOLS_COM_LAUNCH_PERMISSIONS 40317
-#define ID_TOOLS_COM_LAUNCH_RESTRICTIONS 40318
-#define ID_TOOLS_INFORMER 40319
+#define ID_TOOLS_COM_ACCESS_PERMISSIONS 40316
+#define ID_TOOLS_COM_ACCESS_RESTRICTIONS 40317
+#define ID_TOOLS_COM_LAUNCH_PERMISSIONS 40318
+#define ID_TOOLS_COM_LAUNCH_RESTRICTIONS 40319
+#define ID_TOOLS_INFORMER 40320
#define IDDYNAMIC 50000
#define IDPLUGINS 55000