diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataExceptionState.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataExceptionState.cs index 21469f6b53ffc8..1237b70d1ce0b7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataExceptionState.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataExceptionState.cs @@ -14,6 +14,7 @@ public sealed unsafe partial class ClrDataExceptionState : IXCLRDataExceptionSta private readonly Target _target; private readonly TargetPointer _threadAddress; private readonly uint _flags; + private readonly TargetPointer _exceptionInfoAddress; private readonly TargetPointer _thrownObjectHandle; private readonly TargetPointer _previousExInfoAddress; private readonly IXCLRDataExceptionState? _legacyImpl; @@ -22,6 +23,7 @@ public ClrDataExceptionState( Target target, TargetPointer threadAddress, uint flags, + TargetPointer exceptionInfoAddress, TargetPointer thrownObjectHandle, TargetPointer previousExInfoAddress, IXCLRDataExceptionState? legacyImpl) @@ -29,6 +31,7 @@ public ClrDataExceptionState( _target = target; _threadAddress = threadAddress; _flags = flags; + _exceptionInfoAddress = exceptionInfoAddress; _thrownObjectHandle = thrownObjectHandle; _previousExInfoAddress = previousExInfoAddress; _legacyImpl = legacyImpl; @@ -91,6 +94,7 @@ int IXCLRDataExceptionState.GetPrevious(DacComNullableByRef LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.IsSameState(exRecord, contextSize, cxRecord) : HResults.E_NOTIMPL; + { + int hr = IsSameState2((uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_SECOND_CHANCE, exRecord); +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal = _legacyImpl.IsSameState(exRecord, contextSize, cxRecord); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + return hr; + } + int IXCLRDataExceptionState.IsSameState2(uint flags, EXCEPTION_RECORD64* exRecord, uint contextSize, byte* cxRecord) - => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.IsSameState2(flags, exRecord, contextSize, cxRecord) : HResults.E_NOTIMPL; + { + int hr = IsSameState2(flags, exRecord); +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal = _legacyImpl.IsSameState2(flags, exRecord, contextSize, cxRecord); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + return hr; + } + + private int IsSameState2(uint flags, EXCEPTION_RECORD64* exRecord) + { + int hr = HResults.S_FALSE; + try + { + if ((flags & ~(uint)(CLRDataExceptionSameFlag.CLRDATA_EXSAME_SECOND_CHANCE | CLRDataExceptionSameFlag.CLRDATA_EXSAME_FIRST_CHANCE)) != 0) + throw new ArgumentException(); + + if ((_flags & (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_PARTIAL) != 0) + { + if ((flags & (uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_FIRST_CHANCE) != 0) + hr = HResults.S_OK; + } + else + { + if (exRecord is null) + throw new NullReferenceException(); + + TargetPointer exceptionRecord; + if (_exceptionInfoAddress != TargetPointer.Null) + { + Target.TypeInfo exceptionInfoType = _target.GetTypeInfo(DataType.ExceptionInfo); + exceptionRecord = _target.ReadPointer( + _exceptionInfoAddress + (ulong)exceptionInfoType.Fields["ExceptionRecord"].Offset); + } + else + { + ThreadData threadData = _target.Contracts.Thread.GetThreadData(_threadAddress); + exceptionRecord = threadData.OSExceptionRecord; + } + + TargetPointer exceptionAddress = _target.ReadPointer( + exceptionRecord + (ulong)(sizeof(uint) * 2 + _target.PointerSize)); + TargetPointer requestedAddress = new( + _target.PointerSize == sizeof(ulong) + ? exRecord->ExceptionAddress + : (uint)exRecord->ExceptionAddress); + + if (exceptionAddress == requestedAddress) + hr = HResults.S_OK; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + + return hr; + } + int IXCLRDataExceptionState.GetTask(DacComNullableByRef task) { int hr = HResults.S_OK, hrLocal = HResults.S_OK; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataTask.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataTask.cs index 08ac8cc706117e..acf00aa870c80b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataTask.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataTask.cs @@ -114,7 +114,7 @@ int IXCLRDataTask.GetCurrentExceptionState(DacComNullableByRef(); + SetupGetNestedExceptionInfo( + mockException, + previousExInfoAddress, + nextNestedException: TargetPointer.Null, + thrownObjectHandle: TargetPointer.Null); + targetBuilder.AddMockContract(mockException); + targetBuilder.AddTypes(new Dictionary + { + [DataType.ExceptionInfo] = new Target.TypeInfo + { + Size = (uint)helpers.PointerSize, + Fields = new Dictionary + { + ["ExceptionRecord"] = new Target.FieldInfo { Offset = 0 } + } + } + }); + } + + TargetPointer threadAddress = new(0x1000); + var mockThread = new Mock(); + mockThread.Setup(t => t.GetThreadData(threadAddress)).Returns(new ThreadData( + ThreadAddress: threadAddress, + Id: 1, + OSId: new TargetNUInt(1234), + State: default, + PreemptiveGCDisabled: false, + AllocContextPointer: TargetPointer.Null, + AllocContextLimit: TargetPointer.Null, + Frame: TargetPointer.Null, + FirstNestedException: TargetPointer.Null, + ExposedObjectHandle: TargetPointer.Null, + LastThrownObjectHandle: TargetPointer.Null, + CurrentCustomDebuggerNotificationHandle: TargetPointer.Null, + LastThrownObjectIsUnhandled: false, + HasUnhandledException: false, + NextThread: TargetPointer.Null, + ThreadHandle: TargetPointer.Null, + IsInteropDebuggingHijacked: false, + DebuggerFilterContext: TargetPointer.Null, + GCFrame: TargetPointer.Null, + IsExceptionInProgress: true, + OSExceptionRecord: hasPreviousExceptionInfo ? TargetPointer.Null : new TargetPointer(exceptionRecord.Address), + OSExceptionContextRecord: TargetPointer.Null)); + + TestPlaceholderTarget target = targetBuilder + .AddMockContract(mockThread) + .Build(); + return new ClrDataExceptionState( + target, + threadAddress, + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, + TargetPointer.Null, + TargetPointer.Null, + previousExInfoAddress, + null); + } + [Theory] [InlineData((uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, false, (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT)] [InlineData((uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, true, (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_NESTED)] @@ -136,12 +217,13 @@ public void GetFlags(uint inputFlags, bool hasNestedException, uint expectedFlag { TargetPointer previousExInfo = hasNestedException ? new TargetPointer(0x3000) : TargetPointer.Null; IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( - target: null!, - threadAddress: new TargetPointer(0x1000), - flags: inputFlags, - thrownObjectHandle: new TargetPointer(0x2000), - previousExInfoAddress: previousExInfo, - legacyImpl: null); + null!, + new TargetPointer(0x1000), + inputFlags, + TargetPointer.Null, + new TargetPointer(0x2000), + previousExInfo, + null); AssertFlags(exceptionState, expectedFlags); } @@ -155,7 +237,7 @@ public void GetString_WithMessage(MockTarget.Architecture arch) (TestPlaceholderTarget target, TargetPointer thrownObjectHandle) = CreateTargetWithException(arch, messageAddr, expectedMessage); IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( target, new TargetPointer(0x1000), (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle, TargetPointer.Null, null); + TargetPointer.Null, thrownObjectHandle, TargetPointer.Null, null); (int hr, uint strLen, char[] buffer) = CallGetString(exceptionState, bufLen: 256); Assert.Equal(HResults.S_OK, hr); @@ -170,7 +252,7 @@ public void GetString_NullMessage(MockTarget.Architecture arch) (TestPlaceholderTarget target, TargetPointer thrownObjectHandle) = CreateTargetWithException(arch, TargetPointer.Null, null); IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( target, new TargetPointer(0x1000), (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle, TargetPointer.Null, null); + TargetPointer.Null, thrownObjectHandle, TargetPointer.Null, null); (int hr, uint strLen, char[] buffer) = CallGetString(exceptionState, bufLen: 256); Assert.Equal(HResults.S_OK, hr); @@ -185,7 +267,7 @@ public void GetString_NullMessageNonEmptyBuffer(MockTarget.Architecture arch) (TestPlaceholderTarget target, TargetPointer thrownObjectHandle) = CreateTargetWithException(arch, TargetPointer.Null, null); IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( target, new TargetPointer(0x1000), (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle, TargetPointer.Null, null); + TargetPointer.Null, thrownObjectHandle, TargetPointer.Null, null); uint bufferSize = 256; char* str = null; @@ -204,7 +286,7 @@ public void GetString_BufferOverflow(MockTarget.Architecture arch) (TestPlaceholderTarget target, TargetPointer thrownObjectHandle) = CreateTargetWithException(arch, messageAddr, expectedMessage); IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( target, new TargetPointer(0x1000), (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle, TargetPointer.Null, null); + TargetPointer.Null, thrownObjectHandle, TargetPointer.Null, null); (int hr, uint strLen, _) = CallGetString(exceptionState, bufLen: 5); Assert.Equal(HResults.S_FALSE, hr); @@ -219,12 +301,13 @@ public void GetString_BufferOverflow(MockTarget.Architecture arch) public void Request_NullInBuffer_InvalidArgs(uint reqCode, uint inBufferSize, uint outBufferSize) { IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( - target: null!, - threadAddress: default, - flags: (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle: default, - previousExInfoAddress: default, - legacyImpl: null); + null!, + default, + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, + default, + default, + default, + null); byte[] outBuffer = new byte[8]; fixed (byte* outPtr = outBuffer) @@ -238,12 +321,13 @@ public void Request_NullInBuffer_InvalidArgs(uint reqCode, uint inBufferSize, ui public void Request_NonNullInBuffer_InvalidArgs() { IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( - target: null!, - threadAddress: default, - flags: (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle: default, - previousExInfoAddress: default, - legacyImpl: null); + null!, + default, + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, + default, + default, + default, + null); byte inByte = 0; uint outBufferSize = sizeof(uint); @@ -259,12 +343,13 @@ public void Request_NonNullInBuffer_InvalidArgs() public void Request_Success() { IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( - target: null!, - threadAddress: default, - flags: (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle: default, - previousExInfoAddress: default, - legacyImpl: null); + null!, + default, + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, + default, + default, + default, + null); uint outBufferSize = sizeof(uint); byte[] outBuffer = new byte[outBufferSize]; @@ -280,12 +365,13 @@ public void Request_Success() public void GetTask() { IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( - target: null!, - threadAddress: new TargetPointer(0x1000), - flags: (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle: new TargetPointer(0x2000), - previousExInfoAddress: TargetPointer.Null, - legacyImpl: null); + null!, + new TargetPointer(0x1000), + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, + TargetPointer.Null, + new TargetPointer(0x2000), + TargetPointer.Null, + null); DacComNullableByRef task = new(isNullRef: false); int hr = exceptionState.GetTask(task); @@ -293,6 +379,86 @@ public void GetTask() Assert.NotNull(task.Interface); } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsSameState_MatchingAddress(MockTarget.Architecture arch) + { + const ulong exceptionAddress = 0xAA00_0000; + IXCLRDataExceptionState exceptionState = CreateExceptionStateWithRecord(arch, exceptionAddress); + EXCEPTION_RECORD64 inputRecord = new() { ExceptionAddress = exceptionAddress }; + + int hr = exceptionState.IsSameState(&inputRecord, 0, null); + + Assert.Equal(HResults.S_OK, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsSameState2_DifferentAddress(MockTarget.Architecture arch) + { + const ulong exceptionAddress = 0x1234_5678; + IXCLRDataExceptionState exceptionState = CreateExceptionStateWithRecord(arch, exceptionAddress); + EXCEPTION_RECORD64 inputRecord = new() { ExceptionAddress = exceptionAddress + 1 }; + + int hr = exceptionState.IsSameState2((uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_SECOND_CHANCE, &inputRecord, 0, null); + + Assert.Equal(HResults.S_FALSE, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsSameState2_MatchingNestedAddress(MockTarget.Architecture arch) + { + const ulong exceptionAddress = 0x1234_5678; + IXCLRDataExceptionState exceptionState = CreateExceptionStateWithRecord(arch, exceptionAddress, hasPreviousExceptionInfo: true); + DacComNullableByRef previous = new(isNullRef: false); + Assert.Equal(HResults.S_OK, exceptionState.GetPrevious(previous)); + Assert.NotNull(previous.Interface); + EXCEPTION_RECORD64 inputRecord = new() { ExceptionAddress = exceptionAddress }; + + int hr = previous.Interface.IsSameState2((uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_SECOND_CHANCE, &inputRecord, 0, null); + + Assert.Equal(HResults.S_OK, hr); + } + + [Theory] + [InlineData((uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_SECOND_CHANCE)] + [InlineData((uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_FIRST_CHANCE)] + public void IsSameState2_PartialState(uint flags) + { + IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( + null!, + TargetPointer.Null, + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_PARTIAL, + TargetPointer.Null, + TargetPointer.Null, + TargetPointer.Null, + null); + + int hr = exceptionState.IsSameState2(flags, null, 0, null); + + Assert.Equal(flags == (uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_FIRST_CHANCE ? HResults.S_OK : HResults.S_FALSE, hr); + } + + [Theory] + [InlineData(2u)] + [InlineData(uint.MaxValue)] + public void IsSameState2_InvalidFlags(uint flags) + { + IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( + null!, + TargetPointer.Null, + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_PARTIAL, + TargetPointer.Null, + TargetPointer.Null, + TargetPointer.Null, + null); + + int hr = exceptionState.IsSameState2(flags, null, 0, null); + + Assert.Equal(HResults.E_INVALIDARG, hr); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetCurrentExceptionState_NestedException(MockTarget.Architecture arch) @@ -352,12 +518,13 @@ public void GetCurrentExceptionState_NoException(MockTarget.Architecture arch) public void GetPrevious_NoPrevious() { IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( - target: null!, - threadAddress: new TargetPointer(0x1000), - flags: (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle: new TargetPointer(0x2000), - previousExInfoAddress: TargetPointer.Null, - legacyImpl: null); + null!, + new TargetPointer(0x1000), + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, + TargetPointer.Null, + new TargetPointer(0x2000), + TargetPointer.Null, + null); DacComNullableByRef previous = new(isNullRef: false); int hr = exceptionState.GetPrevious(previous); @@ -383,11 +550,12 @@ public void GetPrevious_HasPrevious_ReturnsState(MockTarget.Architecture arch) IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( target, - threadAddress: new TargetPointer(0x1000), - flags: (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle: new TargetPointer(0x2000), - previousExInfoAddress: previousExInfoAddr, - legacyImpl: null); + new TargetPointer(0x1000), + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, + TargetPointer.Null, + new TargetPointer(0x2000), + previousExInfoAddr, + null); DacComNullableByRef previous = new(isNullRef: false); int hr = exceptionState.GetPrevious(previous); @@ -418,11 +586,12 @@ public void GetPrevious_NestedExceptionsChain(MockTarget.Architecture arch) IXCLRDataExceptionState exceptionState = new ClrDataExceptionState( target, - threadAddress: new TargetPointer(0x1000), - flags: (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, - thrownObjectHandle: new TargetPointer(0x2000), - previousExInfoAddress: firstNestedAddr, - legacyImpl: null); + new TargetPointer(0x1000), + (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT, + TargetPointer.Null, + new TargetPointer(0x2000), + firstNestedAddr, + null); DacComNullableByRef first = new(isNullRef: false); int hr1 = exceptionState.GetPrevious(first);