Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GetProcessInfo2 command, response, tests, and docs. #2360

Merged
merged 9 commits into from
Jul 1, 2021
62 changes: 60 additions & 2 deletions documentation/design-docs/ipc-protocol.md
Original file line number Diff line number Diff line change
@@ -376,8 +376,10 @@ See: [Profiler Commands](#Profiler-Commands)
```c++
enum class ProcessCommandId : uint8_t
{
ProcessInfo = 0x00,
ResumeRuntime = 0x01,
ProcessInfo = 0x00,
ResumeRuntime = 0x01,
ProcessEnvironment = 0x02,
ProcessInfo2 = 0x04,
// future
}
```
@@ -787,6 +789,62 @@ struct Payload
}
```

> Available since .NET 6.0

### `ProcessInfo2`

Command Code: `0x0404`

The `ProcessInfo2` command queries the runtime for some basic information about the process. The returned payload has the same information as that of the `ProcessInfo` command in addition to the managed entrypoint assembly name and CLR product version.

In the event of an [error](#Errors), the runtime will attempt to send an error message and subsequently close the connection.

#### Inputs:

Header: `{ Magic; Size; 0x0402; 0x0000 }`

There is no payload.

#### Returns (as an IPC Message Payload):

Header: `{ Magic; size; 0xFF00; 0x0000; }`

Payload:
* `int64 processId`: the process id in the process's PID-space
* `GUID runtimeCookie`: a 128-bit GUID that should be unique across PID-spaces
* `string commandLine`: the command line that invoked the process
* Windows: will be the same as the output of `GetCommandLineW`
* Non-Windows: will be the fully qualified path of the executable in `argv[0]` followed by all arguments as the appear in `argv` separated by spaces, i.e., `/full/path/to/argv[0] argv[1] argv[2] ...`
* `string OS`: the operating system that the process is running on
* macOS => `"macOS"`
* Windows => `"Windows"`
* Linux => `"Linux"`
* other => `"Unknown"`
* `string arch`: the architecture of the process
* 32-bit => `"x86"`
* 64-bit => `"x64"`
* ARM32 => `"arm32"`
* ARM64 => `"arm64"`
* Other => `"Unknown"`
* `string managedEntrypointAssemblyName`: the assembly name from the assembly identity of the entrypoint assembly of the process
* `string clrProductVersion`: the production version of the CLR of the process; may contain prerelease label information e.g. `6.0.0-preview.6.#####`
jander-msft marked this conversation as resolved.
Show resolved Hide resolved

##### Details:

Returns:
```c++
struct Payload
{
uint64_t ProcessId;
LPCWSTR CommandLine;
LPCWSTR OS;
LPCWSTR Arch;
GUID RuntimeCookie;
LPCWSTR ManagedEntrypointAssemblyName;
LPCWSTR ClrProductVersion;
}
```

## Errors

In the event an error occurs in the handling of an Ipc Message, the Diagnostic Server will attempt to send an Ipc Message encoding the error and subsequently close the connection. The connection will be closed **regardless** of the success of sending the error message. The Client is expected to be resilient in the event of a connection being abruptly closed.
12 changes: 10 additions & 2 deletions src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs
Original file line number Diff line number Diff line change
@@ -43,7 +43,9 @@ public static EndpointInfo FromProcessId(int processId)
RuntimeInstanceCookie = processInfo?.RuntimeInstanceCookie ?? Guid.Empty,
CommandLine = processInfo?.CommandLine,
OperatingSystem = processInfo?.OperatingSystem,
ProcessArchitecture = processInfo?.ProcessArchitecture
ProcessArchitecture = processInfo?.ProcessArchitecture,
ManagedEntrypointAssemblyName = processInfo?.ManagedEntrypointAssemblyName,
ClrProductVersionString = processInfo?.ClrProductVersionString
};
}

@@ -78,7 +80,9 @@ public static EndpointInfo FromIpcEndpointInfo(IpcEndpointInfo info)
RuntimeInstanceCookie = info.RuntimeInstanceCookie,
CommandLine = processInfo?.CommandLine,
OperatingSystem = processInfo?.OperatingSystem,
ProcessArchitecture = processInfo?.ProcessArchitecture
ProcessArchitecture = processInfo?.ProcessArchitecture,
ManagedEntrypointAssemblyName = processInfo?.ManagedEntrypointAssemblyName,
ClrProductVersionString = processInfo?.ClrProductVersionString
};
}

