Add some diag info options to host commands (#4528)
authorMike McLaughlin <mikem@microsoft.com>
Wed, 28 Feb 2024 04:07:54 +0000 (20:07 -0800)
committerGitHub <noreply@github.com>
Wed, 28 Feb 2024 04:07:54 +0000 (20:07 -0800)
Added some new options and display the various special runtime exports
and data structures to help diagnose common SOS and managed debugging
failures.

The runtimes, sosstatus and modules -v commands now displays the special
module exports g_dacTable, g_CLREngineMetrics, DotNetRuntimeInfo
(single-file info), DotNetRuntimeDebugHeader (Native AOT data contract).

The "modules -r" or "!sos modules -r" now displays the module's
resources and displays CLRDEBUGINFO in a readable format.

Use Microsoft.FileFormat to read the various in-memory structs like
SpecialDiagInfoHeader, RuntimeInfo, ClrEngineMetrics, etc. by adding a
Microsoft.FileFormat Reader instance to the target services.

17 files changed:
diagnostics.sln
src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs
src/Microsoft.Diagnostics.DebugServices/CommandBase.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/ClrDebugResource.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/Host/ClrEngineMetrics.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/Host/CommandFormatHelpers.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/Host/HelpCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/ModulesCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimeInfo.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/SpecialDiagInfoHeader.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.ExtensionCommands/Host/StatusCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Host/ThreadsCommand.cs
src/Microsoft.Diagnostics.ExtensionCommands/Output/ArrayExtensions.cs [new file with mode: 0644]
src/Tools/dotnet-dump/Commands/SOSCommand.cs
src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/WriteTestData.cs

index 08398b86bf4b531a7471d23ef32cdd00e6738a4a..c34b906204eb17d761b17535268d704db6aa4661 100644 (file)
@@ -227,6 +227,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "inc", "inc", "{BE45F03E-D70
                src\SOS\inc\lldbservices.h = src\SOS\inc\lldbservices.h
                src\SOS\inc\remotememoryservice.h = src\SOS\inc\remotememoryservice.h
                src\SOS\inc\runtime.h = src\SOS\inc\runtime.h
+               src\SOS\inc\specialdiaginfo.h = src\SOS\inc\specialdiaginfo.h
                src\SOS\inc\specialthreadinfo.h = src\SOS\inc\specialthreadinfo.h
                src\SOS\inc\symbolservice.h = src\SOS\inc\symbolservice.h
                src\SOS\inc\target.h = src\SOS\inc\target.h
index c56765c014ab98fe67b96e4aefea88f2cd0d0f49..ef969eb2d23bf2fd2c46ba158d7a8008dda8f821 100644 (file)
@@ -62,21 +62,5 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         }
 
         #endregion
-
-        public override string ToString()
-        {
-            StringBuilder sb = new();
-            if (_runtimes is not null)
-            {
-                IRuntime currentRuntime = _services.GetService<IContextService>()?.GetCurrentRuntime();
-                foreach (IRuntime runtime in _runtimes)
-                {
-                    string current = _runtimes.Count > 1 ? runtime == currentRuntime ? "*" : " " : "";
-                    sb.Append(current);
-                    sb.AppendLine(runtime.ToString());
-                }
-            }
-            return sb.ToString();
-        }
     }
 }
index 9addd99b10b1a66378eb9d16e467cd13d10f9b4c..3f4f899c99d4bf839ac899b1882514fa46d98aba 100644 (file)
@@ -6,6 +6,7 @@ using System.Diagnostics;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Text;
+using Microsoft.FileFormats;
 using Architecture = System.Runtime.InteropServices.Architecture;
 
 namespace Microsoft.Diagnostics.DebugServices.Implementation
@@ -34,6 +35,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             // Initialize the per-target services.
             _serviceContainerFactory = host.Services.GetService<IServiceManager>().CreateServiceContainerFactory(ServiceScope.Target, host.Services);
             _serviceContainerFactory.AddServiceFactory<ITarget>((_) => this);
+            _serviceContainerFactory.AddServiceFactory<Reader>(CreateReader);
         }
 
         protected void Finished()
@@ -132,6 +134,23 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
 
         #endregion
 
