From 07283c947b76208a6e754292c9f26da491992775 Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Tue, 19 Jul 2022 21:20:56 -0700 Subject: [PATCH] Feature: Export inner exceptions to dnSpy Rather than just grabbing the outer exception type and message grab it for inner exceptions as well. --- .../Impl/DbgEngineImpl.Evaluation.cs | 36 +++++++----- .../Impl/DbgEngineImpl.cs | 56 ++++++++++++++----- .../dnSpy.Debugger.DotNet.Metadata/DmdType.cs | 10 ++++ .../Impl/DmdMemberInfoComparer.cs | 12 ++-- .../Impl/DmdTypeBase.cs | 6 +- .../Metadata/KnownMemberNames.cs | 1 + .../dnSpy.Debugger/DbgUI/DebuggerImpl.cs | 11 ++++ .../Impl/DbgObjectFactoryImpl.cs | 5 +- dnSpy/dnSpy.Contracts.Debugger/DbgObject.cs | 6 +- .../Engine/DbgObjectFactory.cs | 3 +- 10 files changed, 105 insertions(+), 41 deletions(-) diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Impl/DbgEngineImpl.Evaluation.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Impl/DbgEngineImpl.Evaluation.cs index 9bd04599b2..882ac7bce7 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Impl/DbgEngineImpl.Evaluation.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Impl/DbgEngineImpl.Evaluation.cs @@ -130,37 +130,45 @@ internal void DisposeHandle_CorDebug(CorValue? value) { } DbgDotNetRawValue? ReadField_CorDebug(DbgDotNetValueImpl obj, string fieldName1, string? fieldName2) { + DbgDotNetValue? dnValue = null; + try { + (_, dnValue) = GetValueField_CorDebug(obj, fieldName1, fieldName2); + + if (dnValue is null) + return null; + return dnValue.GetRawValue(); + } + finally { + dnValue?.Dispose(); + } + } + (CorValue? corValue, DbgDotNetValue? dbgDotNetValue) GetValueField_CorDebug(DbgDotNetValueImpl obj, string fieldName1, string? fieldName2, bool allowTypeVarianceOnPrivateFields=false) { const DmdBindingFlags fieldFlags = DmdBindingFlags.Public | DmdBindingFlags.NonPublic | DmdBindingFlags.Instance; - var field = obj.Type.GetField(fieldName1, fieldFlags); + var field = obj.Type.GetField(fieldName1, fieldFlags, allowTypeVarianceOnPrivateFields); if (field is null && fieldName2 is not null) - field = obj.Type.GetField(fieldName2, fieldFlags); + field = obj.Type.GetField(fieldName2, fieldFlags, allowTypeVarianceOnPrivateFields); Debug2.Assert(field is not null); if (field is null) - return null; + return (null, null); var dnAppDomain = ((DbgCorDebugInternalAppDomainImpl)obj.Type.AppDomain.GetDebuggerAppDomain().InternalAppDomain).DnAppDomain; var corFieldDeclType = GetType(dnAppDomain.CorAppDomain, field.DeclaringType!); var objValue = DbgCorDebugInternalRuntimeImpl.TryGetObjectOrPrimitiveValue(obj.TryGetCorValue(), out int hr); if (objValue is null) - return null; + return (null, null); if (objValue.IsObject) { // This isn't a generic read-field method, so we won't try to load any classes by calling cctors. var fieldValue = objValue.GetFieldValue(corFieldDeclType.Class, (uint)field.MetadataToken, out hr); if (fieldValue is null) - return null; - DbgDotNetValue? dnValue = null; - try { - dnValue = CreateDotNetValue_CorDebug(fieldValue, field.AppDomain, tryCreateStrongHandle: false); - return dnValue.GetRawValue(); - } - finally { - dnValue?.Dispose(); - } + return (null, null); + return (fieldValue, CreateDotNetValue_CorDebug(fieldValue, field.AppDomain, tryCreateStrongHandle: false)); + } - return null; + return (null, null); } + CorType GetType(CorAppDomain appDomain, DmdType type) => CorDebugTypeCreator.GetType(this, appDomain, type); sealed class EvalTimedOut { } diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Impl/DbgEngineImpl.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Impl/DbgEngineImpl.cs index 95337c496b..c9ed2940b0 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Impl/DbgEngineImpl.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Impl/DbgEngineImpl.cs @@ -187,24 +187,18 @@ void DnDebugger_DebugCallbackEvent(DnDebugger dbg, DebugCallbackEventArgs e) { var exObj = e2.CorThread?.CurrentException; var reflectionAppDomain = module?.GetReflectionModule()?.AppDomain; DbgDotNetValueImpl? dnExObj = null; + try { - string? exName = null; - string? exMessage = null; - int? hResult = null; + + var thread = TryGetThread(e2.CorThread); + var messageFlags = GetMessageFlags(); if (exObj is not null) { + if (reflectionAppDomain is not null) dnExObj = CreateDotNetValue_CorDebug(exObj, reflectionAppDomain, false) as DbgDotNetValueImpl; - if (dnExObj is not null) { - exName = TryGetExceptionName(dnExObj); - exMessage = TryGetExceptionMessage(dnExObj); - hResult = TryGetExceptionHResult(dnExObj); - } - - exName ??= TryGetExceptionName(exObj); - exMessage ??= TryGetExceptionMessage(exObj); - hResult ??= TryGetExceptionHResult(exObj); + } - objectFactory.CreateException(new DbgExceptionId(PredefinedExceptionCategories.DotNet, exName ?? "???"), exFlags, exMessage ?? dnSpy_Debugger_DotNet_CorDebug_Resources.ExceptionMessageIsNull, hResult, TryGetThread(e2.CorThread), module, GetMessageFlags()); + CreateException(exObj, dnExObj, exFlags, thread, module, messageFlags, false); e.AddPauseReason(DebuggerPauseReason.Other); } finally { @@ -264,7 +258,43 @@ void DnDebugger_DebugCallbackEvent(DnDebugger dbg, DebugCallbackEventArgs e) { break; } } + DbgException? CreateException(CorValue? exObj, DbgDotNetValueImpl? dnExObj, DbgExceptionEventFlags exFlags, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, bool doNotAddExceptionToRuntime) { + string? exName = null; + string? exMessage = null; + int? hResult = null; + DbgException? innerException = null; + if (exObj?.IsNull != false) + return null; + if (dnExObj?.IsNull == false) { + exName = TryGetExceptionName(dnExObj); + exMessage = TryGetExceptionMessage(dnExObj); + hResult = TryGetExceptionHResult(dnExObj); + innerException = TryGetInnerException(dnExObj, exFlags, thread, module, messageFlags); + } + + exName ??= TryGetExceptionName(exObj); + exMessage ??= TryGetExceptionMessage(exObj); + hResult ??= TryGetExceptionHResult(exObj); + + return objectFactory.CreateException(new DbgExceptionId(PredefinedExceptionCategories.DotNet, exName ?? "???"), exFlags, exMessage ?? dnSpy_Debugger_DotNet_CorDebug_Resources.ExceptionMessageIsNull, hResult, thread, module, messageFlags, innerException, doNotAddExceptionToRuntime: doNotAddExceptionToRuntime); + } + private DbgException? TryGetInnerException(DbgDotNetValueImpl exObj, DbgExceptionEventFlags exFlags, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags) { + if (exObj is null) + return null; + DbgDotNetValue? netValue = null; + try { + (var corValue, netValue) = GetValueField_CorDebug(exObj, KnownMemberNames.Exception_InnerException_FieldName, null, true); + if (corValue is null) + return null; + var netValueImpl = netValue as DbgDotNetValueImpl; + var exception = CreateException(corValue, netValueImpl, exFlags, thread, module, messageFlags,true); + return exception; + } + finally { + netValue?.Dispose(); + } + } internal void RaiseModulesRefreshed(DbgModule module) => dbgModuleMemoryRefreshedNotifier.RaiseModulesRefreshed(new[] { module }); internal DmdDynamicModuleHelperImpl GetDynamicModuleHelper(DnModule dnModule) { diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/DmdType.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/DmdType.cs index 7ccb647de9..3faafc2da7 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/DmdType.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/DmdType.cs @@ -771,6 +771,16 @@ public static TypeCode GetTypeCode(DmdType? type) { /// public abstract DmdFieldInfo? GetField(string name, DmdBindingFlags bindingAttr); + /// + /// Gets a field + /// + /// Name + /// Binding flags + /// Allow the reflected type to not equal the declared type + /// + /// + public abstract DmdFieldInfo? GetField(string name, DmdBindingFlags bindingAttr, bool allowTypeVarianceOnPrivateFields); + /// /// Gets a public static or instance field /// diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/Impl/DmdMemberInfoComparer.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/Impl/DmdMemberInfoComparer.cs index 2b05e29399..1e673ab6c7 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/Impl/DmdMemberInfoComparer.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/Impl/DmdMemberInfoComparer.cs @@ -31,7 +31,7 @@ public static bool IsMatch(DmdType type, DmdBindingFlags bindingAttr) { return (attr & bindingAttr) == attr; } - public static bool IsMatch(DmdMethodBase method, DmdBindingFlags bindingAttr) { + public static bool IsMatch(DmdMethodBase method, DmdBindingFlags bindingAttr, bool allowTypeVarianceOnPrivateFields = false) { var attr = DmdBindingFlags.Default; if (method.IsPublic) attr |= DmdBindingFlags.Public; @@ -48,14 +48,14 @@ public static bool IsMatch(DmdMethodBase method, DmdBindingFlags bindingAttr) { attr |= DmdBindingFlags.FlattenHierarchy; } else { - if (!(method.IsVirtual || method.IsAbstract) && method.IsPrivate) + if (!(method.IsVirtual || method.IsAbstract) && method.IsPrivate && !allowTypeVarianceOnPrivateFields) return false; } } return (attr & bindingAttr) == attr; } - public static bool IsMatch(DmdFieldInfo field, DmdBindingFlags bindingAttr) { + public static bool IsMatch(DmdFieldInfo field, DmdBindingFlags bindingAttr, bool allowTypeVarianceOnPrivateFields=false) { var attr = DmdBindingFlags.Default; if (field.IsPublic) attr |= DmdBindingFlags.Public; @@ -72,7 +72,7 @@ public static bool IsMatch(DmdFieldInfo field, DmdBindingFlags bindingAttr) { attr |= DmdBindingFlags.FlattenHierarchy; } else { - if (field.IsPrivate) + if (field.IsPrivate && !allowTypeVarianceOnPrivateFields) return false; } } @@ -106,7 +106,7 @@ public static bool IsMatch(DmdEventInfo @event, DmdBindingFlags bindingAttr) { return (attr & bindingAttr) == attr; } - public static bool IsMatch(DmdPropertyInfo property, DmdBindingFlags bindingAttr) { + public static bool IsMatch(DmdPropertyInfo property, DmdBindingFlags bindingAttr, bool allowTypeVarianceOnPrivateFields = false) { var attr = DmdBindingFlags.Default; if (property.GetMethod?.IsPublic == true || property.SetMethod?.IsPublic == true) attr |= DmdBindingFlags.Public; @@ -125,7 +125,7 @@ public static bool IsMatch(DmdPropertyInfo property, DmdBindingFlags bindingAttr attr |= DmdBindingFlags.FlattenHierarchy; } else { - if (!(method.IsVirtual || method.IsAbstract) && method.IsPrivate) + if (!(method.IsVirtual || method.IsAbstract) && method.IsPrivate && ! allowTypeVarianceOnPrivateFields) return false; } } diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/Impl/DmdTypeBase.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/Impl/DmdTypeBase.cs index b4e197d112..90cd30c948 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/Impl/DmdTypeBase.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Metadata/Impl/DmdTypeBase.cs @@ -164,12 +164,12 @@ public sealed override DmdMethodInfo[] GetMethods(DmdBindingFlags bindingAttr) { } return methods?.ToArray() ?? Array.Empty(); } - - public sealed override DmdFieldInfo? GetField(string name, DmdBindingFlags bindingAttr) { + public sealed override DmdFieldInfo? GetField(string name, DmdBindingFlags bindingAttr) => GetField(name, bindingAttr, false); + public sealed override DmdFieldInfo? GetField(string name, DmdBindingFlags bindingAttr, bool allowTypeVarianceOnPrivateFields) { if (name is null) throw new ArgumentNullException(nameof(name)); foreach (var field in GetFields(ToGetMemberOptions(bindingAttr))) { - if (DmdMemberInfoComparer.IsMatch(field, name, bindingAttr) && DmdMemberInfoComparer.IsMatch(field, bindingAttr)) + if (DmdMemberInfoComparer.IsMatch(field, name, bindingAttr) && DmdMemberInfoComparer.IsMatch(field, bindingAttr, allowTypeVarianceOnPrivateFields)) return field; } return null; diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet/Metadata/KnownMemberNames.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet/Metadata/KnownMemberNames.cs index 3aad932e4e..65b35b3753 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet/Metadata/KnownMemberNames.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet/Metadata/KnownMemberNames.cs @@ -54,6 +54,7 @@ static class KnownMemberNames { // System.Exception public const string Exception_Message_FieldName = "_message"; public const string Exception_Message_FieldName_Mono = "message"; + public const string Exception_InnerException_FieldName = "_innerException"; public const string Exception_HResult_FieldName = "_HResult"; // System.Threading.Thread diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/DebuggerImpl.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/DebuggerImpl.cs index f5776bd53c..2dc1f3cd9f 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/DebuggerImpl.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/DebuggerImpl.cs @@ -551,9 +551,20 @@ static string GetStatusBarMessage(IList breakInfos) { case DbgMessageKind.ExceptionThrown: var ex = ((DbgMessageExceptionThrownEventArgs)e).Exception; var exMsg = ex.IsUnhandled ? dnSpy_Debugger_Resources.Debug_EventDescription_UnhandledException : dnSpy_Debugger_Resources.Debug_EventDescription_Exception; + string innerExceptionStr = ""; + var loopExp = ex; + while (loopExp?.HasData() == true) {//innerException + loopExp = loopExp.GetData(); + innerExceptionStr += $"\n\tInnerException: {GetExceptionName(loopExp)}"; + if (!string.IsNullOrEmpty(loopExp.Message)) + innerExceptionStr += $" : {loopExp.Message}"; + } + innerExceptionStr = innerExceptionStr.Trim(); exMsg += $" : pid={ex.Process.Id}({GetProcessName(ex.Process)}), {GetExceptionName(ex)}"; if (!string.IsNullOrEmpty(ex.Message)) exMsg += $" : {ex.Message}"; + if (!string.IsNullOrEmpty(innerExceptionStr)) + exMsg += $"\n\t{innerExceptionStr}\n"; return exMsg; case DbgMessageKind.BoundBreakpoint: diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Impl/DbgObjectFactoryImpl.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Impl/DbgObjectFactoryImpl.cs index 55c3607952..9de85134a4 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Impl/DbgObjectFactoryImpl.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Impl/DbgObjectFactoryImpl.cs @@ -96,14 +96,15 @@ public override DbgEngineThread CreateThread(DbgAppDomain? appDomain, string return engineThread; } - public override DbgException CreateException(DbgExceptionId id, DbgExceptionEventFlags flags, string? message, int? hResult, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, T? data, Action? onCreated) where T : class { + public override DbgException CreateException(DbgExceptionId id, DbgExceptionEventFlags flags, string? message, int? hResult, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, T? data, Action? onCreated, bool doNotAddExceptionToRuntime) where T : class { if (id.IsDefaultId) throw new ArgumentException(); var exception = new DbgExceptionImpl(runtime, id, flags, message, hResult, thread, module); if (data is not null) exception.GetOrCreateData(() => data); onCreated?.Invoke(exception); - owner.Dispatcher.BeginInvoke(() => owner.AddException_DbgThread(runtime, exception, messageFlags)); + if (!doNotAddExceptionToRuntime) + owner.Dispatcher.BeginInvoke(() => owner.AddException_DbgThread(runtime, exception, messageFlags)); return exception; } diff --git a/dnSpy/dnSpy.Contracts.Debugger/DbgObject.cs b/dnSpy/dnSpy.Contracts.Debugger/DbgObject.cs index c94ccb1e60..8215016fdf 100644 --- a/dnSpy/dnSpy.Contracts.Debugger/DbgObject.cs +++ b/dnSpy/dnSpy.Contracts.Debugger/DbgObject.cs @@ -81,8 +81,10 @@ public void Close(DbgDispatcher dispatcher) { data = dataList is null || dataList.Count == 0 ? Array.Empty<(RuntimeTypeHandle, object)>() : dataList.ToArray(); dataList?.Clear(); } - foreach (var kv in data) + foreach (var kv in data) { + (kv.data as Exceptions.DbgException)?.Close(dispatcher); (kv.data as IDisposable)?.Dispose(); + } #if DEBUG GC.SuppressFinalize(this); @@ -163,7 +165,7 @@ public T GetOrCreateData(Func create) where T : class { return (T)kv.data; } var value = create(); - Debug.Assert(!(value is DbgObject)); + Debug.Assert(!(value is DbgObject && value is not Exceptions.DbgException)); dataList.Add((type, value)); return value; } diff --git a/dnSpy/dnSpy.Contracts.Debugger/Engine/DbgObjectFactory.cs b/dnSpy/dnSpy.Contracts.Debugger/Engine/DbgObjectFactory.cs index 341e0918f9..604d8b491e 100644 --- a/dnSpy/dnSpy.Contracts.Debugger/Engine/DbgObjectFactory.cs +++ b/dnSpy/dnSpy.Contracts.Debugger/Engine/DbgObjectFactory.cs @@ -208,8 +208,9 @@ public DbgException CreateException(DbgExceptionId id, DbgExceptionEventFlags /// Message flags /// Data to add to the or null if nothing gets added /// Called right after creating the exception but before adding it to internal data structures. This can be null. + /// Do not add this exception to the main runtime through the dispatcher /// - public abstract DbgException CreateException(DbgExceptionId id, DbgExceptionEventFlags flags, string? message, int? hResult, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, T? data, Action? onCreated = null) where T : class; + public abstract DbgException CreateException(DbgExceptionId id, DbgExceptionEventFlags flags, string? message, int? hResult, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, T? data, Action? onCreated = null, bool doNotAddExceptionToRuntime=false) where T : class; /// /// Value used when the bound breakpoint's address isn't known