@@ -94,6 +98,10 @@ public static EndpointInfo FromIpcEndpointInfo(IpcEndpointInfo info)

public string ProcessArchitecture { get; private set; }

public string ManagedEntrypointAssemblyName { get; private set; }

public string ClrProductVersionString { get; private set; }

internal string DebuggerDisplay => FormattableString.Invariant($"PID={ProcessId}, Cookie={RuntimeInstanceCookie}");
}
}
4 changes: 4 additions & 0 deletions src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs
Original file line number Diff line number Diff line change
@@ -23,6 +23,10 @@ internal interface IEndpointInfo
string OperatingSystem { get; }

string ProcessArchitecture { get; }

string ManagedEntrypointAssemblyName { get; }

string ClrProductVersionString { get; }
}

public interface IEndpointInfoSource
Original file line number Diff line number Diff line change
@@ -194,6 +194,12 @@ internal void ResumeRuntimeFallback()

internal ProcessInfo GetProcessInfo()
{
// GetProcessInfo2 command seems to cause target runtime to crash very frequently
//if (TryGetProcessInfo2(out ProcessInfo processInfo))
//{
// return processInfo;
//}

IpcMessage message = new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessInfo);
var response = IpcClient.SendMessage(_endpoint, message);
switch ((DiagnosticsServerResponseId)response.Header.CommandId)
@@ -202,12 +208,27 @@ internal ProcessInfo GetProcessInfo()
var hr = BitConverter.ToInt32(response.Payload, 0);
throw new ServerErrorException($"Get process info failed (HRESULT: 0x{hr:X8})");
case DiagnosticsServerResponseId.OK:
return ProcessInfo.Parse(response.Payload);
return ProcessInfo.ParseV1(response.Payload);
default:
throw new ServerErrorException($"Get process info failed - server responded with unknown command");
}
}

private bool TryGetProcessInfo2(out ProcessInfo processInfo)
jander-msft marked this conversation as resolved.
Show resolved Hide resolved
{
IpcMessage message = new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessInfo2);
var response = IpcClient.SendMessage(_endpoint, message);
switch ((DiagnosticsServerResponseId)response.Header.CommandId)
{
case DiagnosticsServerResponseId.OK:
processInfo = ProcessInfo.ParseV2(response.Payload);
return true;
default:
processInfo = null;
return false;
}
}

public Dictionary<string,string> GetProcessEnvironment()
{
var message = new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessEnvironment);
Original file line number Diff line number Diff line change
@@ -58,5 +58,6 @@ internal enum ProcessCommandId : byte
GetProcessInfo = 0x00,
ResumeRuntime = 0x01,
GetProcessEnvironment = 0x02,
GetProcessInfo2 = 0x04
}
}
Original file line number Diff line number Diff line change
@@ -17,6 +17,18 @@ namespace Microsoft.Diagnostics.NETCore.Client
* # bytes - Operating system string length and data
* # bytes - Process architecture string length and data
*
* ==ProcessInfo2==
* The response payload to issuing the GetProcessInfo2 command.
*
* 8 bytes - PID (little-endian)
* 16 bytes - CLR Runtime Instance Cookie (little-endian)
* # bytes - Command line string length and data
* # bytes - Operating system string length and data
* # bytes - Process architecture string length and data
* # bytes - Managed entrypoint assembly name
* # bytes - CLR product version string (may include prerelease labels)
*
*
* The "string length and data" fields are variable length:
* 4 bytes - Length of string data in UTF-16 characters
* (2 * length) bytes - The data of the string encoded using Unicode
@@ -27,11 +39,33 @@ internal class ProcessInfo
{
private static readonly int GuidSizeInBytes = 16;

public static ProcessInfo Parse(byte[] payload)
/// <summary>
/// Parses a ProcessInfo payload.
/// </summary>
internal static ProcessInfo ParseV1(byte[] payload)
{
ProcessInfo processInfo = new ProcessInfo();
int index = 0;
return ParseCommon(payload, ref index);
}

/// <summary>
/// Parses a ProcessInfo2 payload.
/// </summary>
internal static ProcessInfo ParseV2(byte[] payload)
{
int index = 0;
ProcessInfo processInfo = ParseCommon(payload, ref index);

processInfo.ManagedEntrypointAssemblyName = ReadString(payload, ref index);
processInfo.ClrProductVersionString = ReadString(payload, ref index);

return processInfo;
}

private static ProcessInfo ParseCommon(byte[] payload, ref int index)
{
ProcessInfo processInfo = new ProcessInfo();

processInfo.ProcessId = BitConverter.ToUInt64(payload, index);
index += sizeof(UInt64);

@@ -65,5 +99,7 @@ private static string ReadString(byte[] buffer, ref int index)
public string CommandLine { get; private set; }
public string OperatingSystem { get; private set; }
public string ProcessArchitecture { get; private set; }
public string ManagedEntrypointAssemblyName { get; private set; }
public string ClrProductVersionString { get; private set; }
}
}
Original file line number Diff line number Diff line change
@@ -125,6 +125,10 @@ public async Task ServerSourceAddRemoveSingleConnectionTest()
Assert.NotNull(endpointInfo.CommandLine);
Assert.NotNull(endpointInfo.OperatingSystem);
Assert.NotNull(endpointInfo.ProcessArchitecture);
//Assert.Equal("EventPipeTracee", endpointInfo.ManagedEntrypointAssemblyName);
//Version clrVersion = ParseVersionRemoveLabel(endpointInfo.ClrProductVersionString);
//Assert.True(clrVersion >= new Version(6, 0, 0));

