Skip to content
Open
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
283 changes: 283 additions & 0 deletions src/libraries/Common/src/Interop/OpenBSD/Interop.Process.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

#pragma warning disable CA1823 // analyzer incorrectly flags fixed buffer length const (https://github.com/dotnet/roslyn/issues/37593)

internal static partial class Interop
{
internal static partial class Process
{
private const ulong SecondsToNanoseconds = 1000000000;
private const ulong MicroSecondsToNanoSeconds = 1000;

internal struct proc_stats
{
internal long startTime;
internal int nice;
internal ulong userTime; /* in ticks */
internal ulong systemTime; /* in ticks */
}

/// <summary>
/// Queries the OS for the list of all running processes and returns the PID for each
/// </summary>
/// <returns>Returns a list of PIDs corresponding to all running processes</returns>
internal static unsafe int[] ListAllPids()
{
kinfo_proc* entries = GetProcInfo(0, false, out int numProcesses);
try
{
if (numProcesses <= 0)
{
throw new Win32Exception(SR.CantGetAllPids);
}

var list = new ReadOnlySpan<kinfo_proc>(entries, numProcesses);
var pids = new int[numProcesses];

// walk through process list and skip kernel threads
int idx = 0;
for (int i = 0; i < list.Length; i++)
{
if (list[i].p_ppid == 0)
{
// skip kernel threads
numProcesses -= 1;
}
else
{
pids[idx] = list[i].p_pid;
idx += 1;
}
}

// Remove extra elements
Array.Resize<int>(ref pids, numProcesses);
return pids;
}
finally
{
NativeMemory.Free(entries);
}
}

/// <summary>
/// Gets executable name for process given it's PID
/// </summary>
/// <param name="pid">The PID of the process</param>
public static unsafe string GetProcPath(int pid)
{
// 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>
/// Gets the process information for a given process
/// </summary>
/// <param name="pid">The PID (process ID) of the process</param>
/// <returns>
/// 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)
{
// Negative PIDs are invalid
ArgumentOutOfRangeException.ThrowIfNegative(pid);

ProcessInfo info;

kinfo_proc* kinfo = GetProcInfo(pid, true, out int count);
try
{
// 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;

// 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 = null // OpenBSD's kinfo_proc does not expose a thread start address.
};
info._threadInfoList.Add(ti);
}
}
finally
{
NativeMemory.Free(kinfo);
}

return info;
}

/// <summary>
/// Gets the process information for a given process
/// </summary>
/// <param name="pid">The PID (process ID) of the process</param>
/// <param name="tid">The TID (thread ID) of the process</param>
/// <returns>
/// Returns basic info about thread. If tid is 0, it will return
/// info for process e.g. main thread.
/// </returns>
public static unsafe proc_stats GetThreadInfo(int pid, int tid)
{
proc_stats ret = default;
int count;

kinfo_proc* info = GetProcInfo(pid, (tid != 0), out count);
try
{
if (info != null && count >= 1)
{
if (tid == 0)
{
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_ustime_sec * SecondsToNanoseconds + (ulong)info->p_ustime_usec * MicroSecondsToNanoSeconds;
}
else
{
var list = new ReadOnlySpan<kinfo_proc>(info, count);
for (int i = 0; i < list.Length; i++)
{
if (list[i].p_tid == 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_ustime_sec * SecondsToNanoseconds + (ulong)list[i].p_ustime_usec * MicroSecondsToNanoSeconds;
break;
}
}
}
}
}
finally
{
NativeMemory.Free(info);
}

return ret;
}
}
}
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
Loading
Loading