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 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Diagnostics;
9 using System.Runtime.InteropServices;
10 
11 
12 internal static partial class Interop
13 {
14     internal static partial class libproc
15     {
16         // Constants from sys\param.h
17         private const int MAXCOMLEN = 16;
18         private const int MAXPATHLEN = 1024;
19 
20         // Constants from proc_info.h
21         private const int MAXTHREADNAMESIZE = 64;
22         private const int PROC_PIDTASKALLINFO = 2;
23         private const int PROC_PIDTHREADINFO = 5;
24         private const int PROC_PIDLISTTHREADS = 6;
25         private const int PROC_PIDPATHINFO_MAXSIZE = 4 * MAXPATHLEN;
26 
27         // Constants from sys\resource.h
28         private const int RUSAGE_INFO_V3 = 3;
29 
30         // Defines from proc_info.h
31         internal enum ThreadRunState
32         {
33             TH_STATE_RUNNING            = 1,
34             TH_STATE_STOPPED            = 2,
35             TH_STATE_WAITING            = 3,
36             TH_STATE_UNINTERRUPTIBLE    = 4,
37             TH_STATE_HALTED             = 5
38         }
39 
40         // Defines in proc_info.h
41         [Flags]
42         internal enum ThreadFlags
43         {
44             TH_FLAGS_SWAPPED    = 0x1,
45             TH_FLAGS_IDLE       = 0x2
46         }
47 
48         // From proc_info.h
49         [StructLayout(LayoutKind.Sequential)]
50         internal unsafe struct proc_bsdinfo
51         {
52             internal uint       pbi_flags;
53             internal uint       pbi_status;
54             internal uint       pbi_xstatus;
55             internal uint       pbi_pid;
56             internal uint       pbi_ppid;
57             internal uint       pbi_uid;
58             internal uint       pbi_gid;
59             internal uint       pbi_ruid;
60             internal uint       pbi_rgid;
61             internal uint       pbi_svuid;
62             internal uint       pbi_svgid;
63             internal uint       reserved;
64             internal fixed byte pbi_comm[MAXCOMLEN];
65             internal fixed byte pbi_name[MAXCOMLEN * 2];
66             internal uint       pbi_nfiles;
67             internal uint       pbi_pgid;
68             internal uint       pbi_pjobc;
69             internal uint       e_tdev;
70             internal uint       e_tpgid;
71             internal int        pbi_nice;
72             internal ulong      pbi_start_tvsec;
73             internal ulong      pbi_start_tvusec;
74         }
75 
76         // From proc_info.h
77         [StructLayout(LayoutKind.Sequential)]
78         internal unsafe struct proc_taskinfo
79         {
80             internal ulong   pti_virtual_size;
81             internal ulong   pti_resident_size;
82             internal ulong   pti_total_user;
83             internal ulong   pti_total_system;
84             internal ulong   pti_threads_user;
85             internal ulong   pti_threads_system;
86             internal int     pti_policy;
87             internal int     pti_faults;
88             internal int     pti_pageins;
89             internal int     pti_cow_faults;
90             internal int     pti_messages_sent;
91             internal int     pti_messages_received;
92             internal int     pti_syscalls_mach;
93             internal int     pti_syscalls_unix;
94             internal int     pti_csw;
95             internal int     pti_threadnum;
96             internal int     pti_numrunning;
97             internal int     pti_priority;
98         };
99 
100         // from sys\resource.h
101         [StructLayout(LayoutKind.Sequential)]
102         internal unsafe struct rusage_info_v3
103         {
104             internal fixed byte     ri_uuid[16];
105             internal ulong          ri_user_time;
106             internal ulong          ri_system_time;
107             internal ulong          ri_pkg_idle_wkups;
108             internal ulong          ri_interrupt_wkups;
109             internal ulong          ri_pageins;
110             internal ulong          ri_wired_size;
111             internal ulong          ri_resident_size;
112             internal ulong          ri_phys_footprint;
113             internal ulong          ri_proc_start_abstime;
114             internal ulong          ri_proc_exit_abstime;
115             internal ulong          ri_child_user_time;
116             internal ulong          ri_child_system_time;
117             internal ulong          ri_child_pkg_idle_wkups;
118             internal ulong          ri_child_interrupt_wkups;
119             internal ulong          ri_child_pageins;
120             internal ulong          ri_child_elapsed_abstime;
121             internal ulong          ri_diskio_bytesread;
122             internal ulong          ri_diskio_byteswritten;
123             internal ulong          ri_cpu_time_qos_default;
124             internal ulong          ri_cpu_time_qos_maintenance;
125             internal ulong          ri_cpu_time_qos_background;
126             internal ulong          ri_cpu_time_qos_utility;
127             internal ulong          ri_cpu_time_qos_legacy;
128             internal ulong          ri_cpu_time_qos_user_initiated;
129             internal ulong          ri_cpu_time_qos_user_interactive;
130             internal ulong          ri_billed_system_time;
131             internal ulong          ri_serviced_system_time;
132         }
133 
134         // From proc_info.h
135         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
136         internal unsafe struct proc_taskallinfo
137         {
138             internal proc_bsdinfo    pbsd;
139             internal proc_taskinfo   ptinfo;
140         }
141 
142         // From proc_info.h
143         [StructLayout(LayoutKind.Sequential)]
144         internal unsafe struct proc_threadinfo
145         {
146             internal ulong      pth_user_time;
147             internal ulong      pth_system_time;
148             internal int        pth_cpu_usage;
149             internal int        pth_policy;
150             internal int        pth_run_state;
151             internal int        pth_flags;
152             internal int        pth_sleep_time;
153             internal int        pth_curpri;
154             internal int        pth_priority;
155             internal int        pth_maxpriority;
156             internal fixed byte pth_name[MAXTHREADNAMESIZE];
157         }
158 
159         [StructLayout(LayoutKind.Sequential)]
160         internal struct proc_fdinfo
161         {
162             internal int proc_fd;
163             internal uint proc_fdtype;
164         }
165 
166         /// <summary>
167         /// Queries the OS for the PIDs for all running processes
168         /// </summary>
169         /// <param name="buffer">A pointer to the memory block where the PID array will start</param>
170         /// <param name="buffersize">The length of the block of memory allocated for the PID array</param>
171         /// <returns>Returns the number of elements (PIDs) in the buffer</returns>
172         [DllImport(Interop.Libraries.libproc, SetLastError = true)]
proc_listallpids( int* pBuffer, int buffersize)173         private static extern unsafe int proc_listallpids(
174             int*    pBuffer,
175             int     buffersize);
176 
177         /// <summary>
178         /// Queries the OS for the list of all running processes and returns the PID for each
179         /// </summary>
180         /// <returns>Returns a list of PIDs corresponding to all running processes</returns>
proc_listallpids()181         internal static unsafe int[] proc_listallpids()
182         {
183             // Get the number of processes currently running to know how much data to allocate
184             int numProcesses = proc_listallpids(null, 0);
185             if (numProcesses <= 0)
186             {
187                 throw new Win32Exception(SR.CantGetAllPids);
188             }
189 
190             int[] processes;
191 
192             do
193             {
194                 // Create a new array for the processes (plus a 10% buffer in case new processes have spawned)
195                 // Since we don't know how many threads there could be, if result == size, that could mean two things
196                 // 1) We guessed exactly how many processes there are
197                 // 2) There are more processes that we didn't get since our buffer is too small
198                 // To make sure it isn't #2, when the result == size, increase the buffer and try again
199                 processes = new int[(int)(numProcesses * 1.10)];
200 
201                 fixed (int* pBuffer = &processes[0])
202                 {
203                     numProcesses = proc_listallpids(pBuffer, processes.Length * sizeof(int));
204                     if (numProcesses <= 0)
205                     {
206                         throw new Win32Exception(SR.CantGetAllPids);
207                     }
208                 }
209             }
210             while (numProcesses == processes.Length);
211 
212             // Remove extra elements
213             Array.Resize<int>(ref processes, numProcesses);
214 
215             return processes;
216         }
217 
218         /// <summary>
219         /// Gets information about a process given it's PID
220         /// </summary>
221         /// <param name="pid">The PID of the process</param>
222         /// <param name="flavor">Should be PROC_PIDTASKALLINFO</param>
223         /// <param name="arg">Flavor dependent value</param>
224         /// <param name="buffer">A pointer to a block of memory (of size proc_taskallinfo) allocated that will contain the data</param>
225         /// <param name="bufferSize">The size of the allocated block above</param>
226         /// <returns>
227         /// The amount of data actually returned. If this size matches the bufferSize parameter then
228         /// the data is valid. If the sizes do not match then the data is invalid, most likely due
229         /// to not having enough permissions to query for the data of that specific process
230         /// </returns>
231         [DllImport(Interop.Libraries.libproc, SetLastError = true)]
proc_pidinfo( int pid, int flavor, ulong arg, proc_taskallinfo* buffer, int bufferSize)232         private static extern unsafe int proc_pidinfo(
233             int pid,
234             int flavor,
235             ulong arg,
236             proc_taskallinfo* buffer,
237             int bufferSize);
238 
239         /// <summary>
240         /// Gets information about a process given it's PID
241         /// </summary>
242         /// <param name="pid">The PID of the process</param>
243         /// <param name="flavor">Should be PROC_PIDTHREADINFO</param>
244         /// <param name="arg">Flavor dependent value</param>
245         /// <param name="buffer">A pointer to a block of memory (of size proc_threadinfo) allocated that will contain the data</param>
246         /// <param name="bufferSize">The size of the allocated block above</param>
247         /// <returns>
248         /// The amount of data actually returned. If this size matches the bufferSize parameter then
249         /// the data is valid. If the sizes do not match then the data is invalid, most likely due
250         /// to not having enough permissions to query for the data of that specific process
251         /// </returns>
252         [DllImport(Interop.Libraries.libproc, SetLastError = true)]
proc_pidinfo( int pid, int flavor, ulong arg, proc_threadinfo* buffer, int bufferSize)253         private static extern unsafe int proc_pidinfo(
254             int pid,
255             int flavor,
256             ulong arg,
257             proc_threadinfo* buffer,
258             int bufferSize);
259 
260         /// <summary>
261         /// Gets information about a process given it's PID
262         /// </summary>
263         /// <param name="pid">The PID of the process</param>
264         /// <param name="flavor">Should be PROC_PIDLISTFDS</param>
265         /// <param name="arg">Flavor dependent value</param>
266         /// <param name="buffer">A pointer to a block of memory (of size proc_fdinfo) allocated that will contain the data</param>
267         /// <param name="bufferSize">The size of the allocated block above</param>
268         /// <returns>
269         /// The amount of data actually returned. If this size matches the bufferSize parameter then
270         /// the data is valid. If the sizes do not match then the data is invalid, most likely due
271         /// to not having enough permissions to query for the data of that specific process
272         /// </returns>
273         [DllImport(Interop.Libraries.libproc, SetLastError = true)]
proc_pidinfo( int pid, int flavor, ulong arg, proc_fdinfo* buffer, int bufferSize)274         private static extern unsafe int proc_pidinfo(
275             int pid,
276             int flavor,
277             ulong arg,
278             proc_fdinfo* buffer,
279             int bufferSize);
280 
281         /// <summary>
282         /// Gets information about a process given it's PID
283         /// </summary>
284         /// <param name="pid">The PID of the process</param>
285         /// <param name="flavor">Should be PROC_PIDTASKALLINFO</param>
286         /// <param name="arg">Flavor dependent value</param>
287         /// <param name="buffer">A pointer to a block of memory (of size ulong[]) allocated that will contain the data</param>
288         /// <param name="bufferSize">The size of the allocated block above</param>
289         /// <returns>
290         /// The amount of data actually returned. If this size matches the bufferSize parameter then
291         /// the data is valid. If the sizes do not match then the data is invalid, most likely due
292         /// to not having enough permissions to query for the data of that specific process
293         /// </returns>
294         [DllImport(Interop.Libraries.libproc, SetLastError = true)]
proc_pidinfo( int pid, int flavor, ulong arg, ulong* buffer, int bufferSize)295         private static extern unsafe int proc_pidinfo(
296             int pid,
297             int flavor,
298             ulong arg,
299             ulong* buffer,
300             int bufferSize);
301 
302         /// <summary>
303         /// Gets the process information for a given process
304         /// </summary>
305         /// <param name="pid">The PID (process ID) of the process</param>
306         /// <returns>
307         /// Returns a valid proc_taskallinfo struct for valid processes that the caller
308         /// has permission to access; otherwise, returns null
309         /// </returns>
GetProcessInfoById(int pid)310         internal static unsafe proc_taskallinfo? GetProcessInfoById(int pid)
311         {
312             // Negative PIDs are invalid
313             if (pid < 0)
314             {
315                 throw new ArgumentOutOfRangeException(nameof(pid));
316             }
317 
318             // Get the process information for the specified pid
319             int size = sizeof(proc_taskallinfo);
320             proc_taskallinfo info = default(proc_taskallinfo);
321             int result = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &info, size);
322             return (result == size ? new proc_taskallinfo?(info) : null);
323         }
324 
325         /// <summary>
326         /// Gets the thread information for the given thread
327         /// </summary>
328         /// <param name="thread">The ID of the thread to query for information</param>
329         /// <returns>
330         /// Returns a valid proc_threadinfo struct for valid threads that the caller
331         /// has permissions to access; otherwise, returns null
332         /// </returns>
GetThreadInfoById(int pid, ulong thread)333         internal static unsafe proc_threadinfo? GetThreadInfoById(int pid, ulong thread)
334         {
335             // Negative PIDs are invalid
336             if (pid < 0)
337             {
338                 throw new ArgumentOutOfRangeException(nameof(pid));
339             }
340 
341             // Negative TIDs are invalid
342             if (thread < 0)
343             {
344                 throw new ArgumentOutOfRangeException(nameof(thread));
345             }
346 
347             // Get the thread information for the specified thread in the specified process
348             int size = sizeof(proc_threadinfo);
349             proc_threadinfo info = default(proc_threadinfo);
350             int result = proc_pidinfo(pid, PROC_PIDTHREADINFO, (ulong)thread, &info, size);
351             return (result == size ? new proc_threadinfo?(info) : null);
352         }
353 
GetAllThreadsInProcess(int pid)354         internal static unsafe List<KeyValuePair<ulong, proc_threadinfo?>> GetAllThreadsInProcess(int pid)
355         {
356             // Negative PIDs are invalid
357             if (pid < 0)
358             {
359                 throw new ArgumentOutOfRangeException(nameof(pid));
360             }
361 
362             int result = 0;
363             int size = 20; // start assuming 20 threads is enough
364             ulong[] threadIds = null;
365             var threads = new List<KeyValuePair<ulong, proc_threadinfo?>>();
366 
367             // We have no way of knowing how many threads the process has (and therefore how big our buffer should be)
368             // so while the return value of the function is the same as our buffer size (meaning it completely filled
369             // our buffer), double our buffer size and try again. This ensures that we don't miss any threads
370             do
371             {
372                 threadIds = new ulong[size];
373                 fixed (ulong* pBuffer = &threadIds[0])
374                 {
375                     result = proc_pidinfo(pid, PROC_PIDLISTTHREADS, 0, pBuffer, sizeof(ulong) * threadIds.Length);
376                 }
377 
378                 if (result <= 0)
379                 {
380                     // If we were unable to access the information, just return the empty list.
381                     // This is likely to happen for privileged processes, if the process went away
382                     // by the time we tried to query it, etc.
383                     return threads;
384                 }
385                 else
386                 {
387                     checked
388                     {
389                         size *= 2;
390                     }
391                 }
392             }
393             while (result == sizeof(ulong) * threadIds.Length);
394 
395             Debug.Assert((result % sizeof(ulong)) == 0);
396 
397             // Loop over each thread and get the thread info
398             int count = (int)(result / sizeof(ulong));
399             threads.Capacity = count;
400             for (int i = 0; i < count; i++)
401             {
402                 threads.Add(new KeyValuePair<ulong, proc_threadinfo?>(threadIds[i], GetThreadInfoById(pid, threadIds[i])));
403             }
404 
405             return threads;
406         }
407 
408         /// <summary>
409         /// Gets the full path to the executable file identified by the specified PID
410         /// </summary>
411         /// <param name="pid">The PID of the running process</param>
412         /// <param name="buffer">A pointer to an allocated block of memory that will be filled with the process path</param>
413         /// <param name="bufferSize">The size of the buffer, should be PROC_PIDPATHINFO_MAXSIZE</param>
414         /// <returns>Returns the length of the path returned on success</returns>
415         [DllImport(Interop.Libraries.libproc, SetLastError = true)]
proc_pidpath( int pid, byte* buffer, uint bufferSize)416         private static extern unsafe int proc_pidpath(
417             int pid,
418             byte* buffer,
419             uint bufferSize);
420 
421         /// <summary>
422         /// Gets the full path to the executable file identified by the specified PID
423         /// </summary>
424         /// <param name="pid">The PID of the running process</param>
425         /// <returns>Returns the full path to the process executable</returns>
proc_pidpath(int pid)426         internal static unsafe string proc_pidpath(int pid)
427         {
428             // Negative PIDs are invalid
429             if (pid < 0)
430             {
431                 throw new ArgumentOutOfRangeException(nameof(pid), SR.NegativePidNotSupported);
432             }
433 
434             // The path is a fixed buffer size, so use that and trim it after
435             int result = 0;
436             byte* pBuffer = stackalloc byte[PROC_PIDPATHINFO_MAXSIZE];
437             result = proc_pidpath(pid, pBuffer, (uint)(PROC_PIDPATHINFO_MAXSIZE * sizeof(byte)));
438             if (result <= 0)
439             {
440                 throw new Win32Exception();
441             }
442 
443             // OS X uses UTF-8. The conversion may not strip off all trailing \0s so remove them here
444             return System.Text.Encoding.UTF8.GetString(pBuffer, result);
445         }
446 
447         /// <summary>
448         /// Gets the rusage information for the process identified by the PID
449         /// </summary>
450         /// <param name="pid">The process to retrieve the rusage for</param>
451         /// <param name="flavor">Specifies the type of struct that is passed in to <paramref>buffer</paramref>. Should be RUSAGE_INFO_V3 to specify a rusage_info_v3 struct.</param>
452         /// <param name="buffer">A buffer to be filled with rusage_info data</param>
453         /// <returns>Returns 0 on success; on fail, -1 and errno is set with the error code</returns>
454         [DllImport(Interop.Libraries.libproc, SetLastError = true)]
proc_pid_rusage( int pid, int flavor, rusage_info_v3* buffer)455         private static extern unsafe int proc_pid_rusage(
456             int pid,
457             int flavor,
458             rusage_info_v3* buffer);
459 
460         /// <summary>
461         /// Gets the rusage information for the process identified by the PID
462         /// </summary>
463         /// <param name="pid">The process to retrieve the rusage for</param>
464         /// <returns>On success, returns a struct containing info about the process; on
465         /// failure or when the caller doesn't have permissions to the process, throws a Win32Exception
466         /// </returns>
proc_pid_rusage(int pid)467         internal static unsafe rusage_info_v3 proc_pid_rusage(int pid)
468         {
469             // Negative PIDs are invalid
470             if (pid < 0)
471             {
472                 throw new ArgumentOutOfRangeException(nameof(pid), SR.NegativePidNotSupported);
473             }
474 
475             rusage_info_v3 info = new rusage_info_v3();
476 
477             // Get the PIDs rusage info
478             int result = proc_pid_rusage(pid, RUSAGE_INFO_V3, &info);
479             if (result < 0)
480             {
481                 throw new InvalidOperationException(SR.RUsageFailure);
482             }
483 
484             return info;
485         }
486     }
487 }
488