From 16d30b3f738ee979bfb34055988803ce3ffeead4 Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Fri, 11 Jun 2021 09:35:13 -0700 Subject: [PATCH 1/7] Add GetProcessInfo2 command, response, tests, and docs. --- documentation/design-docs/ipc-protocol.md | 62 ++++++++++++++++++- .../EndpointInfo.cs | 12 +++- .../IEndpointInfoSource.cs | 4 ++ .../DiagnosticsClient/DiagnosticsClient.cs | 22 ++++++- .../DiagnosticsIpc/IpcCommands.cs | 1 + .../DiagnosticsIpc/ProcessInfo.cs | 40 +++++++++++- .../EndpointInfoSourceTests.cs | 18 ++++++ 7 files changed, 152 insertions(+), 7 deletions(-) diff --git a/documentation/design-docs/ipc-protocol.md b/documentation/design-docs/ipc-protocol.md index d5238a0071..71575fe7d2 100644 --- a/documentation/design-docs/ipc-protocol.md +++ b/documentation/design-docs/ipc-protocol.md @@ -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.#####` + +##### 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. diff --git a/src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs b/src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs index dbac65f2ed..88a0958768 100644 --- a/src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs +++ b/src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs @@ -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, + ClrProductionVersionString = 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, + ClrProductionVersionString = 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 ClrProductionVersionString { get; private set; } + internal string DebuggerDisplay => FormattableString.Invariant($"PID={ProcessId}, Cookie={RuntimeInstanceCookie}"); } } diff --git a/src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs b/src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs index f3b90b50e8..444aeaf442 100644 --- a/src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs +++ b/src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs @@ -23,6 +23,10 @@ internal interface IEndpointInfo string OperatingSystem { get; } string ProcessArchitecture { get; } + + string ManagedEntrypointAssemblyName { get; } + + string ClrProductionVersionString { get; } } public interface IEndpointInfoSource diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs index 6b11c75071..10719ed75b 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs @@ -194,6 +194,11 @@ internal void ResumeRuntimeFallback() internal ProcessInfo GetProcessInfo() { + 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 +207,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) + { + 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 GetProcessEnvironment() { var message = new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessEnvironment); diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs index ca59ba2558..1282abe859 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs @@ -58,5 +58,6 @@ internal enum ProcessCommandId : byte GetProcessInfo = 0x00, ResumeRuntime = 0x01, GetProcessEnvironment = 0x02, + GetProcessInfo2 = 0x04 } } diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs index 91bec0c7c9..e4bcfb3b12 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs @@ -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) + /// + /// Parses a ProcessInfo payload. + /// + internal static ProcessInfo ParseV1(byte[] payload) { - ProcessInfo processInfo = new ProcessInfo(); + int index = 0; + return ParseCommon(payload, ref index); + } + /// + /// Parses a ProcessInfo2 payload. + /// + 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; } } } \ No newline at end of file diff --git a/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs b/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs index c1f8871b36..8e28631d8f 100644 --- a/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs +++ b/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs @@ -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.ClrProductionVersionString); + 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.ClrProductionVersionString); + 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; From 426289d260f0bd4585f97a1c6260366da7c1f161 Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Fri, 11 Jun 2021 10:56:30 -0700 Subject: [PATCH 2/7] Add basic ProcessInfo test. Fix IEndpointInfo.ClrProductVersionString property name. --- .../EndpointInfo.cs | 6 +- .../IEndpointInfoSource.cs | 2 +- .../EndpointInfoSourceTests.cs | 4 +- .../GetProcessInfoTests.cs | 55 +++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs diff --git a/src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs b/src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs index 88a0958768..c570d0e863 100644 --- a/src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs +++ b/src/Microsoft.Diagnostics.Monitoring/EndpointInfo.cs @@ -45,7 +45,7 @@ public static EndpointInfo FromProcessId(int processId) OperatingSystem = processInfo?.OperatingSystem, ProcessArchitecture = processInfo?.ProcessArchitecture, ManagedEntrypointAssemblyName = processInfo?.ManagedEntrypointAssemblyName, - ClrProductionVersionString = processInfo?.ClrProductVersionString + ClrProductVersionString = processInfo?.ClrProductVersionString }; } @@ -82,7 +82,7 @@ public static EndpointInfo FromIpcEndpointInfo(IpcEndpointInfo info) OperatingSystem = processInfo?.OperatingSystem, ProcessArchitecture = processInfo?.ProcessArchitecture, ManagedEntrypointAssemblyName = processInfo?.ManagedEntrypointAssemblyName, - ClrProductionVersionString = processInfo?.ClrProductVersionString + ClrProductVersionString = processInfo?.ClrProductVersionString }; } @@ -100,7 +100,7 @@ public static EndpointInfo FromIpcEndpointInfo(IpcEndpointInfo info) public string ManagedEntrypointAssemblyName { get; private set; } - public string ClrProductionVersionString { get; private set; } + public string ClrProductVersionString { get; private set; } internal string DebuggerDisplay => FormattableString.Invariant($"PID={ProcessId}, Cookie={RuntimeInstanceCookie}"); } diff --git a/src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs b/src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs index 444aeaf442..fb93b5e2d0 100644 --- a/src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs +++ b/src/Microsoft.Diagnostics.Monitoring/IEndpointInfoSource.cs @@ -26,7 +26,7 @@ internal interface IEndpointInfo string ManagedEntrypointAssemblyName { get; } - string ClrProductionVersionString { get; } + string ClrProductVersionString { get; } } public interface IEndpointInfoSource diff --git a/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs b/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs index 8e28631d8f..6b90d91f1e 100644 --- a/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs +++ b/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs @@ -126,7 +126,7 @@ public async Task ServerSourceAddRemoveSingleConnectionTest() Assert.NotNull(endpointInfo.OperatingSystem); Assert.NotNull(endpointInfo.ProcessArchitecture); Assert.Equal("EventPipeTracee", endpointInfo.ManagedEntrypointAssemblyName); - Version clrVersion = ParseVersionRemoveLabel(endpointInfo.ClrProductionVersionString); + Version clrVersion = ParseVersionRemoveLabel(endpointInfo.ClrProductVersionString); Assert.True(clrVersion >= new Version(6, 0, 0)); VerifyConnection(execution1.TestRunner, endpointInfo); @@ -185,7 +185,7 @@ public async Task ServerSourceAddRemoveMultipleConnectionTest() Assert.NotNull(endpointInfo.OperatingSystem); Assert.NotNull(endpointInfo.ProcessArchitecture); Assert.Equal("EventPipeTracee", endpointInfo.ManagedEntrypointAssemblyName); - Version clrVersion = ParseVersionRemoveLabel(endpointInfo.ClrProductionVersionString); + Version clrVersion = ParseVersionRemoveLabel(endpointInfo.ClrProductVersionString); Assert.True(clrVersion >= new Version(6, 0, 0)); VerifyConnection(executions[i].TestRunner, endpointInfo); diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs new file mode 100644 index 0000000000..9df9029574 --- /dev/null +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs @@ -0,0 +1,55 @@ +// 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(); + + DiagnosticsClient client = new DiagnosticsClient(runner.Pid); + + // Test fails if execute command too quickly + Thread.Sleep(1_000); + + ProcessInfo processInfo = client.GetProcessInfo(); + + 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)); + } + + 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); + } + } +} From e50e8794d31a61c2de578fab7eeabad59daea64f Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Fri, 11 Jun 2021 12:49:41 -0700 Subject: [PATCH 3/7] Print status of test process before finishing test. --- .../GetProcessInfoTests.cs | 34 +++++++++++-------- .../TestRunner.cs | 2 +- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs index 9df9029574..328fa68529 100644 --- a/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs @@ -24,21 +24,25 @@ public void BasicProcessInfoTest() using TestRunner runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), output); runner.Start(); - DiagnosticsClient client = new DiagnosticsClient(runner.Pid); - - // Test fails if execute command too quickly - Thread.Sleep(1_000); - - ProcessInfo processInfo = client.GetProcessInfo(); - - 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)); + try + { + DiagnosticsClient client = new DiagnosticsClient(runner.Pid); + + ProcessInfo processInfo = client.GetProcessInfo(); + + 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)); + } + finally + { + runner.PrintStatus(); + } } private static Version ParseVersionRemoveLabel(string versionString) diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/TestRunner.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/TestRunner.cs index 4b37afe627..40d3ac0fba 100644 --- a/src/tests/Microsoft.Diagnostics.NETCore.Client/TestRunner.cs +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/TestRunner.cs @@ -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 { From 8b537125850006399922b6e17eb6537871bdd963 Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Fri, 11 Jun 2021 12:53:07 -0700 Subject: [PATCH 4/7] Disable GetProcessInfo2 command. --- .../DiagnosticsClient/DiagnosticsClient.cs | 9 +++++---- .../EndpointInfoSourceTests.cs | 12 ++++++------ .../GetProcessInfoTests.cs | 6 +++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs index 10719ed75b..d94e468d85 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs @@ -194,10 +194,11 @@ internal void ResumeRuntimeFallback() internal ProcessInfo GetProcessInfo() { - if (TryGetProcessInfo2(out ProcessInfo processInfo)) - { - return processInfo; - } + // 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); diff --git a/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs b/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs index 6b90d91f1e..030dfc5236 100644 --- a/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs +++ b/src/tests/Microsoft.Diagnostics.Monitoring/EndpointInfoSourceTests.cs @@ -125,9 +125,9 @@ 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)); + //Assert.Equal("EventPipeTracee", endpointInfo.ManagedEntrypointAssemblyName); + //Version clrVersion = ParseVersionRemoveLabel(endpointInfo.ClrProductVersionString); + //Assert.True(clrVersion >= new Version(6, 0, 0)); VerifyConnection(execution1.TestRunner, endpointInfo); @@ -184,9 +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)); + //Assert.Equal("EventPipeTracee", endpointInfo.ManagedEntrypointAssemblyName); + //Version clrVersion = ParseVersionRemoveLabel(endpointInfo.ClrProductVersionString); + //Assert.True(clrVersion >= new Version(6, 0, 0)); VerifyConnection(executions[i].TestRunner, endpointInfo); } diff --git a/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs index 328fa68529..da832fa6d4 100644 --- a/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs +++ b/src/tests/Microsoft.Diagnostics.NETCore.Client/GetProcessInfoTests.cs @@ -35,9 +35,9 @@ public void BasicProcessInfoTest() 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)); + //Assert.Equal("Tracee", processInfo.ManagedEntrypointAssemblyName); + //Version clrVersion = ParseVersionRemoveLabel(processInfo.ClrProductVersionString); + //Assert.True(clrVersion >= new Version(6, 0, 0)); } finally { From 8eff595d3e65b120c3ec82f728bc3b3801d87d50 Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Thu, 24 Jun 2021 20:54:58 -0700 Subject: [PATCH 5/7] Fix docs --- documentation/design-docs/ipc-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/design-docs/ipc-protocol.md b/documentation/design-docs/ipc-protocol.md index 71575fe7d2..87896ad68d 100644 --- a/documentation/design-docs/ipc-protocol.md +++ b/documentation/design-docs/ipc-protocol.md @@ -827,7 +827,7 @@ Payload: * 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.#####` +* `string clrProductVersion`: the product version of the CLR of the process; may contain prerelease label information e.g. `6.0.0-preview.6.#####` ##### Details: From 3677dc1a7beab02c6229ebe69e3500c8468a560b Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Mon, 28 Jun 2021 12:14:45 -0700 Subject: [PATCH 6/7] Only allow fallback to GetProcessInfo if GetProcessInfo2 is unknown. Fix other minor post-merge issues. --- .../DiagnosticsClient/DiagnosticsClient.cs | 29 ++++++++++++++----- .../DiagnosticsIpc/ProcessInfo.cs | 4 +-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs index e47ab73a70..c5f95d26fa 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs @@ -319,12 +319,18 @@ internal void ResumeRuntimeFallback() internal ProcessInfo GetProcessInfo() { - // GetProcessInfo2 command seems to cause target runtime to crash very frequently - //if (TryGetProcessInfo2(out ProcessInfo processInfo)) + // RE: https://github.com/dotnet/runtime/issues/54083 + // If the GetProcessInfo2 command is sent too early, it will crash the runtime instance. + // Disable the usage of the command until that issue is fixed. + + // Attempt to get ProcessInfo v2 + //ProcessInfo processInfo = GetProcessInfo2(); + //if (null != processInfo) //{ // return processInfo; //} + // Attempt to get ProcessInfo v1 IpcMessage message = new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessInfo); var response = IpcClient.SendMessage(_endpoint, message); switch ((DiagnosticsServerResponseId)response.Header.CommandId) @@ -339,18 +345,25 @@ internal ProcessInfo GetProcessInfo() } } - private bool TryGetProcessInfo2(out ProcessInfo processInfo) + private ProcessInfo GetProcessInfo2() { IpcMessage message = new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessInfo2); var response = IpcClient.SendMessage(_endpoint, message); switch ((DiagnosticsServerResponseId)response.Header.CommandId) { + case DiagnosticsServerResponseId.Error: + uint hr = BitConverter.ToUInt32(response.Payload, 0); + // In the case that the runtime doesn't understand the GetProcessInfo2 command, + // just break to allow fallback to try to get ProcessInfo v1. + if (hr == (uint)DiagnosticsIpcError.UnknownCommand) + { + return null; + } + throw new ServerErrorException($"GetProcessInfo2 failed (HRESULT: 0x{hr:X8})"); case DiagnosticsServerResponseId.OK: - processInfo = ProcessInfo.ParseV2(response.Payload); - return true; + return ProcessInfo.ParseV2(response.Payload); default: - processInfo = null; - return false; + throw new ServerErrorException($"Get process info failed - server responded with unknown command"); } } @@ -425,7 +438,7 @@ private static void SerializePayloadArgument(T obj, BinaryWriter writer) else if (typeof(T) == typeof(bool)) { bool bValue = (bool)((object)obj); - uint uiValue = bValue ? 1 : 0; + uint uiValue = bValue ? (uint)1 : 0; writer.Write(uiValue); } else diff --git a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs index 232b8212d0..887612d866 100644 --- a/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs +++ b/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs @@ -56,8 +56,8 @@ 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); + processInfo.ManagedEntrypointAssemblyName = IpcHelpers.ReadString(payload, ref index); + processInfo.ClrProductVersionString = IpcHelpers.ReadString(payload, ref index); return processInfo; } From 1f62c0f65e36d3d377162bc388eed2ed33786ac3 Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Wed, 30 Jun 2021 21:25:53 -0700 Subject: [PATCH 7/7] Add additional comment to documentation. --- documentation/design-docs/ipc-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/design-docs/ipc-protocol.md b/documentation/design-docs/ipc-protocol.md index 87896ad68d..42c28f7b30 100644 --- a/documentation/design-docs/ipc-protocol.md +++ b/documentation/design-docs/ipc-protocol.md @@ -826,7 +826,7 @@ Payload: * ARM32 => `"arm32"` * ARM64 => `"arm64"` * Other => `"Unknown"` -* `string managedEntrypointAssemblyName`: the assembly name from the assembly identity of the entrypoint assembly of the process +* `string managedEntrypointAssemblyName`: the assembly name from the assembly identity of the entrypoint assembly of the process. This is the same value that is returned from executing `System.Reflection.Assembly.GetEntryAssembly().GetName().Name` in the target process. * `string clrProductVersion`: the product version of the CLR of the process; may contain prerelease label information e.g. `6.0.0-preview.6.#####` ##### Details: