Add the "crashinfo" command for NativeAOT. (#4125)
authorMike McLaughlin <mikem@microsoft.com>
Tue, 3 Oct 2023 00:28:43 +0000 (17:28 -0700)
committerGitHub <noreply@github.com>
Tue, 3 Oct 2023 00:28:43 +0000 (17:28 -0700)
Reads the JSON crash info from the triage buffer in the NativeAOT
runtime.

Adds a ICrashInfoService and SpecialDiagInfo helper class.

28 files changed:
eng/Versions.props
src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj
src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs
src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/IManagedException.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/IRuntime.cs
src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/IType.cs
src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs [new file with mode: 0644]
src/SOS/SOS.Extensions/DebuggerServices.cs
src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs
src/SOS/SOS.Extensions/RemoteMemoryService.cs
src/SOS/SOS.Extensions/SOS.Extensions.csproj
src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs
src/SOS/SOS.Hosting/RuntimeWrapper.cs
src/SOS/Strike/dbgengservices.cpp
src/SOS/Strike/dbgengservices.h
src/SOS/Strike/sos.def
src/SOS/Strike/strike.cpp
src/SOS/inc/debuggerservices.h
src/SOS/inc/specialdiaginfo.h [new file with mode: 0644]
src/SOS/lldbplugin/services.cpp
src/SOS/lldbplugin/soscommand.cpp
src/SOS/lldbplugin/sosplugin.h

index 7fec60baa87c0221e76641181071af5fc2df8d8c..bd4a6589eb924f7e69c423c053c78af36ef5e21e 100644 (file)
@@ -59,8 +59,8 @@
     <SystemBuffersVersion>4.5.1</SystemBuffersVersion>
     <SystemMemoryVersion>4.5.5</SystemMemoryVersion>
     <SystemRuntimeLoaderVersion>4.3.0</SystemRuntimeLoaderVersion>
-    <SystemTextEncodingsWebVersion>4.7.2</SystemTextEncodingsWebVersion>
-    <SystemTextJsonVersion>4.7.1</SystemTextJsonVersion>
+    <SystemTextEncodingsWebVersion>6.0.0</SystemTextEncodingsWebVersion>
+    <SystemTextJsonVersion>6.0.8</SystemTextJsonVersion>
     <XUnitAbstractionsVersion>2.0.3</XUnitAbstractionsVersion>
     <MicrosoftDotNetCodeAnalysisVersion>8.0.0-beta.23463.1</MicrosoftDotNetCodeAnalysisVersion>
     <StyleCopAnalyzersVersion>1.2.0-beta.406</StyleCopAnalyzersVersion>
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs
new file mode 100644 (file)
index 0000000..a10c400
--- /dev/null
@@ -0,0 +1,192 @@
+// 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.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+    public class CrashInfoService : ICrashInfoService
+    {
+        /// <summary>
+        /// This is a "transport" exception code required by Watson to trigger the proper analyzer/provider for bucketing
+        /// </summary>
+        public const uint STATUS_STACK_BUFFER_OVERRUN = 0xC0000409;
+
+        /// <summary>
+        /// This is the Native AOT fail fast subcode used by Watson
+        /// </summary>
+        public const uint FAST_FAIL_EXCEPTION_DOTNET_AOT = 0x48;
+
+        public sealed class CrashInfoJson
+        {
+            [JsonPropertyName("version")]
+            public string Version { get; set; }
+
+            [JsonPropertyName("reason")]
+            public int Reason { get; set; }
+
+            [JsonPropertyName("runtime")]
+            public string Runtime { get; set; }
+
+            [JsonPropertyName("runtime_type")]
+            public int RuntimeType { get; set; }
+
+            [JsonPropertyName("thread")]
+            [JsonConverter(typeof(HexUInt32Converter))]
+            public uint Thread { get; set; }
+
+            [JsonPropertyName("message")]
+            public string Message { get; set; }
+
+            [JsonPropertyName("exception")]
+            public CrashInfoException Exception { get; set; }
+        }
+
+        public sealed class CrashInfoException : IManagedException
+        {
+            [JsonPropertyName("address")]
+            [JsonConverter(typeof(HexUInt64Converter))]
+            public ulong Address { get; set; }
+
+            [JsonPropertyName("hr")]
+            [JsonConverter(typeof(HexUInt32Converter))]
+            public uint HResult { get; set; }
+
+            [JsonPropertyName("message")]
+            public string Message { get; set; }
+
+            [JsonPropertyName("type")]
+            public string Type { get; set; }
+
+            [JsonPropertyName("stack")]
+            public CrashInfoStackFrame[] Stack { get; set; }
+
+            IEnumerable<IStackFrame> IManagedException.Stack => Stack;
+
+            [JsonPropertyName("inner")]
+            public CrashInfoException[] InnerExceptions { get; set; }
+
+            IEnumerable<IManagedException> IManagedException.InnerExceptions => InnerExceptions;
+        }
+
+        public sealed class CrashInfoStackFrame : IStackFrame
+        {
+            [JsonPropertyName("ip")]
+            [JsonConverter(typeof(HexUInt64Converter))]
+            public ulong InstructionPointer { get; set; }
+
+            [JsonPropertyName("sp")]
+            [JsonConverter(typeof(HexUInt64Converter))]
+            public ulong StackPointer { get; set; }
+
+            [JsonPropertyName("module")]
+            [JsonConverter(typeof(HexUInt64Converter))]
+            public ulong ModuleBase { get; set; }
+
+            [JsonPropertyName("offset")]
+            [JsonConverter(typeof(HexUInt32Converter))]
+            public uint Offset { get; set; }
+
+            [JsonPropertyName("name")]
+            public string MethodName { get; set; }
+        }
+
+        public sealed class HexUInt64Converter : JsonConverter<ulong>
+        {
+            public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                string valueString = reader.GetString();
+                if (valueString == null ||
+                    !valueString.StartsWith("0x") ||
+                    !ulong.TryParse(valueString.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out ulong value))
+                {
+                    throw new JsonException("Invalid hex value");
+                }
+                return value;
+            }
+
+            public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options) => throw new NotImplementedException();
+        }
+
+        public sealed class HexUInt32Converter : JsonConverter<uint>
+        {
+            public override uint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+            {
+                string valueString = reader.GetString();
+                if (valueString == null ||
+                    !valueString.StartsWith("0x") ||
+                    !uint.TryParse(valueString.Substring(2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out uint value))
+                {
+                    throw new JsonException("Invalid hex value");
+                }
+                return value;
+            }
+
+            public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOptions options) => throw new NotImplementedException();
+        }
+
+        public static ICrashInfoService Create(uint hresult, ReadOnlySpan<byte> triageBuffer)
+        {
+            CrashInfoService crashInfoService = null;
+            try
+            {
+                JsonSerializerOptions options = new() { AllowTrailingCommas = true, NumberHandling = JsonNumberHandling.AllowReadingFromString };
+                CrashInfoJson crashInfo = JsonSerializer.Deserialize<CrashInfoJson>(triageBuffer, options);
+                if (crashInfo != null)
+                {
+                    if (Version.TryParse(crashInfo.Version, out Version protocolVersion) && protocolVersion.Major >= 1)
+                    {
+                        crashInfoService = new(crashInfo.Thread, hresult, crashInfo);
+                    }
+                    else
+                    {
+                        Trace.TraceError($"CrashInfoService: invalid or not supported protocol version {crashInfo.Version}");
+                    }
+                }
+                else
+                {
+                    Trace.TraceError($"CrashInfoService: JsonSerializer.Deserialize failed");
+                }
+            }
+            catch (Exception ex) when (ex is JsonException or NotSupportedException or DecoderFallbackException or ArgumentException)
+            {
+                Trace.TraceError($"CrashInfoService: {ex}");
+            }
+            return crashInfoService;
+        }
+
+        private CrashInfoService(uint threadId, uint hresult, CrashInfoJson crashInfo)
+        {
+            ThreadId = threadId;
+            HResult = hresult;
+            CrashReason = (CrashReason)crashInfo.Reason;
+            RuntimeVersion = crashInfo.Runtime;
+            RuntimeType = (RuntimeType)crashInfo.RuntimeType;
+            Message = crashInfo.Message;
+            Exception = crashInfo.Exception;
+        }
+
+        #region ICrashInfoService
+
+        public uint ThreadId { get; }
+
+        public uint HResult { get; }
+
+        public CrashReason CrashReason { get; }
+
+        public string RuntimeVersion { get; }
+
+        public RuntimeType RuntimeType { get; }
+
+        public string Message { get; }
+
+        public IManagedException Exception { get; }
+
+        #endregion
+    }
+}
index ba1ecc6d21c70b564f702e7779da88dcddf0b5a2..86e6120208b789037bc32a275cf749a606382cbb 100644 (file)
@@ -13,7 +13,7 @@ using Microsoft.Diagnostics.Runtime;
 namespace Microsoft.Diagnostics.DebugServices.Implementation
 {
     /// <summary>
-    /// ClrMD runtime service implementation
+    /// ClrMD runtime service implementation. This MUST never be disposable.
     /// </summary>
     [ServiceExport(Type = typeof(IDataReader), Scope = ServiceScope.Target)]
     public class DataReader : IDataReader
index 06c99c30c29748ea7f8946d4cc044f85520af3d6..63facc266b8e6b6762eb6218926d41623daa798d 100644 (file)
@@ -1,7 +1,8 @@
-<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
+<!-- Copyright (c)  Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information. -->
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <NoWarn>;1591;1701</NoWarn>
     <Description>Diagnostics debug services</Description>
     <IsPackable>true</IsPackable>
@@ -20,6 +21,7 @@
     <PackageReference Include="System.CommandLine" Version="$(SystemCommandLineVersion)" />
     <PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
     <PackageReference Include="System.Runtime.Loader" Version="$(SystemRuntimeLoaderVersion)" />
+    <PackageReference Include="System.Text.Json" Version="$(SystemTextJsonVersion)" />
   </ItemGroup>
   
   <ItemGroup>
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs
new file mode 100644 (file)
index 0000000..32d7989
--- /dev/null
@@ -0,0 +1,137 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// ******************************************************************************
+// WARNING!!!: This code is also used by createdump in the runtime repo.
+// See: https://github.com/dotnet/runtime/blob/main/src/coreclr/debug/createdump/specialdiaginfo.h
+// ******************************************************************************
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+    /// <summary>
+    /// This is a special memory region added to ELF and MachO dumps that contains extra diagnostics
+    /// information like the exception record for a crash for a NativeAOT app. The exception record
+    /// contains the pointer to the JSON formatted crash info.
+    /// </summary>
+    public unsafe class SpecialDiagInfo
+    {
+        private static readonly byte[] SPECIAL_DIAGINFO_SIGNATURE = Encoding.ASCII.GetBytes("DIAGINFOHEADER");
+        private const int SPECIAL_DIAGINFO_VERSION = 1;
+
+        private const ulong SpecialDiagInfoAddressMacOS64 = 0x7fffffff10000000;
+        private const ulong SpecialDiagInfoAddress64 = 0x00007ffffff10000;
+        private const ulong SpecialDiagInfoAddress32 = 0x7fff1000;
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct SpecialDiagInfoHeader
+        {
+            public const int SignatureSize = 16;
+            public fixed byte Signature[SignatureSize];
+            public int Version;
+            public ulong ExceptionRecordAddress;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct EXCEPTION_RECORD64
+        {
+            public uint ExceptionCode;
+            public uint ExceptionFlags;
+            public ulong ExceptionRecord;
+            public ulong ExceptionAddress;
+            public uint NumberParameters;
+            public uint __unusedAlignment;
+            public fixed ulong ExceptionInformation[15]; //EXCEPTION_MAXIMUM_PARAMETERS
+        }
+
+        private readonly ITarget _target;
+        private readonly IMemoryService _memoryService;
+
+        public SpecialDiagInfo(ITarget target, IMemoryService memoryService)
+        {
+            _target = target;
+            _memoryService = memoryService;
+        }
+
+        private ulong SpecialDiagInfoAddress
+        {
+            get
+            {
+                if (_target.OperatingSystem == OSPlatform.OSX)
+                {
+                    if (_memoryService.PointerSize == 8)
+                    {
+                        return SpecialDiagInfoAddressMacOS64;
+                    }
+                }
+                else if (_target.OperatingSystem == OSPlatform.Linux)
+                {
+                    if (_memoryService.PointerSize == 8)
+                    {
+                        return SpecialDiagInfoAddress64;
+                    }
+                    else
+                    {
+                        return SpecialDiagInfoAddress32;
+                    }
+                }
+                return 0;
+            }
+        }
+
+        public static ICrashInfoService CreateCrashInfoService(IServiceProvider services)
+        {
+            EXCEPTION_RECORD64 exceptionRecord;
+
+            SpecialDiagInfo diagInfo = new(services.GetService<ITarget>(), services.GetService<IMemoryService>());
+            exceptionRecord = diagInfo.GetExceptionRecord();
+
+            if (exceptionRecord.ExceptionCode == CrashInfoService.STATUS_STACK_BUFFER_OVERRUN &&
+                exceptionRecord.NumberParameters >= 4 &&
+                exceptionRecord.ExceptionInformation[0] == CrashInfoService.FAST_FAIL_EXCEPTION_DOTNET_AOT)
+            {
+                uint hresult = (uint)exceptionRecord.ExceptionInformation[1];
+                ulong triageBufferAddress = exceptionRecord.ExceptionInformation[2];
+                int triageBufferSize = (int)exceptionRecord.ExceptionInformation[3];
+
+                Span<byte> buffer = new byte[triageBufferSize];
+                if (services.GetService<IMemoryService>().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead == triageBufferSize)
+                {
+                    return CrashInfoService.Create(hresult, buffer);
+                }
+                else
+                {
+                    Trace.TraceError($"SpecialDiagInfo: ReadMemory({triageBufferAddress}) failed");
+                }
+            }
+            return null;
+        }
+
+        internal EXCEPTION_RECORD64 GetExceptionRecord()
+        {
+            Span<byte> headerBuffer = stackalloc byte[Unsafe.SizeOf<SpecialDiagInfoHeader>()];
+            if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length)
+            {
+                SpecialDiagInfoHeader header = Unsafe.As<byte, SpecialDiagInfoHeader>(ref MemoryMarshal.GetReference(headerBuffer));
+                ReadOnlySpan<byte> signature = new(header.Signature, SPECIAL_DIAGINFO_SIGNATURE.Length);
+                if (signature.SequenceEqual(SPECIAL_DIAGINFO_SIGNATURE))
+                {
+                    if (header.Version >= SPECIAL_DIAGINFO_VERSION && header.ExceptionRecordAddress != 0)
+                    {
+                        Span<byte> exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf<EXCEPTION_RECORD64>()];
+                        if (_memoryService.ReadMemory(header.ExceptionRecordAddress, exceptionRecordBuffer, out bytesRead) && bytesRead == exceptionRecordBuffer.Length)
+                        {
+                            return Unsafe.As<byte, EXCEPTION_RECORD64>(ref MemoryMarshal.GetReference(exceptionRecordBuffer));
+                        }
+                    }
+                }
+            }
+            return default;
+        }
+    }
+}
index 9150458413a81db498181c4076ad1b3cb048ad9c..9addd99b10b1a66378eb9d16e467cd13d10f9b4c 100644 (file)
@@ -43,6 +43,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             Host.OnTargetCreate.Fire(this);
         }
 
