diff --git a/src/libraries/Common/src/Interop/OpenBSD/Interop.Process.GetProcInfo.cs b/src/libraries/Common/src/Interop/OpenBSD/Interop.Process.GetProcInfo.cs
index 0a7dae2df64c73..e62c4e29f8d083 100644
--- a/src/libraries/Common/src/Interop/OpenBSD/Interop.Process.GetProcInfo.cs
+++ b/src/libraries/Common/src/Interop/OpenBSD/Interop.Process.GetProcInfo.cs
@@ -14,7 +14,11 @@ internal static partial class Process
// Constants from sys/sysctl.h
private const int CTL_KERN = 1;
private const int KERN_PROC = 66;
- private const int KERN_PROC_PID = 1;
+ private const int KERN_PROC_ALL = 0; // everything but kernel threads
+ private const int KERN_PROC_PID = 1; // by process id
+ private const int KERN_PROC_SHOW_THREADS = unchecked((int)0x40000000); // also return threads
+ private const int KERN_PROC_ARGS = 55; // node: proc args and env
+ private const int KERN_PROC_ARGV = 1; // KERN_PROC_ARGS subtype: argv
// Constants from sys/sysctl.h that determine the fixed-size members of kinfo_proc
private const int KI_NGROUPS = 16;
@@ -102,9 +106,9 @@ public unsafe struct @kinfo_proc
private LoginBuffer p_login; /* setlogin() name */
public int p_vm_rssize; /* SEGSZ_T: current resident set size in pages */
- private int p_vm_tsize; /* SEGSZ_T: text size (pages) */
- private int p_vm_dsize; /* SEGSZ_T: data size (pages) */
- private int p_vm_ssize; /* SEGSZ_T: stack size (pages) */
+ public int p_vm_tsize; /* SEGSZ_T: text size (pages) */
+ public int p_vm_dsize; /* SEGSZ_T: data size (pages) */
+ public int p_vm_ssize; /* SEGSZ_T: stack size (pages) */
private long p_uvalid; /* CHAR: following p_u* members are valid */
public ulong p_ustart_sec; /* STRUCT TIMEVAL: starting time. */
@@ -115,7 +119,7 @@ public unsafe struct @kinfo_proc
public uint p_ustime_sec; /* STRUCT TIMEVAL: system time. */
public uint p_ustime_usec; /* STRUCT TIMEVAL: system time. */
- private ulong p_uru_maxrss; /* LONG: max resident set size. */
+ public ulong p_uru_maxrss; /* LONG: max resident set size (kilobytes). */
private ulong p_uru_ixrss; /* LONG: integral shared memory size. */
private ulong p_uru_idrss; /* LONG: integral unshared data ". */
private ulong p_uru_isrss; /* LONG: integral unshared stack ". */
@@ -178,22 +182,29 @@ private struct NameBuffer
}
///
- /// Gets information about a single process by its PID.
+ /// Gets information about processes.
///
- /// The PID of the process.
+ /// The PID of the process to query, or 0 to enumerate all processes.
+ /// When querying a single process, also return its threads.
/// The number of kinfo_proc entries returned.
- public static unsafe kinfo_proc* GetProcInfo(int pid, out int count)
+ public static unsafe kinfo_proc* GetProcInfo(int pid, bool threads, out int count)
{
// OpenBSD's KERN_PROC sysctl mib carries the element size and count inline:
- // { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, sizeof(kinfo_proc), elem_count }.
- // A single PID returns at most one entry, so request a count of one.
- ReadOnlySpan sysctlName = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, sizeof(kinfo_proc), 1];
+ // { CTL_KERN, KERN_PROC, op, arg, sizeof(kinfo_proc), elem_count }.
+ int op = pid == 0
+ ? KERN_PROC_ALL
+ : KERN_PROC_PID | (threads ? KERN_PROC_SHOW_THREADS : 0);
+ int arg = pid == 0 ? 0 : pid;
+
+ // The kernel bounds the result by the supplied buffer size, so request the
+ // maximum element count and let Sysctl probe, allocate, and grow the buffer.
+ ReadOnlySpan sysctlName = [CTL_KERN, KERN_PROC, op, arg, sizeof(kinfo_proc), int.MaxValue];
byte* pBuffer = null;
uint bytesLength = 0;
Interop.Sys.Sysctl(sysctlName, ref pBuffer, ref bytesLength);
- count = (int)(bytesLength / sizeof(kinfo_proc));
+ count = (int)(bytesLength / (uint)sizeof(kinfo_proc));
// Buffer ownership transferred to the caller
return (kinfo_proc*)pBuffer;
diff --git a/src/libraries/Common/src/Interop/OpenBSD/Interop.Process.cs b/src/libraries/Common/src/Interop/OpenBSD/Interop.Process.cs
index 8ebd38d80f8d5a..9d508875c027fd 100644
--- a/src/libraries/Common/src/Interop/OpenBSD/Interop.Process.cs
+++ b/src/libraries/Common/src/Interop/OpenBSD/Interop.Process.cs
@@ -74,7 +74,77 @@ internal static unsafe int[] ListAllPids()
/// The PID of the process
public static unsafe string GetProcPath(int pid)
{
- // TODO
+ // OpenBSD has no KERN_PROC_PATHNAME. The closest available information is the
+ // process argv, whose first entry is the path the process was executed with.
+ ReadOnlySpan sysctlName = [CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV];
+
+ byte* pBuffer = null;
+ uint bytesLength = 0;
+ try
+ {
+ Interop.Sys.Sysctl(sysctlName, ref pBuffer, ref bytesLength);
+
+ if (pBuffer == null || bytesLength < (uint)sizeof(byte*))
+ {
+ return string.Empty;
+ }
+
+ // The kernel relocates the argv pointer array to point within the returned buffer.
+ byte* argv0 = ((byte**)pBuffer)[0];
+ return argv0 is null ? string.Empty : Utf8StringMarshaller.ConvertToManaged(argv0) ?? string.Empty;
+ }
+ finally
+ {
+ NativeMemory.Free(pBuffer);
+ }
+ }
+
+ ///
+ /// Attempts to recover a process name that was truncated in kinfo_proc.p_comm by reading
+ /// the full name from the process argv.
+ ///
+ /// The PID of the process.
+ /// The (possibly truncated) p_comm value used to validate the recovered name.
+ /// The full process name, or null if it could not be recovered.
+ private static unsafe string? GetUntruncatedProcessName(int pid, string prefix)
+ {
+ ReadOnlySpan sysctlName = [CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV];
+
+ byte* pBuffer = null;
+ uint bytesLength = 0;
+ try
+ {
+ Interop.Sys.Sysctl(sysctlName, ref pBuffer, ref bytesLength);
+
+ if (pBuffer == null || bytesLength < (uint)sizeof(byte*))
+ {
+ return null;
+ }
+
+ // The kernel relocates the argv pointer array to point within the returned buffer.
+ // For native executables the name is argv[0]; for scripts argv[0] is the interpreter
+ // and argv[1] is the script, so check the first two NULL-terminated arguments.
+ byte** argv = (byte**)pBuffer;
+ for (int i = 0; i < 2 && argv[i] is not null; i++)
+ {
+ string arg = Utf8StringMarshaller.ConvertToManaged(argv[i]) ?? string.Empty;
+
+ // Strip directory names.
+ int nameStart = arg.LastIndexOf('/') + 1;
+ string name = nameStart == 0 ? arg : arg.Substring(nameStart);
+
+ if (name.StartsWith(prefix, StringComparison.Ordinal))
+ {
+ return name;
+ }
+ }
+
+ return null;
+ }
+ finally
+ {
+ NativeMemory.Free(pBuffer);
+ }
}
///
@@ -85,7 +155,7 @@ public static unsafe string GetProcPath(int pid)
/// Returns a valid ProcessInfo struct for valid processes that the caller
/// has permission to access; otherwise, returns null
///
- public static unsafe ProcessInfo GetProcessInfoById(int pid)
+ public static unsafe ProcessInfo? GetProcessInfoById(int pid)
{
// Negative PIDs are invalid
ArgumentOutOfRangeException.ThrowIfNegative(pid);
@@ -95,7 +165,12 @@ public static unsafe ProcessInfo GetProcessInfoById(int pid)
kinfo_proc* kinfo = GetProcInfo(pid, true, out int count);
try
{
- ArgumentOutOfRangeException.ThrowIfLessThan(count, 1, nameof(pid));
+ // The process may have exited between the time its PID was enumerated and now,
+ // in which case no entries are returned. Report it as not found rather than failing.
+ if (count < 1)
+ {
+ return null;
+ }
var process = new ReadOnlySpan(kinfo, count);
@@ -103,19 +178,45 @@ public static unsafe ProcessInfo GetProcessInfoById(int pid)
info = new ProcessInfo();
info.ProcessName = Utf8StringMarshaller.ConvertToManaged(kinfo->p_comm)!;
+
+ // p_comm is limited to KI_MAXCOMLEN - 1 characters. When the name is at that
+ // limit it may be truncated, so try to recover the full name from the process argv.
+ if (info.ProcessName.Length >= KI_MAXCOMLEN - 1)
+ {
+ info.ProcessName = GetUntruncatedProcessName(pid, info.ProcessName) ?? info.ProcessName;
+ }
+
info.BasePriority = kinfo->p_nice;
- info.VirtualBytes = (long)kinfo->p_vm_map_size;
+
+ // OpenBSD's KERN_PROC sysctl always reports p_vm_map_size as 0, so derive the
+ // virtual size from the text, data, and stack segment sizes instead.
+ long pageSize = Environment.SystemPageSize;
+ info.VirtualBytes = ((long)kinfo->p_vm_tsize + kinfo->p_vm_dsize + kinfo->p_vm_ssize) * pageSize;
+ // OpenBSD does not track a separate peak virtual size; report the current size.
+ info.VirtualBytesPeak = info.VirtualBytes;
info.WorkingSet = kinfo->p_vm_rssize;
+ // p_uru_maxrss is the peak resident set size, reported in kilobytes.
+ info.WorkingSetPeak = (long)kinfo->p_uru_maxrss * 1024;
+ // OpenBSD does not expose a private byte count; approximate it with the
+ // anonymous (data + stack) segment sizes.
+ info.PrivateBytes = ((long)kinfo->p_vm_dsize + kinfo->p_vm_ssize) * pageSize;
info.SessionId = kinfo->p_sid;
for (int i = 0; i < process.Length; i++)
{
+ // KERN_PROC_SHOW_THREADS returns a process-summary entry with p_tid == -1
+ // ahead of the real per-thread entries. Skip it so only actual threads are reported.
+ if (process[i].p_tid < 0)
+ {
+ continue;
+ }
+
var ti = new ThreadInfo()
{
_processId = pid,
_threadId = (ulong)process[i].p_tid,
_basePriority = process[i].p_nice,
- _startAddress = // TODO: this doesn't exist on OpenBSD
+ _startAddress = null // OpenBSD's kinfo_proc does not expose a thread start address.
};
info._threadInfoList.Add(ti);
}
@@ -152,7 +253,7 @@ public static unsafe proc_stats GetThreadInfo(int pid, int tid)
ret.startTime = (int)info->p_ustart_sec;
ret.nice = info->p_nice;
ret.userTime = (ulong)info->p_uutime_sec * SecondsToNanoseconds + (ulong)info->p_uutime_usec * MicroSecondsToNanoSeconds;
- ret.systemTime = (ulong)info->p_uutime_sec * SecondsToNanoseconds + (ulong)info->p_uutime_usec * MicroSecondsToNanoSeconds;
+ ret.systemTime = (ulong)info->p_ustime_sec * SecondsToNanoseconds + (ulong)info->p_ustime_usec * MicroSecondsToNanoSeconds;
}
else
{
@@ -164,7 +265,7 @@ public static unsafe proc_stats GetThreadInfo(int pid, int tid)
ret.startTime = (int)list[i].p_ustart_sec;
ret.nice = list[i].p_nice;
ret.userTime = (ulong)list[i].p_uutime_sec * SecondsToNanoseconds + (ulong)list[i].p_uutime_usec * MicroSecondsToNanoSeconds;
- ret.systemTime = (ulong)list[i].p_uutime_sec * SecondsToNanoseconds + (ulong)list[i].p_uutime_usec * MicroSecondsToNanoSeconds;
+ ret.systemTime = (ulong)list[i].p_ustime_sec * SecondsToNanoseconds + (ulong)list[i].p_ustime_usec * MicroSecondsToNanoSeconds;
break;
}
}
diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
index a1648c1da6d8b2..7e352915ac39e3 100644
--- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
+++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs
@@ -43,6 +43,7 @@ public static partial class PlatformDetection
public static bool IsNotCoreClrInterpreter => !IsCoreClrInterpreter;
public static bool IsFreeBSD => RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD"));
public static bool IsNetBSD => RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD"));
+ public static bool IsOpenBSD => RuntimeInformation.IsOSPlatform(OSPlatform.Create("OPENBSD"));
public static bool IsAndroid => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID"));
public static bool IsNotAndroid => !IsAndroid;
public static bool IsAndroidX86 => IsAndroid && IsX86Process;
@@ -64,7 +65,7 @@ public static partial class PlatformDetection
public static bool IsAppleMobile => IsMacCatalyst || IsiOS || IstvOS;
public static bool IsNotAppleMobile => !IsAppleMobile;
public static bool IsNotNetFramework => !IsNetFramework;
- public static bool IsBsdLike => IsApplePlatform || IsFreeBSD || IsNetBSD;
+ public static bool IsBsdLike => IsApplePlatform || IsFreeBSD || IsNetBSD || IsOpenBSD;
public static bool IsArmProcess => RuntimeInformation.ProcessArchitecture == Architecture.Arm;
public static bool IsNotArmProcess => !IsArmProcess;
diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.OpenBSD.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.OpenBSD.cs
index bfb355e51fd9de..19e3933ff2e9f3 100644
--- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.OpenBSD.cs
+++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.OpenBSD.cs
@@ -37,8 +37,13 @@ internal static string GetProcPath(int processId)
ArgumentOutOfRangeException.ThrowIfNegative(pid);
// Try to get the task info. This can fail if the user permissions don't permit
- // this user context to query the specified process
- ProcessInfo iinfo = Interop.Process.GetProcessInfoById(pid);
+ // this user context to query the specified process, or if the process has exited.
+ ProcessInfo? iinfo = Interop.Process.GetProcessInfoById(pid);
+ if (iinfo is null)
+ {
+ return null;
+ }
+
if (processNameFilter != null && !processNameFilter.Equals(iinfo.ProcessName, StringComparison.OrdinalIgnoreCase))
{
return null;
@@ -50,7 +55,10 @@ internal static string GetProcPath(int processId)
ProcessName = iinfo.ProcessName,
BasePriority = iinfo.BasePriority,
VirtualBytes = iinfo.VirtualBytes,
+ VirtualBytesPeak = iinfo.VirtualBytesPeak,
WorkingSet = iinfo.WorkingSet,
+ WorkingSetPeak = iinfo.WorkingSetPeak,
+ PrivateBytes = iinfo.PrivateBytes,
SessionId = iinfo.SessionId,
};
diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
index 89d6c78ec7d025..a8f25b6f282d98 100644
--- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
+++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs
@@ -887,7 +887,7 @@ public void TestMaxWorkingSet()
Assert.InRange((long)p.MinWorkingSet, 0, long.MaxValue);
}
- if (OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD() || PlatformDetection.IsSunOS) {
+ if (OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD() || PlatformDetection.IsOpenBSD || PlatformDetection.IsSunOS) {
return; // doesn't support getting/setting working set for other processes
}
@@ -935,7 +935,7 @@ public void TestMinWorkingSet()
Assert.InRange((long)p.MinWorkingSet, 0, long.MaxValue);
}
- if (OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD() || PlatformDetection.IsSunOS) {
+ if (OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD() || PlatformDetection.IsOpenBSD || PlatformDetection.IsSunOS) {
return; // doesn't support getting/setting working set for other processes
}
@@ -1299,7 +1299,7 @@ public void ExitTime_GetNotStarted_ThrowsInvalidOperationException()
}
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
- [SkipOnPlatform(TestPlatforms.OSX | TestPlatforms.FreeBSD, "getting/setting affinity not supported on OSX and BSD")]
+ [SkipOnPlatform(TestPlatforms.OSX | TestPlatforms.FreeBSD | TestPlatforms.OpenBSD, "getting/setting affinity not supported on OSX and BSD")]
public void TestProcessorAffinity()
{
CreateDefaultProcess();
diff --git a/src/libraries/System.Diagnostics.Process/tests/ProcessThreadTests.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessThreadTests.cs
index 0f9965fb8b9b24..57b24854832bd1 100644
--- a/src/libraries/System.Diagnostics.Process/tests/ProcessThreadTests.cs
+++ b/src/libraries/System.Diagnostics.Process/tests/ProcessThreadTests.cs
@@ -92,7 +92,7 @@ public void ThreadsAreDisposedWhenProcessIsDisposed()
}
[Fact]
- [PlatformSpecific(TestPlatforms.OSX|TestPlatforms.FreeBSD)] // OSX and FreeBSD throw PNSE from StartTime
+ [PlatformSpecific(TestPlatforms.OSX | TestPlatforms.FreeBSD | TestPlatforms.OpenBSD)] // these platforms throw PNSE from StartTime
public void TestStartTimeProperty_OSX()
{
using (Process p = Process.GetCurrentProcess())
diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OpenBSD.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OpenBSD.cs
index 73c747bdfde9f6..96a0a494202bfc 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Environment.OpenBSD.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OpenBSD.cs
@@ -11,7 +11,7 @@ public static unsafe long WorkingSet
{
get
{
- Interop.Process.kinfo_proc* processInfo = Interop.Process.GetProcInfo(ProcessId, out int count);
+ Interop.Process.kinfo_proc* processInfo = Interop.Process.GetProcInfo(ProcessId, false, out int count);
try
{
// p_vm_rssize is the current resident set size in pages.