<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>
--- /dev/null
+// 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
+ }
+}
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
-<!-- 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>
<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>
--- /dev/null
+// 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;
+ }
+ }
+}
Host.OnTargetCreate.Fire(this);
}
+ protected void FlushService<T>() => _serviceContainer?.RemoveService(typeof(T));
+
#region ITarget
/// <summary>
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();
}
}
--- /dev/null
+// 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; }
+ }
+}
--- /dev/null
+// 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; }
+ }
+}
Desktop = 1,
NetCore = 2,
SingleFile = 3,
- Other = 4
+ NativeAOT = 4,
+ Other = 5
}
/// <summary>
--- /dev/null
+// 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; }
+ }
+}
/// </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>
--- /dev/null
+// 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, " ");
+ }
+ }
+ }
+ }
+}
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Diagnostics.Runtime.Utilities;
using SOS.Hosting.DbgEng.Interop;
-namespace SOS
+namespace SOS.Extensions
{
internal sealed unsafe class DebuggerServices : CallableCOMWrapper
{
}
}
+ 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
{
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;
}
}
}
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);
using Microsoft.Diagnostics.Runtime;
using Microsoft.Diagnostics.Runtime.Utilities;
-namespace SOS
+namespace SOS.Extensions
{
internal sealed unsafe class RemoteMemoryService : CallableCOMWrapper, IRemoteMemoryService
{
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>SOS.Extensions</AssemblyName>
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;
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;
+ }
}
}
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;
Debug.Assert(runtime != null);
_services = services;
_runtime = runtime;
- _onFlushEvent = runtime.Target.OnFlushEvent.Register(Flush);
VTableBuilder builder = AddInterface(IID_IRuntime, validate: false);
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)
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
//----------------------------------------------------------------------------
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
//----------------------------------------------------------------------------
#ifdef __cplusplus
};
-#endif
\ No newline at end of file
+#endif
ClrStack
clrstack=ClrStack
CLRStack=ClrStack
+ crashinfo
DumpALC
dumpalc=DumpALC
DumpArray
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(
{
return FALSE;
}
-
return TRUE;
+#else
+ return FALSE;
+#endif
}
HRESULT HandleCLRNotificationEvent()
return ExecuteCommand("clrmodules", args);
}
+//
+// Dumps the Native AOT crash info
+//
+DECLARE_API(crashinfo)
+{
+ INIT_API_EXT();
+ return ExecuteCommand("crashinfo", args);
+}
+
//
// Dumps async stacks
//
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
--- /dev/null
+// 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;
+};
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,
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;
}
*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())
*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
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.");
#include "lldbservices.h"
#include "extensions.h"
#include "dbgtargetcontext.h"
+#include "specialdiaginfo.h"
#include "specialthreadinfo.h"
#include "services.h"