ProcessInfo3 diagnostic command (#3985)
authorJustin Anderson <jander-msft@users.noreply.github.com>
Fri, 30 Jun 2023 07:48:14 +0000 (00:48 -0700)
committerGitHub <noreply@github.com>
Fri, 30 Jun 2023 07:48:14 +0000 (00:48 -0700)
Implement the ProcessInfo3 command as described in #3476

Corresponding runtime change:
https://github.com/dotnet/runtime/pull/87707

src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/DiagnosticsClient.cs
src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/IpcCommands.cs
src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsIpc/ProcessInfo.cs

index 898d6f72a343c1b4a455200dcd15c8cfd9ae472e..8084d0af8e648df22fc86e7646d1b29c41329cde 100644 (file)
@@ -404,8 +404,15 @@ namespace Microsoft.Diagnostics.NETCore.Client
 
         internal ProcessInfo GetProcessInfo()
         {
+            // Attempt to get ProcessInfo v3
+            ProcessInfo processInfo = TryGetProcessInfo3();
+            if (null != processInfo)
+            {
+                return processInfo;
+            }
+
             // Attempt to get ProcessInfo v2
-            ProcessInfo processInfo = TryGetProcessInfo2();
+            processInfo = TryGetProcessInfo2();
             if (null != processInfo)
             {
                 return processInfo;
@@ -418,8 +425,15 @@ namespace Microsoft.Diagnostics.NETCore.Client
 
         internal async Task<ProcessInfo> GetProcessInfoAsync(CancellationToken token)
         {
+            // Attempt to get ProcessInfo v3
+            ProcessInfo processInfo = await TryGetProcessInfo3Async(token).ConfigureAwait(false);
+            if (null != processInfo)
+            {
+                return processInfo;
+            }
+
             // Attempt to get ProcessInfo v2
-            ProcessInfo processInfo = await TryGetProcessInfo2Async(token).ConfigureAwait(false);
+            processInfo = await TryGetProcessInfo2Async(token).ConfigureAwait(false);
             if (null != processInfo)
             {
                 return processInfo;
@@ -444,6 +458,20 @@ namespace Microsoft.Diagnostics.NETCore.Client
             return TryGetProcessInfo2FromResponse(response2, nameof(GetProcessInfoAsync));
         }
 
+        private ProcessInfo TryGetProcessInfo3()
+        {
+            IpcMessage request = CreateProcessInfo3Message();
+            using IpcResponse response2 = IpcClient.SendMessageGetContinuation(_endpoint, request);
+            return TryGetProcessInfo3FromResponse(response2, nameof(GetProcessInfo));
+        }
+
+        private async Task<ProcessInfo> TryGetProcessInfo3Async(CancellationToken token)
+        {
+            IpcMessage request = CreateProcessInfo3Message();
+            using IpcResponse response2 = await IpcClient.SendMessageGetContinuationAsync(_endpoint, request, token).ConfigureAwait(false);
+            return TryGetProcessInfo3FromResponse(response2, nameof(GetProcessInfoAsync));
+        }
+
         private static byte[] SerializePayload<T>(T arg)
         {
             using (MemoryStream stream = new())
@@ -571,6 +599,11 @@ namespace Microsoft.Diagnostics.NETCore.Client
             return new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessInfo2);
         }
 
+        private static IpcMessage CreateProcessInfo3Message()
+        {
+            return new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessInfo3);
+        }
+
         private static IpcMessage CreateResumeRuntimeMessage()
         {
             return new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.ResumeRuntime);
@@ -665,6 +698,16 @@ namespace Microsoft.Diagnostics.NETCore.Client
             return ProcessInfo.ParseV2(response.Message.Payload);
         }
 
+        private static ProcessInfo TryGetProcessInfo3FromResponse(IpcResponse response, string operationName)
+        {
+            if (!ValidateResponseMessage(response.Message, operationName, ValidateResponseOptions.UnknownCommandReturnsFalse))
+            {
+                return null;
+            }
+
+            return ProcessInfo.ParseV3(response.Message.Payload);
+        }
+
         internal static bool ValidateResponseMessage(IpcMessage responseMessage, string operationName, ValidateResponseOptions options = ValidateResponseOptions.None)
         {
             switch ((DiagnosticsServerResponseId)responseMessage.Header.CommandId)
index 6f86eb59a8b25758097af985bb3bf1d928d53a87..f9eb3019ef94704f26865d689848ab9fc9033070 100644 (file)
@@ -49,6 +49,7 @@ namespace Microsoft.Diagnostics.NETCore.Client
         GetProcessInfo2 = 0x04,
         EnablePerfMap = 0x05,
         DisablePerfMap = 0x06,
-        ApplyStartupHook = 0x07
+        ApplyStartupHook = 0x07,
+        GetProcessInfo3 = 0x08
     }
 }
index 044065b14989bfb09d9f86da4667309fe362cf2f..2d84a4b2546a9ae7ef5f6de189577cb33d269c26 100644 (file)
@@ -53,10 +53,28 @@ namespace Microsoft.Diagnostics.NETCore.Client
         internal static ProcessInfo ParseV2(byte[] payload)
         {
             int index = 0;
-            ProcessInfo processInfo = ParseCommon(payload, ref index);
+            return ParseCommon2(payload, ref index);
+        }
 
-            processInfo.ManagedEntrypointAssemblyName = IpcHelpers.ReadString(payload, ref index);
-            processInfo.ClrProductVersionString = IpcHelpers.ReadString(payload, ref index);
+        /// <summary>
+        /// Parses a ProcessInfo3 payload.
+        /// </summary>
+        internal static ProcessInfo ParseV3(byte[] payload)
+        {
+            int index = 0;
+
+            // The ProcessInfo3 command is intended to allow the addition of new fields in future versions so
+            // long as the version field is incremented; prior fields shall not be changed or removed.
+            // Read the version field, parse the common payload, and dynamically parse the remainder depending on the version.
+            uint version = BinaryPrimitives.ReadUInt32LittleEndian(new ReadOnlySpan<byte>(payload, index, 4));
+            index += sizeof(uint);
+
+            ProcessInfo processInfo = ParseCommon2(payload, ref index);
+
+            if (version >= 1)
+            {
+                processInfo.PortableRuntimeIdentifier = IpcHelpers.ReadString(payload, ref index);
+            }
 
             return processInfo;
         }
@@ -80,6 +98,16 @@ namespace Microsoft.Diagnostics.NETCore.Client
             return processInfo;
         }
 
+        private static ProcessInfo ParseCommon2(byte[] payload, ref int index)
+        {
+            ProcessInfo processInfo = ParseCommon(payload, ref index);
+
+            processInfo.ManagedEntrypointAssemblyName = IpcHelpers.ReadString(payload, ref index);
+            processInfo.ClrProductVersionString = IpcHelpers.ReadString(payload, ref index);
+
+            return processInfo;
+        }
+
         public ulong ProcessId { get; private set; }
         public Guid RuntimeInstanceCookie { get; private set; }
         public string CommandLine { get; private set; }
@@ -87,5 +115,6 @@ namespace Microsoft.Diagnostics.NETCore.Client
         public string ProcessArchitecture { get; private set; }
         public string ManagedEntrypointAssemblyName { get; private set; }
         public string ClrProductVersionString { get; private set; }
+        public string PortableRuntimeIdentifier { get; private set; }
     }
 }