From 22d80f37630fed97673269771f9eca6c40ea9ccc Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 18 Jun 2024 11:53:55 -0700 Subject: [PATCH 1/3] [cdac] Implement ISOSDacInterface::GetNestedExceptionData --- src/coreclr/debug/daccess/dacimpl.h | 1 + src/coreclr/debug/daccess/request.cpp | 47 ++++++++++++++----- .../debug/runtimeinfo/datadescriptor.h | 2 + .../cdacreader/src/Contracts/Thread.cs | 10 +++- .../cdacreader/src/Data/ExceptionInfo.cs | 3 ++ .../cdacreader/src/Data/ObjectHandle.cs | 20 ++++++++ .../managed/cdacreader/src/Data/Thread.cs | 5 +- .../cdacreader/src/Legacy/SOSDacImpl.cs | 19 +++++++- 8 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 src/native/managed/cdacreader/src/Data/ObjectHandle.cs diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 92f7081d32033..02fbc85033f2c 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1231,6 +1231,7 @@ class ClrDataAccess HRESULT GetThreadDataImpl(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData); HRESULT GetThreadStoreDataImpl(struct DacpThreadStoreData *data); + HRESULT GetNestedExceptionDataImpl(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException); BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord); #ifndef TARGET_UNIX diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 2c843d1118dd9..8f1041862f324 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3233,7 +3233,6 @@ ClrDataAccess::GetUsefulGlobals(struct DacpUsefulGlobalsData *globalsData) return hr; } - HRESULT ClrDataAccess::GetNestedExceptionData(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException) { @@ -3242,26 +3241,52 @@ ClrDataAccess::GetNestedExceptionData(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS SOSDacEnter(); -#ifdef FEATURE_EH_FUNCLETS - ExceptionTrackerBase *pExData = PTR_ExceptionTrackerBase(TO_TADDR(exception)); -#else - ExInfo *pExData = PTR_ExInfo(TO_TADDR(exception)); -#endif // FEATURE_EH_FUNCLETS - - if (!pExData) + if (m_cdacSos != NULL) { - hr = E_INVALIDARG; + // Try the cDAC first - it will return E_NOTIMPL if it doesn't support this method yet. Fall back to the DAC. + hr = m_cdacSos->GetNestedExceptionData(exception, exceptionObject, nextNestedException); + if (FAILED(hr)) + { + hr = GetNestedExceptionDataImpl(exception, exceptionObject, nextNestedException); + } +#ifdef _DEBUG + else + { + // Assert that the data is the same as what we get from the DAC. + CLRDATA_ADDRESS exceptionObjectLocal; + CLRDATA_ADDRESS nextNestedExceptionLocal; + HRESULT hrLocal = GetNestedExceptionDataImpl(exception, &exceptionObjectLocal, &nextNestedExceptionLocal); + _ASSERTE(hr == hrLocal); + _ASSERTE(*exceptionObject == exceptionObjectLocal); + _ASSERTE(*nextNestedException == nextNestedExceptionLocal); + } +#endif } else { - *exceptionObject = TO_CDADDR(*PTR_TADDR(pExData->m_hThrowable)); - *nextNestedException = PTR_HOST_TO_TADDR(pExData->m_pPrevNestedInfo); + hr = GetNestedExceptionDataImpl(exception, exceptionObject, nextNestedException); } SOSDacLeave(); return hr; } +HRESULT +ClrDataAccess::GetNestedExceptionDataImpl(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException) +{ +#ifdef FEATURE_EH_FUNCLETS + ExceptionTrackerBase *pExData = PTR_ExceptionTrackerBase(TO_TADDR(exception)); +#else + ExInfo *pExData = PTR_ExInfo(TO_TADDR(exception)); +#endif // FEATURE_EH_FUNCLETS + + if (!pExData) + return E_INVALIDARG; + + *exceptionObject = TO_CDADDR(*PTR_TADDR(pExData->m_hThrowable)); + *nextNestedException = PTR_HOST_TO_TADDR(pExData->m_pPrevNestedInfo); + return S_OK; +} HRESULT ClrDataAccess::GetDomainLocalModuleData(CLRDATA_ADDRESS addr, struct DacpDomainLocalModuleData *pLocalModuleData) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 0e2cdc7a39434..e25f5e579be59 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -139,8 +139,10 @@ CDAC_TYPE_END(GCAllocContext) CDAC_TYPE_BEGIN(ExceptionInfo) CDAC_TYPE_INDETERMINATE(ExceptionInfo) #if FEATURE_EH_FUNCLETS +CDAC_TYPE_FIELD(ExceptionInfo, /*pointer*/, ThrownObject, offsetof(ExceptionTrackerBase, m_hThrowable)) CDAC_TYPE_FIELD(PreviousNestedInfo, /*pointer*/, PreviousNestedInfo, offsetof(ExceptionTrackerBase, m_pPrevNestedInfo)) #else +CDAC_TYPE_FIELD(ExceptionInfo, /*pointer*/, ThrownObject, offsetof(ExInfo, m_hThrowable)) CDAC_TYPE_FIELD(PreviousNestedInfo, /*pointer*/, PreviousNestedInfo, offsetof(ExInfo, m_pPrevNestedInfo)) #endif CDAC_TYPE_END(ExceptionInfo) diff --git a/src/native/managed/cdacreader/src/Contracts/Thread.cs b/src/native/managed/cdacreader/src/Contracts/Thread.cs index fa399ebc45498..e569347e15d8d 100644 --- a/src/native/managed/cdacreader/src/Contracts/Thread.cs +++ b/src/native/managed/cdacreader/src/Contracts/Thread.cs @@ -58,6 +58,7 @@ static IContract IContract.Create(Target target, int version) public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); public virtual ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); public virtual ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); + public virtual TargetPointer GetExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException) => throw new NotImplementedException(); } internal readonly struct Thread : IThread @@ -127,10 +128,17 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) thread.Frame, firstNestedException, thread.TEB, - thread.LastThrownObject, + thread.LastThrownObject.Handle, GetThreadFromLink(thread.LinkNext)); } + TargetPointer IThread.GetExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException) + { + Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd(exception); + nextNestedException = exceptionInfo.PreviousNestedInfo; + return exceptionInfo.ThrownObject.Object; + } + private TargetPointer GetThreadFromLink(TargetPointer threadLink) { if (threadLink == TargetPointer.Null) diff --git a/src/native/managed/cdacreader/src/Data/ExceptionInfo.cs b/src/native/managed/cdacreader/src/Data/ExceptionInfo.cs index 1a26b124ca0dc..01056b4557ff9 100644 --- a/src/native/managed/cdacreader/src/Data/ExceptionInfo.cs +++ b/src/native/managed/cdacreader/src/Data/ExceptionInfo.cs @@ -13,7 +13,10 @@ public ExceptionInfo(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.ExceptionInfo); PreviousNestedInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(PreviousNestedInfo)].Offset); + ThrownObject = target.ProcessedData.GetOrAdd( + target.ReadPointer(address + (ulong)type.Fields[nameof(ThrownObject)].Offset)); } public TargetPointer PreviousNestedInfo { get; init; } + public ObjectHandle ThrownObject { get; init; } } diff --git a/src/native/managed/cdacreader/src/Data/ObjectHandle.cs b/src/native/managed/cdacreader/src/Data/ObjectHandle.cs new file mode 100644 index 0000000000000..7e056604b2146 --- /dev/null +++ b/src/native/managed/cdacreader/src/Data/ObjectHandle.cs @@ -0,0 +1,20 @@ +// 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.DataContractReader.Data; + +internal sealed class ObjectHandle : IData +{ + static ObjectHandle IData.Create(Target target, TargetPointer address) + => new ObjectHandle(target, address); + + public ObjectHandle(Target target, TargetPointer address) + { + Handle = address; + if (address != TargetPointer.Null) + Object = target.ReadPointer(address); + } + + public TargetPointer Handle { get; init; } + public TargetPointer Object { get; init; } = TargetPointer.Null; +} diff --git a/src/native/managed/cdacreader/src/Data/Thread.cs b/src/native/managed/cdacreader/src/Data/Thread.cs index 8995d0ede2179..27e6ce7cea816 100644 --- a/src/native/managed/cdacreader/src/Data/Thread.cs +++ b/src/native/managed/cdacreader/src/Data/Thread.cs @@ -27,7 +27,8 @@ public Thread(Target target, TargetPointer address) TEB = type.Fields.TryGetValue(nameof(TEB), out Target.FieldInfo fieldInfo) ? target.ReadPointer(address + (ulong)fieldInfo.Offset) : TargetPointer.Null; - LastThrownObject = target.ReadPointer(address + (ulong)type.Fields[nameof(LastThrownObject)].Offset); + LastThrownObject = target.ProcessedData.GetOrAdd( + target.ReadPointer(address + (ulong)type.Fields[nameof(LastThrownObject)].Offset)); LinkNext = target.ReadPointer(address + (ulong)type.Fields[nameof(LinkNext)].Offset); // Address of the exception tracker - how it should be read depends on EH funclets feature global value @@ -41,7 +42,7 @@ public Thread(Target target, TargetPointer address) public GCAllocContext? AllocContext { get; init; } public TargetPointer Frame { get; init; } public TargetPointer TEB { get; init; } - public TargetPointer LastThrownObject { get; init; } + public ObjectHandle LastThrownObject { get; init; } public TargetPointer LinkNext { get; init; } public TargetPointer ExceptionTracker { get; init; } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 96b058f5a7205..a491e8cbfc9f2 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -89,7 +89,24 @@ public int GetBreakingChangeVersion() public unsafe int GetMethodTableTransparencyData(ulong mt, void* data) => HResults.E_NOTIMPL; public unsafe int GetModule(ulong addr, void** mod) => HResults.E_NOTIMPL; public unsafe int GetModuleData(ulong moduleAddr, void* data) => HResults.E_NOTIMPL; - public unsafe int GetNestedExceptionData(ulong exception, ulong* exceptionObject, ulong* nextNestedException) => HResults.E_NOTIMPL; + + public unsafe int GetNestedExceptionData(ulong exception, ulong* exceptionObject, ulong* nextNestedException) + { + try + { + Contracts.IThread contract = _target.Contracts.Thread; + TargetPointer exceptionObjectLocal = contract.GetExceptionInfo(exception, out TargetPointer nextNestedExceptionLocal); + *exceptionObject = exceptionObjectLocal; + *nextNestedException = nextNestedExceptionLocal; + } + catch (Exception ex) + { + return ex.HResult; + } + + return HResults.S_OK; + } + public unsafe int GetObjectClassName(ulong obj, uint count, char* className, uint* pNeeded) => HResults.E_NOTIMPL; public unsafe int GetObjectData(ulong objAddr, void* data) => HResults.E_NOTIMPL; public unsafe int GetObjectStringData(ulong obj, uint count, char* stringData, uint* pNeeded) => HResults.E_NOTIMPL; From 23f66cc5355631ba9747558e4bb9d4e44da844ba Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 20 Jun 2024 09:44:39 -0700 Subject: [PATCH 2/3] Update src/coreclr/debug/daccess/request.cpp --- src/coreclr/debug/daccess/request.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 8f1041862f324..fd8d94874cb80 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -3250,8 +3250,8 @@ ClrDataAccess::GetNestedExceptionData(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS hr = GetNestedExceptionDataImpl(exception, exceptionObject, nextNestedException); } #ifdef _DEBUG - else - { + else + { // Assert that the data is the same as what we get from the DAC. CLRDATA_ADDRESS exceptionObjectLocal; CLRDATA_ADDRESS nextNestedExceptionLocal; From 8e55e57007a3c4e30f7691873db6bd66d513f6d9 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 21 Jun 2024 12:30:47 -0700 Subject: [PATCH 3/3] Move to Exception contract --- docs/design/datacontracts/Exception.md | 28 +++++++++++++++++++ docs/design/datacontracts/Thread.md | 21 -------------- src/coreclr/debug/runtimeinfo/contracts.jsonc | 2 +- .../cdacreader/src/Contracts/Exception.cs | 26 +++++++++++++++++ .../cdacreader/src/Contracts/Exception_1.cs | 23 +++++++++++++++ .../cdacreader/src/Contracts/Registry.cs | 1 + .../cdacreader/src/Contracts/Thread.cs | 8 ------ .../cdacreader/src/Legacy/SOSDacImpl.cs | 2 +- 8 files changed, 80 insertions(+), 31 deletions(-) create mode 100644 docs/design/datacontracts/Exception.md create mode 100644 src/native/managed/cdacreader/src/Contracts/Exception.cs create mode 100644 src/native/managed/cdacreader/src/Contracts/Exception_1.cs diff --git a/docs/design/datacontracts/Exception.md b/docs/design/datacontracts/Exception.md new file mode 100644 index 0000000000000..e507c23f00c4f --- /dev/null +++ b/docs/design/datacontracts/Exception.md @@ -0,0 +1,28 @@ +# Contract Thread + +This contract is for getting information about exceptions in the process. + +## APIs of contract + +``` csharp +TargetPointer GetExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException); +``` + +## Version 1 + +Data descriptors used: +- `ExceptionInfo` + +``` csharp +TargetPointer GetExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException) +{ + if (exception == TargetPointer.Null) + throw new InvalidArgumentException(); + + nextNestedException = target.ReadPointer(address + /* ExceptionInfo::PreviousNestedInfo offset*/); + TargetPointer thrownObjHandle = target.ReadPointer(address + /* ExceptionInfo::ThrownObject offset */); + return = thrownObjHandle != TargetPointer.Null + ? target.ReadPointer(thrownObjHandle) + : TargetPointer.Null; +} +``` diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index dc17e506ea356..d68ce54bf8b14 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -46,7 +46,6 @@ record struct ThreadData ( ThreadStoreData GetThreadStoreData(); ThreadStoreCounts GetThreadCounts(); ThreadData GetThreadData(TargetPointer threadPointer); -TargetPointer GetNestedExceptionInfo(TargetPointer nestedExceptionPointer, out TargetPointer nextNestedException); TargetPointer GetManagedThreadObject(TargetPointer threadPointer); ``` @@ -116,26 +115,6 @@ ThreadData GetThreadData(TargetPointer threadPointer) ); } -TargetPointer GetNestedExceptionInfo(TargetPointer nestedExceptionPointer, out TargetPointer nextNestedException) -{ - if (nestedExceptionPointer == TargetPointer.Null) - { - throw new InvalidArgumentException(); - } - if (Target.ReadGlobalInt32("FEATURE_EH_FUNCLETS")) - { - var exData = new ExceptionTrackerBase(Target, nestedExceptionPointer); - nextNestedException = exData.m_pPrevNestedInfo; - return Contracts.GCHandle.GetObject(exData.m_hThrowable); - } - else - { - var exData = new ExInfo(Target, nestedExceptionPointer); - nextNestedException = exData.m_pPrevNestedInfo; - return Contracts.GCHandle.GetObject(exData.m_hThrowable); - } -} - TargetPointer GetManagedThreadObject(TargetPointer threadPointer) { var runtimeThread = new Thread(Target, threadPointer); diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index ab82fbc38c40a..0c94cf58c2322 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -9,7 +9,7 @@ // cdac-build-tool can take multiple "-c contract_file" arguments // so to conditionally include contracts, put additional contracts in a separate file { + "Exception": 1, "Thread": 1, "SOSBreakingChangeVersion": 1 // example contract: "runtime exports an SOS breaking change version global" } - diff --git a/src/native/managed/cdacreader/src/Contracts/Exception.cs b/src/native/managed/cdacreader/src/Contracts/Exception.cs new file mode 100644 index 0000000000000..c06a6984db898 --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Exception.cs @@ -0,0 +1,26 @@ +// 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.DataContractReader.Contracts; + +internal interface IException : IContract +{ + static string IContract.Name { get; } = nameof(Exception); + static IContract IContract.Create(Target target, int version) + { + return version switch + { + 1 => new Exception_1(target), + _ => default(Exception), + }; + } + + public virtual TargetPointer GetExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException) => throw new NotImplementedException(); +} + +internal readonly struct Exception : IException +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdacreader/src/Contracts/Exception_1.cs b/src/native/managed/cdacreader/src/Contracts/Exception_1.cs new file mode 100644 index 0000000000000..57249576719ee --- /dev/null +++ b/src/native/managed/cdacreader/src/Contracts/Exception_1.cs @@ -0,0 +1,23 @@ +// 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.DataContractReader.Contracts; + +internal readonly struct Exception_1 : IException +{ + private readonly Target _target; + + internal Exception_1(Target target) + { + _target = target; + } + + TargetPointer IException.GetExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException) + { + Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd(exception); + nextNestedException = exceptionInfo.PreviousNestedInfo; + return exceptionInfo.ThrownObject.Object; + } +} diff --git a/src/native/managed/cdacreader/src/Contracts/Registry.cs b/src/native/managed/cdacreader/src/Contracts/Registry.cs index aad64ce527180..98f2a28d7b564 100644 --- a/src/native/managed/cdacreader/src/Contracts/Registry.cs +++ b/src/native/managed/cdacreader/src/Contracts/Registry.cs @@ -18,6 +18,7 @@ public Registry(Target target) _target = target; } + public IException Exception => GetContract(); public IThread Thread => GetContract(); private T GetContract() where T : IContract diff --git a/src/native/managed/cdacreader/src/Contracts/Thread.cs b/src/native/managed/cdacreader/src/Contracts/Thread.cs index e569347e15d8d..0d23435d19fa2 100644 --- a/src/native/managed/cdacreader/src/Contracts/Thread.cs +++ b/src/native/managed/cdacreader/src/Contracts/Thread.cs @@ -58,7 +58,6 @@ static IContract IContract.Create(Target target, int version) public virtual ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); public virtual ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); public virtual ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); - public virtual TargetPointer GetExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException) => throw new NotImplementedException(); } internal readonly struct Thread : IThread @@ -132,13 +131,6 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) GetThreadFromLink(thread.LinkNext)); } - TargetPointer IThread.GetExceptionInfo(TargetPointer exception, out TargetPointer nextNestedException) - { - Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd(exception); - nextNestedException = exceptionInfo.PreviousNestedInfo; - return exceptionInfo.ThrownObject.Object; - } - private TargetPointer GetThreadFromLink(TargetPointer threadLink) { if (threadLink == TargetPointer.Null) diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index a491e8cbfc9f2..8dc36985a9add 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -94,7 +94,7 @@ public unsafe int GetNestedExceptionData(ulong exception, ulong* exceptionObject { try { - Contracts.IThread contract = _target.Contracts.Thread; + Contracts.IException contract = _target.Contracts.Exception; TargetPointer exceptionObjectLocal = contract.GetExceptionInfo(exception, out TargetPointer nextNestedExceptionLocal); *exceptionObject = exceptionObjectLocal; *nextNestedException = nextNestedExceptionLocal;