+        protected void FlushService<T>() => _serviceContainer?.RemoveService(typeof(T));
+
         #region ITarget
 
         /// <summary>
index 604205ae69fb32055bada4e902b0fb1cf76dbe00..5266ea006cd52e21f66f37a0b677b20192c7f180 100644 (file)
@@ -66,6 +66,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 return memoryService;
             });
 
+            // Add optional crash info service (currently only for Native AOT on Linux/MacOS).
+            _serviceContainerFactory.AddServiceFactory<ICrashInfoService>((services) => SpecialDiagInfo.CreateCrashInfoService(services));
+            OnFlushEvent.Register(() => FlushService<ICrashInfoService>());
+
             Finished();
         }
     }
diff --git a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs
new file mode 100644 (file)
index 0000000..797fb75
--- /dev/null
@@ -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.
+
+using System;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// The kind or reason of crash for the triage JSON
+    /// </summary>
+    public enum CrashReason
+    {
+        Unknown = 0,
+        UnhandledException = 1,
+        EnvironmentFailFast = 2,
+        InternalFailFast = 3,
+    }
+
+    /// <summary>
+    /// Crash information service. Details about the unhandled exception or crash.
+    /// </summary>
+    public interface ICrashInfoService
+    {
+        /// <summary>
+        /// The kind or reason for the crash
+        /// </summary>
+        CrashReason CrashReason { get; }
+
+        /// <summary>
+        /// Crashing OS thread id
+        /// </summary>
+        uint ThreadId { get; }
+
+        /// <summary>
+        /// The HRESULT passed to Watson
+        /// </summary>
+        uint HResult { get; }
+
+        /// <summary>
+        /// Runtime type or flavor
+        /// </summary>
+        RuntimeType RuntimeType { get; }
+
+        /// <summary>
+        /// Runtime version and possible commit id
+        /// </summary>
+        string RuntimeVersion { get; }
+
+        /// <summary>
+        /// Crash or FailFast message
+        /// </summary>
+        string Message { get; }
+
+        /// <summary>
+        /// The exception that caused the crash or null
+        /// </summary>
+        IManagedException Exception { get; }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs b/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs
new file mode 100644 (file)
index 0000000..f73b172
--- /dev/null
@@ -0,0 +1,43 @@
+// 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.Generic;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// Describes a managed exception
+    /// </summary>
+    public interface IManagedException
+    {
+        /// <summary>
+        /// Exception object address
+        /// </summary>
+        ulong Address { get; }
+
+        /// <summary>
+        /// The exception type name
+        /// </summary>
+        string Type { get; }
+
+        /// <summary>
+        /// The exception message
+        /// </summary>
+        string Message { get; }
+
+        /// <summary>
+        /// Exception.HResult
+        /// </summary>
+        uint HResult { get; }
+
+        /// <summary>
+        /// Stack trace of exception
+        /// </summary>
+        IEnumerable<IStackFrame> Stack { get; }
+
+        /// <summary>
+        /// The inner exception or exceptions in the AggregateException case
+        /// </summary>
+        IEnumerable<IManagedException> InnerExceptions { get; }
+    }
+}
index e369639384f3abf0959ed27cd4e0d7e519c8fbd0..5dd9e4ce9319f81c8050b30632384d5f90dd9495 100644 (file)
@@ -14,7 +14,8 @@ namespace Microsoft.Diagnostics.DebugServices
         Desktop = 1,
         NetCore = 2,
         SingleFile = 3,
