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