mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 08:01:56 +00:00
* fix(persistence): identify reused Windows profile-owner processes * fix(persistence): preserve absent-owner recovery without native registry * ci: build Windows registry before native profile identity checks * fix(cli): include native profile-owner dependencies in typecheck * test: register native profile owner test in Windows PR lane * test: use resilient Windows profile-owner cleanup --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
465 lines
18 KiB
Diff
465 lines
18 KiB
Diff
diff --git a/binding.gyp b/binding.gyp
|
|
index 855bd4b86f0a3c18c7594212c0e42b6e35bc4001..0bb2af7923b6e6f1f0da40cae8067304cd1fea14 100644
|
|
--- a/binding.gyp
|
|
+++ b/binding.gyp
|
|
@@ -3,7 +3,6 @@
|
|
{
|
|
"target_name": "windows_process_tree",
|
|
"dependencies": [
|
|
- "<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
|
|
],
|
|
"conditions": [
|
|
['OS=="win"', {
|
|
@@ -14,13 +13,12 @@
|
|
"src/process_worker.cc",
|
|
"src/process_commandline.cc"
|
|
],
|
|
- "include_dirs": [],
|
|
+ "include_dirs": ["deps/node-addon-api"],
|
|
+ "defines": ["NAPI_CPP_EXCEPTIONS", "_HAS_EXCEPTIONS=1"],
|
|
"libraries": [ 'psapi.lib' ],
|
|
- "msvs_configuration_attributes": {
|
|
- "SpectreMitigation": "Spectre"
|
|
- },
|
|
"msvs_settings": {
|
|
"VCCLCompilerTool": {
|
|
+ "ExceptionHandling": 1,
|
|
"AdditionalOptions": [
|
|
"/guard:cf",
|
|
"/sdl",
|
|
diff --git a/lib/index.js b/lib/index.js
|
|
index e586cd6ead522a4ff060f5338201f45e3467d639..0ee62c35558ff40e2298c464fdf6e9485f9d181d 100644
|
|
--- a/lib/index.js
|
|
+++ b/lib/index.js
|
|
@@ -7,11 +7,14 @@
|
|
exports.getAllProcesses = exports.getProcessTree = exports.getProcessCpuUsage = exports.getProcessList = exports.filterProcessList = exports.buildProcessTree = exports.ProcessDataFlag = void 0;
|
|
const util_1 = require("util");
|
|
const native = process.platform === 'win32' ? require('../build/Release/windows_process_tree.node') : undefined;
|
|
+exports.getProcessCreationTime = native === undefined ? undefined : native.getProcessCreationTime;
|
|
+exports.supportedProcessDataFlags = native === undefined ? undefined : native.supportedProcessDataFlags;
|
|
var ProcessDataFlag;
|
|
(function (ProcessDataFlag) {
|
|
ProcessDataFlag[ProcessDataFlag["None"] = 0] = "None";
|
|
ProcessDataFlag[ProcessDataFlag["Memory"] = 1] = "Memory";
|
|
ProcessDataFlag[ProcessDataFlag["CommandLine"] = 2] = "CommandLine";
|
|
+ ProcessDataFlag[ProcessDataFlag["CreationTime"] = 4] = "CreationTime";
|
|
})(ProcessDataFlag = exports.ProcessDataFlag || (exports.ProcessDataFlag = {}));
|
|
// requestInProgress is used for any function that uses CreateToolhelp32Snapshot, as multiple calls
|
|
// to this cannot be done at the same time.
|
|
@@ -66,11 +69,12 @@
|
|
// • the properties are inlined/splatted
|
|
// • the 'ppid' field is omitted
|
|
// • the depth of the tree is limited by `maxDepth`
|
|
- const buildNode = ({ info: { pid, name, memory, commandLine }, children }, depth) => ({
|
|
+ const buildNode = ({ info: { pid, name, memory, commandLine, creationTimeMs }, children }, depth) => ({
|
|
pid,
|
|
name,
|
|
memory,
|
|
commandLine,
|
|
+ creationTimeMs,
|
|
children: depth > 0 ? children.map(c => buildNode(c, depth - 1)) : [],
|
|
});
|
|
return buildNode(root, maxDepth);
|
|
diff --git a/lib/index.ts b/lib/index.ts
|
|
index 7b53aad05c3682a5c7d814d81a39c3f391ae97e3..284dae185b56241244b4d6bcab9e08f7530b8cf3 100644
|
|
--- a/lib/index.ts
|
|
+++ b/lib/index.ts
|
|
@@ -6,12 +6,16 @@
|
|
import { promisify } from 'util';
|
|
|
|
const native = process.platform === 'win32' ? require('../build/Release/windows_process_tree.node') : undefined;
|
|
+export const getProcessCreationTime: ((pid: number) => number | undefined) | undefined = native?.getProcessCreationTime;
|
|
+/** The flag bits this compiled addon reports; undefined off win32. */
|
|
+export const supportedProcessDataFlags: number | undefined = native?.supportedProcessDataFlags;
|
|
import { IProcessInfo, IProcessTreeNode, IProcessCpuInfo } from '@vscode/windows-process-tree';
|
|
|
|
export enum ProcessDataFlag {
|
|
None = 0,
|
|
Memory = 1,
|
|
- CommandLine = 2
|
|
+ CommandLine = 2,
|
|
+ CreationTime = 4
|
|
}
|
|
|
|
type RequestCallback = (processList: IProcessInfo[]) => void;
|
|
@@ -81,11 +85,12 @@
|
|
// • the properties are inlined/splatted
|
|
// • the 'ppid' field is omitted
|
|
// • the depth of the tree is limited by `maxDepth`
|
|
- const buildNode = ({ info: { pid, name, memory, commandLine }, children }: IProcessInfoNode, depth: number): IProcessTreeNode => ({
|
|
+ const buildNode = ({ info: { pid, name, memory, commandLine, creationTimeMs }, children }: IProcessInfoNode, depth: number): IProcessTreeNode => ({
|
|
pid,
|
|
name,
|
|
memory,
|
|
commandLine,
|
|
+ creationTimeMs,
|
|
children: depth > 0 ? children.map(c => buildNode(c, depth - 1)) : [],
|
|
});
|
|
|
|
diff --git a/src/addon.cc b/src/addon.cc
|
|
index 5253960ad97496b53c4a02572b64b270302af22f..a801526e20af72b6442c769a8ba1640386c23f46 100644
|
|
--- a/src/addon.cc
|
|
+++ b/src/addon.cc
|
|
@@ -50,9 +50,32 @@
|
|
worker->Queue();
|
|
}
|
|
|
|
+Napi::Value ReadProcessCreationTime(const Napi::CallbackInfo& args) {
|
|
+ Napi::Env env(args.Env());
|
|
+ if (args.Length() != 1 || !args[0].IsNumber()) {
|
|
+ return env.Undefined();
|
|
+ }
|
|
+ const double pid = args[0].As<Napi::Number>().DoubleValue();
|
|
+ if (!(pid >= 1 && pid <= MAXDWORD) || pid != static_cast<DWORD>(pid)) {
|
|
+ return env.Undefined();
|
|
+ }
|
|
+ ProcessInfo pinfo{};
|
|
+ pinfo.pid = static_cast<DWORD>(pid);
|
|
+ GetProcessCreationTime(pinfo);
|
|
+ if (pinfo.creationTimeMs == 0) {
|
|
+ return env.Undefined();
|
|
+ }
|
|
+ return Napi::Number::New(env, static_cast<double>(pinfo.creationTimeMs));
|
|
+}
|
|
+
|
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
+ exports.Set("getProcessCreationTime", Napi::Function::New(env, ReadProcessCreationTime));
|
|
exports.Set("getProcessList", Napi::Function::New(env, GetProcessList));
|
|
exports.Set("getProcessCpuUsage", Napi::Function::New(env, GetProcessCpuUsage));
|
|
+ // Lets a caller prove THIS BINARY understands CREATIONTIME. The JS enum is
|
|
+ // patched source and says nothing about what the .node was compiled from.
|
|
+ exports.Set("supportedProcessDataFlags",
|
|
+ Napi::Number::New(env, MEMORY | COMMANDLINE | CREATIONTIME));
|
|
return exports;
|
|
}
|
|
|
|
diff --git a/src/process.cc b/src/process.cc
|
|
index 3eea92077c4d1d433119361d5c432881859131e9..22a47421da919c76e2194280974d39c2287b098d 100644
|
|
--- a/src/process.cc
|
|
+++ b/src/process.cc
|
|
@@ -21,7 +21,8 @@ uint32_t GetRawProcessList(std::vector<ProcessInfo>& process_info,
|
|
if (Process32First(snapshot_handle, &process_entry)) {
|
|
do {
|
|
if (process_entry.th32ProcessID != 0) {
|
|
- ProcessInfo pinfo;
|
|
+ // Value-initialize: `memory` is otherwise stack garbage when the flag is unset.
|
|
+ ProcessInfo pinfo{};
|
|
pinfo.pid = process_entry.th32ProcessID;
|
|
pinfo.ppid = process_entry.th32ParentProcessID;
|
|
|
|
@@ -33,23 +34,51 @@ uint32_t GetRawProcessList(std::vector<ProcessInfo>& process_info,
|
|
GetProcessCommandLine(pinfo);
|
|
}
|
|
|
|
+ if (CREATIONTIME & process_data_flags) {
|
|
+ GetProcessCreationTime(pinfo);
|
|
+ }
|
|
+
|
|
strcpy(pinfo.name, process_entry.szExeFile);
|
|
process_info.push_back(std::move(pinfo));
|
|
process_count++;
|
|
}
|
|
- } while (process_count < 1024 && Process32Next(snapshot_handle, &process_entry));
|
|
+ } while (Process32Next(snapshot_handle, &process_entry));
|
|
}
|
|
|
|
CloseHandle(snapshot_handle);
|
|
return process_count;
|
|
}
|
|
|
|
+void GetProcessCreationTime(ProcessInfo& process_info) {
|
|
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_info.pid);
|
|
+ if (hProcess == NULL) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ FILETIME creationTime, exitTime, kernelTime, userTime;
|
|
+ if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {
|
|
+ ULARGE_INTEGER timestamp;
|
|
+ timestamp.LowPart = creationTime.dwLowDateTime;
|
|
+ timestamp.HighPart = creationTime.dwHighDateTime;
|
|
+ constexpr ULONGLONG WINDOWS_EPOCH_OFFSET_100NS = 116444736000000000ULL;
|
|
+ constexpr ULONGLONG HUNDRED_NS_PER_MILLISECOND = 10000ULL;
|
|
+ if (timestamp.QuadPart >= WINDOWS_EPOCH_OFFSET_100NS) {
|
|
+ process_info.creationTimeMs =
|
|
+ (timestamp.QuadPart - WINDOWS_EPOCH_OFFSET_100NS) / HUNDRED_NS_PER_MILLISECOND;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ CloseHandle(hProcess);
|
|
+}
|
|
+
|
|
void GetProcessMemoryUsage(ProcessInfo& process_info) {
|
|
DWORD pid = process_info.pid;
|
|
HANDLE hProcess;
|
|
PROCESS_MEMORY_COUNTERS pmc;
|
|
|
|
- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
|
|
+ // PROCESS_VM_READ is never used here -- GetProcessMemoryInfo reads counters the
|
|
+ // kernel keeps, not the address space -- and acquiring it is what EDR scores.
|
|
+ hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
|
|
|
if (hProcess == NULL) {
|
|
return;
|
|
@@ -81,7 +110,8 @@ void GetCpuUsage(Cpu& cpu_info, bool first_pass) {
|
|
DWORD pid = cpu_info.pid;
|
|
HANDLE hProcess;
|
|
|
|
- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
|
|
+ // GetProcessTimes needs no more than PROCESS_QUERY_LIMITED_INFORMATION.
|
|
+ hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
|
|
|
if (hProcess == NULL) {
|
|
return;
|
|
diff --git a/src/process.h b/src/process.h
|
|
index 82f8e4bcfa742551e5d874a7632736a7611d7aa7..78d1d2c3b2360ed06fd624b4cb2f5042510f7a77 100644
|
|
--- a/src/process.h
|
|
+++ b/src/process.h
|
|
@@ -22,18 +22,22 @@ struct ProcessInfo {
|
|
DWORD ppid;
|
|
DWORD memory; // Reported in bytes
|
|
std::string commandLine;
|
|
+ ULONGLONG creationTimeMs;
|
|
};
|
|
|
|
enum ProcessDataFlags {
|
|
NONE = 0,
|
|
MEMORY = 1,
|
|
- COMMANDLINE = 2
|
|
+ COMMANDLINE = 2,
|
|
+ CREATIONTIME = 4
|
|
};
|
|
|
|
uint32_t GetRawProcessList(std::vector<ProcessInfo>& process_info, DWORD flags);
|
|
|
|
void GetProcessMemoryUsage(ProcessInfo& process_info);
|
|
|
|
+void GetProcessCreationTime(ProcessInfo& process_info);
|
|
+
|
|
void GetCpuUsage(Cpu& cpu_info, bool first_run);
|
|
|
|
#endif // SRC_PROCESS_H_
|
|
diff --git a/src/process_commandline.cc b/src/process_commandline.cc
|
|
index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3210c3cfd 100644
|
|
--- a/src/process_commandline.cc
|
|
+++ b/src/process_commandline.cc
|
|
@@ -7,61 +7,119 @@
|
|
#include "process_commandline.h"
|
|
#include <windows.h>
|
|
#include <winternl.h>
|
|
-#include <iostream>
|
|
+#include <vector>
|
|
|
|
-bool GetProcessCommandLine(ProcessInfo& process_info) {
|
|
- HINSTANCE ntdll = GetModuleHandleW(L"ntdll.dll");
|
|
+namespace {
|
|
+
|
|
+// Windows 8.1 and later hand back a process's command line as a UNICODE_STRING
|
|
+// the kernel builds, needing only PROCESS_QUERY_LIMITED_INFORMATION.
|
|
+//
|
|
+// There is deliberately no PEB fallback. Reading the command line out of the
|
|
+// target's address space -- opening it for VM reads and then chaining
|
|
+// memory reads across every pid on a timer -- is the credential-dumping
|
|
+// primitive this reader exists to not perform, so it is absent from the binary
|
|
+// rather than one anomalous NTSTATUS away. Electron's floor is Windows 10, so
|
|
+// every OS Orca supports has this class; if a hooked ntdll refuses it anyway,
|
|
+// the command line comes back empty, which callers already handle, instead of
|
|
+// silently reinstating the primitive on exactly the instrumented machines this
|
|
+// reader was written for.
|
|
+const ULONG kProcessCommandLineInformation = 60;
|
|
+
|
|
+const NTSTATUS kStatusInfoLengthMismatch = static_cast<NTSTATUS>(0xC0000004L);
|
|
+const NTSTATUS kStatusBufferTooSmall = static_cast<NTSTATUS>(0xC0000023L);
|
|
+
|
|
+// A command line is a UNICODE_STRING, whose Length is a USHORT, so the kernel
|
|
+// can never need more than the header plus 64 KiB. Refusing anything larger
|
|
+// keeps a bogus size from throwing bad_alloc out of a scan that has already
|
|
+// walked most of the table.
|
|
+const ULONG kMaxCommandLineBytes = sizeof(UNICODE_STRING) + 0xFFFF + sizeof(wchar_t);
|
|
+
|
|
+// winternl.h's PROCESSINFOCLASS does not name class 60 and its enumerator range
|
|
+// stops far short of it, so the class travels as a ULONG rather than a cast enum.
|
|
+typedef NTSTATUS(NTAPI* NtQueryInformationProcessFn)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
|
+
|
|
+// ntdll ships no import library for this entry point; it has to be resolved.
|
|
+NtQueryInformationProcessFn ResolveNtQueryInformationProcess() {
|
|
+ HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
|
|
if (!ntdll) {
|
|
+ return nullptr;
|
|
+ }
|
|
+ return reinterpret_cast<NtQueryInformationProcessFn>(
|
|
+ GetProcAddress(ntdll, "NtQueryInformationProcess"));
|
|
+}
|
|
+
|
|
+NtQueryInformationProcessFn NtQueryInformationProcessEntry() {
|
|
+ static NtQueryInformationProcessFn entry = ResolveNtQueryInformationProcess();
|
|
+ return entry;
|
|
+}
|
|
+
|
|
+bool StoreCommandLineUtf8(ProcessInfo& process_info, const wchar_t* data, size_t wide_length) {
|
|
+ if (wide_length == 0) {
|
|
+ return false;
|
|
+ }
|
|
+ int length = static_cast<int>(wide_length);
|
|
+ int charcount = WideCharToMultiByte(CP_UTF8, 0, data, length, NULL, 0, NULL, NULL);
|
|
+ if (!charcount) {
|
|
return false;
|
|
}
|
|
+ process_info.commandLine.resize(static_cast<size_t>(charcount));
|
|
+ WideCharToMultiByte(CP_UTF8, 0, data, length, &process_info.commandLine[0], charcount, NULL,
|
|
+ NULL);
|
|
+ return true;
|
|
+}
|
|
+
|
|
+} // namespace
|
|
|
|
- decltype(NtQueryInformationProcess)* nt_query_information_process =
|
|
- reinterpret_cast<decltype(NtQueryInformationProcess)*>(
|
|
- GetProcAddress(ntdll, "NtQueryInformationProcess"));
|
|
+bool GetProcessCommandLine(ProcessInfo& process_info) {
|
|
+ NtQueryInformationProcessFn query = NtQueryInformationProcessEntry();
|
|
+ if (!query) {
|
|
+ return false;
|
|
+ }
|
|
|
|
- if (!nt_query_information_process) {
|
|
+ HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_info.pid);
|
|
+ if (process == NULL) {
|
|
return false;
|
|
}
|
|
|
|
- PROCESS_BASIC_INFORMATION pbi{};
|
|
- PEB peb = {NULL};
|
|
- RTL_USER_PROCESS_PARAMETERS process_parameters = {NULL};
|
|
+ ULONG size = 0;
|
|
+ NTSTATUS status = query(process, kProcessCommandLineInformation, nullptr, 0, &size);
|
|
+ if (NT_SUCCESS(status)) {
|
|
+ // Nothing was written, so there is no command line to read.
|
|
+ CloseHandle(process);
|
|
+ return false;
|
|
+ }
|
|
+ if (status != kStatusInfoLengthMismatch && status != kStatusBufferTooSmall) {
|
|
+ CloseHandle(process);
|
|
+ return false;
|
|
+ }
|
|
+ if (size < sizeof(UNICODE_STRING) || size > kMaxCommandLineBytes) {
|
|
+ CloseHandle(process);
|
|
+ return false;
|
|
+ }
|
|
|
|
- // Get process handle
|
|
- DWORD pid = process_info.pid;
|
|
- HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
|
|
- if (hProcess == INVALID_HANDLE_VALUE) {
|
|
+ std::vector<unsigned char> buffer(size);
|
|
+ status = query(process, kProcessCommandLineInformation, &buffer[0], size, &size);
|
|
+ CloseHandle(process);
|
|
+ if (!NT_SUCCESS(status)) {
|
|
return false;
|
|
}
|
|
|
|
- // Get Process Environment Block (PEB)
|
|
- NTSTATUS status = nt_query_information_process(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr);
|
|
- if (NT_SUCCESS(status) && pbi.PebBaseAddress) {
|
|
- // Read PEB
|
|
- if (ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), nullptr)) {
|
|
- // Read the processs parameters
|
|
- if (ReadProcessMemory(hProcess, peb.ProcessParameters, &process_parameters, sizeof(RTL_USER_PROCESS_PARAMETERS), nullptr)) {
|
|
- if (process_parameters.CommandLine.Length > 0) {
|
|
- std::wstring buffer;
|
|
- buffer.resize(process_parameters.CommandLine.Length / sizeof(wchar_t));
|
|
- if (ReadProcessMemory(hProcess, process_parameters.CommandLine.Buffer, &buffer[0], process_parameters.CommandLine.Length, nullptr)) {
|
|
- int wide_length = static_cast<int>(buffer.length());
|
|
- int charcount = WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length,
|
|
- NULL, 0, NULL, NULL);
|
|
- if (charcount) {
|
|
- process_info.commandLine.resize(static_cast<size_t>(charcount));
|
|
- WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length,
|
|
- &process_info.commandLine[0], charcount,
|
|
- NULL, NULL);
|
|
- }
|
|
- CloseHandle(hProcess);
|
|
- return true;
|
|
- }
|
|
- }
|
|
- }
|
|
- }
|
|
+ // Header and characters arrive in one allocation, but treat the header as
|
|
+ // untrusted: a hooked ntdll is the case this reader is written for, and an
|
|
+ // unchecked Buffer/Length here would be an over-read encoded straight into JS.
|
|
+ // Bound against buffer.size(), never `size` -- the second query overwrote it.
|
|
+ const UNICODE_STRING* command_line = reinterpret_cast<const UNICODE_STRING*>(&buffer[0]);
|
|
+ const unsigned char* begin = &buffer[0];
|
|
+ const unsigned char* end = begin + buffer.size();
|
|
+ const unsigned char* chars = reinterpret_cast<const unsigned char*>(command_line->Buffer);
|
|
+ if (chars == nullptr || chars < begin + sizeof(UNICODE_STRING) || chars > end ||
|
|
+ command_line->Length > static_cast<ULONG>(end - chars)) {
|
|
+ return false;
|
|
}
|
|
|
|
- CloseHandle(hProcess);
|
|
- return false;
|
|
+ // True only when a command line was actually stored, so "empty" and "not
|
|
+ // recovered" stay the same answer they were before this reader replaced the
|
|
+ // PEB read. `src/process.cc` discards the result either way.
|
|
+ return StoreCommandLineUtf8(process_info, command_line->Buffer,
|
|
+ command_line->Length / sizeof(wchar_t));
|
|
}
|
|
diff --git a/src/process_worker.cc b/src/process_worker.cc
|
|
index c9e3457a759c1acaa2644231a4917d45aed951f8..3f26a354477f062b34bd31fbd17be529e6a2fd7a 100644
|
|
--- a/src/process_worker.cc
|
|
+++ b/src/process_worker.cc
|
|
@@ -43,6 +43,11 @@ void GetProcessesWorker::OnOK() {
|
|
Napi::String::New(env, pinfo.commandLine));
|
|
}
|
|
|
|
+ if ((CREATIONTIME & process_data_flags_) && pinfo.creationTimeMs != 0) {
|
|
+ object.Set("creationTimeMs",
|
|
+ Napi::Number::New(env, static_cast<double>(pinfo.creationTimeMs)));
|
|
+ }
|
|
+
|
|
result.Set(i, object);
|
|
}
|
|
|
|
diff --git a/typings/windows-process-tree.d.ts b/typings/windows-process-tree.d.ts
|
|
index 70e242b123e76d43c452007be1ce92a6d224c8bb..b1d0a53c0c18cc16531b394c19bb256bdbaf782f 100644
|
|
--- a/typings/windows-process-tree.d.ts
|
|
+++ b/typings/windows-process-tree.d.ts
|
|
@@ -7,8 +7,17 @@
|
|
export enum ProcessDataFlag {
|
|
None = 0,
|
|
Memory = 1,
|
|
- CommandLine = 2
|
|
+ CommandLine = 2,
|
|
+ CreationTime = 4
|
|
}
|
|
+
|
|
+ /**
|
|
+ * The flag bits the compiled addon actually understands, or undefined off
|
|
+ * win32. `ProcessDataFlag` above is source; this is what the binary reports,
|
|
+ * so it is the only way to tell a patched build from a stale prebuilt.
|
|
+ */
|
|
+ export const supportedProcessDataFlags: number | undefined;
|
|
+ export const getProcessCreationTime: ((pid: number) => number | undefined) | undefined;
|
|
|
|
export interface IProcessInfo {
|
|
pid: number;
|
|
@@ -24,6 +33,9 @@
|
|
* The string returned is at most 512 chars, strings exceeding this length are truncated.
|
|
*/
|
|
commandLine?: string;
|
|
+
|
|
+ /** Process creation time in Unix milliseconds. */
|
|
+ creationTimeMs?: number;
|
|
}
|
|
|
|
export interface IProcessCpuInfo extends IProcessInfo {
|
|
@@ -35,6 +47,7 @@
|
|
name: string;
|
|
memory?: number;
|
|
commandLine?: string;
|
|
+ creationTimeMs?: number;
|
|
children: IProcessTreeNode[];
|
|
}
|
|
|