-        Other = 4
+        NativeAOT = 4,
+        Other = 5
     }
 
     /// <summary>
diff --git a/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs b/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs
new file mode 100644 (file)
index 0000000..42f33eb
--- /dev/null
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// Describes a stack frame
+    /// </summary>
+    public interface IStackFrame
+    {
+        /// <summary>
+        /// The instruction pointer for this frame
+        /// </summary>
+        ulong InstructionPointer { get; }
+
+        /// <summary>
+        /// The stack pointer of this frame or 0
+        /// </summary>
+        ulong StackPointer { get; }
+
+        /// <summary>
+        /// The module base of the IP
+        /// </summary>
+        public ulong ModuleBase { get; }
+
+        /// <summary>
+        /// Offset from beginning of method
+        /// </summary>
+        uint Offset { get; }
+
+        /// <summary>
+        /// The exception type name
+        /// </summary>
+        string MethodName { get; }
+    }
+}
index 28a135570903e32219bb98fed98920896da9cd5a..6682f059f70dacb09afa795a78b9549c2478de66 100644 (file)
@@ -20,11 +20,6 @@ namespace Microsoft.Diagnostics.DebugServices
         /// </summary>
         IModule Module { get; }
 
-        /// <summary>
-        /// A list of all the fields in the type
-        /// </summary>
-        List<IField> Fields { get; }
-
         /// <summary>
         /// Get a field by name
         /// </summary>
diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs
new file mode 100644 (file)
index 0000000..994522d
--- /dev/null
@@ -0,0 +1,81 @@
+// 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.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Microsoft.Diagnostics.DebugServices;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+    [Command(Name = "crashinfo", Help = "Displays the crash details that created the dump.")]
+    public class CrashInfoCommand : CommandBase
+    {
+        [ServiceImport(Optional = true)]
+        public ICrashInfoService CrashInfo { get; set; }
+
+        [ServiceImport]
+        public IModuleService ModuleService { get; set; }
+
+        public override void Invoke()
+        {
+            if (CrashInfo == null)
+            {
+                throw new DiagnosticsException("No crash info to display");
+            }
+            WriteLine();
+
+            WriteLine($"CrashReason:        {CrashInfo.CrashReason}");
+            WriteLine($"ThreadId:           {CrashInfo.ThreadId:X4}");
+            WriteLine($"HResult:            {CrashInfo.HResult:X4}");
+            WriteLine($"RuntimeType:        {CrashInfo.RuntimeType}");
+            WriteLine($"RuntimeVersion:     {CrashInfo.RuntimeVersion}");
+            WriteLine($"Message:            {CrashInfo.Message}");
+
+            if (CrashInfo.Exception != null)
+            {
+                WriteLine("-----------------------------------------------");
+                PrintException(CrashInfo.Exception, string.Empty);
+            }
+        }
+
+        private void PrintException(IManagedException exception, string indent)
+        {
+            WriteLine($"{indent}Exception object:   {exception.Address:X16}");
+            WriteLine($"{indent}Exception type:     {exception.Type}");
+            WriteLine($"{indent}HResult:            {exception.HResult:X8}");
+            WriteLine($"{indent}Message:            {exception.Message}");
+
+            if (exception.Stack != null && exception.Stack.Any())
+            {
+                WriteLine($"{indent}StackTrace:");
+                WriteLine($"{indent}    IP               Function");
+                foreach (IStackFrame frame in exception.Stack)
+                {
+                    string moduleName = "<unknown_module>";
+                    if (frame.ModuleBase != 0)
+                    {
+                        IModule module = ModuleService.GetModuleFromBaseAddress(frame.ModuleBase);
+                        if (module != null)
+                        {
+                            moduleName = Path.GetFileName(module.FileName);
+                        }
+                    }
+                    string methodName = frame.MethodName ?? "<unknown>";
+                    WriteLine($"{indent}    {frame.InstructionPointer:X16} {moduleName}!{methodName} + 0x{frame.Offset:X}");
+                }
+            }
+
+            if (exception.InnerExceptions != null)
+            {
+                WriteLine("InnerExceptions:");
+                foreach (IManagedException inner in exception.InnerExceptions)
+                {
+                    WriteLine("-----------------------------------------------");
+                    PrintException(inner, "    ");
+                }
+            }
+        }
+    }
+}
index 0df0a5fd4b5a76820cac0c256396be01f73a9875..97ea031083ca4b563ff314ea54bd238d30ff8cdf 100644 (file)
@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
@@ -12,7 +13,7 @@ using Microsoft.Diagnostics.Runtime;
 using Microsoft.Diagnostics.Runtime.Utilities;
 using SOS.Hosting.DbgEng.Interop;
 
