diff --git a/README.md b/README.md
index 4830edc..6fa353e 100644
--- a/README.md
+++ b/README.md
@@ -267,3 +267,4 @@ Latest:
* Fix: Pool and Heap parsing improved.
* Fix: Compressed memory recovery rates improved.
* New module: /sys/eventlog (Windows event log recovery).
+* New module: /sys/proc/cpu - process CPU usage, current and total.
diff --git a/m_vmemd/version.h b/m_vmemd/version.h
index f2535e2..a943e7e 100644
--- a/m_vmemd/version.h
+++ b/m_vmemd/version.h
@@ -3,8 +3,8 @@
#define VERSION_MAJOR 5
#define VERSION_MINOR 18
-#define VERSION_REVISION 7
-#define VERSION_BUILD 246
+#define VERSION_REVISION 8
+#define VERSION_BUILD 247
#define VER_FILE_DESCRIPTION_STR "MemProcFS : Plugin vmemd"
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
diff --git a/memprocfs/version.h b/memprocfs/version.h
index 9c185f9..e447e33 100644
--- a/memprocfs/version.h
+++ b/memprocfs/version.h
@@ -3,8 +3,8 @@
#define VERSION_MAJOR 5
#define VERSION_MINOR 18
-#define VERSION_REVISION 7
-#define VERSION_BUILD 246
+#define VERSION_REVISION 8
+#define VERSION_BUILD 247
#define VER_FILE_DESCRIPTION_STR "MemProcFS"
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
diff --git a/vmm/Makefile b/vmm/Makefile
index 229f419..4f0a244 100644
--- a/vmm/Makefile
+++ b/vmm/Makefile
@@ -52,8 +52,8 @@ OBJ = oscompatibility.o charutil.o util.o pe.o vmmdll.o vmmdll_core.o \
modules/m_sys.o modules/m_sys_driver.o modules/m_sys_eventlog.o \
modules/m_sys_mem.o modules/m_sys_net.o modules/m_sys_netdns.o \
modules/m_sys_obj.o modules/m_sys_pool.o modules/m_sys_proc.o \
- modules/m_sys_svc.o modules/m_sys_syscall.o modules/m_sys_sysinfo.o \
- modules/m_sys_task.o modules/m_sys_user.o \
+ modules/m_sys_proccpu.o modules/m_sys_svc.o modules/m_sys_syscall.o \
+ modules/m_sys_sysinfo.o modules/m_sys_task.o modules/m_sys_user.o \
modules/m_phys2virt.o modules/m_search.o modules/m_searchyara.o \
modules/m_proc_console.o modules/m_proc_file_handles_vads.o \
modules/m_proc_file_modules.o modules/m_proc_handle.o \
diff --git a/vmm/Makefile.macos b/vmm/Makefile.macos
index 68bf09a..c635661 100644
--- a/vmm/Makefile.macos
+++ b/vmm/Makefile.macos
@@ -44,8 +44,8 @@ OBJ = oscompatibility.o charutil.o util.o pe.o vmmdll.o vmmdll_core.o \
modules/m_sys.o modules/m_sys_driver.o modules/m_sys_eventlog.o \
modules/m_sys_mem.o modules/m_sys_net.o modules/m_sys_netdns.o \
modules/m_sys_obj.o modules/m_sys_pool.o modules/m_sys_proc.o \
- modules/m_sys_svc.o modules/m_sys_syscall.o modules/m_sys_sysinfo.o \
- modules/m_sys_task.o modules/m_sys_user.o \
+ modules/m_sys_proccpu.o modules/m_sys_svc.o modules/m_sys_syscall.o \
+ modules/m_sys_sysinfo.o modules/m_sys_task.o modules/m_sys_user.o \
modules/m_phys2virt.o modules/m_search.o modules/m_searchyara.o \
modules/m_proc_console.o modules/m_proc_file_handles_vads.o \
modules/m_proc_file_modules.o modules/m_proc_handle.o \
diff --git a/vmm/modules/m_sys_proccpu.c b/vmm/modules/m_sys_proccpu.c
new file mode 100644
index 0000000..152a398
--- /dev/null
+++ b/vmm/modules/m_sys_proccpu.c
@@ -0,0 +1,537 @@
+// m_sys_proccpu.c : Windows process CPU accounting and sampled CPU usage.
+//
+// (c) Ulf Frisk, 2026
+// Author: Ulf Frisk, pcileech@frizk.net
+//
+
+#include "modules.h"
+
+#define MPERF_LINE_LENGTH 120
+#define MPERF_LINE_HEADER " PID Process Kernel(s) User(s) Total(s) CPU% Delta(s) Thrd Status"
+#define MPERF_MAX_PROCESSES 0x4000
+#define MPERF_MAX_THREADS 0x10000
+#define MPERF_MAX_PROC_THREADS 0x4000
+#define MPERF_READ_FLAGS (VMM_FLAG_NOCACHE | VMM_FLAG_NOCACHEPUT | VMM_FLAG_NOPAGING)
+
+static const CHAR szMPerfReadme[] =
+ "Windows process CPU accounting\n"
+ "==============================\n"
+ "cpu.txt lists cumulative kernel/user CPU seconds and sampled CPU percent. \n"
+ "The tables are available at /sys/proc/cpu. \n"
+ " \n"
+ "cpu.txt is sorted by PID. \n"
+ "cpu-time is sorted by active time. \n"
+ "cpu-usage.txt is sorted by active time since last refresh on live targets. \n"
+ " \n"
+ "More information: https://github.com/ufrisk/MemProcFS-dev/wiki/FS_Sys_Cpu \n";
+
+typedef struct tdMCPU_OFFSETS {
+ BOOL fValid;
+ WORD cbProcess;
+ WORD cbThread;
+ WORD cbCounter;
+ WORD oPID;
+ WORD oCreate;
+ WORD oExit;
+ WORD oHead;
+ WORD oKernel;
+ WORD oUser;
+ WORD oThreadLink;
+ WORD oThreadCID;
+ WORD oThreadKernel;
+ WORD oThreadUser;
+ QWORD vaIncrement;
+} MCPU_OFFSETS, *PMCPU_OFFSETS;
+
+typedef struct tdMCPU_ENTRY {
+ DWORD dwPID;
+ DWORD cThreads;
+ CHAR szName[16];
+ QWORD vaProcess;
+ QWORD ftCreate;
+ QWORD ftExit;
+ QWORD vaHead;
+ QWORD vaFirst;
+ QWORD vaNext;
+ QWORD vaPrev;
+ QWORD vaTail;
+ QWORD cKernel;
+ QWORD cUser;
+ QWORD cKernelProcess;
+ QWORD cUserProcess;
+ BOOL fValid;
+ BOOL fDone;
+ BOOL fPercent;
+ double cpu;
+ double seconds;
+ LPCSTR szStatus;
+} MCPU_ENTRY, *PMCPU_ENTRY;
+
+typedef struct tdMCPU_THREAD {
+ BOOL fValid;
+ DWORD dwPID;
+ DWORD cKernel;
+ DWORD cUser;
+ QWORD vaNext;
+ QWORD vaPrev;
+} MCPU_THREAD, *PMCPU_THREAD;
+
+typedef struct tdMCPU_SNAPSHOT {
+ OB ObHdr;
+ POB_MAP pmProcess;
+ POB_SET psThread;
+ PMCPU_ENTRY *ppTime;
+ PMCPU_ENTRY *ppUsage;
+ QWORD timeStart;
+ QWORD timeEnd;
+ DWORD dwIncrement;
+ DWORD cCPUs;
+} MCPU_SNAPSHOT, *PMCPU_SNAPSHOT;
+
+typedef struct tdMCPU_CONTEXT {
+ OB ObHdr;
+ SRWLOCK LockSRW;
+ QWORD qwRefresh;
+ QWORD qwRefreshSeen;
+ MCPU_OFFSETS o;
+ PMCPU_SNAPSHOT pSnapshot;
+} MCPU_CONTEXT, *PMCPU_CONTEXT;
+
+VOID MSysProcCpu_SnapshotCleanupCB(_In_ PMCPU_SNAPSHOT pObSnapshot)
+{
+ LocalFree(pObSnapshot->ppTime);
+ LocalFree(pObSnapshot->ppUsage);
+ Ob_DECREF(pObSnapshot->pmProcess);
+ Ob_DECREF(pObSnapshot->psThread);
+}
+
+VOID MSysProcCpu_ContextCleanupCB(_In_ PMCPU_CONTEXT pObContext)
+{
+ Ob_DECREF(pObContext->pSnapshot);
+}
+
+/*
+* Resolve only accounting fields; no dependency on optional stack/thread maps.
+* -- H
+* -- po
+*/
+_Success_(return)
+BOOL MSysProcCpu_Offsets(_In_ VMM_HANDLE H, _Inout_ PMCPU_OFFSETS po)
+{
+ BOOL f;
+ if(po->fValid) { return TRUE; }
+ ZeroMemory(po, sizeof(*po));
+ po->cbCounter = (H->vmm.f32 || (H->vmm.kernel.dwVersionBuild < 26100)) ? 4 : 8;
+ f = PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_EPROCESS", "UniqueProcessId", &po->oPID);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_EPROCESS", "CreateTime", &po->oCreate);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_EPROCESS", "ExitTime", &po->oExit);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_EPROCESS", "ThreadListHead", &po->oHead);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_KPROCESS", "KernelTime", &po->oKernel);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_KPROCESS", "UserTime", &po->oUser);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_ETHREAD", "ThreadListEntry", &po->oThreadLink);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_ETHREAD", "Cid", &po->oThreadCID);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_KTHREAD", "KernelTime", &po->oThreadKernel);
+ f = f && PDB_GetTypeChildOffsetShort(H, PDB_HANDLE_KERNEL, "_KTHREAD", "UserTime", &po->oThreadUser);
+ f = f && PDB_GetSymbolAddress(H, PDB_HANDLE_KERNEL, "KeMaximumIncrement", &po->vaIncrement);
+ if(!f) { return FALSE; }
+ f = po->oCreate && (po->oUser == po->oKernel + po->cbCounter) && !(po->vaIncrement & 3);
+ f = f && VMM_KADDR_DUAL(H->vmm.f32, po->vaIncrement);
+ if(!f) { return FALSE; }
+ po->cbProcess = max(max(max(po->oPID, po->oCreate), max(po->oExit, po->oHead)), max(po->oKernel, po->oUser));
+ po->cbThread = max(max(po->oThreadLink, po->oThreadCID), max(po->oThreadKernel, po->oThreadUser));
+ if((po->cbProcess > 0x1000 - 16) || (po->cbThread > 0x1000 - 16)) { return FALSE; }
+ po->cbProcess += 16;
+ po->cbThread += 16;
+ po->fValid = TRUE;
+ return TRUE;
+}
+
+VOID MSysProcCpu_Calculate(_In_ BOOL fLive, _In_ PMCPU_SNAPSHOT pSnap, _In_opt_ PMCPU_SNAPSHOT pPrev, _Inout_ PMCPU_ENTRY pe)
+{
+ PMCPU_ENTRY old;
+ QWORD delta, elapsed, time, oldTime, limit;
+ double cpu;
+ if(!pe->fValid) { return; }
+ limit = 0xffffffffffffffffULL / max(1, pSnap->dwIncrement);
+ if((pe->cUser > limit) || (pe->cKernel > limit - pe->cUser)) {
+ pe->fValid = FALSE;
+ pe->szStatus = "counter-overflow";
+ return;
+ }
+ pe->szStatus = "no-clock";
+ if(!pSnap->dwIncrement) { pe->fValid = FALSE; return; }
+ if(!fLive) { pe->szStatus = "static"; return; }
+ if(pe->ftExit) { pe->szStatus = "exited"; return; }
+ if(!pSnap->timeStart || (pSnap->timeEnd < pSnap->timeStart) || !pSnap->cCPUs) { return; }
+ pe->szStatus = "first-sample";
+ if(!pPrev || !pPrev->timeStart || (pPrev->timeEnd < pPrev->timeStart) || (pPrev->dwIncrement != pSnap->dwIncrement) || (pPrev->cCPUs != pSnap->cCPUs)) { return; }
+ old = ObMap_GetByKey(pPrev->pmProcess, pe->dwPID);
+ if(!old || !old->fValid) { return; }
+ pe->szStatus = "identity-changed";
+ if(!pe->ftCreate || (old->ftCreate != pe->ftCreate) || (old->vaProcess != pe->vaProcess) || old->ftExit) { return; }
+ pe->szStatus = "counter-reset";
+ if((pe->cKernel < old->cKernel) || (pe->cUser < old->cUser)) { return; }
+ pe->szStatus = "no-clock";
+ if(pSnap->timeStart <= pPrev->timeEnd) { return; }
+ time = pSnap->timeStart + (pSnap->timeEnd - pSnap->timeStart) / 2;
+ oldTime = pPrev->timeStart + (pPrev->timeEnd - pPrev->timeStart) / 2;
+ elapsed = time - oldTime;
+ delta = (pe->cKernel - old->cKernel) + (pe->cUser - old->cUser);
+ cpu = 100.0 * (double)delta * pSnap->dwIncrement / (double)elapsed / pSnap->cCPUs;
+ pe->szStatus = "inconsistent";
+ // Permit two ticks of acquisition/rounding skew; reject implausible jumps.
+ if(cpu > 100.0 + 200.0 * pSnap->dwIncrement / elapsed) { return; }
+ pe->cpu = min(cpu, 100.0);
+ pe->seconds = elapsed / 10000000.0;
+ pe->fPercent = TRUE;
+ pe->szStatus = "ok";
+}
+
+/* Walk each process ring against private, freshly read scatter data. Unknown
+ * nodes are read together in the next batch; known nodes arrive in the first
+ * process batch. Never sum a partial ring as a complete process. */
+BOOL MSysProcCpu_Threads(_In_ VMM_HANDLE H, _In_ PMCPU_OFFSETS po, _Inout_ PMCPU_SNAPSHOT pSnap, _In_opt_ PMCPU_SNAPSHOT prev, _In_opt_ PVMMOB_SCATTER hS)
+{
+ POB_MAP pObMapThread = NULL;
+ POB_SET pObSetPending = NULL;
+ PVMMOB_SCATTER pObScatter = Ob_INCREF(hS);
+ PMCPU_ENTRY e;
+ PMCPU_THREAD t;
+ MCPU_THREAD entry;
+ BYTE pb[0x1000];
+ DWORD i;
+ QWORD va, qwPID;
+ BOOL f, fResult = FALSE;
+ if(!(pObMapThread = ObMap_New(H, OB_MAP_FLAGS_OBJECT_LOCALFREE)) || !(pObSetPending = ObSet_New(H))) { goto finish; }
+ if(prev && !ObSet_PushSet(pObSetPending, prev->psThread)) { goto finish; }
+ while(TRUE) {
+ while((va = ObSet_Pop(pObSetPending))) {
+ ZeroMemory(&entry, sizeof(entry));
+ entry.fValid = VmmScatter_Read(pObScatter, va, po->cbThread, pb);
+ if(entry.fValid) {
+ qwPID = VMM_PTR_OFFSET(H->vmm.f32, pb, po->oThreadCID);
+ entry.dwPID = (DWORD)qwPID;
+ entry.fValid = qwPID == entry.dwPID;
+ entry.cKernel = *(PDWORD)(pb + po->oThreadKernel);
+ entry.cUser = *(PDWORD)(pb + po->oThreadUser);
+ entry.vaNext = VMM_PTR_OFFSET(H->vmm.f32, pb, po->oThreadLink);
+ entry.vaPrev = VMM_PTR_OFFSET(H->vmm.f32, pb, po->oThreadLink + (H->vmm.f32 ? 4 : 8));
+ }
+ if(!ObMap_PushCopy(pObMapThread, va, &entry, sizeof(entry))) { goto finish; }
+ }
+ Ob_DECREF_NULL(&pObScatter);
+ for(i = 0; i < ObMap_Size(pSnap->pmProcess); i++) {
+ e = ObMap_GetByIndex(pSnap->pmProcess, i);
+ while(e->fValid && !e->fDone) {
+ if(e->vaNext == e->vaHead) {
+ e->fDone = TRUE;
+ e->fValid = e->vaPrev == e->vaTail;
+ break;
+ }
+ // Embedded list entries need only pointer alignment; ETHREAD needs 8/16.
+ va = e->vaNext - po->oThreadLink;
+ f = VMM_KADDR_DUAL_4_8(H->vmm.f32, e->vaNext) && VMM_KADDR_DUAL_8_16(H->vmm.f32, va);
+ f = f && (e->cThreads < MPERF_MAX_PROC_THREADS) && (ObSet_Size(pSnap->psThread) < MPERF_MAX_THREADS);
+ if(!f) { e->fValid = FALSE; break; }
+ if(!(t = ObMap_GetByKey(pObMapThread, va))) {
+ if(!ObSet_Push(pObSetPending, va) && !ObSet_Exists(pObSetPending, va)) { goto finish; }
+ break;
+ }
+ f = t->fValid && (t->dwPID == e->dwPID) && (t->vaPrev == e->vaPrev);
+ f = f && !ObSet_Exists(pSnap->psThread, va) && ObSet_Push(pSnap->psThread, va);
+ f = f && (e->cKernel <= 0xffffffffffffffffULL - t->cKernel) && (e->cUser <= 0xffffffffffffffffULL - t->cUser);
+ if(!f) {
+ e->fValid = FALSE;
+ break;
+ }
+ e->cKernel += t->cKernel;
+ e->cUser += t->cUser;
+ e->cThreads++;
+ e->vaPrev = e->vaNext;
+ e->vaNext = t->vaNext;
+ }
+ }
+ if(!ObSet_Size(pObSetPending)) { break; }
+ if(ObMap_Size(pObMapThread) + ObSet_Size(pObSetPending) > MPERF_MAX_THREADS) { goto finish; }
+ f = (pObScatter = VmmScatter_Initialize(H, MPERF_READ_FLAGS)) != NULL;
+ f = f && VmmScatter_Prepare3(pObScatter, pObSetPending, po->cbThread) && VmmScatter_Execute(pObScatter, PVMM_PROCESS_SYSTEM);
+ if(!f) { goto finish; }
+ }
+ fResult = TRUE;
+finish:
+ Ob_DECREF(pObScatter);
+ Ob_DECREF(pObMapThread);
+ Ob_DECREF(pObSetPending);
+ return fResult;
+}
+
+/* CALLER DECREF: return. Batch the process fields and target clock with optional known threads. */
+_Success_(return != NULL)
+PVMMOB_SCATTER MSysProcCpu_ReadProcesses(_In_ VMM_HANDLE H, _In_ PMCPU_OFFSETS po, _Inout_ PMCPU_SNAPSHOT pSnap, _In_opt_ POB_SET psThread, _Out_ PQWORD pTime)
+{
+ PVMMOB_SCATTER pObScatter = VmmScatter_Initialize(H, MPERF_READ_FLAGS);
+ PMCPU_ENTRY e;
+ DWORD i, t[3];
+ BOOL f;
+ *pTime = 0;
+ if(!pObScatter) { return NULL; }
+ for(i = 0; i < ObMap_Size(pSnap->pmProcess); i++) {
+ e = ObMap_GetByIndex(pSnap->pmProcess, i);
+ if(VMM_KADDR_DUAL_8_16(H->vmm.f32, e->vaProcess) && !VmmScatter_Prepare(pObScatter, e->vaProcess, po->cbProcess)) { goto fail; }
+ }
+ f = VmmScatter_Prepare3(pObScatter, psThread, po->cbThread);
+ f = f && VmmScatter_PrepareEx(pObScatter, po->vaIncrement, sizeof(DWORD), (PBYTE)&pSnap->dwIncrement, NULL);
+ f = f && VmmScatter_Prepare(pObScatter, H->vmm.f32 ? 0xffdf0008 : 0xfffff78000000008, sizeof(t));
+ if(!f || !VmmScatter_Execute(pObScatter, PVMM_PROCESS_SYSTEM)) { goto fail; }
+ f = VmmScatter_Read(pObScatter, H->vmm.f32 ? 0xffdf0008 : 0xfffff78000000008, sizeof(t), (PBYTE)t) && (t[1] == t[2]);
+ *pTime = f ? ((QWORD)t[1] << 32) | t[0] : 0;
+ return pObScatter;
+fail:
+ Ob_DECREF(pObScatter);
+ return NULL;
+}
+
+_Success_(return != NULL)
+PMCPU_SNAPSHOT MSysProcCpu_Capture(_In_ VMM_HANDLE H, _Inout_ PMCPU_CONTEXT ctx)
+{
+ PMCPU_SNAPSHOT pObSnapshot = NULL;
+ PVMM_PROCESS pObProcess = NULL;
+ PVMMOB_SCATTER pObScatter = NULL;
+ PMCPU_OFFSETS o = &ctx->o;
+ MCPU_ENTRY entry;
+ PMCPU_ENTRY e;
+ DWORD i;
+ BYTE pb[0x1000];
+ BOOL f, fThreads;
+ pObSnapshot = Ob_AllocEx(H, OB_TAG_MOD_CPU, LMEM_ZEROINIT, sizeof(*pObSnapshot), (OB_CLEANUP_CB)MSysProcCpu_SnapshotCleanupCB, NULL);
+ if(!pObSnapshot) { goto fail; }
+ if(!(pObSnapshot->pmProcess = ObMap_New(H, OB_MAP_FLAGS_OBJECT_LOCALFREE)) || !(pObSnapshot->psThread = ObSet_New(H))) { goto fail; }
+ MSysProcCpu_Offsets(H, o);
+ while((pObProcess = VmmProcessGetNext(H, pObProcess, VMM_FLAG_PROCESS_SHOW_TERMINATED))) {
+ if(ObMap_Size(pObSnapshot->pmProcess) >= MPERF_MAX_PROCESSES) { goto fail; }
+ ZeroMemory(&entry, sizeof(entry));
+ entry.dwPID = pObProcess->dwPID;
+ entry.vaProcess = pObProcess->win.EPROCESS.va;
+ entry.ftCreate = VmmProcess_GetCreateTimeOpt(H, pObProcess);
+ memcpy(entry.szName, pObProcess->szName, sizeof(entry.szName));
+ entry.szName[15] = 0;
+ entry.szStatus = o->fValid ? "incomplete" : "no-symbols";
+ if(!ObMap_PushCopy(pObSnapshot->pmProcess, entry.dwPID, &entry, sizeof(entry))) { goto fail; }
+ }
+ if(!o->fValid) { return pObSnapshot; }
+ pObSnapshot->cCPUs = H->vmm.kernel.opt.cCPUs;
+ if(pObSnapshot->cCPUs > 4096) { pObSnapshot->cCPUs = 0; }
+ pObScatter = MSysProcCpu_ReadProcesses(H, o, pObSnapshot, ctx->pSnapshot ? ctx->pSnapshot->psThread : NULL, &pObSnapshot->timeStart);
+ if(!pObScatter) { goto fail; }
+ for(i = 0; i < ObMap_Size(pObSnapshot->pmProcess); i++) {
+ e = ObMap_GetByIndex(pObSnapshot->pmProcess, i);
+ f = VmmScatter_Read(pObScatter, e->vaProcess, o->cbProcess, pb) && (VMM_PTR_OFFSET(H->vmm.f32, pb, o->oPID) == e->dwPID);
+ f = f && (!e->ftCreate || (e->ftCreate == *(PQWORD)(pb + o->oCreate)));
+ if(!f) { continue; }
+ e->ftCreate = *(PQWORD)(pb + o->oCreate);
+ e->ftExit = *(PQWORD)(pb + o->oExit);
+ e->cKernel = e->cKernelProcess = (o->cbCounter == 8) ? *(PQWORD)(pb + o->oKernel) : *(PDWORD)(pb + o->oKernel);
+ e->cUser = e->cUserProcess = (o->cbCounter == 8) ? *(PQWORD)(pb + o->oUser) : *(PDWORD)(pb + o->oUser);
+ e->vaHead = e->vaPrev = e->vaProcess + o->oHead;
+ e->vaFirst = e->vaNext = VMM_PTR_OFFSET(H->vmm.f32, pb, o->oHead);
+ e->vaTail = VMM_PTR_OFFSET(H->vmm.f32, pb, o->oHead + (H->vmm.f32 ? 4 : 8));
+ e->fValid = VMM_KADDR_DUAL_4_8(H->vmm.f32, e->vaNext) && VMM_KADDR_DUAL_4_8(H->vmm.f32, e->vaTail);
+ }
+ fThreads = MSysProcCpu_Threads(H, o, pObSnapshot, ctx->pSnapshot, pObScatter);
+ Ob_DECREF_NULL(&pObScatter);
+ // A thread may exit and transfer its ticks while we read its process ring.
+ // Reject such samples instead of counting transferred ticks twice/partially.
+ if(!(pObScatter = MSysProcCpu_ReadProcesses(H, o, pObSnapshot, NULL, &pObSnapshot->timeEnd))) { fThreads = FALSE; }
+ if((pObSnapshot->dwIncrement < 1000) || (pObSnapshot->dwIncrement > 10000000)) { pObSnapshot->dwIncrement = 0; }
+ for(i = 0; pObScatter && (i < ObMap_Size(pObSnapshot->pmProcess)); i++) {
+ e = ObMap_GetByIndex(pObSnapshot->pmProcess, i);
+ if(!e->fValid) { continue; }
+ f = VmmScatter_Read(pObScatter, e->vaProcess, o->cbProcess, pb) && (VMM_PTR_OFFSET(H->vmm.f32, pb, o->oPID) == e->dwPID);
+ f = f && (*(PQWORD)(pb + o->oCreate) == e->ftCreate) && (*(PQWORD)(pb + o->oExit) == e->ftExit);
+ f = f && (VMM_PTR_OFFSET(H->vmm.f32, pb, o->oHead) == e->vaFirst);
+ f = f && (VMM_PTR_OFFSET(H->vmm.f32, pb, o->oHead + (H->vmm.f32 ? 4 : 8)) == e->vaTail);
+ f = f && (((o->cbCounter == 8) ? *(PQWORD)(pb + o->oKernel) : *(PDWORD)(pb + o->oKernel)) == e->cKernelProcess);
+ f = f && (((o->cbCounter == 8) ? *(PQWORD)(pb + o->oUser) : *(PDWORD)(pb + o->oUser)) == e->cUserProcess);
+ if(!f) {
+ e->fValid = FALSE;
+ e->szStatus = "inconsistent";
+ }
+ }
+ for(i = 0; i < ObMap_Size(pObSnapshot->pmProcess); i++) {
+ e = ObMap_GetByIndex(pObSnapshot->pmProcess, i);
+ e->fValid = e->fValid && e->fDone && fThreads;
+ if(o->cbCounter == 4) { e->cKernel = (DWORD)e->cKernel; e->cUser = (DWORD)e->cUser; }
+ MSysProcCpu_Calculate(H->dev.fVolatile, pObSnapshot, ctx->pSnapshot, e);
+ }
+ Ob_DECREF(pObScatter);
+ return pObSnapshot;
+fail:
+ Ob_DECREF(pObProcess);
+ Ob_DECREF(pObScatter);
+ Ob_DECREF(pObSnapshot);
+ return NULL;
+}
+
+/* CALLER DECREF: return. Refresh notifications only invalidate; no target I/O. */
+PMCPU_SNAPSHOT MSysProcCpu_GetSnapshot(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
+{
+ PMCPU_CONTEXT ctx = (PMCPU_CONTEXT)ctxP->ctxM;
+ PMCPU_SNAPSHOT pObSnapshot;
+ QWORD qwRefresh;
+ AcquireSRWLockExclusive(&ctx->LockSRW);
+ qwRefresh = InterlockedAdd64(&ctx->qwRefresh, 0);
+ if(!ctx->pSnapshot || (qwRefresh != ctx->qwRefreshSeen)) {
+ ctx->qwRefreshSeen = qwRefresh;
+ pObSnapshot = MSysProcCpu_Capture(H, ctx);
+ Ob_DECREF(ctx->pSnapshot);
+ ctx->pSnapshot = pObSnapshot;
+ }
+ pObSnapshot = Ob_INCREF(ctx->pSnapshot);
+ ReleaseSRWLockExclusive(&ctx->LockSRW);
+ return pObSnapshot;
+}
+
+int MSysProcCpu_CompareTime(_In_ const void *p1, _In_ const void *p2)
+{
+ PMCPU_ENTRY e1 = *(PMCPU_ENTRY *)p1, e2 = *(PMCPU_ENTRY *)p2;
+ QWORD t1, t2;
+ if(e1->fValid != e2->fValid) { return e1->fValid ? -1 : 1; }
+ if(e1->fValid) {
+ t1 = e1->cKernel + e1->cUser;
+ t2 = e2->cKernel + e2->cUser;
+ if(t1 != t2) { return (t1 < t2) ? 1 : -1; }
+ }
+ return (e1->dwPID > e2->dwPID) - (e1->dwPID < e2->dwPID);
+}
+
+int MSysProcCpu_CompareUsage(_In_ const void *p1, _In_ const void *p2)
+{
+ PMCPU_ENTRY e1 = *(PMCPU_ENTRY *)p1, e2 = *(PMCPU_ENTRY *)p2;
+ if(e1->fPercent != e2->fPercent) { return e1->fPercent ? -1 : 1; }
+ if(e1->fPercent && (e1->cpu != e2->cpu)) { return (e1->cpu < e2->cpu) ? 1 : -1; }
+ return (e1->dwPID > e2->dwPID) - (e1->dwPID < e2->dwPID);
+}
+
+/* Borrowed from s. Only readers of a sorted file build its order; the source map stays unchanged. */
+_Success_(return != NULL)
+PMCPU_ENTRY *MSysProcCpu_GetSorted(_In_ PMCPU_CONTEXT ctx, _In_ PMCPU_SNAPSHOT pS, _In_ BOOL fUsage)
+{
+ PMCPU_ENTRY *pp;
+ DWORD i, c = ObMap_Size(pS->pmProcess);
+ AcquireSRWLockExclusive(&ctx->LockSRW);
+ pp = fUsage ? pS->ppUsage : pS->ppTime;
+ if(!pp && (pp = LocalAlloc(LMEM_ZEROINIT, max(1, c) * sizeof(*pp)))) {
+ for(i = 0; i < c; i++) { pp[i] = ObMap_GetByIndex(pS->pmProcess, i); }
+ qsort(pp, c, sizeof(*pp), fUsage ? MSysProcCpu_CompareUsage : MSysProcCpu_CompareTime);
+ if(fUsage) { pS->ppUsage = pp; } else { pS->ppTime = pp; }
+ }
+ ReleaseSRWLockExclusive(&ctx->LockSRW);
+ return pp;
+}
+
+VOID MSysProcCpu_ReadLine(_In_ VMM_HANDLE H, _In_ PMCPU_SNAPSHOT pS, _In_ DWORD cbLine, _In_ DWORD i, _In_ PMCPU_ENTRY pe, _Out_ LPSTR sz)
+{
+ CHAR kernel[32] = "---", user[32] = "---", total[32] = "---", cpu[16] = "---", interval[24] = "---";
+ if(pe->fValid) {
+ snprintf(kernel, sizeof(kernel), "%.3f", (double)pe->cKernel * pS->dwIncrement / 10000000.0);
+ snprintf(user, sizeof(user), "%.3f", (double)pe->cUser * pS->dwIncrement / 10000000.0);
+ snprintf(total, sizeof(total), "%.3f", (double)(pe->cKernel + pe->cUser) * pS->dwIncrement / 10000000.0);
+ }
+ if(pe->fPercent) {
+ snprintf(cpu, sizeof(cpu), "%.2f", pe->cpu);
+ snprintf(interval, sizeof(interval), "%.3f", pe->seconds);
+ }
+ Util_usnprintf_ln(sz, cbLine, "%7u %-15s %17s %17s %17s %6s %8s %5u %s",
+ pe->dwPID, pe->szName, kernel, user, total, cpu, interval, pe->cThreads, pe->szStatus);
+}
+
+PVOID MSysProcCpu_SortedEntry(_In_ VMM_HANDLE H, _In_ PMCPU_ENTRY *pp, _In_ DWORD i)
+{
+ return pp[i];
+}
+
+PVOID MSysProcCpu_MapEntry(_In_ VMM_HANDLE H, _In_ POB_MAP pm, _In_ DWORD i)
+{
+ return ObMap_GetByIndex(pm, i);
+}
+
+NTSTATUS MSysProcCpu_Read(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _Out_writes_(cb) PBYTE pb, _In_ DWORD cb,
+ _Out_ PDWORD pcbRead, _In_ QWORD cbOffset)
+{
+ NTSTATUS nt = VMMDLL_STATUS_FILE_INVALID;
+ PMCPU_SNAPSHOT pObSnapshot;
+ PVOID pvMap;
+ UTIL_VFSLINEFIXED_MAP_PFN_CB pfnMap = (UTIL_VFSLINEFIXED_MAP_PFN_CB)MSysProcCpu_SortedEntry;
+ DWORD c;
+ BOOL f, fTime, fUsage;
+ *pcbRead = 0;
+ if(!_stricmp(ctxP->uszPath, "readme.txt")) {
+ return Util_VfsReadFile_FromPBYTE((PBYTE)szMPerfReadme, sizeof(szMPerfReadme) - 1, pb, cb, pcbRead, cbOffset);
+ }
+ fTime = !_stricmp(ctxP->uszPath, "cpu-time.txt");
+ fUsage = !_stricmp(ctxP->uszPath, "cpu-usage.txt");
+ f = !_stricmp(ctxP->uszPath, "cpu.txt") || fTime || (fUsage && H->dev.fVolatile);
+ if(!f) { return nt; }
+ if(!cb) { return VMMDLL_STATUS_END_OF_FILE; }
+ if(!(pObSnapshot = MSysProcCpu_GetSnapshot(H, ctxP))) { return nt; }
+ c = ObMap_Size(pObSnapshot->pmProcess);
+ if(cbOffset >= UTIL_VFSLINEFIXED_LINECOUNT(H, c) * MPERF_LINE_LENGTH) { nt = VMMDLL_STATUS_END_OF_FILE; goto finish; }
+ if(fTime || fUsage) {
+ if(!(pvMap = MSysProcCpu_GetSorted((PMCPU_CONTEXT)ctxP->ctxM, pObSnapshot, fUsage))) { goto finish; }
+ } else {
+ pvMap = pObSnapshot->pmProcess;
+ pfnMap = (UTIL_VFSLINEFIXED_MAP_PFN_CB)MSysProcCpu_MapEntry;
+ }
+ nt = Util_VfsLineFixedMapCustom_Read(H, (UTIL_VFSLINEFIXED_PFN_CB)MSysProcCpu_ReadLine, pObSnapshot, MPERF_LINE_LENGTH, MPERF_LINE_HEADER,
+ pvMap, c, pfnMap, pb, cb, pcbRead, cbOffset);
+finish:
+ Ob_DECREF(pObSnapshot);
+ return nt;
+}
+
+BOOL MSysProcCpu_List(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _Inout_ PHANDLE pFileList)
+{
+ PMCPU_SNAPSHOT pObSnapshot;
+ QWORD cbFile;
+ if(ctxP->uszPath[0]) { return FALSE; }
+ VMMDLL_VfsList_AddFile(pFileList, "readme.txt", sizeof(szMPerfReadme) - 1, NULL);
+ if((pObSnapshot = MSysProcCpu_GetSnapshot(H, ctxP))) {
+ cbFile = UTIL_VFSLINEFIXED_LINECOUNT(H, ObMap_Size(pObSnapshot->pmProcess)) * MPERF_LINE_LENGTH;
+ VMMDLL_VfsList_AddFile(pFileList, "cpu.txt", cbFile, NULL);
+ VMMDLL_VfsList_AddFile(pFileList, "cpu-time.txt", cbFile, NULL);
+ if(H->dev.fVolatile) { VMMDLL_VfsList_AddFile(pFileList, "cpu-usage.txt", cbFile, NULL); }
+ Ob_DECREF(pObSnapshot);
+ }
+ return TRUE;
+}
+
+VOID MSysProcCpu_Notify(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_ DWORD cbEvent)
+{
+ if((fEvent == VMMDLL_PLUGIN_NOTIFY_REFRESH_FAST) || (fEvent == VMMDLL_PLUGIN_NOTIFY_REFRESH_MEDIUM)) {
+ InterlockedIncrement64(&((PMCPU_CONTEXT)ctxP->ctxM)->qwRefresh);
+ }
+}
+
+VOID MSysProcCpu_Close(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_CONTEXT ctxP)
+{
+ Ob_DECREF(ctxP->ctxM);
+}
+
+VOID M_SysProcCpu_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pRI)
+{
+ PMCPU_CONTEXT pObContext;
+ if((pRI->magic != VMMDLL_PLUGIN_REGINFO_MAGIC) || (pRI->wVersion != VMMDLL_PLUGIN_REGINFO_VERSION)) { return; }
+ if((pRI->tpSystem != VMMDLL_SYSTEM_WINDOWS_64) && (pRI->tpSystem != VMMDLL_SYSTEM_WINDOWS_32)) { return; }
+ if(pRI->sysinfo.dwVersionBuild < 6000) { return; } // Vista+ required.
+ if(H->vmm.tpMemoryModel == VMM_MEMORYMODEL_ARM64) { return; }
+ if(!(pObContext = Ob_AllocEx(H, OB_TAG_MOD_CPU, LMEM_ZEROINIT, sizeof(*pObContext), (OB_CLEANUP_CB)MSysProcCpu_ContextCleanupCB, NULL))) { return; }
+ InitializeSRWLock(&pObContext->LockSRW);
+ pRI->reg_info.ctxM = (PVMMDLL_PLUGIN_INTERNAL_CONTEXT)pObContext;
+ strcpy_s(pRI->reg_info.uszPathName, 128, "\\sys\\proc\\cpu");
+ pRI->reg_info.fRootModule = TRUE;
+ pRI->reg_fn.pfnList = MSysProcCpu_List;
+ pRI->reg_fn.pfnRead = MSysProcCpu_Read;
+ pRI->reg_fn.pfnNotify = MSysProcCpu_Notify;
+ pRI->reg_fn.pfnClose = MSysProcCpu_Close;
+ if(!pRI->pfnPluginManager_Register(H, pRI)) { Ob_DECREF(pObContext); }
+}
diff --git a/vmm/modules/modules_init.h b/vmm/modules/modules_init.h
index 70471ed..b87aa50 100644
--- a/vmm/modules/modules_init.h
+++ b/vmm/modules/modules_init.h
@@ -43,6 +43,7 @@ VOID M_SysNetDns_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pP
VOID M_SysObj_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo);
VOID M_SysPool_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo);
VOID M_SysProc_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo);
+VOID M_SysProcCpu_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo);
VOID M_SysSvc_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo);
VOID M_SysSyscall_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo);
VOID M_SysSysinfo_Initialize(_In_ VMM_HANDLE H, _Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo);
@@ -137,6 +138,7 @@ VOID(*g_pfnModulesAllInternal[])(_In_ VMM_HANDLE H, _In_ PVMMDLL_PLUGIN_REGINFO
M_SysObj_Initialize,
M_SysPool_Initialize,
M_SysProc_Initialize,
+ M_SysProcCpu_Initialize,
M_SysSvc_Initialize,
M_SysSyscall_Initialize,
M_SysSysinfo_Initialize,
diff --git a/vmm/ob/ob_tag.h b/vmm/ob/ob_tag.h
index 606983d..3ef0be6 100644
--- a/vmm/ob/ob_tag.h
+++ b/vmm/ob/ob_tag.h
@@ -43,6 +43,7 @@
#define OB_TAG_MAP_WEB 'Mweb'
#define OB_TAG_MAP_AMCACHE 'MAmC'
#define OB_TAG_MOD_CERTIFICATES 'mCer'
+#define OB_TAG_MOD_CPU 'mCpu'
#define OB_TAG_MOD_FCNTFS_CTX 'mFnt'
#define OB_TAG_MOD_FCTIMELINE 'mFtl'
#define OB_TAG_MOD_MINIDUMP_CTX 'mMDx'
diff --git a/vmm/version.h b/vmm/version.h
index b3afd27..92a0c2a 100644
--- a/vmm/version.h
+++ b/vmm/version.h
@@ -3,8 +3,8 @@
#define VERSION_MAJOR 5
#define VERSION_MINOR 18
-#define VERSION_REVISION 7
-#define VERSION_BUILD 246
+#define VERSION_REVISION 8
+#define VERSION_BUILD 247
#define VER_FILE_DESCRIPTION_STR "MemProcFS : Core"
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
diff --git a/vmm/vmm.vcxproj b/vmm/vmm.vcxproj
index 480f7f0..b4934d8 100644
--- a/vmm/vmm.vcxproj
+++ b/vmm/vmm.vcxproj
@@ -441,6 +441,7 @@
+
diff --git a/vmm/vmm.vcxproj.filters b/vmm/vmm.vcxproj.filters
index 2af2209..8fab2b4 100644
--- a/vmm/vmm.vcxproj.filters
+++ b/vmm/vmm.vcxproj.filters
@@ -545,6 +545,9 @@
Source Files\modules
+
+ Source Files\modules
+
diff --git a/vmmpyc/version.h b/vmmpyc/version.h
index 8b332aa..9bad670 100644
--- a/vmmpyc/version.h
+++ b/vmmpyc/version.h
@@ -3,8 +3,8 @@
#define VERSION_MAJOR 5
#define VERSION_MINOR 18
-#define VERSION_REVISION 7
-#define VERSION_BUILD 246
+#define VERSION_REVISION 8
+#define VERSION_BUILD 247
#define VER_FILE_DESCRIPTION_STR "MemProcFS : Python API"
#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
diff --git a/vmmrust/leechcore_example/Cargo.toml b/vmmrust/leechcore_example/Cargo.toml
index b7f5912..1b5b3e1 100644
--- a/vmmrust/leechcore_example/Cargo.toml
+++ b/vmmrust/leechcore_example/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "leechcore_example"
-version = "5.18.7"
+version = "5.18.8"
edition = "2021"
publish = false
diff --git a/vmmrust/m_example_plugin/Cargo.toml b/vmmrust/m_example_plugin/Cargo.toml
index 0d8f313..7588ccd 100644
--- a/vmmrust/m_example_plugin/Cargo.toml
+++ b/vmmrust/m_example_plugin/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "m_example_plugin"
-version = "5.18.7"
+version = "5.18.8"
edition = "2021"
publish = false
diff --git a/vmmrust/memprocfs/Cargo.toml b/vmmrust/memprocfs/Cargo.toml
index eda6e28..b8854a9 100644
--- a/vmmrust/memprocfs/Cargo.toml
+++ b/vmmrust/memprocfs/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "memprocfs"
-version = "5.18.7"
+version = "5.18.8"
edition = "2021"
description = "MemProcFS - Physical Memory Analysis Framework"
documentation = "https://docs.rs/memprocfs"
diff --git a/vmmrust/memprocfs_example/Cargo.toml b/vmmrust/memprocfs_example/Cargo.toml
index 8fe3ade..8ce047b 100644
--- a/vmmrust/memprocfs_example/Cargo.toml
+++ b/vmmrust/memprocfs_example/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "memprocfs_example"
-version = "5.18.7"
+version = "5.18.8"
edition = "2021"
publish = false
diff --git a/vmmsharp/vmmsharp/Properties/AssemblyInfo.cs b/vmmsharp/vmmsharp/Properties/AssemblyInfo.cs
index 7bb5106..10faa1d 100644
--- a/vmmsharp/vmmsharp/Properties/AssemblyInfo.cs
+++ b/vmmsharp/vmmsharp/Properties/AssemblyInfo.cs
@@ -32,5 +32,5 @@ using System.Runtime.Versioning;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("5.18.7.246")]
-[assembly: AssemblyFileVersion("5.18.7.246")]
+[assembly: AssemblyVersion("5.18.8.247")]
+[assembly: AssemblyFileVersion("5.18.8.247")]
diff --git a/vmmsharp/vmmsharp/vmmsharp.csproj b/vmmsharp/vmmsharp/vmmsharp.csproj
index e7768f1..6127fb1 100644
--- a/vmmsharp/vmmsharp/vmmsharp.csproj
+++ b/vmmsharp/vmmsharp/vmmsharp.csproj
@@ -109,7 +109,7 @@
- 5.18.7
+ 5.18.8
https://github.com/ufrisk/MemProcFS
git
LICENSE