From: Mike McLaughlin Date: Tue, 3 Oct 2023 00:28:43 +0000 (-0700) Subject: Add the "crashinfo" command for NativeAOT. (#4125) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055542~35^2~1^2~64 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=4592978e0cc8c28c0fa1ebb2694638bc78bcaaee;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add the "crashinfo" command for NativeAOT. (#4125) Reads the JSON crash info from the triage buffer in the NativeAOT runtime. Adds a ICrashInfoService and SpecialDiagInfo helper class. --- diff --git a/eng/Versions.props b/eng/Versions.props index 7fec60baa..bd4a6589e 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -59,8 +59,8 @@ 4.5.1 4.5.5 4.3.0 - 4.7.2 - 4.7.1 + 6.0.0 + 6.0.8 2.0.3 8.0.0-beta.23463.1 1.2.0-beta.406 diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs new file mode 100644 index 000000000..a10c4001a --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/CrashInfoService.cs @@ -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 + { + /// + /// This is a "transport" exception code required by Watson to trigger the proper analyzer/provider for bucketing + /// + public const uint STATUS_STACK_BUFFER_OVERRUN = 0xC0000409; + + /// + /// This is the Native AOT fail fast subcode used by Watson + /// + 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 IManagedException.Stack => Stack; + + [JsonPropertyName("inner")] + public CrashInfoException[] InnerExceptions { get; set; } + + IEnumerable 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 + { + 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 + { + 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 triageBuffer) + { + CrashInfoService crashInfoService = null; + try + { + JsonSerializerOptions options = new() { AllowTrailingCommas = true, NumberHandling = JsonNumberHandling.AllowReadingFromString }; + CrashInfoJson crashInfo = JsonSerializer.Deserialize(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 + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs index ba1ecc6d2..86e612020 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/DataReader.cs @@ -13,7 +13,7 @@ using Microsoft.Diagnostics.Runtime; namespace Microsoft.Diagnostics.DebugServices.Implementation { /// - /// ClrMD runtime service implementation + /// ClrMD runtime service implementation. This MUST never be disposable. /// [ServiceExport(Type = typeof(IDataReader), Scope = ServiceScope.Target)] public class DataReader : IDataReader diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj index 06c99c30c..63facc266 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Microsoft.Diagnostics.DebugServices.Implementation.csproj @@ -1,7 +1,8 @@ - + netstandard2.0 + true ;1591;1701 Diagnostics debug services true @@ -20,6 +21,7 @@ + diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs new file mode 100644 index 000000000..32d7989a9 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SpecialDiagInfo.cs @@ -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 +{ + /// + /// 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. + /// + 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(), services.GetService()); + 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 buffer = new byte[triageBufferSize]; + if (services.GetService().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 headerBuffer = stackalloc byte[Unsafe.SizeOf()]; + if (_memoryService.ReadMemory(SpecialDiagInfoAddress, headerBuffer, out int bytesRead) && bytesRead == headerBuffer.Length) + { + SpecialDiagInfoHeader header = Unsafe.As(ref MemoryMarshal.GetReference(headerBuffer)); + ReadOnlySpan signature = new(header.Signature, SPECIAL_DIAGINFO_SIGNATURE.Length); + if (signature.SequenceEqual(SPECIAL_DIAGINFO_SIGNATURE)) + { + if (header.Version >= SPECIAL_DIAGINFO_VERSION && header.ExceptionRecordAddress != 0) + { + Span exceptionRecordBuffer = stackalloc byte[Unsafe.SizeOf()]; + if (_memoryService.ReadMemory(header.ExceptionRecordAddress, exceptionRecordBuffer, out bytesRead) && bytesRead == exceptionRecordBuffer.Length) + { + return Unsafe.As(ref MemoryMarshal.GetReference(exceptionRecordBuffer)); + } + } + } + } + return default; + } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs index 915045841..9addd99b1 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs @@ -43,6 +43,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation Host.OnTargetCreate.Fire(this); } + protected void FlushService() => _serviceContainer?.RemoveService(typeof(T)); + #region ITarget /// diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs index 604205ae6..5266ea006 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs @@ -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((services) => SpecialDiagInfo.CreateCrashInfoService(services)); + OnFlushEvent.Register(() => FlushService()); + Finished(); } } diff --git a/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs new file mode 100644 index 000000000..797fb75fd --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/ICrashInfoService.cs @@ -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 +{ + /// + /// The kind or reason of crash for the triage JSON + /// + public enum CrashReason + { + Unknown = 0, + UnhandledException = 1, + EnvironmentFailFast = 2, + InternalFailFast = 3, + } + + /// + /// Crash information service. Details about the unhandled exception or crash. + /// + public interface ICrashInfoService + { + /// + /// The kind or reason for the crash + /// + CrashReason CrashReason { get; } + + /// + /// Crashing OS thread id + /// + uint ThreadId { get; } + + /// + /// The HRESULT passed to Watson + /// + uint HResult { get; } + + /// + /// Runtime type or flavor + /// + RuntimeType RuntimeType { get; } + + /// + /// Runtime version and possible commit id + /// + string RuntimeVersion { get; } + + /// + /// Crash or FailFast message + /// + string Message { get; } + + /// + /// The exception that caused the crash or null + /// + IManagedException Exception { get; } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs b/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs new file mode 100644 index 000000000..f73b172e5 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/IManagedException.cs @@ -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 +{ + /// + /// Describes a managed exception + /// + public interface IManagedException + { + /// + /// Exception object address + /// + ulong Address { get; } + + /// + /// The exception type name + /// + string Type { get; } + + /// + /// The exception message + /// + string Message { get; } + + /// + /// Exception.HResult + /// + uint HResult { get; } + + /// + /// Stack trace of exception + /// + IEnumerable Stack { get; } + + /// + /// The inner exception or exceptions in the AggregateException case + /// + IEnumerable InnerExceptions { get; } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs b/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs index e36963938..5dd9e4ce9 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs @@ -14,7 +14,8 @@ namespace Microsoft.Diagnostics.DebugServices Desktop = 1, NetCore = 2, SingleFile = 3, - Other = 4 + NativeAOT = 4, + Other = 5 } /// diff --git a/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs b/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs new file mode 100644 index 000000000..42f33eb7f --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/IStackFrame.cs @@ -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 +{ + /// + /// Describes a stack frame + /// + public interface IStackFrame + { + /// + /// The instruction pointer for this frame + /// + ulong InstructionPointer { get; } + + /// + /// The stack pointer of this frame or 0 + /// + ulong StackPointer { get; } + + /// + /// The module base of the IP + /// + public ulong ModuleBase { get; } + + /// + /// Offset from beginning of method + /// + uint Offset { get; } + + /// + /// The exception type name + /// + string MethodName { get; } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/IType.cs b/src/Microsoft.Diagnostics.DebugServices/IType.cs index 28a135570..6682f059f 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IType.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IType.cs @@ -20,11 +20,6 @@ namespace Microsoft.Diagnostics.DebugServices /// IModule Module { get; } - /// - /// A list of all the fields in the type - /// - List Fields { get; } - /// /// Get a field by name /// diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs new file mode 100644 index 000000000..994522dd2 --- /dev/null +++ b/src/Microsoft.Diagnostics.ExtensionCommands/CrashInfoCommand.cs @@ -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 = ""; + if (frame.ModuleBase != 0) + { + IModule module = ModuleService.GetModuleFromBaseAddress(frame.ModuleBase); + if (module != null) + { + moduleName = Path.GetFileName(module.FileName); + } + } + string methodName = frame.MethodName ?? ""; + 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, " "); + } + } + } + } +} diff --git a/src/SOS/SOS.Extensions/DebuggerServices.cs b/src/SOS/SOS.Extensions/DebuggerServices.cs index 0df0a5fd4..97ea03108 100644 --- a/src/SOS/SOS.Extensions/DebuggerServices.cs +++ b/src/SOS/SOS.Extensions/DebuggerServices.cs @@ -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(), + 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] SupportsDml; public readonly delegate* unmanaged[Stdcall] OutputDmlString; public readonly delegate* unmanaged[Stdcall] AddModuleSymbol; + public readonly delegate* unmanaged[Stdcall] GetLastEventInformation; } } } diff --git a/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs index ed88c3770..4137ee1df 100644 --- a/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/ModuleServiceFromDebuggerServices.cs @@ -49,8 +49,6 @@ namespace SOS.Extensions public string Name { get; } - public List 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); diff --git a/src/SOS/SOS.Extensions/RemoteMemoryService.cs b/src/SOS/SOS.Extensions/RemoteMemoryService.cs index a6d24bcfa..d844b6d44 100644 --- a/src/SOS/SOS.Extensions/RemoteMemoryService.cs +++ b/src/SOS/SOS.Extensions/RemoteMemoryService.cs @@ -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 { diff --git a/src/SOS/SOS.Extensions/SOS.Extensions.csproj b/src/SOS/SOS.Extensions/SOS.Extensions.csproj index 5deb18457..444196d7b 100644 --- a/src/SOS/SOS.Extensions/SOS.Extensions.csproj +++ b/src/SOS/SOS.Extensions/SOS.Extensions.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 SOS.Extensions diff --git a/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs index 71e4e6002..60aea8588 100644 --- a/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs @@ -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((services) => CreateCrashInfoService(services, debuggerServices)); + OnFlushEvent.Register(() => FlushService()); + 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 buffer = new byte[triageBufferSize]; + if (services.GetService().ReadMemory(triageBufferAddress, buffer, out int bytesRead) && bytesRead == triageBufferSize) + { + return CrashInfoService.Create(hresult, buffer); + } + else + { + Trace.TraceError($"CrashInfoService: ReadMemory({triageBufferAddress}) failed"); + } + } + } + return null; + } } } diff --git a/src/SOS/SOS.Hosting/RuntimeWrapper.cs b/src/SOS/SOS.Hosting/RuntimeWrapper.cs index 09c93312e..c537b270e 100644 --- a/src/SOS/SOS.Hosting/RuntimeWrapper.cs +++ b/src/SOS/SOS.Hosting/RuntimeWrapper.cs @@ -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) diff --git a/src/SOS/Strike/dbgengservices.cpp b/src/SOS/Strike/dbgengservices.cpp index d538079f3..58187515b 100644 --- a/src/SOS/Strike/dbgengservices.cpp +++ b/src/SOS/Strike/dbgengservices.cpp @@ -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 //---------------------------------------------------------------------------- diff --git a/src/SOS/Strike/dbgengservices.h b/src/SOS/Strike/dbgengservices.h index d8530646c..f7ba96cac 100644 --- a/src/SOS/Strike/dbgengservices.h +++ b/src/SOS/Strike/dbgengservices.h @@ -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 diff --git a/src/SOS/Strike/sos.def b/src/SOS/Strike/sos.def index 0e9876503..4b9b8fcd7 100644 --- a/src/SOS/Strike/sos.def +++ b/src/SOS/Strike/sos.def @@ -11,6 +11,7 @@ EXPORTS ClrStack clrstack=ClrStack CLRStack=ClrStack + crashinfo DumpALC dumpalc=DumpALC DumpArray diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index 20feea9e7..b6f36b5db 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -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 // diff --git a/src/SOS/inc/debuggerservices.h b/src/SOS/inc/debuggerservices.h index cc39b44ca..1ec815f4a 100644 --- a/src/SOS/inc/debuggerservices.h +++ b/src/SOS/inc/debuggerservices.h @@ -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 index 000000000..9dfb1e914 --- /dev/null +++ b/src/SOS/inc/specialdiaginfo.h @@ -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; +}; diff --git a/src/SOS/lldbplugin/services.cpp b/src/SOS/lldbplugin/services.cpp index e23dc1e38..1ecc09571 100644 --- a/src/SOS/lldbplugin/services.cpp +++ b/src/SOS/lldbplugin/services.cpp @@ -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 diff --git a/src/SOS/lldbplugin/soscommand.cpp b/src/SOS/lldbplugin/soscommand.cpp index 7f79ec8bc..cdb5c5537 100644 --- a/src/SOS/lldbplugin/soscommand.cpp +++ b/src/SOS/lldbplugin/soscommand.cpp @@ -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."); diff --git a/src/SOS/lldbplugin/sosplugin.h b/src/SOS/lldbplugin/sosplugin.h index ad3b2395e..4f387de02 100644 --- a/src/SOS/lldbplugin/sosplugin.h +++ b/src/SOS/lldbplugin/sosplugin.h @@ -9,6 +9,7 @@ #include "lldbservices.h" #include "extensions.h" #include "dbgtargetcontext.h" +#include "specialdiaginfo.h" #include "specialthreadinfo.h" #include "services.h"