diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index c245c25e4e8eeb..f2e07c8debe428 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -25,6 +25,7 @@ enum ThreadState Unstarted = 0x00000400, // Thread has never been started Stopped = 0x00010000, // Thread has started to shut down ThreadPoolWorker = 0x01000000, // is this a threadpool worker thread? + Detached = unchecked((int)0x80000000), // Thread was detached } record struct ThreadData ( @@ -36,7 +37,11 @@ record struct ThreadData ( TargetPointer AllocContextLimit; TargetPointer Frame; TargetPointer FirstNestedException; + TargetPointer ExposedObjectHandle; TargetPointer LastThrownObjectHandle; + TargetPointer CurrentCustomDebuggerNotificationHandle; + bool LastThrownObjectIsUnhandled; + bool HasUnhandledException; TargetPointer NextThread; ); ``` @@ -96,7 +101,10 @@ The contract additionally depends on these data descriptors | `Thread` | `Frame` | Pointer to current frame | | `Thread` | `CachedStackBase` | Pointer to the base of the stack | | `Thread` | `CachedStackLimit` | Pointer to the limit of the stack | +| `Thread` | `ExposedObject` | Handle to the managed `Thread` object exposed to the debugger | | `Thread` | `LastThrownObject` | Handle to last thrown exception object | +| `Thread` | `LastThrownObjectIsUnhandled` | Whether `LastThrownObject` should be treated as unhandled | +| `Thread` | `CurrentCustomDebuggerNotification` | Handle to the current custom debugger notification object | | `Thread` | `LinkNext` | Pointer to get next thread | | `Thread` | `ExceptionTracker` | Pointer to exception tracking information | | `Thread` | `RuntimeThreadLocals` | Pointer to some thread-local storage | diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index c5f084c986c463..a13b9b275ce31c 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -48,10 +48,12 @@ CDAC_TYPE_FIELD(Thread, T_POINTER, DebuggerFilterContext, cdac_data::Deb #ifdef PROFILING_SUPPORTED CDAC_TYPE_FIELD(Thread, T_POINTER, ProfilerFilterContext, cdac_data::ProfilerFilterContext) #endif // PROFILING_SUPPORTED -CDAC_TYPE_FIELD(Thread, TYPE(GCHandle), GCHandle, cdac_data::ExposedObject) +CDAC_TYPE_FIELD(Thread, T_POINTER, ExposedObject, cdac_data::ExposedObject) CDAC_TYPE_FIELD(Thread, T_POINTER, LastThrownObject, cdac_data::LastThrownObject) +CDAC_TYPE_FIELD(Thread, T_UINT32, LastThrownObjectIsUnhandled, cdac_data::LastThrownObjectIsUnhandled) CDAC_TYPE_FIELD(Thread, T_POINTER, LinkNext, cdac_data::Link) CDAC_TYPE_FIELD(Thread, T_POINTER, ThreadLocalDataPtr, cdac_data::ThreadLocalDataPtr) +CDAC_TYPE_FIELD(Thread, T_POINTER, CurrentCustomDebuggerNotification, cdac_data::CurrentCustomDebuggerNotification) #ifndef TARGET_UNIX CDAC_TYPE_FIELD(Thread, T_POINTER, UEWatsonBucketTrackerBuckets, cdac_data::UEWatsonBucketTrackerBuckets) #endif diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 46a687943a87a7..bb17344a8fe625 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -3760,8 +3760,10 @@ struct cdac_data static constexpr size_t CachedStackLimit = offsetof(Thread, m_CacheStackLimit); static constexpr size_t ExposedObject = offsetof(Thread, m_ExposedObject); static constexpr size_t LastThrownObject = offsetof(Thread, m_LastThrownObjectHandle); + static constexpr size_t LastThrownObjectIsUnhandled = offsetof(Thread, m_ltoIsUnhandled); static constexpr size_t Link = offsetof(Thread, m_Link); static constexpr size_t ThreadLocalDataPtr = offsetof(Thread, m_ThreadLocalDataPtr); + static constexpr size_t CurrentCustomDebuggerNotification = offsetof(Thread, m_hCurrNotification); static_assert(std::is_same().m_ExceptionState), ThreadExceptionState>::value, "Thread::m_ExceptionState is of type ThreadExceptionState"); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs index f699c639cdc86c..8d30a898632f76 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs @@ -26,6 +26,7 @@ public enum ThreadState Unstarted = 0x00000400, // Thread has never been started Stopped = 0x00010000, // Thread has started to shut down ThreadPoolWorker = 0x01000000, // Thread is a thread pool worker thread + Detached = unchecked((int)0x80000000), // Thread was detached } public record struct ThreadData( @@ -38,7 +39,11 @@ public record struct ThreadData( TargetPointer AllocContextLimit, TargetPointer Frame, TargetPointer FirstNestedException, + TargetPointer ExposedObjectHandle, TargetPointer LastThrownObjectHandle, + TargetPointer CurrentCustomDebuggerNotificationHandle, + bool LastThrownObjectIsUnhandled, + bool HasUnhandledException, TargetPointer NextThread); public interface IThread : IContract @@ -54,7 +59,6 @@ void GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, TargetPointer IdToThread(uint id) => throw new NotImplementedException(); TargetPointer GetThreadLocalStaticBase(TargetPointer threadPointer, TargetPointer tlsIndexPtr) => throw new NotImplementedException(); TargetPointer GetCurrentExceptionHandle(TargetPointer threadPointer) => throw new NotImplementedException(); - TargetPointer GetThrowableObject(TargetPointer threadPointer) => throw new NotImplementedException(); byte[] GetWatsonBuckets(TargetPointer threadPointer) => throw new NotImplementedException(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs index c152faa61e4f1a..34846f9fdaf8ce 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -6,6 +6,7 @@ namespace Microsoft.Diagnostics.DataContractReader; public static class CorDbgHResults { public const int CORDBG_E_NOTREADY = unchecked((int)0x80131c10); + public const int CORDBG_E_BAD_THREAD_STATE = unchecked((int)0x8013132d); public const int CORDBG_E_READVIRTUAL_FAILURE = unchecked((int)0x80131c49); public const int ERROR_BUFFER_OVERFLOW = unchecked((int)0x8007006F); // HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW) public const int CORDBG_E_CLASS_NOT_LOADED = unchecked((int)0x80131303); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index c0b5cfc619e441..b811041fb1a856 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -26,7 +26,15 @@ private enum ThreadState_1 Background = 0x200, Unstarted = 0x400, Stopped = 0x10000, - ThreadPoolWorker = 0x1000000 + ThreadPoolWorker = 0x1000000, + Detached = unchecked((int)0x80000000) + } + + [Flags] + private enum ExceptionFlags + { + DebuggerInterceptInfo = 0x00000200, + IsUnhandled = 0x00000800, } internal Thread_1(Target target) @@ -73,6 +81,8 @@ private static Contracts.ThreadState GetThreadState(ThreadState_1 state) result |= Contracts.ThreadState.Stopped; if (state.HasFlag(ThreadState_1.ThreadPoolWorker)) result |= Contracts.ThreadState.ThreadPoolWorker; + if (state.HasFlag(ThreadState_1.Detached)) + result |= Contracts.ThreadState.Detached; return result; } @@ -82,12 +92,23 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) TargetPointer address = _target.ReadPointer(thread.ExceptionTracker); TargetPointer firstNestedException = TargetPointer.Null; + bool hasUnhandledException = false; if (address != TargetPointer.Null) { Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd(address); firstNestedException = exceptionInfo.PreviousNestedInfo; + + if (exceptionInfo.ThrownObjectHandle != TargetPointer.Null) + { + uint exceptionFlags = exceptionInfo.ExceptionFlags; + hasUnhandledException = (exceptionFlags & (uint)ExceptionFlags.IsUnhandled) != 0 + && (exceptionFlags & (uint)ExceptionFlags.DebuggerInterceptInfo) == 0; + } } + if (thread.LastThrownObjectIsUnhandled != 0) + hasUnhandledException = true; + return new ThreadData( threadPointer, thread.Id, @@ -98,7 +119,11 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.Limit ?? TargetPointer.Null, thread.Frame, firstNestedException, + thread.ExposedObject, thread.LastThrownObject.Handle, + thread.CurrentCustomDebuggerNotification, + thread.LastThrownObjectIsUnhandled != 0, + hasUnhandledException, GetThreadFromLink(thread.LinkNext)); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs index 83579602848ab9..7f77498aa24cd5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs @@ -23,8 +23,10 @@ public Thread(Target target, TargetPointer address) CachedStackBase = target.ReadPointerField(address, type, nameof(CachedStackBase)); CachedStackLimit = target.ReadPointerField(address, type, nameof(CachedStackLimit)); + ExposedObject = target.ReadPointerField(address, type, nameof(ExposedObject)); LastThrownObject = target.ProcessedData.GetOrAdd( target.ReadPointerField(address, type, nameof(LastThrownObject))); + LastThrownObjectIsUnhandled = target.ReadField(address, type, nameof(LastThrownObjectIsUnhandled)); LinkNext = target.ReadPointerField(address, type, nameof(LinkNext)); // Address of the exception tracker @@ -34,6 +36,7 @@ public Thread(Target target, TargetPointer address) ThreadLocalDataPtr = target.ReadPointerField(address, type, nameof(ThreadLocalDataPtr)); DebuggerFilterContext = target.ReadPointerField(address, type, nameof(DebuggerFilterContext)); ProfilerFilterContext = target.ReadPointerFieldOrNull(address, type, nameof(ProfilerFilterContext)); + CurrentCustomDebuggerNotification = target.ReadPointerField(address, type, nameof(CurrentCustomDebuggerNotification)); } public uint Id { get; init; } @@ -44,11 +47,14 @@ public Thread(Target target, TargetPointer address) public TargetPointer Frame { get; init; } public TargetPointer CachedStackBase { get; init; } public TargetPointer CachedStackLimit { get; init; } + public TargetPointer ExposedObject { get; init; } public ObjectHandle LastThrownObject { get; init; } + public uint LastThrownObjectIsUnhandled { get; init; } public TargetPointer LinkNext { get; init; } public TargetPointer ExceptionTracker { get; init; } public TargetPointer UEWatsonBucketTrackerBuckets { get; init; } public TargetPointer ThreadLocalDataPtr { get; init; } public TargetPointer DebuggerFilterContext { get; init; } public TargetPointer ProfilerFilterContext { get; init; } + public TargetPointer CurrentCustomDebuggerNotification { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index f2018b1f1a0385..6448703ec5be38 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -381,7 +381,33 @@ public int GetThreadHandle(ulong vmThread, nint pRetVal) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetThreadHandle(vmThread, pRetVal) : HResults.E_NOTIMPL; public int GetThreadObject(ulong vmThread, ulong* pRetVal) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetThreadObject(vmThread, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + if ((threadData.State & (Contracts.ThreadState.Stopped | Contracts.ThreadState.Unstarted | Contracts.ThreadState.Detached)) != 0) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_BAD_THREAD_STATE)!; + + *pRetVal = threadData.ExposedObjectHandle.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetThreadObject(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } public int GetThreadAllocInfo(ulong vmThread, DacDbiThreadAllocInfo* pThreadAllocInfo) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetThreadAllocInfo(vmThread, pThreadAllocInfo) : HResults.E_NOTIMPL; @@ -390,7 +416,30 @@ public int SetDebugState(ulong vmThread, int debugState) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.SetDebugState(vmThread, debugState) : HResults.E_NOTIMPL; public int HasUnhandledException(ulong vmThread, Interop.BOOL* pResult) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.HasUnhandledException(vmThread, pResult) : HResults.E_NOTIMPL; + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + *pResult = threadData.HasUnhandledException ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.HasUnhandledException(vmThread, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } public int GetUserState(ulong vmThread, int* pRetVal) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetUserState(vmThread, pRetVal) : HResults.E_NOTIMPL; @@ -493,8 +542,15 @@ public int GetCurrentException(ulong vmThread, ulong* pRetVal) int hr = HResults.S_OK; try { - TargetPointer throwable = _target.Contracts.Thread.GetThrowableObject(new TargetPointer(vmThread)); - *pRetVal = throwable.Value; + TargetPointer threadPtr = new TargetPointer(vmThread); + TargetPointer exceptionHandle = _target.Contracts.Thread.GetCurrentExceptionHandle(threadPtr); + if (exceptionHandle == TargetPointer.Null) + { + ThreadData data = _target.Contracts.Thread.GetThreadData(threadPtr); + if (data.LastThrownObjectIsUnhandled) + exceptionHandle = data.LastThrownObjectHandle; + } + *pRetVal = exceptionHandle.Value; } catch (System.Exception ex) { @@ -517,7 +573,30 @@ public int GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetObjectForCCW(ccwPtr, pRetVal) : HResults.E_NOTIMPL; public int GetCurrentCustomDebuggerNotification(ulong vmThread, ulong* pRetVal) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetCurrentCustomDebuggerNotification(vmThread, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + *pRetVal = threadData.CurrentCustomDebuggerNotificationHandle.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetCurrentCustomDebuggerNotification(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal:x}, DAC: {retValLocal:x}"); + } +#endif + return hr; + } public int GetCurrentAppDomain(ulong* pRetVal) { diff --git a/src/native/managed/cdac/tests/ClrDataExceptionStateTests.cs b/src/native/managed/cdac/tests/ClrDataExceptionStateTests.cs index 8643bc225e2d56..d3d83ed0b99ef7 100644 --- a/src/native/managed/cdac/tests/ClrDataExceptionStateTests.cs +++ b/src/native/managed/cdac/tests/ClrDataExceptionStateTests.cs @@ -84,7 +84,11 @@ private static (TestPlaceholderTarget Target, IXCLRDataTask Task) CreateTargetWi AllocContextLimit: TargetPointer.Null, Frame: TargetPointer.Null, FirstNestedException: firstNestedException, + ExposedObjectHandle: TargetPointer.Null, LastThrownObjectHandle: lastThrownObjectHandle, + CurrentCustomDebuggerNotificationHandle: TargetPointer.Null, + LastThrownObjectIsUnhandled: false, + HasUnhandledException: false, NextThread: TargetPointer.Null)); var target = new TestPlaceholderTarget.Builder(arch) @@ -461,7 +465,11 @@ private static (IXCLRDataTask Task, string ExpectedMessage) CreateTargetWithLast AllocContextLimit: TargetPointer.Null, Frame: TargetPointer.Null, FirstNestedException: firstNestedException, + ExposedObjectHandle: TargetPointer.Null, LastThrownObjectHandle: lastThrownObjectHandle, + CurrentCustomDebuggerNotificationHandle: TargetPointer.Null, + LastThrownObjectIsUnhandled: false, + HasUnhandledException: false, NextThread: TargetPointer.Null)); var mockException = new Mock(); diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs index 5ec7625383e899..f4405ee2aea91a 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs @@ -107,6 +107,87 @@ public unsafe void TryGetVolatileOSThreadID_MatchesContract(TestConfiguration co } } + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetThreadObject_MatchesContractAndThreadStateRules(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + while (current != TargetPointer.Null) + { + ThreadData data = threadContract.GetThreadData(current); + + ulong threadObject; + int hr = dbi.GetThreadObject(current, &threadObject); + + bool shouldReturnBadThreadState = (data.State & (Contracts.ThreadState.Stopped | Contracts.ThreadState.Unstarted | Contracts.ThreadState.Detached)) != 0; + if (shouldReturnBadThreadState) + { + Assert.Equal(CorDbgHResults.CORDBG_E_BAD_THREAD_STATE, hr); + } + else + { + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(data.ExposedObjectHandle.Value, threadObject); + } + + current = data.NextThread; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void HasUnhandledException_MatchesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + while (current != TargetPointer.Null) + { + Interop.BOOL hasUnhandled; + int hr = dbi.HasUnhandledException(current, &hasUnhandled); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + Assert.Equal(data.HasUnhandledException, hasUnhandled == Interop.BOOL.TRUE); + + current = data.NextThread; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetCurrentCustomDebuggerNotification_MatchesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + while (current != TargetPointer.Null) + { + ulong notificationHandle; + int hr = dbi.GetCurrentCustomDebuggerNotification(current, ¬ificationHandle); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + Assert.Equal(data.CurrentCustomDebuggerNotificationHandle.Value, notificationHandle); + + current = data.NextThread; + } + } + [ConditionalTheory] [MemberData(nameof(TestConfigurations))] public unsafe void GetUniqueThreadID_MatchesContract(TestConfiguration config) @@ -136,7 +217,7 @@ public unsafe void GetUniqueThreadID_MatchesContract(TestConfiguration config) [ConditionalTheory] [MemberData(nameof(TestConfigurations))] - public unsafe void GetCurrentException_CrossValidateWithContract(TestConfiguration config) + public unsafe void GetCurrentException_AtLeastOneThreadHasException(TestConfiguration config) { InitializeDumpTest(config); DacDbiImpl dbi = CreateDacDbi(); @@ -147,17 +228,20 @@ public unsafe void GetCurrentException_CrossValidateWithContract(TestConfigurati TargetPointer current = storeData.FirstThread; Assert.NotEqual(TargetPointer.Null, current); - ulong exception; - int hr = dbi.GetCurrentException(current, &exception); - - // GetCurrentException depends on Thread.GetThrowableObject which is not yet - // implemented in the Thread contract. Skip until the contract is available. - if (hr == unchecked((int)0x80004001)) // E_NOTIMPL — GetThrowableObject not yet in Thread contract + bool foundException = false; + while (current != TargetPointer.Null) { - throw new SkipTestException("GetThrowableObject not yet implemented in Thread contract"); + ulong exception; + int hr = dbi.GetCurrentException(current, &exception); + Assert.Equal(System.HResults.S_OK, hr); + if (exception != 0ul) + foundException = true; + + ThreadData data = threadContract.GetThreadData(current); + current = data.NextThread; } - Assert.Equal(System.HResults.S_OK, hr); + Assert.True(foundException, "Expected at least one thread to have a current exception in the FailFast dump."); } [UnmanagedCallersOnly] diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs index f46a9858ab6fdc..6f9760394731e6 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs @@ -178,7 +178,10 @@ internal sealed class MockThread : TypedView private const string FrameFieldName = "Frame"; private const string CachedStackBaseFieldName = "CachedStackBase"; private const string CachedStackLimitFieldName = "CachedStackLimit"; + private const string ExposedObjectFieldName = "ExposedObject"; private const string LastThrownObjectFieldName = "LastThrownObject"; + private const string LastThrownObjectIsUnhandledFieldName = "LastThrownObjectIsUnhandled"; + private const string CurrentCustomDebuggerNotificationFieldName = "CurrentCustomDebuggerNotification"; private const string LinkNextFieldName = "LinkNext"; private const string ExceptionTrackerFieldName = "ExceptionTracker"; private const string ThreadLocalDataPtrFieldName = "ThreadLocalDataPtr"; @@ -197,7 +200,10 @@ public static Layout CreateLayout(MockTarget.Architecture architectu .AddPointerField(FrameFieldName) .AddPointerField(CachedStackBaseFieldName) .AddPointerField(CachedStackLimitFieldName) + .AddPointerField(ExposedObjectFieldName) .AddPointerField(LastThrownObjectFieldName) + .AddUInt32Field(LastThrownObjectIsUnhandledFieldName) + .AddPointerField(CurrentCustomDebuggerNotificationFieldName) .AddPointerField(LinkNextFieldName) .AddPointerField(ExceptionTrackerFieldName) .AddPointerField(ThreadLocalDataPtrFieldName) @@ -254,6 +260,24 @@ public ulong ExceptionTracker set => WritePointerField(ExceptionTrackerFieldName, value); } + public ulong ExposedObject + { + get => ReadPointerField(ExposedObjectFieldName); + set => WritePointerField(ExposedObjectFieldName, value); + } + + public uint LastThrownObjectIsUnhandled + { + get => ReadUInt32Field(LastThrownObjectIsUnhandledFieldName); + set => WriteUInt32Field(LastThrownObjectIsUnhandledFieldName, value); + } + + public ulong CurrentCustomDebuggerNotification + { + get => ReadPointerField(CurrentCustomDebuggerNotificationFieldName); + set => WritePointerField(CurrentCustomDebuggerNotificationFieldName, value); + } + public ulong FrameAddress => GetFieldAddress(FrameFieldName); public ulong LinkAddress => GetFieldAddress(LinkNextFieldName);