VerifyConnection(execution1.TestRunner, endpointInfo);

_outputHelper.WriteLine("Stopping tracee.");
@@ -180,6 +184,9 @@ public async Task ServerSourceAddRemoveMultipleConnectionTest()
Assert.NotNull(endpointInfo.CommandLine);
Assert.NotNull(endpointInfo.OperatingSystem);
Assert.NotNull(endpointInfo.ProcessArchitecture);
//Assert.Equal("EventPipeTracee", endpointInfo.ManagedEntrypointAssemblyName);
//Version clrVersion = ParseVersionRemoveLabel(endpointInfo.ClrProductVersionString);
//Assert.True(clrVersion >= new Version(6, 0, 0));

VerifyConnection(executions[i].TestRunner, endpointInfo);
}
@@ -240,6 +247,17 @@ private static void VerifyConnection(TestRunner runner, IEndpointInfo endpointIn
Assert.NotNull(endpointInfo.Endpoint);
}

private static Version ParseVersionRemoveLabel(string versionString)
{
Assert.NotNull(versionString);
int prereleaseLabelIndex = versionString.IndexOf('-');
if (prereleaseLabelIndex >= 0)
{
versionString = versionString.Substring(0, prereleaseLabelIndex);
}
return Version.Parse(versionString);
}

private sealed class TestServerEndpointInfoSource : ServerEndpointInfoSource
{
private readonly ITestOutputHelper _outputHelper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Threading;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.Diagnostics.NETCore.Client
{
public class GetProcessInfoTests
{
private readonly ITestOutputHelper output;

public GetProcessInfoTests(ITestOutputHelper outputHelper)
{
output = outputHelper;
}

[Fact]
public void BasicProcessInfoTest()
{
using TestRunner runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), output);
runner.Start();

try
{
DiagnosticsClient client = new DiagnosticsClient(runner.Pid);

ProcessInfo processInfo = client.GetProcessInfo();
jander-msft marked this conversation as resolved.
Show resolved Hide resolved

Assert.NotNull(processInfo);
Assert.Equal(runner.Pid, (int)processInfo.ProcessId);
Assert.NotNull(processInfo.CommandLine);
Assert.NotNull(processInfo.OperatingSystem);
Assert.NotNull(processInfo.ProcessArchitecture);
//Assert.Equal("Tracee", processInfo.ManagedEntrypointAssemblyName);
//Version clrVersion = ParseVersionRemoveLabel(processInfo.ClrProductVersionString);
//Assert.True(clrVersion >= new Version(6, 0, 0));
jander-msft marked this conversation as resolved.
Show resolved Hide resolved
}
finally
{
runner.PrintStatus();
}
}

private static Version ParseVersionRemoveLabel(string versionString)
{
Assert.NotNull(versionString);
int prereleaseLabelIndex = versionString.IndexOf('-');
if (prereleaseLabelIndex >= 0)
{
versionString = versionString.Substring(0, prereleaseLabelIndex);
}
return Version.Parse(versionString);
}
}
}
Original file line number Diff line number Diff line change
@@ -143,7 +143,7 @@ public void PrintStatus()
{
if (testProcess.HasExited)
{
outputHelper.WriteLine($"Process {testProcess.Id} status: Exited {testProcess.ExitCode}");
outputHelper.WriteLine($"Process {testProcess.Id} status: Exited 0x{testProcess.ExitCode:X}");
}
else
{