-namespace SOS
+namespace SOS.Extensions
 {
     internal sealed unsafe class DebuggerServices : CallableCOMWrapper
     {
@@ -424,6 +425,41 @@ namespace SOS
             }
         }
 
+        public HResult GetLastException(out uint processId, out int threadId, out EXCEPTION_RECORD64 exceptionRecord)
+        {
+            exceptionRecord = default;
+
+            uint type;
+            HResult hr = VTable.GetLastEventInformation(Self, out type, out processId, out threadId, null, 0, null, null, 0, null);
+            if (hr.IsOK)
+            {
+                if (type != (uint)DEBUG_EVENT.EXCEPTION)
+                {
+                    return HResult.E_FAIL;
+                }
+            }
+
+            DEBUG_LAST_EVENT_INFO_EXCEPTION exceptionInfo;
+            hr = VTable.GetLastEventInformation(
+                Self,
+                out _,
+                out processId,
+                out threadId,
+                &exceptionInfo,
+                Unsafe.SizeOf<DEBUG_LAST_EVENT_INFO_EXCEPTION>(),
+                null,
+                null,
+                0,
+                null);
+
+            if (hr.IsOK)
+            {
+                exceptionRecord = exceptionInfo.ExceptionRecord;
+            }
+            Debug.Assert(hr != HResult.S_FALSE);
+            return hr;
+        }
+
         [StructLayout(LayoutKind.Sequential)]
         private readonly unsafe struct IDebuggerServicesVTable
         {
@@ -455,6 +491,7 @@ namespace SOS
             public readonly delegate* unmanaged[Stdcall]<IntPtr, uint*, int> SupportsDml;
             public readonly delegate* unmanaged[Stdcall]<IntPtr, DEBUG_OUTPUT, byte*, void> OutputDmlString;
             public readonly delegate* unmanaged[Stdcall]<IntPtr, IntPtr, byte*, int> AddModuleSymbol;
+            public readonly delegate* unmanaged[Stdcall]<IntPtr, out uint, out uint, out int, void*, int, uint*, byte*, int, uint*, int> GetLastEventInformation;
         }
     }
 }
index ed88c377057582b99262103491becee34af9368c..4137ee1dfdbb34f6d0ad260ff56d991dc068c3ca 100644 (file)
@@ -49,8 +49,6 @@ namespace SOS.Extensions
 
             public string Name { get; }
 
-            public List<IField> Fields => throw new NotImplementedException();
-
             public bool TryGetField(string fieldName, out IField field)
             {
                 HResult hr = _moduleService._debuggerServices.GetFieldOffset(Module.ModuleIndex, _typeId, Name, fieldName, out uint offset);
index a6d24bcfa2505daa77f27844fed40e9752f8c69c..d844b6d44c164b267d67cd22ee8a31ae253b93d4 100644 (file)
@@ -8,7 +8,7 @@ using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.Runtime;
 using Microsoft.Diagnostics.Runtime.Utilities;
 
-namespace SOS
+namespace SOS.Extensions
 {
     internal sealed unsafe class RemoteMemoryService : CallableCOMWrapper, IRemoteMemoryService
     {
index 5deb184576ed62b950bfd898b1664c8716b15d83..444196d7b487853d4f84f86061a8b5c701b5ae84 100644 (file)
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFramework>netstandard2.0</TargetFramework>
     <AssemblyName>SOS.Extensions</AssemblyName>
index 71e4e6002016aa4c9f83b13f673d51449e70bf9d..60aea85883c5c6e2eb3ab1e47007c113226af941 100644 (file)
@@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
 using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.DebugServices.Implementation;
 using Microsoft.Diagnostics.Runtime.Utilities;
+using SOS.Hosting;
 using SOS.Hosting.DbgEng.Interop;
 using Architecture = System.Runtime.InteropServices.Architecture;
 
@@ -94,7 +95,43 @@ namespace SOS.Extensions
                 return memoryService;
             });
 
+            // Add optional crash info service (currently only for Native AOT).
+            _serviceContainerFactory.AddServiceFactory<ICrashInfoService>((services) => CreateCrashInfoService(services, debuggerServices));
+            OnFlushEvent.Register(() => FlushService<ICrashInfoService>());
+
             Finished();
         }
