forked from microsoft/vstest
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProcessCodeMethods.cs
More file actions
221 lines (192 loc) · 7.64 KB
/
ProcessCodeMethods.cs
File metadata and controls
221 lines (192 loc) · 7.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
namespace Microsoft.TestPlatform.Extensions.BlameDataCollector;
/// <summary>
/// Helper functions for process info.
/// </summary>
internal static class ProcessCodeMethods
{
private const int InvalidProcessId = -1;
public static void Suspend(this Process process)
{
if (process.HasExited)
{
EqtTrace.Verbose($"ProcessCodeMethods.Suspend: Process {process.Id} - {process.ProcessName} already exited, skipping.");
return;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// There is no supported or documented API to suspend a process. If there was you should call it here.
// SuspendWindows(process);
}
else
{
// TODO: do not suspend on Mac and Linux, this prevents the process from being dumped when we use the net client dumper, checking if we can post a different signal
// SuspendLinuxMacOs(process);
}
}
public static List<ProcessTreeNode> GetProcessTree(this Process process)
{
var childProcesses = Process.GetProcesses()
.Where(p => IsChildCandidate(p, process))
.ToList();
var acc = new List<ProcessTreeNode>();
foreach (var c in childProcesses)
{
try
{
var parentId = GetParentPid(c);
// c.ParentId = parentId;
acc.Add(new ProcessTreeNode { ParentId = parentId, Process = c });
}
catch
{
// many things can go wrong with this
// just ignore errors
}
}
var level = 1;
var limit = 10;
ResolveChildren(process, acc, level, limit);
return new List<ProcessTreeNode> { new ProcessTreeNode { Process = process, Level = 0 } }.Concat(acc.Where(a => a.Level > 0)).ToList();
}
/// <summary>
/// Returns the parent id of a process or -1 if it fails.
/// </summary>
/// <param name="process">The process to find parent of.</param>
/// <returns>The pid of the parent process.</returns>
internal static int GetParentPid(Process process)
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? GetParentPidWindows(process)
: RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
? GetParentPidLinux(process)
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
GetParentPidMacOs(process)
: throw new PlatformNotSupportedException();
}
internal static int GetParentPidWindows(Process process)
{
try
{
var handle = process.Handle;
var res = NtQueryInformationProcess(handle, 0, out var pbi, Marshal.SizeOf<ProcessBasicInformation>(), out int size);
var p = res != 0 ? InvalidProcessId : pbi.InheritedFromUniqueProcessId.ToInt32();
return p;
}
catch (Exception ex)
{
EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidLinux: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}.");
return InvalidProcessId;
}
}
/// <summary>Read the /proc file system for information about the parent.</summary>
/// <param name="process">The process to get the parent process from.</param>
/// <returns>The process id.</returns>
internal static int GetParentPidLinux(Process process)
{
var pid = process.Id;
// read /proc/<pid>/stat
// 4th column will contain the ppid, 92 in the example below
// ex: 93 (bash) S 92 93 2 4294967295 ...
var path = $"/proc/{pid}/stat";
try
{
var stat = File.ReadAllText(path);
var parts = stat.Split(' ');
return parts.Length < 5 ? InvalidProcessId : int.Parse(parts[3], CultureInfo.CurrentCulture);
}
catch (Exception ex)
{
EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidLinux: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}.");
return InvalidProcessId;
}
}
internal static int GetParentPidMacOs(Process process)
{
try
{
var output = new StringBuilder();
var err = new StringBuilder();
Process ps = new();
ps.StartInfo.FileName = "ps";
ps.StartInfo.Arguments = $"-o ppid= {process.Id}";
ps.StartInfo.UseShellExecute = false;
ps.StartInfo.RedirectStandardOutput = true;
ps.OutputDataReceived += (_, e) => output.Append(e.Data);
ps.ErrorDataReceived += (_, e) => err.Append(e.Data);
ps.Start();
ps.BeginOutputReadLine();
ps.WaitForExit(5_000);
var o = output.ToString();
var parent = int.TryParse(o.Trim(), out var ppid) ? ppid : InvalidProcessId;
if (err.ToString() is string error && !error.IsNullOrWhiteSpace())
{
EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidMacOs: Error getting parent of process {process.Id} - {process.ProcessName}, {error}.");
}
return parent;
}
catch (Exception ex)
{
EqtTrace.Verbose($"ProcessCodeMethods.GetParentPidMacOs: Error getting parent of process {process.Id} - {process.ProcessName}, {ex}.");
return InvalidProcessId;
}
}
private static void ResolveChildren(Process parent, List<ProcessTreeNode> acc, int level, int limit)
{
if (limit < 0)
{
// hit recursion limit, just returning
return;
}
// only take children that are newer than the parent, because process ids (PIDs) get recycled
var children = acc.Where(p => p.ParentId == parent.Id && p.Process?.StartTime > parent.StartTime).ToList();
foreach (var child in children)
{
child.Level = level;
ResolveChildren(child.Process!, acc, level + 1, limit);
}
}
private static bool IsChildCandidate(Process child, Process parent)
{
// this is extremely slow under debugger, but fast without it
try
{
return child.StartTime > parent.StartTime && child.Id != parent.Id;
}
catch
{
/* access denied or process has exits most likely */
return false;
}
}
[StructLayout(LayoutKind.Sequential)]
private struct ProcessBasicInformation
{
public readonly IntPtr ExitStatus;
public readonly IntPtr PebBaseAddress;
public readonly IntPtr AffinityMask;
public readonly IntPtr BasePriority;
public readonly IntPtr UniqueProcessId;
public IntPtr InheritedFromUniqueProcessId;
}
[DllImport("ntdll.dll", SetLastError = true)]
private static extern int NtQueryInformationProcess(
IntPtr processHandle,
int processInformationClass,
out ProcessBasicInformation processInformation,
int processInformationLength,
out int returnLength);
// This call is undocumented api, and should not be used.
//[DllImport("ntdll.dll", SetLastError = true)]
//private static extern IntPtr NtSuspendProcess(IntPtr processHandle);
}