Skip to content

Commit

Permalink
Add some diag info options to host commands (#4528)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mikem8361 committed Feb 28, 2024
1 parent aad90b9 commit 0f06d33
Show file tree
Hide file tree
Showing 17 changed files with 521 additions and 55 deletions.
1 change: 1 addition & 0 deletions diagnostics.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,5 @@ public IEnumerable<IRuntime> EnumerateRuntimes()
}

#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();
}
}
}
32 changes: 23 additions & 9 deletions src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.FileFormats;
using Architecture = System.Runtime.InteropServices.Architecture;

namespace Microsoft.Diagnostics.DebugServices.Implementation
Expand Down Expand Up @@ -34,6 +35,7 @@ public Target(IHost host, int id, string dumpPath)
// 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()
Expand Down Expand Up @@ -132,6 +134,23 @@ public void Destroy()

#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)
Expand Down Expand Up @@ -165,19 +184,14 @@ public override string ToString()
{
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();
}
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.Diagnostics.DebugServices/CommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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>");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ public class HelpCommand : CommandBase
[ServiceImport]
public ICommandService CommandService { get; set; }

[ServiceImport]
public IServiceProvider Services { get; set; }

public override void Invoke()
{
if (string.IsNullOrWhiteSpace(Command))
Expand Down
Loading

0 comments on commit 0f06d33

Please sign in to comment.