+
+        private unsafe ICrashInfoService CreateCrashInfoService(IServiceProvider services, DebuggerServices debuggerServices)
+        {
+            // For Linux/OSX dumps loaded under dbgeng the GetLastException API doesn't return the necessary information
+            if (Host.HostType == HostType.DbgEng && (OperatingSystem == OSPlatform.Linux || OperatingSystem == OSPlatform.OSX))
+            {
+                return SpecialDiagInfo.CreateCrashInfoService(services);
+            }
+            HResult hr = debuggerServices.GetLastException(out uint processId, out int threadIndex, out EXCEPTION_RECORD64 exceptionRecord);
+            if (hr.IsOK)
+            {
+                if (exceptionRecord.ExceptionCode == CrashInfoService.STATUS_STACK_BUFFER_OVERRUN &&
+                    exceptionRecord.NumberParameters >= 4 &&
+                    exceptionRecord.ExceptionInformation[0] == CrashInfoService.FAST_FAIL_EXCEPTION_DOTNET_AOT)
+                {
+                    uint hresult = (uint)exceptionRecord.ExceptionInformation[1];
+                    ulong triageBufferAddress = exceptionRecord.ExceptionInformation[2];
+                    int triageBufferSize = (int)exceptionRecord.ExceptionInformation[3];
+
+                    Span<byte> buffer = new byte[triageBufferSize];
+                    if (services.GetService<IMemoryService>().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead == triageBufferSize)
+                    {
+                        return CrashInfoService.Create(hresult, buffer);
+                    }
+                    else
+                    {
+                        Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed");
+                    }
+                }
+            }
+            return null;
+        }
     }
 }
index 09c93312edac098ae283e2f94eac141cc9a58350..c537b270e041ac61dff88770d95574e42e13491c 100644 (file)
@@ -83,7 +83,6 @@ namespace SOS.Hosting
 
         private readonly IServiceProvider _services;
         private readonly IRuntime _runtime;
-        private readonly IDisposable _onFlushEvent;
         private IntPtr _clrDataProcess = IntPtr.Zero;
         private IntPtr _corDebugProcess = IntPtr.Zero;
         private IntPtr _dacHandle = IntPtr.Zero;
@@ -97,7 +96,6 @@ namespace SOS.Hosting
             Debug.Assert(runtime != null);
             _services = services;
             _runtime = runtime;
-            _onFlushEvent = runtime.Target.OnFlushEvent.Register(Flush);
 
             VTableBuilder builder = AddInterface(IID_IRuntime, validate: false);
 
@@ -124,34 +122,26 @@ namespace SOS.Hosting
         protected override void Destroy()
         {
             Trace.TraceInformation("RuntimeWrapper.Destroy");
-            _onFlushEvent.Dispose();
-            Flush();
-            if (_dacHandle != IntPtr.Zero)
-            {
-                DataTarget.PlatformFunctions.FreeLibrary(_dacHandle);
-                _dacHandle = IntPtr.Zero;
-            }
-            if (_dbiHandle != IntPtr.Zero)
-            {
-                DataTarget.PlatformFunctions.FreeLibrary(_dbiHandle);
-                _dbiHandle = IntPtr.Zero;
-            }
-        }
-
-        private void Flush()
-        {
-            // TODO: there is a better way to flush _corDebugProcess with ICorDebugProcess4::ProcessStateChanged(FLUSH_ALL)
             if (_corDebugProcess != IntPtr.Zero)
             {
                 ComWrapper.ReleaseWithCheck(_corDebugProcess);
                 _corDebugProcess = IntPtr.Zero;
             }
-            // TODO: there is a better way to flush _clrDataProcess with ICLRDataProcess::Flush()
             if (_clrDataProcess != IntPtr.Zero)
             {
                 ComWrapper.ReleaseWithCheck(_clrDataProcess);
                 _clrDataProcess = IntPtr.Zero;
             }
+            if (_dacHandle != IntPtr.Zero)
+            {
+                DataTarget.PlatformFunctions.FreeLibrary(_dacHandle);
+                _dacHandle = IntPtr.Zero;
+            }
+            if (_dbiHandle != IntPtr.Zero)
+            {
+                DataTarget.PlatformFunctions.FreeLibrary(_dbiHandle);
+                _dbiHandle = IntPtr.Zero;
+            }
         }
 
         #region IRuntime (native)
index d538079f33f5962ad18e7764d698cdf907a95b41..58187515b9c78b95e545bff62d1cfb21b60e6003 100644 (file)
@@ -512,6 +512,30 @@ DbgEngServices::AddModuleSymbol(
     return S_OK;
 }
 
+HRESULT
+DbgEngServices::GetLastEventInformation(
+        PULONG type,
+        PULONG processId,
+        PULONG threadId,
+        PVOID extraInformation,
+        ULONG extraInformationSize,
+        PULONG extraInformationUsed,
+        PSTR description,
+        ULONG descriptionSize,
+        PULONG descriptionUsed)
+{
+    return m_control->GetLastEventInformation(
+        type,
+        processId,
+        threadId,
+        extraInformation,
+        extraInformationSize,
+        extraInformationUsed,
+        description,
+        descriptionSize,
+        descriptionUsed);
+}
+
 //----------------------------------------------------------------------------
 // IRemoteMemoryService
 //----------------------------------------------------------------------------
index d8530646cad4eced30e37644ddf8ee7be3385cd5..f7ba96cac24d68931c4c6d301cfd1ecbef87659a 100644 (file)
@@ -207,6 +207,17 @@ public:
         void* param,
         const char* symbolFileName);
 
