Skip to content

Commit 3394f7a

Browse files
committed
SunOS process and thread support
Read binary psinfo for System.Diagnostic.Process on SunOS Thanks for initial prototype help from: Austin Wise <[email protected]>
1 parent 1477734 commit 3394f7a

File tree

13 files changed

+856
-85
lines changed

13 files changed

+856
-85
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.InteropServices;
5+
6+
// The const int PRARGSZ show up as unused. Not sure why.
7+
#pragma warning disable CA1823
8+
9+
internal static partial class Interop
10+
{
11+
internal static partial class @procfs
12+
{
13+
internal const string RootPath = "/proc/";
14+
private const string psinfoFileName = "/psinfo";
15+
private const string lwpDirName = "/lwp";
16+
private const string lwpsinfoFileName = "/lwpsinfo";
17+
18+
// Constants from sys/procfs.h
19+
private const int PRARGSZ = 80;
20+
private const int PRCLSZ = 8;
21+
private const int PRFNSZ = 16;
22+
23+
[StructLayout(LayoutKind.Sequential)]
24+
internal struct @timestruc_t
25+
{
26+
public long tv_sec;
27+
public long tv_nsec;
28+
}
29+
30+
// lwp ps(1) information file. /proc/<pid>/lwp/<lwpid>/lwpsinfo
31+
// "unsafe" because it has fixed sized arrays.
32+
[StructLayout(LayoutKind.Sequential)]
33+
internal unsafe struct @lwpsinfo
34+
{
35+
private int pr_flag; /* lwp flags (DEPRECATED; do not use) */
36+
public uint pr_lwpid; /* lwp id */
37+
private long pr_addr; /* internal address of lwp */
38+
private long pr_wchan; /* wait addr for sleeping lwp */
39+
public byte pr_stype; /* synchronization event type */
40+
public byte pr_state; /* numeric lwp state */
41+
public byte pr_sname; /* printable character for pr_state */
42+
public byte pr_nice; /* nice for cpu usage */
43+
private short pr_syscall; /* system call number (if in syscall) */
44+
private byte pr_oldpri; /* pre-SVR4, low value is high priority */
45+
private byte pr_cpu; /* pre-SVR4, cpu usage for scheduling */
46+
public int pr_pri; /* priority, high value is high priority */
47+
private ushort pr_pctcpu; /* fixed pt. % of recent cpu time */
48+
private ushort pr_pad;
49+
public timestruc_t pr_start; /* lwp start time, from the epoch */
50+
public timestruc_t pr_time; /* usr+sys cpu time for this lwp */
51+
private fixed byte pr_clname[PRCLSZ]; /* scheduling class name */
52+
private fixed byte pr_name[PRFNSZ]; /* name of system lwp */
53+
private int pr_onpro; /* processor which last ran this lwp */
54+
private int pr_bindpro; /* processor to which lwp is bound */
55+
private int pr_bindpset; /* processor set to which lwp is bound */
56+
private int pr_lgrp; /* lwp home lgroup */
57+
private fixed int pr_filler[4]; /* reserved for future use */
58+
}
59+
60+
// process ps(1) information file. /proc/<pid>/psinfo
61+
// "unsafe" because it has fixed sized arrays.
62+
[StructLayout(LayoutKind.Sequential)]
63+
internal unsafe struct @psinfo
64+
{
65+
private int pr_flag; /* process flags (DEPRECATED; do not use) */
66+
public int pr_nlwp; /* number of active lwps in the process */
67+
public int pr_pid; /* unique process id */
68+
public int pr_ppid; /* process id of parent */
69+
public int pr_pgid; /* pid of process group leader */
70+
public int pr_sid; /* session id */
71+
public uint pr_uid; /* real user id */
72+
public uint pr_euid; /* effective user id */
73+
public uint pr_gid; /* real group id */
74+
public uint pr_egid; /* effective group id */
75+
private long pr_addr; /* address of process */
76+
public ulong pr_size; /* size of process image in Kbytes */
77+
public ulong pr_rssize; /* resident set size in Kbytes */
78+
private ulong pr_pad1;
79+
private ulong pr_ttydev; /* controlling tty device (or PRNODEV) */
80+
private ushort pr_pctcpu; /* % of recent cpu time used by all lwps */
81+
private ushort pr_pctmem; /* % of system memory used by process */
82+
public timestruc_t pr_start; /* process start time, from the epoch */
83+
public timestruc_t pr_time; /* usr+sys cpu time for this process */
84+
public timestruc_t pr_ctime; /* usr+sys cpu time for reaped children */
85+
public fixed byte pr_fname[PRFNSZ]; /* name of execed file */
86+
public fixed byte pr_psargs[PRARGSZ]; /* initial characters of arg list */
87+
public int pr_wstat; /* if zombie, the wait() status */
88+
public int pr_argc; /* initial argument count */
89+
private long pr_argv; /* address of initial argument vector */
90+
private long pr_envp; /* address of initial environment vector */
91+
private byte pr_dmodel; /* data model of the process */
92+
private fixed byte pr_pad2[3];
93+
public int pr_taskid; /* task id */
94+
public int pr_projid; /* project id */
95+
public int pr_nzomb; /* number of zombie lwps in the process */
96+
public int pr_poolid; /* pool id */
97+
public int pr_zoneid; /* zone id */
98+
public int pr_contract; /* process contract */
99+
private fixed int pr_filler[1]; /* reserved for future use */
100+
public lwpsinfo pr_lwp; /* information for representative lwp */
101+
}
102+
103+
// Ouput type for GetThreadInfoById()
104+
internal struct ThreadInfo
105+
{
106+
internal uint Tid;
107+
internal int Priority;
108+
internal int NiceVal;
109+
internal char Status;
110+
internal Interop.Sys.TimeSpec StartTime;
111+
internal Interop.Sys.TimeSpec CpuTotalTime; // user+sys
112+
// add more fields when needed.
113+
}
114+
115+
// Ouput type for GetProcessInfoById()
116+
internal struct ProcessInfo
117+
{
118+
internal int Pid;
119+
internal int ParentPid;
120+
internal int SessionId;
121+
internal nuint VirtualSize;
122+
internal nuint ResidentSetSize;
123+
internal Interop.Sys.TimeSpec StartTime;
124+
internal Interop.Sys.TimeSpec CpuTotalTime; // user+sys
125+
internal string? Args;
126+
// add more fields when needed.
127+
internal ThreadInfo Lwp1;
128+
}
129+
130+
internal static string GetInfoFilePathForProcess(int pid) =>
131+
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{psinfoFileName}");
132+
133+
internal static string GetLwpDirForProcess(int pid) =>
134+
string.Create(null, stackalloc char[256], $"{RootPath}{(uint)pid}{lwpDirName}");
135+
136+
internal static string GetInfoFilePathForThread(int pid, int tid) =>
137+
string.Create(null, stackalloc char[256],
138+
$"{RootPath}{(uint)pid}{lwpDirName}/{(uint)tid}{lwpsinfoFileName}");
139+
140+
}
141+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Runtime.InteropServices;
8+
using System.IO;
9+
10+
internal static partial class Interop
11+
{
12+
internal static partial class @procfs
13+
{
14+
15+
/// <summary>
16+
/// Attempts to get status info for the specified process ID.
17+
/// </summary>
18+
/// <param name="pid">PID of the process to read status info for.</param>
19+
/// <param name="result">The pointer to ProcessInfo instance.</param>
20+
/// <returns>
21+
/// true if the process status was read; otherwise, false.
22+
/// </returns>
23+
24+
// ProcessManager.SunOS.cs calls this
25+
// "unsafe" due to use of fixed-size buffers
26+
27+
internal static unsafe bool GetProcessInfoById(int pid, out ProcessInfo result)
28+
{
29+
result = default;
30+
bool ret = false;
31+
string fileName = "?";
32+
IntPtr ptr = 0;
33+
34+
try
35+
{
36+
fileName = GetInfoFilePathForProcess(pid);
37+
int size = Marshal.SizeOf<psinfo>();
38+
ptr = Marshal.AllocHGlobal(size);
39+
40+
BinaryReader br = new BinaryReader(File.OpenRead(fileName));
41+
byte[] buf = br.ReadBytes(size);
42+
Marshal.Copy(buf, 0, ptr, size);
43+
44+
procfs.psinfo pr = Marshal.PtrToStructure<psinfo>(ptr);
45+
46+
result.Pid = pr.pr_pid;
47+
result.ParentPid = pr.pr_ppid;
48+
result.SessionId = pr.pr_sid;
49+
result.VirtualSize = (nuint)pr.pr_size * 1024; // pr_rssize is in Kbytes
50+
result.ResidentSetSize = (nuint)pr.pr_rssize * 1024; // pr_rssize is in Kbytes
51+
result.StartTime.TvSec = pr.pr_start.tv_sec;
52+
result.StartTime.TvNsec = pr.pr_start.tv_nsec;
53+
result.CpuTotalTime.TvSec = pr.pr_time.tv_sec;
54+
result.CpuTotalTime.TvNsec = pr.pr_time.tv_nsec;
55+
result.Args = Marshal.PtrToStringUTF8((IntPtr)pr.pr_psargs);
56+
57+
// We get LWP[1] for "free"
58+
result.Lwp1.Tid = pr.pr_lwp.pr_lwpid;
59+
result.Lwp1.Priority = pr.pr_lwp.pr_pri;
60+
result.Lwp1.NiceVal = (int)pr.pr_lwp.pr_nice;
61+
result.Lwp1.Status = (char)pr.pr_lwp.pr_sname;
62+
63+
ret = true;
64+
}
65+
catch (Exception e)
66+
{
67+
Debug.Fail($"Failed to read \"{fileName}\": {e}");
68+
}
69+
finally
70+
{
71+
Marshal.FreeHGlobal(ptr);
72+
}
73+
74+
return ret;
75+
}
76+
77+
}
78+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Runtime.InteropServices;
8+
using System.IO;
9+
10+
internal static partial class Interop
11+
{
12+
internal static partial class @procfs
13+
{
14+
15+
/// <summary>
16+
/// Attempts to get status info for the specified Thread ID.
17+
/// </summary>
18+
/// <param name="pid">PID of the process to read status info for.</param>
19+
/// <param name="tid">TID of the thread to read status info for.</param>
20+
/// <param name="result">The pointer to processStatusInfo instance.</param>
21+
/// <returns>
22+
/// true if the process status was read; otherwise, false.
23+
/// </returns>
24+
25+
// ProcessManager.SunOS.cs calls this
26+
// "unsafe" due to use of fixed-size buffers
27+
28+
internal static unsafe bool GetThreadInfoById(int pid, int tid, out ThreadInfo result)
29+
{
30+
result = default;
31+
bool ret = false;
32+
string fileName = "?";
33+
IntPtr ptr = 0;
34+
35+
try
36+
{
37+
fileName = GetInfoFilePathForThread(pid, tid);
38+
int size = Marshal.SizeOf<lwpsinfo>();
39+
ptr = Marshal.AllocHGlobal(size);
40+
41+
BinaryReader br = new BinaryReader(File.OpenRead(fileName));
42+
byte[] buf = br.ReadBytes(size);
43+
Marshal.Copy(buf, 0, ptr, size);
44+
45+
procfs.lwpsinfo lwp = Marshal.PtrToStructure<lwpsinfo>(ptr);
46+
47+
result.Tid = lwp.pr_lwpid;
48+
result.Priority = lwp.pr_pri;
49+
result.NiceVal = (int)lwp.pr_nice;
50+
result.Status = (char)lwp.pr_sname;
51+
result.StartTime.TvSec = lwp.pr_start.tv_sec;
52+
result.StartTime.TvNsec = lwp.pr_start.tv_nsec;
53+
result.CpuTotalTime.TvSec = lwp.pr_time.tv_sec;
54+
result.CpuTotalTime.TvNsec = lwp.pr_time.tv_nsec;
55+
56+
ret = true;
57+
}
58+
catch (Exception e)
59+
{
60+
Debug.Fail($"Failed to read \"{fileName}\": {e}");
61+
}
62+
finally
63+
{
64+
Marshal.FreeHGlobal(ptr);
65+
}
66+
67+
return ret;
68+
}
69+
70+
}
71+
}

src/libraries/Common/src/Interop/SunOS/procfs/Interop.ProcFsStat.TryReadProcessStatusInfo.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)</TargetFrameworks>
4+
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent)-illumos;$(NetCoreAppCurrent)</TargetFrameworks>
55
<DefineConstants>$(DefineConstants);FEATURE_REGISTRY</DefineConstants>
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
77
<UseCompilerGeneratedDocXmlFile>false</UseCompilerGeneratedDocXmlFile>
@@ -363,6 +363,21 @@
363363
Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
364364
</ItemGroup>
365365

366+
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'illumos' or '$(TargetPlatformIdentifier)' == 'solaris'">
367+
<Compile Include="System\Diagnostics\Process.BSD.cs" />
368+
<Compile Include="System\Diagnostics\Process.SunOS.cs" />
369+
<Compile Include="System\Diagnostics\ProcessManager.SunOS.cs" />
370+
<Compile Include="System\Diagnostics\ProcessThread.SunOS.cs" />
371+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.TimeSpec.cs"
372+
Link="Common\Interop\Unix\System.Native\Interop.TimeSpec.cs" />
373+
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.Definitions.cs"
374+
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.Definitions.cs" />
375+
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.GetProcessInfoById.cs"
376+
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.GetProcessInfoById.cs" />
377+
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFs.GetThreadInfoById.cs"
378+
Link="Common\Interop\SunOS\procfs\Interop.ProcFs.GetThreadInfoById.cs" />
379+
</ItemGroup>
380+
366381
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos'">
367382
<Compile Include="System\Diagnostics\Process.iOS.cs" />
368383
<Compile Include="System\Diagnostics\ProcessManager.iOS.cs" />

0 commit comments

Comments
 (0)