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.Collections.Generic; 6 using System.Globalization; 7 using System.IO; 8 using System.Text; 9 10 namespace System.Diagnostics 11 { 12 internal static partial class ProcessManager 13 { 14 /// <summary>Gets the IDs of all processes on the current machine.</summary> GetProcessIds()15 public static int[] GetProcessIds() 16 { 17 return EnumerableHelpers.ToArray(EnumerateProcessIds()); 18 } 19 20 /// <summary>Gets process infos for each process on the specified machine.</summary> 21 /// <param name="machineName">The target machine.</param> 22 /// <returns>An array of process infos, one per found process.</returns> GetProcessInfos(string machineName)23 public static ProcessInfo[] GetProcessInfos(string machineName) 24 { 25 ThrowIfRemoteMachine(machineName); 26 int[] procIds = GetProcessIds(machineName); 27 28 // Iterate through all process IDs to load information about each process 29 var reusableReader = new ReusableTextReader(); 30 var processes = new List<ProcessInfo>(procIds.Length); 31 foreach (int pid in procIds) 32 { 33 ProcessInfo pi = CreateProcessInfo(pid, reusableReader); 34 if (pi != null) 35 { 36 processes.Add(pi); 37 } 38 } 39 40 return processes.ToArray(); 41 } 42 43 /// <summary>Gets an array of module infos for the specified process.</summary> 44 /// <param name="processId">The ID of the process whose modules should be enumerated.</param> 45 /// <returns>The array of modules.</returns> GetModules(int processId)46 internal static ProcessModuleCollection GetModules(int processId) 47 { 48 var modules = new ProcessModuleCollection(0); 49 50 // Process from the parsed maps file each entry representing a module 51 foreach (Interop.procfs.ParsedMapsModule entry in Interop.procfs.ParseMapsModules(processId)) 52 { 53 int sizeOfImage = (int)(entry.AddressRange.Value - entry.AddressRange.Key); 54 55 // A single module may be split across multiple map entries; consolidate based on 56 // the name and address ranges of sequential entries. 57 if (modules.Count > 0) 58 { 59 ProcessModule module = modules[modules.Count - 1]; 60 if (module.FileName == entry.FileName && 61 ((long)module.BaseAddress + module.ModuleMemorySize == entry.AddressRange.Key)) 62 { 63 // Merge this entry with the previous one 64 module.ModuleMemorySize += sizeOfImage; 65 continue; 66 } 67 } 68 69 // It's not a continuation of a previous entry but a new one: add it. 70 unsafe 71 { 72 modules.Add(new ProcessModule() 73 { 74 FileName = entry.FileName, 75 ModuleName = Path.GetFileName(entry.FileName), 76 BaseAddress = new IntPtr(unchecked((void*)entry.AddressRange.Key)), 77 ModuleMemorySize = sizeOfImage, 78 EntryPointAddress = IntPtr.Zero // unknown 79 }); 80 } 81 } 82 83 // Move the main executable module to be the first in the list if it's not already 84 string exePath = Process.GetExePath(processId); 85 for (int i = 0; i < modules.Count; i++) 86 { 87 ProcessModule module = modules[i]; 88 if (module.FileName == exePath) 89 { 90 if (i > 0) 91 { 92 modules.RemoveAt(i); 93 modules.Insert(0, module); 94 } 95 break; 96 } 97 } 98 99 // Return the set of modules found 100 return modules; 101 } 102 103 // ----------------------------- 104 // ---- PAL layer ends here ---- 105 // ----------------------------- 106 107 /// <summary> 108 /// Creates a ProcessInfo from the specified process ID. 109 /// </summary> CreateProcessInfo(int pid, ReusableTextReader reusableReader = null)110 internal static ProcessInfo CreateProcessInfo(int pid, ReusableTextReader reusableReader = null) 111 { 112 if (reusableReader == null) 113 { 114 reusableReader = new ReusableTextReader(); 115 } 116 117 Interop.procfs.ParsedStat stat; 118 return Interop.procfs.TryReadStatFile(pid, out stat, reusableReader) ? 119 CreateProcessInfo(stat, reusableReader) : 120 null; 121 } 122 123 /// <summary> 124 /// Creates a ProcessInfo from the data parsed from a /proc/pid/stat file and the associated tasks directory. 125 /// </summary> CreateProcessInfo(Interop.procfs.ParsedStat procFsStat, ReusableTextReader reusableReader)126 internal static ProcessInfo CreateProcessInfo(Interop.procfs.ParsedStat procFsStat, ReusableTextReader reusableReader) 127 { 128 int pid = procFsStat.pid; 129 130 var pi = new ProcessInfo() 131 { 132 ProcessId = pid, 133 ProcessName = procFsStat.comm, 134 BasePriority = (int)procFsStat.nice, 135 VirtualBytes = (long)procFsStat.vsize, 136 WorkingSet = procFsStat.rss * Environment.SystemPageSize, 137 SessionId = procFsStat.session, 138 139 // We don't currently fill in the other values. 140 // A few of these could probably be filled in from getrusage, 141 // but only for the current process or its children, not for 142 // arbitrary other processes. 143 }; 144 145 // Then read through /proc/pid/task/ to find each thread in the process... 146 string tasksDir = Interop.procfs.GetTaskDirectoryPathForProcess(pid); 147 try 148 { 149 foreach (string taskDir in Directory.EnumerateDirectories(tasksDir)) 150 { 151 // ...and read its associated /proc/pid/task/tid/stat file to create a ThreadInfo 152 string dirName = Path.GetFileName(taskDir); 153 int tid; 154 Interop.procfs.ParsedStat stat; 155 if (int.TryParse(dirName, NumberStyles.Integer, CultureInfo.InvariantCulture, out tid) && 156 Interop.procfs.TryReadStatFile(pid, tid, out stat, reusableReader)) 157 { 158 unsafe 159 { 160 pi._threadInfoList.Add(new ThreadInfo() 161 { 162 _processId = pid, 163 _threadId = (ulong)tid, 164 _basePriority = pi.BasePriority, 165 _currentPriority = (int)stat.nice, 166 _startAddress = IntPtr.Zero, 167 _threadState = ProcFsStateToThreadState(stat.state), 168 _threadWaitReason = ThreadWaitReason.Unknown 169 }); 170 } 171 } 172 } 173 } 174 catch (IOException) 175 { 176 // Between the time that we get an ID and the time that we try to read the associated 177 // directories and files in procfs, the process could be gone. 178 } 179 180 // Finally return what we've built up 181 return pi; 182 } 183 184 // ---------------------------------- 185 // ---- Unix PAL layer ends here ---- 186 // ---------------------------------- 187 188 /// <summary>Enumerates the IDs of all processes on the current machine.</summary> EnumerateProcessIds()189 internal static IEnumerable<int> EnumerateProcessIds() 190 { 191 // Parse /proc for any directory that's named with a number. Each such 192 // directory represents a process. 193 foreach (string procDir in Directory.EnumerateDirectories(Interop.procfs.RootPath)) 194 { 195 string dirName = Path.GetFileName(procDir); 196 int pid; 197 if (int.TryParse(dirName, NumberStyles.Integer, CultureInfo.InvariantCulture, out pid)) 198 { 199 Debug.Assert(pid >= 0); 200 yield return pid; 201 } 202 } 203 } 204 205 /// <summary>Gets a ThreadState to represent the value returned from the status field of /proc/pid/stat.</summary> 206 /// <param name="c">The status field value.</param> 207 /// <returns></returns> ProcFsStateToThreadState(char c)208 private static ThreadState ProcFsStateToThreadState(char c) 209 { 210 switch (c) 211 { 212 case 'R': // Running 213 return ThreadState.Running; 214 215 case 'D': // Waiting on disk 216 case 'P': // Parked 217 case 'S': // Sleeping in a wait 218 case 't': // Tracing/debugging 219 case 'T': // Stopped on a signal 220 return ThreadState.Wait; 221 222 case 'x': // dead 223 case 'X': // Dead 224 case 'Z': // Zombie 225 return ThreadState.Terminated; 226 227 case 'W': // Paging or waking 228 case 'K': // Wakekill 229 return ThreadState.Transition; 230 231 default: 232 Debug.Fail($"Unexpected status character: {c}"); 233 return ThreadState.Unknown; 234 } 235 } 236 237 } 238 } 239