Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Export inner exceptions to dnSpy #114

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,16 @@ public static TypeCode GetTypeCode(DmdType? type) {
/// <returns></returns>
public abstract DmdFieldInfo? GetField(string name, DmdBindingFlags bindingAttr);

/// <summary>
/// Gets a field
/// </summary>
/// <param name="name">Name</param>
/// <param name="bindingAttr">Binding flags</param>
/// <param name="allowTypeVarianceOnPrivateFields">Allow the reflected type to not equal the declared type</param>
///
/// <returns></returns>
public abstract DmdFieldInfo? GetField(string name, DmdBindingFlags bindingAttr, bool allowTypeVarianceOnPrivateFields);

/// <summary>
/// Gets a public static or instance field
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,12 @@ public sealed override DmdMethodInfo[] GetMethods(DmdBindingFlags bindingAttr) {
}
return methods?.ToArray() ?? Array.Empty<DmdMethodInfo>();
}

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/DebuggerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -551,9 +551,20 @@ static string GetStatusBarMessage(IList<DbgBreakInfo> 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<DbgException>() == true) {//innerException
loopExp = loopExp.GetData<DbgException>();
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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,15 @@ public override DbgEngineThread CreateThread<T>(DbgAppDomain? appDomain, string
return engineThread;
}

public override DbgException CreateException<T>(DbgExceptionId id, DbgExceptionEventFlags flags, string? message, int? hResult, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, T? data, Action<DbgException>? onCreated) where T : class {
public override DbgException CreateException<T>(DbgExceptionId id, DbgExceptionEventFlags flags, string? message, int? hResult, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, T? data, Action<DbgException>? 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;
}

Expand Down
6 changes: 4 additions & 2 deletions dnSpy/dnSpy.Contracts.Debugger/DbgObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -163,7 +165,7 @@ public T GetOrCreateData<T>(Func<T> 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;
}
Expand Down
3 changes: 2 additions & 1 deletion dnSpy/dnSpy.Contracts.Debugger/Engine/DbgObjectFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,9 @@ public DbgException CreateException<T>(DbgExceptionId id, DbgExceptionEventFlags
/// <param name="messageFlags">Message flags</param>
/// <param name="data">Data to add to the <see cref="DbgException"/> or null if nothing gets added</param>
/// <param name="onCreated">Called right after creating the exception but before adding it to internal data structures. This can be null.</param>
/// <param name="doNotAddExceptionToRuntime">Do not add this exception to the main runtime through the dispatcher</param>
/// <returns></returns>
public abstract DbgException CreateException<T>(DbgExceptionId id, DbgExceptionEventFlags flags, string? message, int? hResult, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, T? data, Action<DbgException>? onCreated = null) where T : class;
public abstract DbgException CreateException<T>(DbgExceptionId id, DbgExceptionEventFlags flags, string? message, int? hResult, DbgThread? thread, DbgModule? module, DbgEngineMessageFlags messageFlags, T? data, Action<DbgException>? onCreated = null, bool doNotAddExceptionToRuntime=false) where T : class;

/// <summary>
/// Value used when the bound breakpoint's address isn't known
Expand Down