+    HRESULT STDMETHODCALLTYPE GetLastEventInformation(
+        PULONG type,
+        PULONG processId,
+        PULONG threadId,
+        PVOID extraInformation,
+        ULONG extraInformationSize,
+        PULONG extraInformationUsed,
+        PSTR description,
+        ULONG descriptionSize,
+        PULONG descriptionUsed);
+
     //----------------------------------------------------------------------------
     // IRemoteMemoryService
     //----------------------------------------------------------------------------
@@ -296,4 +307,4 @@ public:
 
 #ifdef __cplusplus
 };
-#endif
\ No newline at end of file
+#endif
index 0e9876503f10bfd5111ee243ed429da043adfe07..4b9b8fcd7de5c77a15e4ea6fc3bb57d927215eb1 100644 (file)
@@ -11,6 +11,7 @@ EXPORTS
     ClrStack
     clrstack=ClrStack
     CLRStack=ClrStack
+    crashinfo
     DumpALC
     dumpalc=DumpALC
     DumpArray
index 20feea9e7931395001491c1ea6b9850807130cae..b6f36b5db4cadb0b7ccd312d9bf273631584b530 100644 (file)
@@ -5857,10 +5857,9 @@ BOOL CheckCLRNotificationEvent(DEBUG_LAST_EVENT_INFO_EXCEPTION* pdle)
         return FALSE;
     }
 
-    // The new DAC based interface doesn't exists so ask the debugger for the last exception
-    // information. NOTE: this function doesn't work on xplat version when the coreclr symbols
-    // have been stripped.
+    // The new DAC based interface doesn't exists so ask the debugger for the last exception information. 
 
+#ifdef HOST_WINDOWS
     ULONG Type, ProcessId, ThreadId;
     ULONG ExtraInformationUsed;
     Status = g_ExtControl->GetLastEventInformation(
@@ -5883,8 +5882,10 @@ BOOL CheckCLRNotificationEvent(DEBUG_LAST_EVENT_INFO_EXCEPTION* pdle)
     {
         return FALSE;
     }
-
     return TRUE;
+#else
+    return FALSE;
+#endif
 }
 
 HRESULT HandleCLRNotificationEvent()
@@ -13799,6 +13800,15 @@ DECLARE_API(clrmodules)
     return ExecuteCommand("clrmodules", args);
 }
 
+//
+// Dumps the Native AOT crash info
+//
+DECLARE_API(crashinfo)
+{
+    INIT_API_EXT();
+    return ExecuteCommand("crashinfo", args);
+}
+
 //
 // Dumps async stacks
 //
index cc39b44ca6882c629b90a0ea1b1d6de0a93797f2..1ec815f4a0662eeb2395fd6989c89bedcb832f20 100644 (file)
@@ -166,6 +166,17 @@ public:
     virtual HRESULT STDMETHODCALLTYPE AddModuleSymbol(
         void* param,
         const char* symbolFileName) = 0;
+
+    virtual HRESULT STDMETHODCALLTYPE GetLastEventInformation(
+        PULONG type,
+        PULONG processId,
+        PULONG threadId,
+        PVOID extraInformation,
+        ULONG extraInformationSize,
+        PULONG extraInformationUsed,
+        PSTR description,
+        ULONG descriptionSize,
+        PULONG descriptionUsed) = 0;
 };
 
 #ifdef __cplusplus
diff --git a/src/SOS/inc/specialdiaginfo.h b/src/SOS/inc/specialdiaginfo.h
new file mode 100644 (file)
index 0000000..9dfb1e9
--- /dev/null
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// ******************************************************************************
+// WARNING!!!: This code is also used by createdump in the runtime repo.
+// See: https://github.com/dotnet/runtime/blob/main/src/coreclr/debug/createdump/specialdiaginfo.h
+// ******************************************************************************
+
+// This is a special memory region added to ELF and MachO dumps that contains extra diagnostics
+// information like the exception record for a crash for a NativeAOT app. The exception record
+// contains the pointer to the JSON formatted crash info.
+
+#define SPECIAL_DIAGINFO_SIGNATURE "DIAGINFOHEADER"
+#define SPECIAL_DIAGINFO_VERSION 1
+
+#ifdef __APPLE__
+const uint64_t SpecialDiagInfoAddress = 0x7fffffff10000000;
+#else
+#if TARGET_64BIT
+const uint64_t SpecialDiagInfoAddress = 0x00007ffffff10000;
+#else
+const uint64_t SpecialDiagInfoAddress = 0x7fff1000;
+#endif
+#endif
+
+struct SpecialDiagInfoHeader
+{
+    char Signature[16];
+    int32_t Version;
+    uint64_t ExceptionRecordAddress;
+};
index e23dc1e38248f219ebc55dc2e1b7b5162fff4984..1ecc0957107ac5a0a4e92ed239fe60d4ad8db317 100644 (file)
@@ -471,12 +471,6 @@ LLDBServices::Execute(
     return status <= lldb::eReturnStatusSuccessContinuingResult ? S_OK : E_FAIL;
 }
 
