Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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. */
Expand All @@ -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 ". */
Expand Down Expand Up @@ -178,22 +182,29 @@ private struct NameBuffer
}

/// <summary>
/// Gets information about a single process by its PID.
/// Gets information about processes.
/// </summary>
/// <param name="pid">The PID of the process.</param>
/// <param name="pid">The PID of the process to query, or 0 to enumerate all processes.</param>
/// <param name="threads">When querying a single process, also return its threads.</param>
/// <param name="count">The number of kinfo_proc entries returned.</param>
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<int> 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<int> 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;
Expand Down
115 changes: 108 additions & 7 deletions src/libraries/Common/src/Interop/OpenBSD/Interop.Process.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,77 @@ internal static unsafe int[] ListAllPids()
/// <param name="pid">The PID of the process</param>
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<int> 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);
}
}

/// <summary>
/// Attempts to recover a process name that was truncated in kinfo_proc.p_comm by reading
/// the full name from the process argv.
/// </summary>
/// <param name="pid">The PID of the process.</param>
/// <param name="prefix">The (possibly truncated) p_comm value used to validate the recovered name.</param>
/// <returns>The full process name, or null if it could not be recovered.</returns>
private static unsafe string? GetUntruncatedProcessName(int pid, string prefix)
{
ReadOnlySpan<int> 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);
}
}

/// <summary>
Expand All @@ -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
/// </returns>
public static unsafe ProcessInfo GetProcessInfoById(int pid)
public static unsafe ProcessInfo? GetProcessInfoById(int pid)
{
// Negative PIDs are invalid
ArgumentOutOfRangeException.ThrowIfNegative(pid);
Expand All @@ -95,27 +165,58 @@ 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_proc>(kinfo, count);

// Get the process information for the specified 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);
}
Expand Down Expand Up @@ -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
{
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down