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