-// PAL raise exception function and exception record pointer variable name
-// See coreclr\src\pal\src\exception\seh-unwind.cpp for the details. This
-// function depends on RtlpRaisException not being inlined or optimized.
-#define FUNCTION_NAME "RtlpRaiseException"
-#define VARIABLE_NAME "ExceptionRecord"
-
 HRESULT 
 LLDBServices::GetLastEventInformation(
     PULONG type,
@@ -489,8 +483,7 @@ LLDBServices::GetLastEventInformation(
     ULONG descriptionSize,
     PULONG descriptionUsed)
 {
-    if (extraInformationSize < sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION) || 
-        type == NULL || processId == NULL || threadId == NULL || extraInformationUsed == NULL) 
+    if (type == NULL || processId == NULL || threadId == NULL)
     {
         return E_INVALIDARG;
     }
@@ -498,10 +491,25 @@ LLDBServices::GetLastEventInformation(
     *type = DEBUG_EVENT_EXCEPTION;
     *processId = 0;
     *threadId = 0;
-    *extraInformationUsed = sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION);
+
+    if (extraInformationUsed != nullptr)
+    {
+        *extraInformationUsed = sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION);
+    }
+
+    if (extraInformation == nullptr)
+    {
+        return S_OK;
+    }
+
+    if (extraInformationSize < sizeof(DEBUG_LAST_EVENT_INFO_EXCEPTION))
+    {
+        return E_INVALIDARG;
+    }
 
     DEBUG_LAST_EVENT_INFO_EXCEPTION *pdle = (DEBUG_LAST_EVENT_INFO_EXCEPTION *)extraInformation;
     pdle->FirstChance = 1; 
+    lldb::SBError error;
 
     lldb::SBProcess process = GetCurrentProcess();
     if (!process.IsValid())
@@ -518,47 +526,31 @@ LLDBServices::GetLastEventInformation(
     *processId = GetProcessId(process);
     *threadId = GetThreadId(thread);
 
-    // Enumerate each stack frame at the special "throw"
-    // breakpoint and find the raise exception function 
-    // with the exception record parameter.
-    int numFrames = thread.GetNumFrames();
-    for (int i = 0; i < numFrames; i++)
+    SpecialDiagInfoHeader header;
+    size_t read = process.ReadMemory(SpecialDiagInfoAddress, &header, sizeof(header), error);
+    if (error.Fail() || read != sizeof(header))
     {
-        lldb::SBFrame frame = thread.GetFrameAtIndex(i);
-        if (!frame.IsValid())
-        {
-            break;
-        }
-
-        const char *functionName = frame.GetFunctionName();
-        if (functionName == NULL || strncmp(functionName, FUNCTION_NAME, sizeof(FUNCTION_NAME) - 1) != 0)
-        {
-            continue;
-        }
-
-        lldb::SBValue exValue = frame.FindVariable(VARIABLE_NAME);
-        if (!exValue.IsValid())
-        {
-            break;
-        }
-
-        lldb::SBError error;
-        ULONG64 pExceptionRecord = exValue.GetValueAsUnsigned(error);
-        if (error.Fail())
-        {
-            break;
-        }
-
-        process.ReadMemory(pExceptionRecord, &pdle->ExceptionRecord, sizeof(pdle->ExceptionRecord), error);
-        if (error.Fail())
-        {
-            break;
-        }
-
-        return S_OK;
+        Output(DEBUG_OUTPUT_WARNING, "Special diagnostics info read failed\n");
+        return E_FAIL;
+    }
+    if (strncmp(header.Signature, SPECIAL_DIAGINFO_SIGNATURE, sizeof(SPECIAL_DIAGINFO_SIGNATURE)) != 0)
+    {
+        Output(DEBUG_OUTPUT_WARNING, "Special diagnostics info signature invalid\n");
+        return E_FAIL;
+    }
+    if (header.Version < SPECIAL_DIAGINFO_VERSION || header.ExceptionRecordAddress == 0)
+    {
+        Output(DEBUG_OUTPUT_WARNING, "No exception record in special diagnostics info\n");
+        return E_FAIL;
+    }
+    read = process.ReadMemory(header.ExceptionRecordAddress, &pdle->ExceptionRecord, sizeof(pdle->ExceptionRecord), error);
+    if (error.Fail() || read != sizeof(pdle->ExceptionRecord))
+    {
+        Output(DEBUG_OUTPUT_WARNING, "Exception record in special diagnostics info read failed\n");
+        return E_FAIL;
     }
 
-    return E_FAIL;
+    return S_OK;
 }
 
 HRESULT 
index 7f79ec8bcb58fa1ca48655194794fe303886a6de..cdb5c553767b750e927abfffc6bc88f45419b3b3 100644 (file)
@@ -161,6 +161,7 @@ sosCommandInitialize(lldb::SBDebugger debugger)
     g_services->AddCommand("clrstack", new sosCommand("ClrStack"), "Provides a stack trace of managed code only.");
     g_services->AddCommand("clrthreads", new sosCommand("Threads"), "Lists the managed threads running.");
     g_services->AddCommand("clru", new sosCommand("u"), "Displays an annotated disassembly of a managed method.");
+    g_services->AddManagedCommand("crashinfo", "Displays the Native AOT crash info.");
     g_services->AddCommand("dbgout", new sosCommand("dbgout"), "Enables/disables (-off) internal SOS logging.");
     g_services->AddCommand("dumpalc", new sosCommand("DumpALC"), "Displays details about a collectible AssemblyLoadContext to which the specified object is loaded.");
     g_services->AddCommand("dumparray", new sosCommand("DumpArray"), "Displays details about a managed array.");
index ad3b2395e159b1f06fbd78731d31c45ace7e5537..4f387de02766f9c46a1daf6481829afa98cb33bb 100644 (file)
@@ -9,6 +9,7 @@
 #include "lldbservices.h"
 #include "extensions.h"
 #include "dbgtargetcontext.h"
+#include "specialdiaginfo.h"
 #include "specialthreadinfo.h"
 #include "services.h"