+        /// <summary>
+        /// Create the file format reader used to read and layout TStruct derived structures from memory
+        /// </summary>
+        private static Reader CreateReader(IServiceProvider services)
+        {
+            IMemoryService memoryService = services.GetService<IMemoryService>();
+            Stream stream = memoryService.CreateMemoryStream();
+            LayoutManager layoutManager = new LayoutManager()
+                                            .AddPrimitives()
+                                            .AddEnumTypes()
+                                            .AddSizeT(memoryService.PointerSize)
+                                            .AddPointerTypes()
+                                            .AddNullTerminatedString()
+                                            .AddTStructTypes();
+            return new Reader(new StreamAddressSpace(stream), layoutManager);
+        }
+
         private void CleanupTempDirectory()
         {
             if (_tempDirectory != null)
@@ -165,19 +184,14 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         {
             StringBuilder sb = new();
             string process = ProcessId.HasValue ? string.Format("{0} (0x{0:X})", ProcessId.Value) : "<none>";
-            sb.AppendLine($"Target OS: {OperatingSystem} Architecture: {Architecture} ProcessId: {process}");
-            if (_tempDirectory != null)
-            {
-                sb.AppendLine($"Temp path: {_tempDirectory}");
-            }
+            sb.Append($"Target OS: {OperatingSystem} Architecture: {Architecture} ProcessId: {process}");
             if (_dumpPath != null)
             {
-                sb.AppendLine($"Dump path: {_dumpPath}");
+                sb.Append($" {_dumpPath}");
             }
-            IRuntimeService runtimeService = Services.GetService<IRuntimeService>();
-            if (runtimeService != null)
+            if (_tempDirectory != null)
             {
-                sb.AppendLine(runtimeService.ToString());
+                sb.Append($" {_tempDirectory}");
             }
             return sb.ToString();
         }
index 39c0508fae960bbffdcda86d7fb73674b3301f04..7a1cf6842d5d028c3dde150c2680d9f6e81f8e0f 100644 (file)
@@ -10,6 +10,12 @@ namespace Microsoft.Diagnostics.DebugServices
     /// </summary>
     public abstract class CommandBase
     {
+        /// <summary>
+        /// The services provided to this command
+        /// </summary>
+        [ServiceImport]
+        public IServiceProvider Services { get; set; }
+
         /// <summary>
         /// Console service
         /// </summary>
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/ClrDebugResource.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ClrDebugResource.cs
new file mode 100644 (file)
index 0000000..58c6b47
--- /dev/null
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    /// <summary>
+    /// Native CLR_DEBUG_RESOURCE struct
+    /// </summary>
+    [StructLayout(LayoutKind.Sequential, Pack = 1)]
+    public struct ClrDebugResource
+    {
+        public uint dwVersion;
+        public Guid signature;
+        public int dwDacTimeStamp;
+        public int dwDacSizeOfImage;
+        public int dwDbiTimeStamp;
+        public int dwDbiSizeOfImage;
+    }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/ClrEngineMetrics.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ClrEngineMetrics.cs
new file mode 100644 (file)
index 0000000..e972865
--- /dev/null
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.FileFormats;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    public class ClrEngineMetrics : TStruct
+    {
+        public const string Symbol = "g_CLREngineMetrics";
+
+        public readonly int Size;
+        public readonly int DbiVersion;
+        public readonly SizeT ContinueStartupEvent;
+
+        public static bool TryRead(IServiceProvider services, ulong address, out ClrEngineMetrics metrics)
+        {
+            metrics = default;
+
+            Reader reader = services.GetService<Reader>();
+            if (reader is null)
+            {
+                return false;
+            }
+
+            try
+            {
+                metrics = reader.Read<ClrEngineMetrics>(address);
+            }
+            catch (Exception ex) when (ex is InvalidVirtualAddressException or BadInputFormatException)
+            {
+                return false;
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/CommandFormatHelpers.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/CommandFormatHelpers.cs
new file mode 100644 (file)
index 0000000..b5e7652
--- /dev/null
@@ -0,0 +1,207 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.ExtensionCommands.Output;
+using Microsoft.Diagnostics.Runtime;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    public static class CommandFormatHelpers
+    {
+        public const string DacTableSymbol = "g_dacTable";
+        public const string DebugHeaderSymbol = "DotNetRuntimeDebugHeader";
+
+        /// <summary>
+        /// Displays the special diagnostics info header memory block (.NET Core 8 or later on Linux/MacOS)
+        /// </summary>
+        public static void DisplaySpecialInfo(this CommandBase command, string indent = "")
+        {
+            if (command.Services.GetService<ITarget>().OperatingSystem != OSPlatform.Windows)
+            {
+                ulong address = SpecialDiagInfoHeader.GetAddress(command.Services);
+                command.Console.Write($"{indent}SpecialDiagInfoHeader   : {address:X16}");
+                if (SpecialDiagInfoHeader.TryRead(command.Services, address, out SpecialDiagInfoHeader info))
+                {
+                    command.Console.WriteLine(info.IsValid ? "" : " <INVALID>");
+                    command.Console.WriteLine($"{indent}    Signature:              {info.Signature}");
+                    command.Console.WriteLine($"{indent}    Version:                {info.Version}");
+                    command.Console.WriteLine($"{indent}    ExceptionRecordAddress: {info.ExceptionRecordAddress:X16}");
+                    command.Console.WriteLine($"{indent}    RuntimeBaseAddress:     {info.RuntimeBaseAddress:X16}");
+
+                    if (info.Version >= SpecialDiagInfoHeader.SPECIAL_DIAGINFO_RUNTIME_BASEADDRESS)
+                    {
+                        IModule runtimeModule = command.Services.GetService<IModuleService>().GetModuleFromBaseAddress(info.RuntimeBaseAddress);
+                        if (runtimeModule != null)
+                        {
+                            command.DisplayRuntimeExports(runtimeModule, error: true, indent + "    ");
+                        }
+                    }
+                }
+                else
+                {
+                    command.Console.WriteLine(" <NONE>");
+                }
+            }
+        }
+
+        /// <summary>
+        /// Display the module's resources. The ClrDebugResource is pretty formatted.
+        /// </summary>
+        /// <exception cref="DiagnosticsException"></exception>
+        public static void DisplayResources(this CommandBase command, IModule module, bool all, string indent)
+        {
+            if (module.IsPEImage)
+            {
+                command.Console.WriteLine($"{indent}Resources:");
+                IDataReader reader = command.Services.GetService<IDataReader>() ?? throw new DiagnosticsException("IDataReader service needed");
+                IResourceNode resourceRoot = ModuleInfo.TryCreateResourceRoot(reader, module.ImageBase, module.ImageSize, module.IsFileLayout.GetValueOrDefault(false));
+                if (resourceRoot != null)
+                {
+                    foreach (IResourceNode child in resourceRoot.Children)
+                    {
+                        DisplayResources(command.Console, child, all, indent + "    ");
+                    }
+                }
+            }
+        }
+
+        private static void DisplayResources(IConsoleService console, IResourceNode resourceNode, bool all, string indent)
+        {
+            if (resourceNode.Name.StartsWith("CLRDEBUGINFO"))
+            {
+                console.WriteLine($"{indent}Name: {resourceNode.Name}");
+                IResourceNode node = resourceNode.Children.FirstOrDefault();
+                if (node is not null)
+                {
+                    ClrDebugResource clrDebugResource = node.Read<ClrDebugResource>(0);
+                    console.WriteLine($"{indent}    Size:           {node.Size:X8}");
+                    console.WriteLine($"{indent}    Version:        {clrDebugResource.dwVersion:X8}");
+                    console.WriteLine($"{indent}    Signature:      {clrDebugResource.signature}");
+                    console.WriteLine($"{indent}    DacTimeStamp:   {clrDebugResource.dwDacTimeStamp:X8}");
+                    console.WriteLine($"{indent}    DacSizeOfImage: {clrDebugResource.dwDacSizeOfImage:X8}");
+                    console.WriteLine($"{indent}    DbiTimeStamp:   {clrDebugResource.dwDbiTimeStamp:X8}");
+                    console.WriteLine($"{indent}    DbiSizeOfImage: {clrDebugResource.dwDbiSizeOfImage:X8}");
+                }
+            }
+            else
+            {
+                if (all)
+                {
+                    console.WriteLine($"{indent}Name: {resourceNode.Name}");
+                    int size = resourceNode.Size;
+                    if (size > 0)
+                    {
+                        console.WriteLine($"{indent}Size: {size:X8}");
+                    }
+                    indent += "    ";
+                }
+                foreach (IResourceNode child in resourceNode.Children)
+                {
+                    DisplayResources(console, child, all, indent);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Displays the module's special runtime exports
+        /// </summary>
+        public static void DisplayRuntimeExports(this CommandBase command, IModule module, bool error, string indent)
+        {
+            bool header = false;
+            IConsoleService Console()
+            {
+                if (!header)
+                {
+                    header = true;
+                    command.Console.WriteLine($"{indent}Exports:");
+                    indent += "    ";
+                }
+                return command.Console;
+            }
+            // Print the runtime info (.NET Core single-file)
+            IExportSymbols symbols = module.Services.GetService<IExportSymbols>();
+            if (symbols != null && symbols.TryGetSymbolAddress(RuntimeInfo.RUNTIME_INFO_SYMBOL, out ulong infoAddress))
+            {
+                Console().Write($"{indent}{RuntimeInfo.RUNTIME_INFO_SYMBOL,-24}: {infoAddress:X16}");
+                if (RuntimeInfo.TryRead(command.Services, infoAddress, out RuntimeInfo info))
+                {
+                    Console().WriteLine(info.IsValid ? "" : " <INVALID>");
+                    Console().WriteLine($"{indent}    Signature:                  {info.Signature}");
+                    Console().WriteLine($"{indent}    Version:                    {info.Version}");
+                    Console().WriteLine($"{indent}    RuntimeModuleIndex:         {info.RawRuntimeModuleIndex.ToHex()}");
+                    Console().WriteLine($"{indent}    DacModuleIndex:             {info.RawDacModuleIndex.ToHex()}");
+                    Console().WriteLine($"{indent}    DbiModuleIndex:             {info.RawDbiModuleIndex.ToHex()}");
+                    if (module.IsPEImage)
+                    {
+                        Console().WriteLine($"{indent}    RuntimePEIndex:             {info.RuntimePEIIndex.timeStamp:X8}/{info.RuntimePEIIndex.fileSize:X}");
+                        Console().WriteLine($"{indent}    DacPEIndex:                 {info.DacPEIndex.timeStamp:X8}/{info.DacPEIndex.fileSize:X}");
+                        Console().WriteLine($"{indent}    DbiPEIndex:                 {info.DbiPEIndex.timeStamp:X8}/{info.DbiPEIndex.fileSize:X}");
+                    }
+                    else
+                    {
+                        Console().WriteLine($"{indent}    RuntimeBuildId:             {info.RuntimeBuildId.ToHex()}");
+                        Console().WriteLine($"{indent}    DacBuildId:                 {info.DacBuildId.ToHex()}");
+                        Console().WriteLine($"{indent}    DbiBuildId:                 {info.DbiBuildId.ToHex()}");
+                    }
+                    Console().WriteLine($"{indent}    RuntimeVersion:             {info.RuntimeVersion?.ToString() ?? "<none>"}");
+                }
+                else
+                {
+                    Console().WriteLineError(" <NONE>");
+                }
+            }
+            else if (error)
+            {
+                Console().WriteLineError($"{indent}{RuntimeInfo.RUNTIME_INFO_SYMBOL,-24}: <NO SYMBOL>");
+            }
+
+            // Print the Windows runtime engine metrics (.NET Core and .NET Framework)
+            if (command.Services.GetService<ITarget>().OperatingSystem == OSPlatform.Windows)
+            {
+                if (symbols != null && symbols.TryGetSymbolAddress(ClrEngineMetrics.Symbol, out ulong metricsAddress))
+                {
+                    Console().Write($"{indent}{ClrEngineMetrics.Symbol,-24}: ({metricsAddress:X16})");
+                    if (ClrEngineMetrics.TryRead(command.Services, metricsAddress, out ClrEngineMetrics metrics))
+                    {
+                        Console().WriteLine();
+                        Console().WriteLine($"{indent}    Size:                   {metrics.Size} (0x{metrics.Size:X2})");
+                        Console().WriteLine($"{indent}    DbiVersion:             {metrics.DbiVersion}");
+                        Console().WriteLine($"{indent}    ContinueStartupEvent:   {((ulong)metrics.ContinueStartupEvent):X16}");
+                    }
+                    else
+                    {
+                        Console().WriteLineError(" <NONE>");
+                    }
+                }
+                else if (error)
+                {
+                    Console().WriteLineError($"{indent}{ClrEngineMetrics.Symbol,-24}: <NO SYMBOL>");
+                }
+            }
+
+            // Print the DAC table address (g_dacTable)
+            if (symbols != null && symbols.TryGetSymbolAddress(DacTableSymbol, out ulong dacTableAddress))
+            {
+                Console().WriteLine($"{indent}{DacTableSymbol,-24}: {dacTableAddress:X16}");
+            }
+            else if (error)
+            {
+                Console().WriteLineError($"{indent}{DacTableSymbol,-24}: <NO SYMBOL>");
+            }
+
+            // Print the Native AOT contract data address (DotNetRuntimeDebugHeader)
+            if (symbols != null && symbols.TryGetSymbolAddress(DebugHeaderSymbol, out ulong debugHeaderAddress))
+            {
+                Console().WriteLine($"{indent}{DebugHeaderSymbol,-24}: {debugHeaderAddress:X16}");
+            }
+            else if (error)
+            {
+                Console().WriteLineError($"{indent}{DebugHeaderSymbol,-24}: <NO SYMBOL>");
+            }
+        }
+    }
+}
index 2770c53247b636c505b609567a60b7c972cee17e..1fef058515ea6c3e49e3dde41d7837a9a8ce4573 100644 (file)
@@ -17,9 +17,6 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [ServiceImport]
         public ICommandService CommandService { get; set; }
 
-        [ServiceImport]
-        public IServiceProvider Services { get; set; }
-
         public override void Invoke()
         {
             if (string.IsNullOrWhiteSpace(Command))
index 7aefb80c8649b583d433ab5165557a3005e3b36d..bd2fa89dfb9fcd7f299b03b12a3e632f7e7ce8db 100644 (file)
@@ -5,8 +5,10 @@ using System;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
+using System.Runtime.InteropServices;
 using System.Text.RegularExpressions;
 using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
 using Microsoft.FileFormats;
 using Microsoft.FileFormats.ELF;
 using Microsoft.FileFormats.MachO;
@@ -22,15 +24,24 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Option(Name = "--segments", Aliases = new string[] { "-s" }, Help = "Displays the module segments.")]
         public bool Segment { get; set; }
 
+        [Option(Name = "--resources", Aliases = new string[] { "-r" }, Help = "Displays the module resources.")]
+        public bool Resources { get; set; }
+
         [Option(Name = "--name", Aliases = new string[] { "-n" }, Help = "RegEx filter on module name (path not included).")]
         public string ModuleName { get; set; }
 
         [Option(Name = "--address", Aliases = new string[] { "-a" }, Help = "Lookup address in module list.")]
         public ulong? Address { get; set; }
 
+        [ServiceImport]
+        public ITarget Target { get; set; }
+
         [ServiceImport]
         public IModuleService ModuleService { get; set; }
 
+        [ServiceImport]
+        public IMemoryService MemoryService { get; set; }
+
         public override void Invoke()
         {
             if (Address.HasValue)
@@ -84,6 +95,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                     WriteLine("    PdbInfo:         {0}", pdbFileInfo);
                 }
                 WriteLine("    BuildId:         {0}", !module.BuildId.IsDefaultOrEmpty ? string.Concat(module.BuildId.Select((b) => b.ToString("x2"))) : "<none>");
+
+                this.DisplayRuntimeExports(module, error: false, indent: "    ");
             }
             else
             {
@@ -93,14 +106,12 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             {
                 DisplaySegments(module);
             }
+            if (Resources)
+            {
+                this.DisplayResources(module, all: true, indent: "    ");
+            }
         }
 
-        [ServiceImport]
-        public ITarget Target { get; set; }
-
-        [ServiceImport]
-        public IMemoryService MemoryService { get; set; }
-
         private void DisplaySegments(IModule module)
         {
             try
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimeInfo.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimeInfo.cs
new file mode 100644 (file)
index 0000000..f4dcbf0
--- /dev/null
@@ -0,0 +1,85 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.FileFormats;
+
+namespace Microsoft.Diagnostics.Runtime
+{
+    public class RuntimeInfo : TStruct
+    {
+        public const string RUNTIME_INFO_SYMBOL = "DotNetRuntimeInfo";
+        public const string RUNTIME_INFO_SIGNATURE = "DotNetRuntimeInfo";
+        public const int RUNTIME_INFO_RUNTIME_VERSION = 2;
+        public const int RUNTIME_INFO_LATEST = 2;
+
+        [ArraySize(18)]
+        public readonly byte[] RawSignature;
+        public readonly int Version;
+        [ArraySize(24)]
+        public readonly byte[] RawRuntimeModuleIndex;
+        [ArraySize(24)]
+        public readonly byte[] RawDacModuleIndex;
+        [ArraySize(24)]
+        public readonly byte[] RawDbiModuleIndex;
+        [ArraySize(4)]
+        public readonly int[] RawRuntimeVersion;                // major, minor, build, revision - added in version RUNTIME_INFO_RUNTIME_VERSION
+
+        public static unsafe bool TryRead(IServiceProvider services, ulong address, out RuntimeInfo info)
+        {
+            info = default;
+
+            Reader reader = services.GetService<Reader>();
+            if (reader is null)
+            {
+                return false;
+            }
+
+            try
+            {
+                info = reader.Read<RuntimeInfo>(address);
+            }
+            catch (Exception ex) when (ex is InvalidVirtualAddressException or BadInputFormatException)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public string Signature => Encoding.ASCII.GetString(RawSignature.Take(RUNTIME_INFO_SIGNATURE.Length).ToArray());
+
+        public bool IsValid => Version > 0 && Signature == RUNTIME_INFO_SIGNATURE;
+
+        public (int timeStamp, int fileSize) RuntimePEIIndex => GetPEIndex(RawRuntimeModuleIndex);
+
+        public (int timeStamp, int fileSize) DacPEIndex => GetPEIndex(RawDacModuleIndex);
+
+        public (int timeStamp, int fileSize) DbiPEIndex => GetPEIndex(RawDbiModuleIndex);
+
+        private static (int timeStamp, int fileSize) GetPEIndex(byte[] index)
+        {
+            if (index[0] < 2 * sizeof(int))
+            {
+                return (0, 0);
+            }
+            return (BitConverter.ToInt32(index, 1), BitConverter.ToInt32(index, 1 + sizeof(int)));
+        }
+
+        public ImmutableArray<byte> RuntimeBuildId => GetBuildId(RawRuntimeModuleIndex);
+
+        public ImmutableArray<byte> DacBuildId => GetBuildId(RawDacModuleIndex);
+
+        public ImmutableArray<byte> DbiBuildId => GetBuildId(RawDbiModuleIndex);
+
+        private static ImmutableArray<byte> GetBuildId(byte[] index) => index.Skip(1).Take(index[0]).ToImmutableArray();
+
+        public Version RuntimeVersion => Version >= RUNTIME_INFO_RUNTIME_VERSION ? new Version(RawRuntimeVersion[0], RawRuntimeVersion[1], RawRuntimeVersion[2], RawRuntimeVersion[3]) : null;
+    }
+}
index 6954e6bab97422b1e0a64a58a60edb1e8bc5ace6..1d678a23aa492938de0e8bd9cbad6947ef671b63 100644 (file)
@@ -1,9 +1,10 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Collections.Immutable;
+using System;
 using System.Linq;
 using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.ExtensionCommands.Output;
 using Microsoft.Diagnostics.Runtime;
 
 namespace Microsoft.Diagnostics.ExtensionCommands
@@ -76,13 +77,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands
                             WriteLine($"        {library.Kind} {library.FileName} {library.Platform} {library.TargetArchitecture} {library.ArchivedUnder} {index}");
                         }
                     }
+                    this.DisplayResources(runtime.RuntimeModule, all: false, indent: "    ");
+                    this.DisplayRuntimeExports(runtime.RuntimeModule, error: true, indent: "    ");
                 }
             }
         }
     }
-
-    public static class CommandUtilities
-    {
-        public static string ToHex(this ImmutableArray<byte> array) => string.Concat(array.Select((b) => b.ToString("x2")));
-    }
 }
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SpecialDiagInfoHeader.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SpecialDiagInfoHeader.cs
new file mode 100644 (file)
index 0000000..3a7a154
--- /dev/null
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.FileFormats;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    public class SpecialDiagInfoHeader : TStruct
+    {
+        public const string SPECIAL_DIAGINFO_SIGNATURE = "DIAGINFOHEADER";
+        public const int SPECIAL_DIAGINFO_RUNTIME_BASEADDRESS = 2;
+        public const int SPECIAL_DIAGINFO_LATEST = 2;
+
+        public const ulong SpecialDiagInfoAddress_OSX = 0x7fffffff10000000UL;
+        public const ulong SpecialDiagInfoAddress_64BIT = 0x00007ffffff10000UL;
+        public const ulong SpecialDiagInfoAddress_32BIT = 0x000000007fff1000UL;
+        public const int SpecialDiagInfoSize = 0x1000;
+
+        [ArraySize(16)]
+        public readonly byte[] RawSignature;
+        public readonly int Version;
+        public readonly ulong ExceptionRecordAddress;
+        public readonly ulong RuntimeBaseAddress;       // Exists in version SPECIAL_DIAGINFO_RUNTIME_BASEADDRESS
+
+        public static bool TryRead(IServiceProvider services, ulong address, out SpecialDiagInfoHeader info)
+        {
+            info = default;
+
+            Reader reader = services.GetService<Reader>();
+            if (reader is null)
+            {
+                return false;
+            }
+
+            try
+            {
+                info = reader.Read<SpecialDiagInfoHeader>(address);
+            }
+            catch (Exception ex) when (ex is InvalidVirtualAddressException or BadInputFormatException)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public static ulong GetAddress(IServiceProvider services)
+        {
+            ITarget target = services.GetService<ITarget>();
+            IMemoryService memoryService = services.GetService<IMemoryService>();
+            return target.OperatingSystem == OSPlatform.OSX ? SpecialDiagInfoAddress_OSX : (memoryService.PointerSize == 4 ? SpecialDiagInfoAddress_32BIT : SpecialDiagInfoAddress_64BIT);
+        }
+
+        public string Signature => Encoding.ASCII.GetString(RawSignature.Take(SPECIAL_DIAGINFO_SIGNATURE.Length).ToArray());
+
+        public bool IsValid => Version > 0 && Signature == SPECIAL_DIAGINFO_SIGNATURE;
+    }
+}
index 956aefee4161c5f7200c4fd723bb50263954b019..36cda4e67091321f1167e1175a012540285611ad 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Linq;
 using Microsoft.Diagnostics.DebugServices;
 
 namespace Microsoft.Diagnostics.ExtensionCommands
@@ -11,11 +12,14 @@ namespace Microsoft.Diagnostics.ExtensionCommands
     public class StatusCommand : CommandBase
     {
         [ServiceImport]
-        public ITarget Target { get; set; }
+        public IHost Host { get; set; }
 
         [ServiceImport]
         public ISymbolService SymbolService { get; set; }
 
+        [ServiceImport]
+        public IContextService ContextService { get; set; }
+
         [Option(Name = "--reset", Aliases = new[] { "-reset" }, Help = "Resets the internal cached state.")]
         public bool Reset { get; set; }
 
@@ -23,12 +27,35 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         {
             if (Reset)
             {
-                Target.Flush();
+                foreach (ITarget target in Host.EnumerateTargets())
+                {
+                    target.Flush();
+                }
                 WriteLine("Internal cached state reset");
             }
             else
             {
-                Write(Target.ToString());
+                IRuntime currentRuntime = ContextService.GetCurrentRuntime();
+                foreach (ITarget target in Host.EnumerateTargets())
+                {
+                    WriteLine(target.ToString());
+
+                    IRuntimeService runtimeService = target.Services.GetService<IRuntimeService>();
+
+                    // Display the current runtime star ("*") only if there is more than one runtime
+                    bool displayStar = runtimeService.EnumerateRuntimes().Count() > 1;
+
+                    foreach (IRuntime runtime in runtimeService.EnumerateRuntimes())
+                    {
+                        string current = displayStar ? (runtime == currentRuntime ? "*" : " ") : "";
+                        Write($"    {current}");
+                        WriteLine(runtime.ToString());
+                        string indent = new(' ', 8);
+                        this.DisplayResources(runtime.RuntimeModule, all: false, indent);
+                        this.DisplayRuntimeExports(runtime.RuntimeModule, error: true, indent);
+                    }
+                }
+                this.DisplaySpecialInfo();
                 Write(SymbolService.ToString());
                 long memoryUsage = GC.GetTotalMemory(forceFullCollection: true);
                 WriteLine($"GC memory usage for managed SOS components: {memoryUsage:##,#} bytes");
index dc340804364cfa3750c625d4ccb31bbf6472e4c3..9f001a83dcac45bdf5b3d37ceafabb7e0024b21e 100644 (file)
@@ -5,7 +5,7 @@ using Microsoft.Diagnostics.DebugServices;
 
 namespace Microsoft.Diagnostics.ExtensionCommands
 {
-    [Command(Name = "threads", Aliases = new string[] { "setthread" }, Help = "Displays threads or sets the current thread.")]
+    [Command(Name = "threads", Aliases = new string[] { "setthread" }, Help = "Lists the threads in the target or sets the current thread.")]
     public class ThreadsCommand : CommandBase
     {
         [Argument(Help = "The thread index or id to set, otherwise displays the list of threads.")]
@@ -17,9 +17,6 @@ namespace Microsoft.Diagnostics.ExtensionCommands
         [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays more details.")]
         public bool Verbose { get; set; }
 
-        [ServiceImport(Optional = true)]
-        public IThread CurrentThread { get; set; }
-
         [ServiceImport]
         public IThreadService ThreadService { get; set; }
 
@@ -43,10 +40,10 @@ namespace Microsoft.Diagnostics.ExtensionCommands
             }
             else
             {
-                uint currentThreadId = CurrentThread != null ? CurrentThread.ThreadId : uint.MaxValue;
+                IThread currentThread = ContextService.GetCurrentThread();
                 foreach (IThread thread in ThreadService.EnumerateThreads())
                 {
-                    WriteLine("{0}{1} 0x{2:X4} ({2})", thread.ThreadId == currentThreadId ? "*" : " ", thread.ThreadIndex, thread.ThreadId);
+                    WriteLine("{0}{1} 0x{2:X4} ({2})", thread == currentThread ? "*" : " ", thread.ThreadIndex, thread.ThreadId);
                     if (Verbose)
                     {
                         thread.TryGetRegisterValue(ThreadService.InstructionPointerIndex, out ulong ip);
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Output/ArrayExtensions.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Output/ArrayExtensions.cs
new file mode 100644 (file)
index 0000000..34ec149
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace Microsoft.Diagnostics.ExtensionCommands.Output
+{
+    public static class ArrayExtensions
+    {
+        public static string ToHex(this ImmutableArray<byte> array) => string.Concat(array.Select((b) => b.ToString("x2")));
+
+        public static string ToHex(this byte[] array) => string.Concat(array.Select((b) => b.ToString("x2")));
+    }
+}
index a2045070c4313a6ca98b1cddb42bdb0e5b7bb543..8a93d496f51b5b449cc8700958e064e59c4d3d51 100644 (file)
@@ -15,9 +15,6 @@ namespace Microsoft.Diagnostics.Tools.Dump
         [ServiceImport]
         public CommandService CommandService { get; set; }
 
-        [ServiceImport]
-        public IServiceProvider Services { get; set; }
-
         [ServiceImport(Optional = true)]
         public SOSHost SOSHost { get; set; }
 
index ae5c116a23303c69f0f095298a912c5198c567cd..d0a17cd26b31162063030a47becdd5584e2b909d 100644 (file)
@@ -9,9 +9,6 @@ namespace Microsoft.Diagnostics.DebugServices.UnitTests
     [Command(Name = "writetestdata", Help = "Writes the test data xml file.")]
     public class WriteTestDataCommand : CommandBase
     {
-        [ServiceImport]
-        public IServiceProvider Services { get; set; }
-
         [Argument(Name = "FileName", Help = "Test data file path.")]
         public string FileName { get; set; }