From d63cd1191094e2e85aeff2ee4f56dc606ce85397 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 1 Apr 2026 11:37:19 -0400 Subject: [PATCH 1/7] Vectorize Acos for Vector64/128/256/512 and TensorPrimitives Add vectorized Acos implementations for float and double across all SIMD vector types. - AcosDouble: AMD acos.c [5,4] rational polynomial with s1/c high-precision reconstruction - AcosSingle: widens to double, uses AMD acosf.c [3,1] rational polynomial - Hook up TensorPrimitives.Acos to use vectorized implementations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tensors/netcore/TensorPrimitives.Acos.cs | 63 +++++- .../tests/TensorPrimitives.Generic.cs | 4 +- .../System/Runtime/Intrinsics/Vector128.cs | 47 +++++ .../System/Runtime/Intrinsics/Vector256.cs | 47 +++++ .../System/Runtime/Intrinsics/Vector512.cs | 40 ++++ .../src/System/Runtime/Intrinsics/Vector64.cs | 55 ++++++ .../System/Runtime/Intrinsics/VectorMath.cs | 182 ++++++++++++++++++ .../ref/System.Runtime.Intrinsics.cs | 8 + 8 files changed, 440 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.Acos.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.Acos.cs index cdd36a41dc7f65..6e4dee38b61c6c 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.Acos.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorPrimitives.Acos.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.Intrinsics; namespace System.Numerics.Tensors @@ -29,11 +30,65 @@ public static void Acos(ReadOnlySpan x, Span destination) private readonly struct AcosOperator : IUnaryOperator where T : ITrigonometricFunctions { - public static bool Vectorizable => false; // TODO: Vectorize + public static bool Vectorizable => +#if NET11_0_OR_GREATER + typeof(T) == typeof(float) || typeof(T) == typeof(double); +#else + false; +#endif + public static T Invoke(T x) => T.Acos(x); - public static Vector128 Invoke(Vector128 x) => throw new NotSupportedException(); - public static Vector256 Invoke(Vector256 x) => throw new NotSupportedException(); - public static Vector512 Invoke(Vector512 x) => throw new NotSupportedException(); + + public static Vector128 Invoke(Vector128 x) + { +#if NET11_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector128.Acos(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector128.Acos(x.AsSingle()).As(); + } +#else + throw new NotSupportedException(); +#endif + } + + public static Vector256 Invoke(Vector256 x) + { +#if NET11_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector256.Acos(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector256.Acos(x.AsSingle()).As(); + } +#else + throw new NotSupportedException(); +#endif + } + + public static Vector512 Invoke(Vector512 x) + { +#if NET11_0_OR_GREATER + if (typeof(T) == typeof(double)) + { + return Vector512.Acos(x.AsDouble()).As(); + } + else + { + Debug.Assert(typeof(T) == typeof(float)); + return Vector512.Acos(x.AsSingle()).As(); + } +#else + throw new NotSupportedException(); +#endif + } } } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitives.Generic.cs b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitives.Generic.cs index 771813177bb19c..63bd0e0f0c2538 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorPrimitives.Generic.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorPrimitives.Generic.cs @@ -458,8 +458,8 @@ public static IEnumerable SpanDestinationFunctionsToTest() T? trigTolerance = IsFmaSupported ? null : Helpers.DetermineTolerance(doubleTolerance: 1e-10, floatTolerance: 1e-4f); yield return Create(TensorPrimitives.Acosh, T.Acosh); - yield return Create(TensorPrimitives.AcosPi, T.AcosPi); - yield return Create(TensorPrimitives.Acos, T.Acos); + yield return Create(TensorPrimitives.AcosPi, T.AcosPi, Helpers.DetermineTolerance(doubleTolerance: 2e-9, floatTolerance: 1e-6f)); + yield return Create(TensorPrimitives.Acos, T.Acos, Helpers.DetermineTolerance(doubleTolerance: 2e-9, floatTolerance: 1e-6f)); yield return Create(TensorPrimitives.Asinh, T.Asinh); yield return Create(TensorPrimitives.AsinPi, T.AsinPi); yield return Create(TensorPrimitives.Asin, T.Asin, trigTolerance); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs index dc20ffbd556bf5..2d07945f85d3da 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs @@ -854,6 +854,53 @@ public static Vector128 Asin(Vector128 vector) } } + /// Computes the arc cosine of each element in a vector. + /// The vector whose arc cosine is to be computed. + /// A vector whose elements are the arc cosine of the corresponding elements in . + /// The angles are returned in radians, and the input should be in the range [-1, 1]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Acos(Vector128 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcosDouble, Vector128>(vector); + } + else + { + return Create( + Vector64.Acos(vector._lower), + Vector64.Acos(vector._upper) + ); + } + } + + /// Computes the arc cosine of each element in a vector. + /// The vector whose arc cosine is to be computed. + /// A vector whose elements are the arc cosine of the corresponding elements in . + /// The angles are returned in radians, and the input should be in the range [-1, 1]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Acos(Vector128 vector) + { + if (IsHardwareAccelerated) + { + if (Vector256.IsHardwareAccelerated) + { + return VectorMath.AcosSingle, Vector128, Vector256, Vector256>(vector); + } + else + { + return VectorMath.AcosSingle, Vector128, Vector128, Vector128>(vector); + } + } + else + { + return Create( + Vector64.Acos(vector._lower), + Vector64.Acos(vector._upper) + ); + } + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Cos(Vector128 vector) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs index c51c1f5329ef74..4a3dc0e6ad7cc7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs @@ -855,6 +855,53 @@ public static Vector256 Asin(Vector256 vector) } } + /// Computes the arc cosine of each element in a vector. + /// The vector whose arc cosine is to be computed. + /// A vector whose elements are the arc cosine of the corresponding elements in . + /// The angles are returned in radians, and the input should be in the range [-1, 1]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Acos(Vector256 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcosDouble, Vector256>(vector); + } + else + { + return Create( + Vector128.Acos(vector._lower), + Vector128.Acos(vector._upper) + ); + } + } + + /// Computes the arc cosine of each element in a vector. + /// The vector whose arc cosine is to be computed. + /// A vector whose elements are the arc cosine of the corresponding elements in . + /// The angles are returned in radians, and the input should be in the range [-1, 1]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Acos(Vector256 vector) + { + if (IsHardwareAccelerated) + { + if (Vector512.IsHardwareAccelerated) + { + return VectorMath.AcosSingle, Vector256, Vector512, Vector512>(vector); + } + else + { + return VectorMath.AcosSingle, Vector256, Vector256, Vector256>(vector); + } + } + else + { + return Create( + Vector128.Acos(vector._lower), + Vector128.Acos(vector._upper) + ); + } + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Cos(Vector256 vector) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs index 5bc4b5e0964a52..4ad206276bfa80 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs @@ -758,6 +758,46 @@ public static Vector512 Asin(Vector512 vector) } } + /// Computes the arc cosine of each element in a vector. + /// The vector whose arc cosine is to be computed. + /// A vector whose elements are the arc cosine of the corresponding elements in . + /// The angles are returned in radians, and the input should be in the range [-1, 1]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Acos(Vector512 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcosDouble, Vector512>(vector); + } + else + { + return Create( + Vector256.Acos(vector._lower), + Vector256.Acos(vector._upper) + ); + } + } + + /// Computes the arc cosine of each element in a vector. + /// The vector whose arc cosine is to be computed. + /// A vector whose elements are the arc cosine of the corresponding elements in . + /// The angles are returned in radians, and the input should be in the range [-1, 1]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Acos(Vector512 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcosSingle, Vector512, Vector512, Vector512>(vector); + } + else + { + return Create( + Vector256.Acos(vector._lower), + Vector256.Acos(vector._upper) + ); + } + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Cos(Vector512 vector) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs index 7077fe391347e6..6cdb9704a15b0d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs @@ -819,6 +819,47 @@ public static Vector64 Asin(Vector64 vector) } } + /// Computes the arc cosine of each element in a vector. + /// The vector whose arc cosine is to be computed. + /// A vector whose elements are the arc cosine of the corresponding elements in . + /// The angles are returned in radians, and the input should be in the range [-1, 1]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector64 Acos(Vector64 vector) + { + if (IsHardwareAccelerated) + { + return VectorMath.AcosDouble, Vector64>(vector); + } + else + { + return Acos(vector); + } + } + + /// Computes the arc cosine of each element in a vector. + /// The vector whose arc cosine is to be computed. + /// A vector whose elements are the arc cosine of the corresponding elements in . + /// The angles are returned in radians, and the input should be in the range [-1, 1]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector64 Acos(Vector64 vector) + { + if (IsHardwareAccelerated) + { + if (Vector128.IsHardwareAccelerated) + { + return VectorMath.AcosSingle, Vector64, Vector128, Vector128>(vector); + } + else + { + return VectorMath.AcosSingle, Vector64, Vector64, Vector64>(vector); + } + } + else + { + return Acos(vector); + } + } + /// Computes the cos of each element in a vector. /// The vector that will have its Cos computed. /// A vector whose elements are the cos of the elements in . @@ -3699,6 +3740,20 @@ public static Vector64 ShuffleNative(Vector64 vector, Vector64 Acos(Vector64 vector) + where T : ITrigonometricFunctions + { + Unsafe.SkipInit(out Vector64 result); + + for (int index = 0; index < Vector64.Count; index++) + { + T value = T.Acos(vector.GetElementUnsafe(index)); + result.SetElementUnsafe(index, value); + } + + return result; + } + internal static Vector64 Asin(Vector64 vector) where T : ITrigonometricFunctions { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs index bdbe668efa326b..0eaf2d490feb7d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -3221,5 +3221,187 @@ private static TVectorDouble AsinSingleCoreDouble(TVectorDouble a return ax + ax * g * poly + (TVectorDouble.Create(PIBY2) & gtHalf); } + + public static TVectorDouble AcosDouble(TVectorDouble x) + where TVectorDouble : unmanaged, ISimdVector + where TVectorUInt64 : unmanaged, ISimdVector + { + // This code is based on `acos` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + // Implementation Notes + // -------------------- + // Based on the value of x, acos(x) is calculated as: + // For |x| <= 0.5: acos(x) = pi/2 - (x + x^3*R(x^2)) + // For |x| > 0.5: use acos(x) = pi - 2*asin(sqrt((1-|x|)/2)) or 2*asin(sqrt((1-x)/2)) + // where R(x^2) is a [5,4] rational minimax approximation (same as asin.c). + + // Rational polynomial coefficients (same as asin.c / AMD acos.c) + const double C1 = 0.227485835556935010735943483075; + const double C2 = -0.445017216867635649900123110649; + const double C3 = 0.275558175256937652532686256258; + const double C4 = -0.0549989809235685841612020091328; + const double C5 = 0.00109242697235074662306043804220; + const double C6 = 0.0000482901920344786991880522822991; + + const double D1 = 1.36491501334161032038194214209; + const double D2 = -3.28431505720958658909889444194; + const double D3 = 2.76568859157270989520376345954; + const double D4 = -0.943639137032492685763471240072; + const double D5 = 0.105869422087204370341222318533; + + const double PI = 3.1415926535897933e+00; // 0x400921fb54442d18 + const double PIBY2_HEAD = 1.5707963267948966e+00; // 0x3ff921fb54442d18 + const double PIBY2_TAIL = 6.1232339957367660e-17; // 0x3c91a62633145c07 + + TVectorDouble xneg = TVectorDouble.LessThan(x, TVectorDouble.Zero); + TVectorDouble ax = TVectorDouble.Abs(x); + + TVectorDouble transformMask = TVectorDouble.GreaterThanOrEqual(ax, TVectorDouble.Create(0.5)); + + // For |x| >= 0.5: r = 0.5*(1-ax), s = sqrt(r) + // For |x| < 0.5: r = ax*ax + TVectorDouble r = TVectorDouble.ConditionalSelect(transformMask, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); + TVectorDouble s = TVectorDouble.Sqrt(r); + + // Evaluate numerator: r*(C1 + r*(C2 + r*(C3 + r*(C4 + r*(C5 + r*C6))))) + TVectorDouble polyNum = TVectorDouble.Create(C6); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C5)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C4)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C3)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C2)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C1)); + + // Evaluate denominator: D1 + r*(D2 + r*(D3 + r*(D4 + r*D5))) + TVectorDouble polyDen = TVectorDouble.Create(D5); + polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D4)); + polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D3)); + polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D2)); + polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D1)); + + // u = r * polyNum / polyDen + TVectorDouble u = r * polyNum / polyDen; + + // For transform region (|x| >= 0.5): + // s1 = high part of s (clear low 32 bits for precision) + // c = (r - s1*s1) / (s + s1) + TVectorDouble s1 = Unsafe.BitCast(Unsafe.BitCast(s) & TVectorUInt64.Create(0xFFFFFFFF00000000)); + TVectorDouble c = (r - s1 * s1) / (s + s1); + + // For x < 0, |x| >= 0.5: acos(x) = pi - 2*(s + (s*u - piby2_tail)) + TVectorDouble transformNeg = TVectorDouble.Create(PI) - TVectorDouble.Create(2.0) * (s + (s * u - TVectorDouble.Create(PIBY2_TAIL))); + // For x > 0, |x| >= 0.5: acos(x) = 2*s1 + (2*c + 2*s*u) + TVectorDouble transformPos = TVectorDouble.Create(2.0) * s1 + (TVectorDouble.Create(2.0) * c + TVectorDouble.Create(2.0) * s * u); + TVectorDouble vTransform = TVectorDouble.ConditionalSelect(xneg, transformNeg, transformPos); + + // For |x| < 0.5: acos(x) = piby2_head - (x - (piby2_tail - x*u)) + TVectorDouble vNormal = TVectorDouble.Create(PIBY2_HEAD) - (x - (TVectorDouble.Create(PIBY2_TAIL) - x * u)); + + TVectorDouble result = TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); + + // Handle special cases: |x| > 1 returns NaN, x = ±1 returns 0 or π + TVectorDouble absXGreaterThanOne = TVectorDouble.GreaterThan(TVectorDouble.Abs(x), TVectorDouble.One); + result = TVectorDouble.ConditionalSelect(absXGreaterThanOne, TVectorDouble.Create(double.NaN), result); + + TVectorDouble xEqualsOne = TVectorDouble.Equals(x, TVectorDouble.One); + result = TVectorDouble.ConditionalSelect(xEqualsOne, TVectorDouble.Zero, result); + + TVectorDouble xEqualsNegOne = TVectorDouble.Equals(x, TVectorDouble.Create(-1.0)); + result = TVectorDouble.ConditionalSelect(xEqualsNegOne, TVectorDouble.Create(PI), result); + + return result; + } + + public static TVectorSingle AcosSingle(TVectorSingle x) + where TVectorSingle : unmanaged, ISimdVector + where TVectorInt32 : unmanaged, ISimdVector + where TVectorDouble : unmanaged, ISimdVector + where TVectorInt64 : unmanaged, ISimdVector + { + // This code is based on `acosf` from amd/aocl-libm-ose + // Copyright (C) 2021-2023 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + TVectorSingle outOfRange = TVectorSingle.GreaterThan(TVectorSingle.Abs(x), TVectorSingle.One); + TVectorSingle xEqualsOne = TVectorSingle.Equals(x, TVectorSingle.One); + TVectorSingle xEqualsNegOne = TVectorSingle.Equals(x, TVectorSingle.Create(-1.0f)); + + TVectorSingle result; + + if (TVectorSingle.ElementCount == TVectorDouble.ElementCount) + { + TVectorDouble dx = Widen(x); + result = Narrow(AcosSingleCoreDouble(dx)); + } + else + { + TVectorDouble dxLo = WidenLower(x); + TVectorDouble dxHi = WidenUpper(x); + result = Narrow( + AcosSingleCoreDouble(dxLo), + AcosSingleCoreDouble(dxHi)); + } + + result = TVectorSingle.ConditionalSelect(outOfRange, TVectorSingle.Create(float.NaN), result); + result = TVectorSingle.ConditionalSelect(xEqualsOne, TVectorSingle.Zero, result); + result = TVectorSingle.ConditionalSelect(xEqualsNegOne, TVectorSingle.Create(3.1415927f), result); + + return result; + } + + private static TVectorDouble AcosSingleCoreDouble(TVectorDouble dx) + where TVectorDouble : unmanaged, ISimdVector + { + // Rational polynomial coefficients from AMD acosf.c (same as asinf.c) + const double C1 = 0.184161606965100694821398249421; + const double C2 = -0.0565298683201845211985026327361; + const double C3 = -0.0133819288943925804214011424456; + const double C4 = -0.00396137437848476485201154797087; + const double D1 = 1.10496961524520294485512696706; + const double D2 = -0.836411276854206731913362287293; + + // High-precision pi constants + const double PI = 3.1415926535897933e+00; // 0x400921fb54442d18 + const double PIBY2_HEAD = 1.5707963267948966e+00; // 0x3ff921fb54442d18 + const double PIBY2_TAIL = 6.1232339957367660e-17; // 0x3c91a62633145c07 + + TVectorDouble xneg = TVectorDouble.LessThan(dx, TVectorDouble.Zero); + TVectorDouble ax = TVectorDouble.Abs(dx); + + TVectorDouble transformMask = TVectorDouble.GreaterThanOrEqual(ax, TVectorDouble.Create(0.5)); + + // For |x| >= 0.5: r = 0.5*(1-ax), s = sqrt(r) + // For |x| < 0.5: r = ax*ax + TVectorDouble r = TVectorDouble.ConditionalSelect(transformMask, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); + TVectorDouble s = TVectorDouble.Sqrt(r); + + // Rational polynomial: u = r * (C1 + (C2 + (C3 + C4*r)*r)*r) / (D1 + D2*r) + TVectorDouble polyNum = TVectorDouble.Create(C4); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C3)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C2)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C1)); + + TVectorDouble polyDen = TVectorDouble.MultiplyAddEstimate(TVectorDouble.Create(D2), r, TVectorDouble.Create(D1)); + + TVectorDouble u = r * polyNum / polyDen; + + // For x < 0, |x| >= 0.5: acos(x) = pi - 2*(s + (s*u - piby2_tail)) + // Since we're in double for float output, piby2_tail correction provides extra precision + TVectorDouble transformNeg = TVectorDouble.Create(PI) - TVectorDouble.Create(2.0) * (s + (s * u - TVectorDouble.Create(PIBY2_TAIL))); + // For x > 0, |x| >= 0.5: acos(x) = 2*s + 2*s*u + // (s1/c correction omitted: computing in double for float output, precision is sufficient) + TVectorDouble transformPos = TVectorDouble.Create(2.0) * s + TVectorDouble.Create(2.0) * s * u; + TVectorDouble vTransform = TVectorDouble.ConditionalSelect(xneg, transformNeg, transformPos); + + // For |x| < 0.5: acos(x) = piby2_head - (x - (piby2_tail - x*u)) + TVectorDouble vNormal = TVectorDouble.Create(PIBY2_HEAD) - (dx - (TVectorDouble.Create(PIBY2_TAIL) - dx * u)); + + return TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); + } } } diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index ada0999ed91e96..1a60f29dd7b321 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -19,6 +19,8 @@ public static partial class Vector128 public static System.Runtime.Intrinsics.Vector128 AndNot(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static bool Any(System.Runtime.Intrinsics.Vector128 vector, T value) { throw null; } public static bool AnyWhereAllBitsSet(System.Runtime.Intrinsics.Vector128 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector128 Acos(System.Runtime.Intrinsics.Vector128 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector128 Acos(System.Runtime.Intrinsics.Vector128 vector) { throw null; } public static System.Runtime.Intrinsics.Vector128 Asin(System.Runtime.Intrinsics.Vector128 vector) { throw null; } public static System.Runtime.Intrinsics.Vector128 Asin(System.Runtime.Intrinsics.Vector128 vector) { throw null; } public static System.Runtime.Intrinsics.Vector128 AsByte(this System.Runtime.Intrinsics.Vector128 vector) { throw null; } @@ -482,6 +484,8 @@ public static partial class Vector256 public static System.Runtime.Intrinsics.Vector256 AndNot(System.Runtime.Intrinsics.Vector256 left, System.Runtime.Intrinsics.Vector256 right) { throw null; } public static bool Any(System.Runtime.Intrinsics.Vector256 vector, T value) { throw null; } public static bool AnyWhereAllBitsSet(System.Runtime.Intrinsics.Vector256 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector256 Acos(System.Runtime.Intrinsics.Vector256 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector256 Acos(System.Runtime.Intrinsics.Vector256 vector) { throw null; } public static System.Runtime.Intrinsics.Vector256 Asin(System.Runtime.Intrinsics.Vector256 vector) { throw null; } public static System.Runtime.Intrinsics.Vector256 Asin(System.Runtime.Intrinsics.Vector256 vector) { throw null; } public static System.Runtime.Intrinsics.Vector256 AsByte(this System.Runtime.Intrinsics.Vector256 vector) { throw null; } @@ -934,6 +938,8 @@ public static partial class Vector512 public static System.Runtime.Intrinsics.Vector512 AndNot(System.Runtime.Intrinsics.Vector512 left, System.Runtime.Intrinsics.Vector512 right) { throw null; } public static bool Any(System.Runtime.Intrinsics.Vector512 vector, T value) { throw null; } public static bool AnyWhereAllBitsSet(System.Runtime.Intrinsics.Vector512 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Acos(System.Runtime.Intrinsics.Vector512 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Acos(System.Runtime.Intrinsics.Vector512 vector) { throw null; } public static System.Runtime.Intrinsics.Vector512 Asin(System.Runtime.Intrinsics.Vector512 vector) { throw null; } public static System.Runtime.Intrinsics.Vector512 Asin(System.Runtime.Intrinsics.Vector512 vector) { throw null; } public static System.Runtime.Intrinsics.Vector512 AsByte(this System.Runtime.Intrinsics.Vector512 vector) { throw null; } @@ -1385,6 +1391,8 @@ public static partial class Vector64 public static System.Runtime.Intrinsics.Vector64 AndNot(System.Runtime.Intrinsics.Vector64 left, System.Runtime.Intrinsics.Vector64 right) { throw null; } public static bool Any(System.Runtime.Intrinsics.Vector64 vector, T value) { throw null; } public static bool AnyWhereAllBitsSet(System.Runtime.Intrinsics.Vector64 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector64 Acos(System.Runtime.Intrinsics.Vector64 vector) { throw null; } + public static System.Runtime.Intrinsics.Vector64 Acos(System.Runtime.Intrinsics.Vector64 vector) { throw null; } public static System.Runtime.Intrinsics.Vector64 Asin(System.Runtime.Intrinsics.Vector64 vector) { throw null; } public static System.Runtime.Intrinsics.Vector64 Asin(System.Runtime.Intrinsics.Vector64 vector) { throw null; } public static System.Runtime.Intrinsics.Vector64 AsByte(this System.Runtime.Intrinsics.Vector64 vector) { throw null; } From e7ddf77cdc9189ab248574bff825b5ee73f70702 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 18:57:07 +0000 Subject: [PATCH 2/7] Address review feedback: reuse ax, use inheritdoc, add Acos tests Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e622ed4f-7db6-4d3e-a09c-d121e640f0fe Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- docs/design/datacontracts/Debugger.md | 68 + src/coreclr/vm/cdacstress.cpp | 1211 ++++++++++++++++ src/coreclr/vm/cdacstress.h | 125 ++ .../tests/System/GenericMathTestMemberData.cs | 52 + .../Fuzzers/SslStreamClientHelloFuzzer.cs | 48 + .../Fuzzing/DotnetFuzzing/SR.Fake.cs | 5 + .../System/Runtime/Intrinsics/Vector128.cs | 10 +- .../System/Runtime/Intrinsics/Vector256.cs | 10 +- .../System/Runtime/Intrinsics/Vector512.cs | 10 +- .../System/Runtime/Intrinsics/VectorMath.cs | 2 +- .../tests/Vectors/Vector128Tests.cs | 16 + .../tests/Vectors/Vector256Tests.cs | 16 + .../tests/Vectors/Vector512Tests.cs | 16 + .../tests/Vectors/Vector64Tests.cs | 16 + src/libraries/es-metadata.yml | 8 + .../Contracts/IDebugger.cs | 22 + .../Contracts/DebuggerFactory.cs | 18 + .../Contracts/Debugger_1.cs | 48 + .../Contracts/StackWalk/ExceptionHandling.cs | 182 +++ .../Contracts/StackWalk/GC/GcScanContext.cs | 117 ++ .../Contracts/StackWalk/GC/GcScanFlags.cs | 14 + .../StackWalk/GC/GcScanSlotLocation.cs | 6 + .../Contracts/StackWalk/GC/GcScanner.cs | 109 ++ .../Contracts/StackWalk/GC/StackRefData.cs | 23 + .../Data/Debugger.cs | 23 + .../Dbi/DacDbiImpl.cs | 1274 +++++++++++++++++ .../managed/cdac/tests/DebuggerTests.cs | 171 +++ .../DacDbi/DacDbiAppDomainDumpTests.cs | 217 +++ .../DacDbi/DacDbiDebuggerDumpTests.cs | 126 ++ .../DumpTests/DacDbi/DacDbiGCDumpTests.cs | 85 ++ .../DumpTests/DacDbi/DacDbiLoaderDumpTests.cs | 36 + .../DumpTests/DacDbi/DacDbiObjectDumpTests.cs | 72 + .../DumpTests/DacDbi/DacDbiThreadDumpTests.cs | 168 +++ .../DumpTests/DacDbi/NativeStringHolder.cs | 68 + .../DumpTests/Debuggees/StackRefs/Program.cs | 42 + .../Debuggees/StackRefs/StackRefs.csproj | 5 + .../tests/DumpTests/DumpTestStackWalker.cs | 302 ++++ .../tests/DumpTests/SkipOnArchAttribute.cs | 25 + .../DumpTests/StackReferenceDumpTests.cs | 213 +++ .../Debuggees/BasicAlloc/BasicAlloc.csproj | 1 + .../Debuggees/BasicAlloc/Program.cs | 56 + .../Comprehensive/Comprehensive.csproj | 1 + .../Debuggees/Comprehensive/Program.cs | 253 ++++ .../Debuggees/Directory.Build.props | 15 + .../managed/cdac/tests/StressTests/README.md | 108 ++ .../cdac/tests/StressTests/RunStressTests.ps1 | 309 ++++ .../cdac/tests/StressTests/known-issues.md | 57 + src/tests/restore.proj | 7 + src/tests/restore.projects.props | 17 + 49 files changed, 5778 insertions(+), 25 deletions(-) create mode 100644 docs/design/datacontracts/Debugger.md create mode 100644 src/coreclr/vm/cdacstress.cpp create mode 100644 src/coreclr/vm/cdacstress.h create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/SslStreamClientHelloFuzzer.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/SR.Fake.cs create mode 100644 src/libraries/es-metadata.yml create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ExceptionHandling.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanFlags.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanSlotLocation.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs create mode 100644 src/native/managed/cdac/tests/DebuggerTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/Program.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/StackRefs.csproj create mode 100644 src/native/managed/cdac/tests/DumpTests/DumpTestStackWalker.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/SkipOnArchAttribute.cs create mode 100644 src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs create mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/BasicAlloc.csproj create mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/Program.cs create mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Comprehensive.csproj create mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Program.cs create mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/Directory.Build.props create mode 100644 src/native/managed/cdac/tests/StressTests/README.md create mode 100644 src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 create mode 100644 src/native/managed/cdac/tests/StressTests/known-issues.md create mode 100644 src/tests/restore.proj create mode 100644 src/tests/restore.projects.props diff --git a/docs/design/datacontracts/Debugger.md b/docs/design/datacontracts/Debugger.md new file mode 100644 index 00000000000000..03ee1ebae704d2 --- /dev/null +++ b/docs/design/datacontracts/Debugger.md @@ -0,0 +1,68 @@ +# Contract Debugger + +This contract is for reading debugger state from the target process, including initialization status, configuration flags, metadata update state, and JIT attach state. + +## APIs of contract + +```csharp +record struct DebuggerData(uint DefinesBitField, uint MDStructuresVersion); +``` + +```csharp +bool TryGetDebuggerData(out DebuggerData data); +int GetAttachStateFlags(); +bool MetadataUpdatesApplied(); +``` + +## Version 1 + +The contract depends on the following globals + +| Global Name | Type | Description | +| --- | --- | --- | +| `Debugger` | TargetPointer | Address of the pointer to the Debugger instance (`&g_pDebugger`) | +| `CLRJitAttachState` | TargetPointer | Pointer to the CLR JIT attach state flags | +| `MetadataUpdatesApplied` | TargetPointer | Pointer to the g_metadataUpdatesApplied flag | + +The contract additionally depends on these data descriptors + +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `Debugger` | `LeftSideInitialized` | Whether the left-side debugger infrastructure is initialized | +| `Debugger` | `Defines` | Bitfield of compile-time debugger feature defines | +| `Debugger` | `MDStructuresVersion` | Version of metadata data structures | + +```csharp +bool TryGetDebuggerData(out DebuggerData data) +{ + data = default; + // The Debugger global points to g_pDebugger (a pointer-to-pointer). + // First read gets the address of g_pDebugger, second dereferences it. + TargetPointer debuggerPtrPtr = target.ReadGlobalPointer("Debugger"); + if (debuggerPtrPtr == TargetPointer.Null) + return false; + TargetPointer debuggerPtr = target.ReadPointer(debuggerPtrPtr); + if (debuggerPtr == TargetPointer.Null) + return false; + int leftSideInitialized = target.Read(debuggerPtr + /* Debugger::LeftSideInitialized offset */); + if (leftSideInitialized == 0) + return false; + data = new DebuggerData( + DefinesBitField: target.Read(debuggerPtr + /* Debugger::Defines offset */), + MDStructuresVersion: target.Read(debuggerPtr + /* Debugger::MDStructuresVersion offset */)); + return true; +} + +int GetAttachStateFlags() +{ + TargetPointer addr = target.ReadGlobalPointer("CLRJitAttachState"); + return (int)target.Read(addr); +} + +bool MetadataUpdatesApplied() +{ + if (target.TryReadGlobalPointer("MetadataUpdatesApplied", out TargetPointer addr)) + return target.Read(addr) != 0; + return false; +} +``` diff --git a/src/coreclr/vm/cdacstress.cpp b/src/coreclr/vm/cdacstress.cpp new file mode 100644 index 00000000000000..81564b0b37a6ec --- /dev/null +++ b/src/coreclr/vm/cdacstress.cpp @@ -0,0 +1,1211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// +// CdacStress.cpp +// +// Implements in-process cDAC loading and stack reference verification. +// Enabled via DOTNET_CdacStress (bit flags) or legacy DOTNET_GCStress=0x20. +// At each enabled stress point we: +// 1. Ask the cDAC to enumerate stack GC references via ISOSDacInterface::GetStackReferences +// 2. Ask the runtime to enumerate stack GC references via StackWalkFrames + GcInfoDecoder +// 3. Compare the two sets and report any mismatches +// + +#include "common.h" + +#ifdef HAVE_GCCOVER + +#include "cdacstress.h" +#include "../../native/managed/cdac/inc/cdac_reader.h" +#include "../../debug/datadescriptor-shared/inc/contract-descriptor.h" +#include +#include +#include "threads.h" +#include "eeconfig.h" +#include "gccover.h" +#include "sstring.h" +#include "exinfo.h" + +// Forward-declare the 3-param GcEnumObject used as a GCEnumCallback. +// Defined in gcenv.ee.common.cpp; not exposed in any header. +extern void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags); + +#define CDAC_LIB_NAME MAKEDLLNAME_W(W("mscordaccore_universal")) + +// Represents a single GC stack reference for comparison purposes. +struct StackRef +{ + CLRDATA_ADDRESS Address; // Location on stack holding the ref + CLRDATA_ADDRESS Object; // The object pointer value + unsigned int Flags; // SOSRefFlags (interior, pinned) + CLRDATA_ADDRESS Source; // IP or Frame that owns this ref + int SourceType; // SOS_StackSourceIP or SOS_StackSourceFrame + int Register; // Register number (cDAC only) + int Offset; // Register offset (cDAC only) + CLRDATA_ADDRESS StackPointer; // Stack pointer at this ref (cDAC only) +}; + +// Fixed-size buffer for collecting refs during stack walk. +// No heap allocation inside the promote callback — we're under NOTHROW contracts. +static const int MAX_COLLECTED_REFS = 4096; + +// Static state — cDAC +static HMODULE s_cdacModule = NULL; +static intptr_t s_cdacHandle = 0; +static IUnknown* s_cdacSosInterface = nullptr; +static IXCLRDataProcess* s_cdacProcess = nullptr; // Cached QI result for Flush() +static ISOSDacInterface* s_cdacSosDac = nullptr; // Cached QI result for GetStackReferences() + +// Static state — legacy DAC (for three-way comparison) +static HMODULE s_dacModule = NULL; +static ISOSDacInterface* s_dacSosDac = nullptr; +static IXCLRDataProcess* s_dacProcess = nullptr; + +// Static state — common +static bool s_initialized = false; +static bool s_failFast = true; +static DWORD s_step = 1; // Verify every Nth stress point (1=every point) +static DWORD s_cdacStressLevel = 0; // Resolved CdacStressFlags +static FILE* s_logFile = nullptr; +static CrstStatic s_cdacLock; // Serializes cDAC access from concurrent GC stress threads + +// Unique-stack filtering: hash set of previously seen stack traces. +// Protected by s_cdacLock (already held during VerifyAtStressPoint). + +static SHash>>* s_seenStacks = nullptr; + +// Thread-local reentrancy guard — prevents infinite recursion when +// allocations inside VerifyAtStressPoint trigger VerifyAtAllocPoint. +thread_local bool t_inVerification = false; + +// Verification counters (reported at shutdown) +static volatile LONG s_verifyCount = 0; +static volatile LONG s_verifyPass = 0; +static volatile LONG s_verifyFail = 0; +static volatile LONG s_verifySkip = 0; + +// Thread-local storage for the current thread context at the stress point. +static thread_local PCONTEXT s_currentContext = nullptr; +static thread_local DWORD s_currentThreadId = 0; + +// Extern declaration for the contract descriptor symbol exported from coreclr. +extern "C" struct ContractDescriptor DotNetRuntimeContractDescriptor; + +//----------------------------------------------------------------------------- +// In-process callbacks for the cDAC reader. +// These allow the cDAC to read memory from the current process. +//----------------------------------------------------------------------------- + +// Helper for ReadFromTargetCallback — AVInRuntimeImplOkayHolder cannot be +// directly inside PAL_TRY scope (see controller.cpp:109). +static void ReadFromTargetHelper(void* src, uint8_t* dest, uint32_t count) +{ + AVInRuntimeImplOkayHolder AVOkay; + memcpy(dest, src, count); +} + +static int ReadFromTargetCallback(uint64_t addr, uint8_t* dest, uint32_t count, void* context) +{ + void* src = reinterpret_cast(static_cast(addr)); + struct Param { void* src; uint8_t* dest; uint32_t count; } param; + param.src = src; param.dest = dest; param.count = count; + PAL_TRY(Param *, pParam, ¶m) + { + ReadFromTargetHelper(pParam->src, pParam->dest, pParam->count); + } + PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + return E_FAIL; + } + PAL_ENDTRY + return S_OK; +} + +static int WriteToTargetCallback(uint64_t addr, const uint8_t* buff, uint32_t count, void* context) +{ + return E_NOTIMPL; +} + +static int ReadThreadContextCallback(uint32_t threadId, uint32_t contextFlags, uint32_t contextBufferSize, uint8_t* contextBuffer, void* context) +{ + // Return the thread context that was stored by VerifyAtStressPoint. + if (s_currentContext != nullptr && s_currentThreadId == threadId) + { + DWORD copySize = min(contextBufferSize, (uint32_t)sizeof(CONTEXT)); + memcpy(contextBuffer, s_currentContext, copySize); + return S_OK; + } + + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: ReadThreadContext mismatch: requested=%u stored=%u\n", + threadId, s_currentThreadId)); + return E_FAIL; +} + +//----------------------------------------------------------------------------- +// Minimal ICLRDataTarget implementation for loading the legacy DAC in-process. +// Routes ReadVirtual/GetThreadContext to the same callbacks as the cDAC. +//----------------------------------------------------------------------------- +class InProcessDataTarget : public ICLRDataTarget, public ICLRRuntimeLocator +{ + volatile LONG m_refCount; +public: + InProcessDataTarget() : m_refCount(1) {} + virtual ~InProcessDataTarget() = default; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppObj) override + { + if (riid == IID_IUnknown || riid == __uuidof(ICLRDataTarget)) + { + *ppObj = static_cast(this); + AddRef(); + return S_OK; + } + if (riid == __uuidof(ICLRRuntimeLocator)) + { + *ppObj = static_cast(this); + AddRef(); + return S_OK; + } + *ppObj = nullptr; + return E_NOINTERFACE; + } + ULONG STDMETHODCALLTYPE AddRef() override { return InterlockedIncrement(&m_refCount); } + ULONG STDMETHODCALLTYPE Release() override + { + ULONG c = InterlockedDecrement(&m_refCount); + if (c == 0) delete this; + return c; + } + + // ICLRRuntimeLocator — provides the CLR base address directly so the DAC + // does not fall back to GetImageBase (which needs GetModuleHandleW, unavailable on Linux). + HRESULT STDMETHODCALLTYPE GetRuntimeBase(CLRDATA_ADDRESS* baseAddress) override + { + *baseAddress = (CLRDATA_ADDRESS)GetCurrentModuleBase(); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetMachineType(ULONG32* machineType) override + { +#ifdef TARGET_AMD64 + *machineType = IMAGE_FILE_MACHINE_AMD64; +#elif defined(TARGET_ARM64) + *machineType = IMAGE_FILE_MACHINE_ARM64; +#elif defined(TARGET_X86) + *machineType = IMAGE_FILE_MACHINE_I386; +#else + return E_NOTIMPL; +#endif + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetPointerSize(ULONG32* pointerSize) override + { + *pointerSize = sizeof(void*); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetImageBase(LPCWSTR imagePath, CLRDATA_ADDRESS* baseAddress) override + { + // Not needed — the DAC uses ICLRRuntimeLocator::GetRuntimeBase() instead. + return E_NOTIMPL; + } + + HRESULT STDMETHODCALLTYPE ReadVirtual(CLRDATA_ADDRESS address, BYTE* buffer, ULONG32 bytesRequested, ULONG32* bytesRead) override + { + int hr = ReadFromTargetCallback((uint64_t)address, buffer, bytesRequested, nullptr); + if (hr == S_OK && bytesRead != nullptr) + *bytesRead = bytesRequested; + return hr; + } + + HRESULT STDMETHODCALLTYPE WriteVirtual(CLRDATA_ADDRESS, BYTE*, ULONG32, ULONG32*) override { return E_NOTIMPL; } + + HRESULT STDMETHODCALLTYPE GetTLSValue(ULONG32 threadId, ULONG32 index, CLRDATA_ADDRESS* value) override { return E_NOTIMPL; } + HRESULT STDMETHODCALLTYPE SetTLSValue(ULONG32 threadId, ULONG32 index, CLRDATA_ADDRESS value) override { return E_NOTIMPL; } + HRESULT STDMETHODCALLTYPE GetCurrentThreadID(ULONG32* threadId) override + { + *threadId = ::GetCurrentThreadId(); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetThreadContext(ULONG32 threadId, ULONG32 contextFlags, ULONG32 contextSize, BYTE* contextBuffer) override + { + return ReadThreadContextCallback(threadId, contextFlags, contextSize, contextBuffer, nullptr); + } + + HRESULT STDMETHODCALLTYPE SetThreadContext(ULONG32, ULONG32, BYTE*) override { return E_NOTIMPL; } + HRESULT STDMETHODCALLTYPE Request(ULONG32, ULONG32, BYTE*, ULONG32, BYTE*) override { return E_NOTIMPL; } +}; + +//----------------------------------------------------------------------------- +// Initialization / Shutdown +//----------------------------------------------------------------------------- + +bool CdacStress::IsEnabled() +{ + // Check DOTNET_CdacStress first (new config) + DWORD cdacStress = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStress); + if (cdacStress != 0) + return true; + + // Fall back to legacy DOTNET_GCStress=0x20 + return (g_pConfig->GetGCStressLevel() & EEConfig::GCSTRESS_CDAC) != 0; +} + +bool CdacStress::IsInitialized() +{ + return s_initialized; +} + +DWORD GetCdacStressLevel() +{ + return s_cdacStressLevel; +} + +bool CdacStress::IsUniqueEnabled() +{ + return (s_cdacStressLevel & CDACSTRESS_UNIQUE) != 0; +} + +bool CdacStress::Initialize() +{ + if (!IsEnabled()) + return false; + + // Resolve the stress level from DOTNET_CdacStress or legacy GCSTRESS_CDAC + DWORD cdacStress = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStress); + if (cdacStress != 0) + { + s_cdacStressLevel = cdacStress; + } + else + { + // Legacy: GCSTRESS_CDAC maps to allocation-point + reference verification + s_cdacStressLevel = CDACSTRESS_ALLOC | CDACSTRESS_REFS; + } + + // Load mscordaccore_universal from next to coreclr + PathString path; + if (WszGetModuleFileName(reinterpret_cast(GetCurrentModuleBase()), path) == 0) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to get module file name\n")); + return false; + } + + SString::Iterator iter = path.End(); + if (!path.FindBack(iter, DIRECTORY_SEPARATOR_CHAR_W)) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to find directory separator\n")); + return false; + } + + iter++; + path.Truncate(iter); + path.Append(CDAC_LIB_NAME); + + s_cdacModule = CLRLoadLibrary(path.GetUnicode()); + if (s_cdacModule == NULL) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to load %S\n", path.GetUnicode())); + return false; + } + + // Resolve cdac_reader_init + auto init = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_init")); + if (init == nullptr) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to resolve cdac_reader_init\n")); + ::FreeLibrary(s_cdacModule); + s_cdacModule = NULL; + return false; + } + + // Get the address of the contract descriptor in our own process + uint64_t descriptorAddr = reinterpret_cast(&DotNetRuntimeContractDescriptor); + + // Initialize the cDAC reader with in-process callbacks + if (init(descriptorAddr, &ReadFromTargetCallback, &WriteToTargetCallback, &ReadThreadContextCallback, nullptr, &s_cdacHandle) != 0) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: cdac_reader_init failed\n")); + ::FreeLibrary(s_cdacModule); + s_cdacModule = NULL; + return false; + } + + // Create the SOS interface + auto createSos = reinterpret_cast( + ::GetProcAddress(s_cdacModule, "cdac_reader_create_sos_interface")); + if (createSos == nullptr) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to resolve cdac_reader_create_sos_interface\n")); + auto freeFn = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_free")); + if (freeFn != nullptr) + freeFn(s_cdacHandle); + ::FreeLibrary(s_cdacModule); + s_cdacModule = NULL; + s_cdacHandle = 0; + return false; + } + + if (createSos(s_cdacHandle, nullptr, &s_cdacSosInterface) != 0) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: cdac_reader_create_sos_interface failed\n")); + auto freeFn = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_free")); + if (freeFn != nullptr) + freeFn(s_cdacHandle); + ::FreeLibrary(s_cdacModule); + s_cdacModule = NULL; + s_cdacHandle = 0; + return false; + } + + // Read configuration for fail-fast behavior + s_failFast = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStressFailFast) != 0; + + // Read step interval for throttling verifications + s_step = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStressStep); + if (s_step == 0) + s_step = 1; + + // Cache QI results so we don't QI on every stress point + { + HRESULT hr = s_cdacSosInterface->QueryInterface(__uuidof(IXCLRDataProcess), reinterpret_cast(&s_cdacProcess)); + if (FAILED(hr) || s_cdacProcess == nullptr) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to QI for IXCLRDataProcess (hr=0x%08x)\n", hr)); + } + + hr = s_cdacSosInterface->QueryInterface(__uuidof(ISOSDacInterface), reinterpret_cast(&s_cdacSosDac)); + if (FAILED(hr) || s_cdacSosDac == nullptr) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to QI for ISOSDacInterface (hr=0x%08x) - cannot verify\n", hr)); + if (s_cdacProcess != nullptr) + { + s_cdacProcess->Release(); + s_cdacProcess = nullptr; + } + auto freeFn = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_free")); + if (freeFn != nullptr) + freeFn(s_cdacHandle); + ::FreeLibrary(s_cdacModule); + s_cdacModule = NULL; + s_cdacHandle = 0; + return false; + } + } + + // Open log file if configured + CLRConfigStringHolder logFilePath(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStressLogFile)); + if (logFilePath != nullptr) + { + SString sLogPath(logFilePath); + fopen_s(&s_logFile, sLogPath.GetUTF8(), "w"); + if (s_logFile != nullptr) + { + fprintf(s_logFile, "=== cDAC GC Stress Verification Log ===\n"); + fprintf(s_logFile, "FailFast: %s\n", s_failFast ? "true" : "false"); + fprintf(s_logFile, "Step: %u (verify every %u stress points)\n\n", s_step, s_step); + } + } + + s_cdacLock.Init(CrstGCCover, CRST_DEFAULT); + + if (IsUniqueEnabled()) + { + s_seenStacks = new SHash>>(); + } + + // Load the legacy DAC for three-way comparison (optional — non-fatal if it fails). + { + PathString dacPath; + if (WszGetModuleFileName(reinterpret_cast(GetCurrentModuleBase()), dacPath) != 0) + { + SString::Iterator dacIter = dacPath.End(); + if (dacPath.FindBack(dacIter, DIRECTORY_SEPARATOR_CHAR_W)) + { + dacIter++; + dacPath.Truncate(dacIter); + dacPath.Append(W("mscordaccore.dll")); + + s_dacModule = CLRLoadLibrary(dacPath.GetUnicode()); + if (s_dacModule != NULL) + { + typedef HRESULT (STDAPICALLTYPE *PFN_CLRDataCreateInstance)(REFIID, ICLRDataTarget*, void**); + auto pfnCreate = reinterpret_cast( + ::GetProcAddress(s_dacModule, "CLRDataCreateInstance")); + if (pfnCreate != nullptr) + { + InProcessDataTarget* pTarget = new (nothrow) InProcessDataTarget(); + if (pTarget != nullptr) + { + IUnknown* pDacUnk = nullptr; + HRESULT hr = pfnCreate(__uuidof(IUnknown), pTarget, (void**)&pDacUnk); + pTarget->Release(); + if (SUCCEEDED(hr) && pDacUnk != nullptr) + { + pDacUnk->QueryInterface(__uuidof(ISOSDacInterface), (void**)&s_dacSosDac); + pDacUnk->QueryInterface(__uuidof(IXCLRDataProcess), (void**)&s_dacProcess); + pDacUnk->Release(); + } + } + } + if (s_dacSosDac == nullptr) + { + LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Legacy DAC loaded but QI for ISOSDacInterface failed\n")); + } + } + else + { + LOG((LF_GCROOTS, LL_INFO10, "CDAC GC Stress: Legacy DAC not found (three-way comparison disabled)\n")); + } + } + } + } + + s_initialized = true; + LOG((LF_GCROOTS, LL_INFO10, "CDAC GC Stress: Initialized successfully (failFast=%d, logFile=%s)\n", + s_failFast, s_logFile != nullptr ? "yes" : "no")); + return true; +} + +void CdacStress::Shutdown() +{ + if (!s_initialized) + return; + + // Print summary to stderr so results are always visible + LONG actualVerifications = s_verifyPass + s_verifyFail + s_verifySkip; + fprintf(stderr, "CDAC GC Stress: %ld stress points, %ld verifications (%ld pass / %ld fail, %ld skipped)\n", + (long)s_verifyCount, (long)actualVerifications, (long)s_verifyPass, (long)s_verifyFail, (long)s_verifySkip); + STRESS_LOG3(LF_GCROOTS, LL_ALWAYS, + "CDAC GC Stress shutdown: %d verifications (%d pass / %d fail)\n", + (int)actualVerifications, (int)s_verifyPass, (int)s_verifyFail); + + if (s_logFile != nullptr) + { + fprintf(s_logFile, "\n=== Summary ===\n"); + fprintf(s_logFile, "Total stress points: %ld\n", (long)s_verifyCount); + fprintf(s_logFile, "Total verifications: %ld\n", (long)actualVerifications); + fprintf(s_logFile, " Passed: %ld\n", (long)s_verifyPass); + fprintf(s_logFile, " Failed: %ld\n", (long)s_verifyFail); + fprintf(s_logFile, " Skipped: %ld\n", (long)s_verifySkip); + fclose(s_logFile); + s_logFile = nullptr; + } + + if (s_cdacSosDac != nullptr) + { + s_cdacSosDac->Release(); + s_cdacSosDac = nullptr; + } + + if (s_cdacProcess != nullptr) + { + s_cdacProcess->Release(); + s_cdacProcess = nullptr; + } + + if (s_cdacSosInterface != nullptr) + { + s_cdacSosInterface->Release(); + s_cdacSosInterface = nullptr; + } + + if (s_cdacHandle != 0) + { + auto freeFn = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_free")); + if (freeFn != nullptr) + freeFn(s_cdacHandle); + s_cdacHandle = 0; + } + + // Legacy DAC cleanup + if (s_dacSosDac != nullptr) { s_dacSosDac->Release(); s_dacSosDac = nullptr; } + if (s_dacProcess != nullptr) { s_dacProcess->Release(); s_dacProcess = nullptr; } + + if (s_seenStacks != nullptr) + { + delete s_seenStacks; + s_seenStacks = nullptr; + } + + s_initialized = false; + LOG((LF_GCROOTS, LL_INFO10, "CDAC GC Stress: Shutdown complete\n")); +} + +//----------------------------------------------------------------------------- +// Collect stack refs from the cDAC +//----------------------------------------------------------------------------- + +static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray* pRefs) +{ + if (pSosDac == nullptr) + return false; + + ISOSStackRefEnum* pEnum = nullptr; + HRESULT hr = pSosDac->GetStackReferences(osThreadId, &pEnum); + + if (FAILED(hr) || pEnum == nullptr) + return false; + + SOSStackRefData refData; + unsigned int fetched = 0; + while (true) + { + hr = pEnum->Next(1, &refData, &fetched); + if (FAILED(hr) || fetched == 0) + break; + + StackRef ref; + ref.Address = refData.Address; + ref.Object = refData.Object; + ref.Flags = refData.Flags; + ref.Source = refData.Source; + ref.SourceType = refData.SourceType; + ref.Register = refData.Register; + ref.Offset = refData.Offset; + ref.StackPointer = refData.StackPointer; + pRefs->Append(ref); + } + + // Release twice: once for the normal ref, and once for the extra ref-count + // leaked by SOSDacImpl.GetStackReferences for COM compat (see ConvertToUnmanaged call). + pEnum->Release(); + pEnum->Release(); + return true; +} + +//----------------------------------------------------------------------------- +// Collect stack refs from the runtime's own GC scanning +//----------------------------------------------------------------------------- + +struct RuntimeRefCollectionContext +{ + StackRef refs[MAX_COLLECTED_REFS]; + int count; + bool overflow; +}; + +static void CollectRuntimeRefsPromoteFunc(PTR_PTR_Object ppObj, ScanContext* sc, uint32_t flags) +{ + RuntimeRefCollectionContext* ctx = reinterpret_cast(sc->_unused1); + if (ctx == nullptr) + return; + if (ctx->count >= MAX_COLLECTED_REFS) + { + ctx->overflow = true; + return; + } + + StackRef& ref = ctx->refs[ctx->count++]; + + // Always report the real ppObj address. For register-based refs, ppObj points + // into the REGDISPLAY/CONTEXT on the native stack — we can't reliably distinguish + // these from managed stack slots on the runtime side. The comparison logic handles + // this by matching register refs (cDAC Address=0) by (Object, Flags) only. + ref.Address = reinterpret_cast(ppObj); + ref.Object = reinterpret_cast(*ppObj); + + ref.Flags = 0; + if (flags & GC_CALL_INTERIOR) + ref.Flags |= SOSRefInterior; + if (flags & GC_CALL_PINNED) + ref.Flags |= SOSRefPinned; + ref.Source = 0; + ref.SourceType = 0; +} + +static bool CollectRuntimeStackRefs(Thread* pThread, PCONTEXT regs, StackRef* outRefs, int* outCount) +{ + RuntimeRefCollectionContext collectCtx; + collectCtx.count = 0; + collectCtx.overflow = false; + + GCCONTEXT gcctx = {}; + + // Set up ScanContext the same way ScanStackRoots does — the stack_limit and + // thread_under_crawl fields are required for PromoteCarefully/IsAddressInStack. + ScanContext sc; + sc.promotion = TRUE; + sc.thread_under_crawl = pThread; + sc._unused1 = &collectCtx; + + Frame* pTopFrame = pThread->GetFrame(); + Object** topStack = (Object**)pTopFrame; + if (InlinedCallFrame::FrameHasActiveCall(pTopFrame)) + { + InlinedCallFrame* pInlinedFrame = dac_cast(pTopFrame); + topStack = (Object**)pInlinedFrame->GetCallSiteSP(); + } + sc.stack_limit = (uintptr_t)topStack; + + gcctx.f = CollectRuntimeRefsPromoteFunc; + gcctx.sc = ≻ + gcctx.cf = NULL; + + // Set FORBIDGC_LOADER_USE_ENABLED so MethodDesc::GetName uses NOTHROW + // instead of THROWS inside EECodeManager::EnumGcRefs. + GCForbidLoaderUseHolder forbidLoaderUse; + + unsigned flagsStackWalk = ALLOW_ASYNC_STACK_WALK | ALLOW_INVALID_OBJECTS; + flagsStackWalk |= GC_FUNCLET_REFERENCE_REPORTING; + + // Use a callback that matches DAC behavior (DacStackReferenceWalker::Callback): + // Only call EnumGcRefs for frameless frames and GcScanRoots for explicit frames. + // Deliberately skip the post-scan logic (LCG resolver promotion, + // GcReportLoaderAllocator, generic param context) that GcStackCrawlCallBack + // includes — the DAC's callback has that logic disabled (#if 0). + struct DiagContext { GCCONTEXT* gcctx; RuntimeRefCollectionContext* collectCtx; }; + DiagContext diagCtx = { &gcctx, &collectCtx }; + + auto dacLikeCallback = [](CrawlFrame* pCF, VOID* pData) -> StackWalkAction + { + DiagContext* dCtx = (DiagContext*)pData; + GCCONTEXT* gcctx = dCtx->gcctx; + + ResetPointerHolder rph(&gcctx->cf); + gcctx->cf = pCF; + + bool fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences(); + + if (fReportGCReferences) + { + if (pCF->IsFrameless()) + { + ICodeManager* pCM = pCF->GetCodeManager(); + _ASSERTE(pCM != NULL); + unsigned flags = pCF->GetCodeManagerFlags(); + pCM->EnumGcRefs(pCF->GetRegisterSet(), + pCF->GetCodeInfo(), + flags, + GcEnumObject, + gcctx); + } + else + { + Frame* pFrame = pCF->GetFrame(); + pFrame->GcScanRoots(gcctx->f, gcctx->sc); + } + } + + return SWA_CONTINUE; + }; + + pThread->StackWalkFrames(dacLikeCallback, &diagCtx, flagsStackWalk); + + // NOTE: ScanStackRoots also scans the separate GCFrame linked list + // (Thread::GetGCFrame), but the DAC's GetStackReferences / DacStackReferenceWalker + // does NOT include those. We intentionally omit GCFrame scanning here so our + // runtime-side collection matches what the cDAC is expected to produce. + + // Copy results out + *outCount = collectCtx.count; + memcpy(outRefs, collectCtx.refs, collectCtx.count * sizeof(StackRef)); + return !collectCtx.overflow; +} + +//----------------------------------------------------------------------------- +// Filter cDAC refs to match runtime PromoteCarefully behavior. +// The runtime's PromoteCarefully (siginfo.cpp) skips interior pointers whose +// object value is a stack address. The cDAC reports all GcInfo slots without +// this filter, so we apply it here before comparing against runtime refs. +//----------------------------------------------------------------------------- + +static int FilterInteriorStackRefs(StackRef* refs, int count, Thread* pThread, uintptr_t stackLimit) +{ + int writeIdx = 0; + for (int i = 0; i < count; i++) + { + bool isInterior = (refs[i].Flags & SOSRefInterior) != 0; + if (isInterior && + pThread->IsAddressInStack((void*)(size_t)refs[i].Object) && + (size_t)refs[i].Object >= stackLimit) + { + continue; + } + refs[writeIdx++] = refs[i]; + } + return writeIdx; +} + +//----------------------------------------------------------------------------- +// Deduplicate cDAC refs that have the same (Address, Object, Flags). +// The cDAC may walk the same managed frame at two different offsets due to +// Frames restoring context (e.g. InlinedCallFrame). The same stack slots +// get reported from both offsets. The runtime only walks each frame once, +// so we deduplicate to match. +//----------------------------------------------------------------------------- + +static int __cdecl CompareStackRefKey(const void* a, const void* b) +{ + const StackRef* refA = static_cast(a); + const StackRef* refB = static_cast(b); + if (refA->Address != refB->Address) + return (refA->Address < refB->Address) ? -1 : 1; + if (refA->Object != refB->Object) + return (refA->Object < refB->Object) ? -1 : 1; + if (refA->Flags != refB->Flags) + return (refA->Flags < refB->Flags) ? -1 : 1; + return 0; +} + +static int DeduplicateRefs(StackRef* refs, int count) +{ + if (count <= 1) + return count; + qsort(refs, count, sizeof(StackRef), CompareStackRefKey); + int writeIdx = 1; + for (int i = 1; i < count; i++) + { + // Only dedup stack-based refs (Address != 0). + // Register refs (Address == 0) are legitimately different entries + // even when Address/Object/Flags match (different registers). + if (refs[i].Address != 0 && + refs[i].Address == refs[i-1].Address && + refs[i].Object == refs[i-1].Object && + refs[i].Flags == refs[i-1].Flags) + { + continue; + } + refs[writeIdx++] = refs[i]; + } + return writeIdx; +} + +//----------------------------------------------------------------------------- +// Report mismatch +//----------------------------------------------------------------------------- + +static void ReportMismatch(const char* message, Thread* pThread, PCONTEXT regs) +{ + LOG((LF_GCROOTS, LL_ERROR, "CDAC GC Stress: %s (Thread=0x%x, IP=0x%p)\n", + message, pThread->GetOSThreadId(), (void*)GetIP(regs))); + + if (s_failFast) + { + _ASSERTE_MSG(false, message); + } +} + +//----------------------------------------------------------------------------- +// Compare IXCLRDataStackWalk frame-by-frame between cDAC and legacy DAC. +// Creates a stack walk on each, advances in lockstep, and compares +// GetContext + Request(FRAME_DATA) at each step. +//----------------------------------------------------------------------------- + +static void CompareStackWalks(Thread* pThread, PCONTEXT regs) +{ + if (s_cdacProcess == nullptr || s_dacProcess == nullptr) + return; + + DWORD osThreadId = pThread->GetOSThreadId(); + + // Get IXCLRDataTask for the thread from both processes + IXCLRDataTask* cdacTask = nullptr; + IXCLRDataTask* dacTask = nullptr; + + HRESULT hr1 = s_cdacProcess->GetTaskByOSThreadID(osThreadId, &cdacTask); + HRESULT hr2 = s_dacProcess->GetTaskByOSThreadID(osThreadId, &dacTask); + + if (FAILED(hr1) || cdacTask == nullptr || FAILED(hr2) || dacTask == nullptr) + { + if (cdacTask) cdacTask->Release(); + if (dacTask) dacTask->Release(); + return; + } + + // Create stack walks + IXCLRDataStackWalk* cdacWalk = nullptr; + IXCLRDataStackWalk* dacWalk = nullptr; + + hr1 = cdacTask->CreateStackWalk(0xF /* CLRDATA_SIMPFRAME_MANAGED_METHOD | ... */, &cdacWalk); + hr2 = dacTask->CreateStackWalk(0xF, &dacWalk); + + cdacTask->Release(); + dacTask->Release(); + + if (FAILED(hr1) || cdacWalk == nullptr || FAILED(hr2) || dacWalk == nullptr) + { + if (cdacWalk) cdacWalk->Release(); + if (dacWalk) dacWalk->Release(); + return; + } + + // Walk in lockstep comparing each frame + int frameIdx = 0; + bool mismatch = false; + while (frameIdx < 200) // safety limit + { + // Compare GetContext + BYTE cdacCtx[4096] = {}; + BYTE dacCtx[4096] = {}; + ULONG32 cdacCtxSize = 0, dacCtxSize = 0; + + hr1 = cdacWalk->GetContext(0, sizeof(cdacCtx), &cdacCtxSize, cdacCtx); + hr2 = dacWalk->GetContext(0, sizeof(dacCtx), &dacCtxSize, dacCtx); + + if (hr1 != hr2) + { + if (s_logFile) + fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: GetContext hr mismatch cDAC=0x%x DAC=0x%x\n", + frameIdx, hr1, hr2); + mismatch = true; + break; + } + if (hr1 != S_OK) + break; // both finished + + if (cdacCtxSize != dacCtxSize) + { + if (s_logFile) + fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: Context size differs cDAC=%u DAC=%u\n", + frameIdx, cdacCtxSize, dacCtxSize); + mismatch = true; + } + else if (cdacCtxSize >= sizeof(CONTEXT)) + { + // Compare IP and SP — these are what matter for stack walk parity. + // Other CONTEXT fields (floating-point, debug registers, xstate) may + // differ between cDAC and DAC without affecting the walk. + PCODE cdacIP = GetIP((CONTEXT*)cdacCtx); + PCODE dacIP = GetIP((CONTEXT*)dacCtx); + TADDR cdacSP = GetSP((CONTEXT*)cdacCtx); + TADDR dacSP = GetSP((CONTEXT*)dacCtx); + + if (cdacIP != dacIP || cdacSP != dacSP) + { + if (s_logFile) + fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: Context differs cDAC_IP=0x%llx cDAC_SP=0x%llx DAC_IP=0x%llx DAC_SP=0x%llx\n", + frameIdx, + (unsigned long long)cdacIP, (unsigned long long)cdacSP, + (unsigned long long)dacIP, (unsigned long long)dacSP); + mismatch = true; + } + } + + // Compare Request(FRAME_DATA) + ULONG64 cdacFrameAddr = 0, dacFrameAddr = 0; + hr1 = cdacWalk->Request(0xf0000000, 0, nullptr, sizeof(cdacFrameAddr), (BYTE*)&cdacFrameAddr); + hr2 = dacWalk->Request(0xf0000000, 0, nullptr, sizeof(dacFrameAddr), (BYTE*)&dacFrameAddr); + + if (hr1 == S_OK && hr2 == S_OK && cdacFrameAddr != dacFrameAddr) + { + if (s_logFile) + { + PCODE cdacIP = 0, dacIP = 0; + if (cdacCtxSize >= sizeof(CONTEXT)) + cdacIP = GetIP((CONTEXT*)cdacCtx); + if (dacCtxSize >= sizeof(CONTEXT)) + dacIP = GetIP((CONTEXT*)dacCtx); + fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: FrameAddr cDAC=0x%llx DAC=0x%llx (cDAC_IP=0x%llx DAC_IP=0x%llx)\n", + frameIdx, (unsigned long long)cdacFrameAddr, (unsigned long long)dacFrameAddr, + (unsigned long long)cdacIP, (unsigned long long)dacIP); + } + mismatch = true; + } + + // Advance both + hr1 = cdacWalk->Next(); + hr2 = dacWalk->Next(); + + if (hr1 != hr2) + { + if (s_logFile) + fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: Next hr mismatch cDAC=0x%x DAC=0x%x\n", + frameIdx, hr1, hr2); + mismatch = true; + break; + } + if (hr1 != S_OK) + break; // both finished + + frameIdx++; + } + + if (!mismatch && s_logFile) + fprintf(s_logFile, " [WALK_OK] %d frames matched between cDAC and DAC\n", frameIdx); + + cdacWalk->Release(); + dacWalk->Release(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Compare two ref sets using two-phase matching. +// Phase 1: Match stack refs (Address != 0) by exact (Address, Object, Flags). +// Phase 2: Match register refs (Address == 0) by (Object, Flags) only. +// Returns true if all refs in setA have a match in setB and counts are equal. +//----------------------------------------------------------------------------- + +static bool CompareRefSets(StackRef* refsA, int countA, StackRef* refsB, int countB) +{ + if (countA != countB) + return false; + if (countA == 0) + return true; + if (countA > MAX_COLLECTED_REFS) + return false; + + bool matched[MAX_COLLECTED_REFS] = {}; + + for (int i = 0; i < countA; i++) + { + if (refsA[i].Address == 0) + continue; + bool found = false; + for (int j = 0; j < countB; j++) + { + if (matched[j]) continue; + if (refsA[i].Address == refsB[j].Address && + refsA[i].Object == refsB[j].Object && + refsA[i].Flags == refsB[j].Flags) + { + matched[j] = true; + found = true; + break; + } + } + if (!found) return false; + } + + for (int i = 0; i < countA; i++) + { + if (refsA[i].Address != 0) + continue; + bool found = false; + for (int j = 0; j < countB; j++) + { + if (matched[j]) continue; + if (refsA[i].Object == refsB[j].Object && + refsA[i].Flags == refsB[j].Flags) + { + matched[j] = true; + found = true; + break; + } + } + if (!found) return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Filter interior stack pointers and deduplicate a ref set in place. +//----------------------------------------------------------------------------- + +static int FilterAndDedup(StackRef* refs, int count, Thread* pThread, uintptr_t stackLimit) +{ + count = FilterInteriorStackRefs(refs, count, pThread, stackLimit); + count = DeduplicateRefs(refs, count); + return count; +} + +//----------------------------------------------------------------------------- +// Main entry point: verify at a GC stress point +//----------------------------------------------------------------------------- + +bool CdacStress::ShouldSkipStressPoint() +{ + LONG count = InterlockedIncrement(&s_verifyCount); + + if (s_step <= 1) + return false; + + return (count % s_step) != 0; +} + +void CdacStress::VerifyAtAllocPoint() +{ + if (!s_initialized) + return; + + // Reentrancy guard: allocations inside VerifyAtStressPoint (e.g., SArray) + // would trigger this function again, causing deadlock on s_cdacLock. + if (t_inVerification) + return; + + Thread* pThread = GetThreadNULLOk(); + if (pThread == nullptr || !pThread->PreemptiveGCDisabled()) + return; + + CONTEXT ctx; + RtlCaptureContext(&ctx); + VerifyAtStressPoint(pThread, &ctx); +} + +void CdacStress::VerifyAtStressPoint(Thread* pThread, PCONTEXT regs) +{ + _ASSERTE(s_initialized); + _ASSERTE(pThread != nullptr); + _ASSERTE(regs != nullptr); + + // RAII guard: set t_inVerification=true on entry, false on exit. + // Prevents infinite recursion when allocations inside this function + // trigger VerifyAtAllocPoint again (which would deadlock on s_cdacLock). + struct ReentrancyGuard { + ReentrancyGuard() { t_inVerification = true; } + ~ReentrancyGuard() { t_inVerification = false; } + } reentrancyGuard; + + // Serialize cDAC access — the cDAC's ProcessedData cache and COM interfaces + // are not thread-safe, and GC stress can fire on multiple threads. + CrstHolder cdacLock(&s_cdacLock); + + // Unique-stack filtering: use IP + SP as a stack identity. + // This skips re-verification at the same code location with the same stack depth. + if (IsUniqueEnabled() && s_seenStacks != nullptr) + { + SIZE_T stackHash = GetIP(regs) ^ (GetSP(regs) * 2654435761u); + if (s_seenStacks->LookupPtr(stackHash) != nullptr) + return; + s_seenStacks->Add(stackHash); + } + + // Set the thread context for the cDAC's ReadThreadContext callback. + s_currentContext = regs; + s_currentThreadId = pThread->GetOSThreadId(); + + // Flush the cDAC's ProcessedData cache so it re-reads from the live process. + if (s_cdacProcess != nullptr) + { + s_cdacProcess->Flush(); + } + + // Flush the legacy DAC cache too. + if (s_dacProcess != nullptr) + { + s_dacProcess->Flush(); + } + + // Compare IXCLRDataStackWalk frame-by-frame between cDAC and legacy DAC. + if (s_cdacStressLevel & CDACSTRESS_WALK) + { + CompareStackWalks(pThread, regs); + } + + // Compare GC stack references. + if (!(s_cdacStressLevel & CDACSTRESS_REFS)) + { + s_currentContext = nullptr; + s_currentThreadId = 0; + return; + } + + // Step 1: Collect raw refs from cDAC (always) and DAC (if USE_DAC). + DWORD osThreadId = pThread->GetOSThreadId(); + + SArray cdacRefs; + bool haveCdac = CollectStackRefs(s_cdacSosDac, osThreadId, &cdacRefs); + + SArray dacRefs; + bool haveDac = false; + if (s_cdacStressLevel & CDACSTRESS_USE_DAC) + { + haveDac = (s_dacSosDac != nullptr) && CollectStackRefs(s_dacSosDac, osThreadId, &dacRefs); + } + + s_currentContext = nullptr; + s_currentThreadId = 0; + + StackRef runtimeRefsBuf[MAX_COLLECTED_REFS]; + int runtimeCount = 0; + bool haveRuntime = CollectRuntimeStackRefs(pThread, regs, runtimeRefsBuf, &runtimeCount); + + if (!haveCdac || !haveRuntime) + { + InterlockedIncrement(&s_verifySkip); + if (s_logFile != nullptr) + { + if (!haveCdac) + fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - cDAC GetStackReferences failed\n", + osThreadId, (void*)GetIP(regs)); + else + fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - runtime CollectRuntimeStackRefs overflowed\n", + osThreadId, (void*)GetIP(regs)); + } + return; + } + + // Step 2: Compare cDAC vs DAC raw (before any filtering). + int rawCdacCount = (int)cdacRefs.GetCount(); + int rawDacCount = haveDac ? (int)dacRefs.GetCount() : -1; + bool dacMatch = true; + if (haveDac) + { + StackRef* cdacBuf = cdacRefs.OpenRawBuffer(); + StackRef* dacBuf = dacRefs.OpenRawBuffer(); + dacMatch = CompareRefSets(cdacBuf, rawCdacCount, dacBuf, rawDacCount); + cdacRefs.CloseRawBuffer(); + dacRefs.CloseRawBuffer(); + } + + // Step 3: Filter cDAC refs and compare vs RT (always). + Frame* pTopFrame = pThread->GetFrame(); + Object** topStack = (Object**)pTopFrame; + if (InlinedCallFrame::FrameHasActiveCall(pTopFrame)) + { + InlinedCallFrame* pInlinedFrame = dac_cast(pTopFrame); + topStack = (Object**)pInlinedFrame->GetCallSiteSP(); + } + uintptr_t stackLimit = (uintptr_t)topStack; + + int filteredCdacCount = rawCdacCount; + if (filteredCdacCount > 0) + { + StackRef* cdacBuf = cdacRefs.OpenRawBuffer(); + filteredCdacCount = FilterAndDedup(cdacBuf, filteredCdacCount, pThread, stackLimit); + cdacRefs.CloseRawBuffer(); + } + runtimeCount = DeduplicateRefs(runtimeRefsBuf, runtimeCount); + + StackRef* cdacBuf = cdacRefs.OpenRawBuffer(); + bool rtMatch = CompareRefSets(cdacBuf, filteredCdacCount, runtimeRefsBuf, runtimeCount); + cdacRefs.CloseRawBuffer(); + + // Step 4: Pass requires cDAC vs RT match. + // DAC mismatch is logged separately but doesn't affect pass/fail. + bool pass = rtMatch; + + if (pass) + InterlockedIncrement(&s_verifyPass); + else + InterlockedIncrement(&s_verifyFail); + + // Step 5: Log results. + if (s_logFile != nullptr) + { + const char* label = pass ? "PASS" : "FAIL"; + if (pass && !dacMatch) + label = "DAC_MISMATCH"; + fprintf(s_logFile, "[%s] Thread=0x%x IP=0x%p cDAC=%d DAC=%d RT=%d\n", + label, osThreadId, (void*)GetIP(regs), + rawCdacCount, rawDacCount, runtimeCount); + + if (!pass || !dacMatch) + { + for (int i = 0; i < rawCdacCount; i++) + fprintf(s_logFile, " cDAC [%d]: Address=0x%llx Object=0x%llx Flags=0x%x Source=0x%llx SourceType=%d SP=0x%llx\n", + i, (unsigned long long)cdacRefs[i].Address, (unsigned long long)cdacRefs[i].Object, + cdacRefs[i].Flags, (unsigned long long)cdacRefs[i].Source, cdacRefs[i].SourceType, + (unsigned long long)cdacRefs[i].StackPointer); + if (haveDac) + { + for (int i = 0; i < rawDacCount; i++) + fprintf(s_logFile, " DAC [%d]: Address=0x%llx Object=0x%llx Flags=0x%x Source=0x%llx\n", + i, (unsigned long long)dacRefs[i].Address, (unsigned long long)dacRefs[i].Object, + dacRefs[i].Flags, (unsigned long long)dacRefs[i].Source); + } + for (int i = 0; i < runtimeCount; i++) + fprintf(s_logFile, " RT [%d]: Address=0x%llx Object=0x%llx Flags=0x%x\n", + i, (unsigned long long)runtimeRefsBuf[i].Address, (unsigned long long)runtimeRefsBuf[i].Object, + runtimeRefsBuf[i].Flags); + + fflush(s_logFile); + } + } +} + +#endif // HAVE_GCCOVER diff --git a/src/coreclr/vm/cdacstress.h b/src/coreclr/vm/cdacstress.h new file mode 100644 index 00000000000000..b151155559e9c5 --- /dev/null +++ b/src/coreclr/vm/cdacstress.h @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// +// CdacStress.h +// +// Infrastructure for verifying cDAC stack reference reporting against the +// runtime's own GC root enumeration at stress trigger points. +// +// Enabled via DOTNET_CdacStress (bit flags) or legacy DOTNET_GCStress=0x20. +// + +#ifndef _CDAC_STRESS_H_ +#define _CDAC_STRESS_H_ + +// Trigger points for cDAC stress verification. +enum cdac_trigger_points +{ + cdac_on_alloc, // Verify at allocation points + cdac_on_gc, // Verify at GC trigger points + cdac_on_instr, // Verify at instruction-level stress points (needs GCStress=0x4) +}; + +#ifdef HAVE_GCCOVER + +// Bit flags for DOTNET_CdacStress configuration. +// +// Low nibble: WHERE to trigger verification +// High nibble: WHAT to validate +// Modifier: HOW to filter +enum CdacStressFlags : DWORD +{ + // Trigger points (low nibble — where stress fires) + CDACSTRESS_ALLOC = 0x1, // Verify at allocation points + CDACSTRESS_GC = 0x2, // Verify at GC trigger points (future) + CDACSTRESS_INSTR = 0x4, // Verify at instruction stress points (needs GCStress=0x4) + + // Validation types (high nibble — what to check) + CDACSTRESS_REFS = 0x10, // Compare GC stack references + CDACSTRESS_WALK = 0x20, // Compare IXCLRDataStackWalk frame-by-frame + CDACSTRESS_USE_DAC = 0x40, // Also load legacy DAC and compare cDAC against it + + // Modifiers + CDACSTRESS_UNIQUE = 0x100, // Only verify on unique (IP, SP) pairs +}; + +// Forward declarations +class Thread; + +// Accessor for the resolved stress level — called by template specializations. +DWORD GetCdacStressLevel(); + +class CdacStress +{ +public: + static bool Initialize(); + static void Shutdown(); + static bool IsInitialized(); + + // Returns true if cDAC stress is enabled via DOTNET_CdacStress or legacy GCSTRESS_CDAC. + static bool IsEnabled(); + + // Template-based trigger point check, following the GCStress pattern. + template + static bool IsEnabled(); + + // Returns true if unique-stack filtering is active. + static bool IsUniqueEnabled(); + + // Verify at a stress point if the given trigger is enabled and not skipped. + // Follows the GCStress::MaybeTrigger pattern — call sites are one-liners. + template + FORCEINLINE static void MaybeVerify(Thread* pThread, PCONTEXT regs) + { + if (IsEnabled() && !ShouldSkipStressPoint()) + VerifyAtStressPoint(pThread, regs); + } + + // Allocation-point variant: captures thread context automatically. + template + FORCEINLINE static void MaybeVerify() + { + if (IsEnabled() && !ShouldSkipStressPoint()) + VerifyAtAllocPoint(); + } + + // Main entry point: verify cDAC stack refs match runtime stack refs. + static void VerifyAtStressPoint(Thread* pThread, PCONTEXT regs); + + // Verify at an allocation point. Captures current thread context. + static void VerifyAtAllocPoint(); + + // Returns true if this stress point should be skipped (step throttling). + static bool ShouldSkipStressPoint(); +}; + +template<> FORCEINLINE bool CdacStress::IsEnabled() +{ + return IsInitialized() && (GetCdacStressLevel() & CDACSTRESS_ALLOC) != 0; +} + +template<> FORCEINLINE bool CdacStress::IsEnabled() +{ + return IsInitialized() && (GetCdacStressLevel() & CDACSTRESS_GC) != 0; +} + +template<> FORCEINLINE bool CdacStress::IsEnabled() +{ + return IsInitialized() && (GetCdacStressLevel() & CDACSTRESS_INSTR) != 0; +} + +#else // !HAVE_GCCOVER + +// Stub when HAVE_GCCOVER is not defined — all calls compile to nothing. +class CdacStress +{ +public: + template + FORCEINLINE static void MaybeVerify(Thread* pThread, PCONTEXT regs) { } + template + FORCEINLINE static void MaybeVerify() { } +}; + +#endif // HAVE_GCCOVER +#endif // _CDAC_STRESS_H_ diff --git a/src/libraries/Common/tests/System/GenericMathTestMemberData.cs b/src/libraries/Common/tests/System/GenericMathTestMemberData.cs index 49feea063c84d7..58bc6810aebdf5 100644 --- a/src/libraries/Common/tests/System/GenericMathTestMemberData.cs +++ b/src/libraries/Common/tests/System/GenericMathTestMemberData.cs @@ -198,6 +198,58 @@ public static IEnumerable CopySignSingle } } + public static IEnumerable AcosDouble + { + get + { + yield return new object[] { double.NegativeInfinity, double.NaN, 0.0 }; + yield return new object[] { -1.0, 3.1415926535897932, DoubleCrossPlatformMachineEpsilon * 10 }; + yield return new object[] { -0.78539816339744831, 2.4741354375614093, DoubleCrossPlatformMachineEpsilon * 10 }; // value: -(pi / 4) + yield return new object[] { -0.70710678118654752, 2.3561944901923450, DoubleCrossPlatformMachineEpsilon * 10 }; // value: -(1 / sqrt(2)) + yield return new object[] { -0.69314718055994531, 2.3366425216139770, DoubleCrossPlatformMachineEpsilon * 10 }; // value: -(ln(2)) + yield return new object[] { -0.63661977236758134, 2.2609034181694367, DoubleCrossPlatformMachineEpsilon * 10 }; // value: -(2 / pi) + yield return new object[] { -0.43429448190325183, 2.0200512174769663, DoubleCrossPlatformMachineEpsilon * 10 }; // value: -(log10(e)) + yield return new object[] { -0.31830988618379067, 1.8947424337268775, DoubleCrossPlatformMachineEpsilon * 10 }; // value: -(1 / pi) + yield return new object[] { -0.0, 1.5707963267948966, DoubleCrossPlatformMachineEpsilon * 10 }; + yield return new object[] { double.NaN, double.NaN, 0.0 }; + yield return new object[] { 0.0, 1.5707963267948966, DoubleCrossPlatformMachineEpsilon * 10 }; + yield return new object[] { 0.31830988618379067, 1.2468502198629159, DoubleCrossPlatformMachineEpsilon * 10 }; // value: (1 / pi) + yield return new object[] { 0.43429448190325183, 1.1215414361128270, DoubleCrossPlatformMachineEpsilon * 10 }; // value: (log10(e)) + yield return new object[] { 0.63661977236758134, 0.88068923542035660, DoubleCrossPlatformMachineEpsilon }; // value: (2 / pi) + yield return new object[] { 0.69314718055994531, 0.80495013197581640, DoubleCrossPlatformMachineEpsilon }; // value: (ln(2)) + yield return new object[] { 0.70710678118654752, 0.78539816339744828, DoubleCrossPlatformMachineEpsilon }; // value: (1 / sqrt(2)), expected: (pi / 4) + yield return new object[] { 0.78539816339744831, 0.66745721602838380, DoubleCrossPlatformMachineEpsilon }; // value: (pi / 4) + yield return new object[] { 1.0, 0.0, 0.0 }; + yield return new object[] { double.PositiveInfinity, double.NaN, 0.0 }; + } + } + + public static IEnumerable AcosSingle + { + get + { + yield return new object[] { float.NegativeInfinity, float.NaN, 0.0f }; + yield return new object[] { -1.0f, 3.14159274f, SingleCrossPlatformMachineEpsilon * 10 }; + yield return new object[] { -0.785398163f, 2.47413540f, SingleCrossPlatformMachineEpsilon * 10 }; // value: -(pi / 4) + yield return new object[] { -0.707106781f, 2.35619450f, SingleCrossPlatformMachineEpsilon * 10 }; // value: -(1 / sqrt(2)) + yield return new object[] { -0.693147181f, 2.33664250f, SingleCrossPlatformMachineEpsilon * 10 }; // value: -(ln(2)) + yield return new object[] { -0.636619772f, 2.26090336f, SingleCrossPlatformMachineEpsilon * 10 }; // value: -(2 / pi) + yield return new object[] { -0.434294482f, 2.02005124f, SingleCrossPlatformMachineEpsilon * 10 }; // value: -(log10(e)) + yield return new object[] { -0.318309886f, 1.89474249f, SingleCrossPlatformMachineEpsilon * 10 }; // value: -(1 / pi) + yield return new object[] { -0.0f, 1.57079637f, SingleCrossPlatformMachineEpsilon * 10 }; + yield return new object[] { float.NaN, float.NaN, 0.0f }; + yield return new object[] { 0.0f, 1.57079637f, SingleCrossPlatformMachineEpsilon * 10 }; + yield return new object[] { 0.318309886f, 1.24685025f, SingleCrossPlatformMachineEpsilon * 10 }; // value: (1 / pi) + yield return new object[] { 0.434294482f, 1.12154138f, SingleCrossPlatformMachineEpsilon * 10 }; // value: (log10(e)) + yield return new object[] { 0.636619772f, 0.880689263f, SingleCrossPlatformMachineEpsilon }; // value: (2 / pi) + yield return new object[] { 0.693147181f, 0.804950058f, SingleCrossPlatformMachineEpsilon }; // value: (ln(2)) + yield return new object[] { 0.707106781f, 0.785398185f, SingleCrossPlatformMachineEpsilon }; // value: (1 / sqrt(2)), expected: (pi / 4) + yield return new object[] { 0.785398163f, 0.667457163f, SingleCrossPlatformMachineEpsilon }; // value: (pi / 4) + yield return new object[] { 1.0f, 0.0f, 0.0f }; + yield return new object[] { float.PositiveInfinity, float.NaN, 0.0f }; + } + } + public static IEnumerable AsinDouble { get diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/SslStreamClientHelloFuzzer.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/SslStreamClientHelloFuzzer.cs new file mode 100644 index 00000000000000..9e75a0cebf12a0 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/SslStreamClientHelloFuzzer.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Net.Security; +using System.Security.Authentication; + +namespace DotnetFuzzing.Fuzzers +{ + internal sealed class SslStreamClientHelloFuzzer : IFuzzer + { + public string[] TargetAssemblies => ["System.Net.Security"]; + + public string[] TargetCoreLibPrefixes => []; + + public void FuzzTarget(ReadOnlySpan bytes) + { + byte[] buffer = ArrayPool.Shared.Rent(bytes.Length); + try + { + bytes.CopyTo(buffer); + using MemoryStream ms = new MemoryStream(buffer, 0, bytes.Length); + using SslStream sslStream = new SslStream(ms); + sslStream.AuthenticateAsServerAsync((stream, clientHelloInfo, b, token) => + { + // This callback should be called when ClientHello is + // received, Since we don't parse any other TLS messages, + // we can terminate the handshake here. Fuzzing the rest of + // the handshake would fuzz the platform TLS implementation + // which is not instrumented and is out of scope for this + // fuzzer. + throw new MyCustomException(); + }, null).GetAwaiter().GetResult(); + } + catch (Exception ex) when (ex is AuthenticationException or IOException or MyCustomException) + { + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + internal class MyCustomException : Exception + { + } + } +} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/SR.Fake.cs b/src/libraries/Fuzzing/DotnetFuzzing/SR.Fake.cs new file mode 100644 index 00000000000000..22f4e066ee7153 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/SR.Fake.cs @@ -0,0 +1,5 @@ +internal static class SR +{ + public const string ObjectDisposed_StreamClosed = "Cannot access a closed Stream."; + public const string IO_SeekBeforeBegin = "An attempt was made to move the position before the beginning of the stream."; +} \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs index 2d07945f85d3da..4b75443b4cecb4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs @@ -854,10 +854,7 @@ public static Vector128 Asin(Vector128 vector) } } - /// Computes the arc cosine of each element in a vector. - /// The vector whose arc cosine is to be computed. - /// A vector whose elements are the arc cosine of the corresponding elements in . - /// The angles are returned in radians, and the input should be in the range [-1, 1]. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Acos(Vector128 vector) { @@ -874,10 +871,7 @@ public static Vector128 Acos(Vector128 vector) } } - /// Computes the arc cosine of each element in a vector. - /// The vector whose arc cosine is to be computed. - /// A vector whose elements are the arc cosine of the corresponding elements in . - /// The angles are returned in radians, and the input should be in the range [-1, 1]. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector128 Acos(Vector128 vector) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs index 4a3dc0e6ad7cc7..45c26508a31e92 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs @@ -855,10 +855,7 @@ public static Vector256 Asin(Vector256 vector) } } - /// Computes the arc cosine of each element in a vector. - /// The vector whose arc cosine is to be computed. - /// A vector whose elements are the arc cosine of the corresponding elements in . - /// The angles are returned in radians, and the input should be in the range [-1, 1]. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Acos(Vector256 vector) { @@ -875,10 +872,7 @@ public static Vector256 Acos(Vector256 vector) } } - /// Computes the arc cosine of each element in a vector. - /// The vector whose arc cosine is to be computed. - /// A vector whose elements are the arc cosine of the corresponding elements in . - /// The angles are returned in radians, and the input should be in the range [-1, 1]. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Acos(Vector256 vector) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs index 4ad206276bfa80..c90455e40ce80d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs @@ -758,10 +758,7 @@ public static Vector512 Asin(Vector512 vector) } } - /// Computes the arc cosine of each element in a vector. - /// The vector whose arc cosine is to be computed. - /// A vector whose elements are the arc cosine of the corresponding elements in . - /// The angles are returned in radians, and the input should be in the range [-1, 1]. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Acos(Vector512 vector) { @@ -778,10 +775,7 @@ public static Vector512 Acos(Vector512 vector) } } - /// Computes the arc cosine of each element in a vector. - /// The vector whose arc cosine is to be computed. - /// A vector whose elements are the arc cosine of the corresponding elements in . - /// The angles are returned in radians, and the input should be in the range [-1, 1]. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector512 Acos(Vector512 vector) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs index 0eaf2d490feb7d..d7c709a2e69c46 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -3303,7 +3303,7 @@ public static TVectorDouble AcosDouble(TVectorDoub TVectorDouble result = TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); // Handle special cases: |x| > 1 returns NaN, x = ±1 returns 0 or π - TVectorDouble absXGreaterThanOne = TVectorDouble.GreaterThan(TVectorDouble.Abs(x), TVectorDouble.One); + TVectorDouble absXGreaterThanOne = TVectorDouble.GreaterThan(ax, TVectorDouble.One); result = TVectorDouble.ConditionalSelect(absXGreaterThanOne, TVectorDouble.Create(double.NaN), result); TVectorDouble xEqualsOne = TVectorDouble.Equals(x, TVectorDouble.One); diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs index 6246c1231c32d2..b4fa6a2d28a725 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs @@ -5305,6 +5305,22 @@ private static void TestCreateSequence(T start, T step) } } + [Theory] + [MemberData(nameof(GenericMathTestMemberData.AcosDouble), MemberType = typeof(GenericMathTestMemberData))] + public void AcosDoubleTest(double value, double expectedResult, double variance) + { + Vector128 actualResult = Vector128.Acos(Vector128.Create(value)); + AssertEqual(Vector128.Create(expectedResult), actualResult, Vector128.Create(variance)); + } + + [Theory] + [MemberData(nameof(GenericMathTestMemberData.AcosSingle), MemberType = typeof(GenericMathTestMemberData))] + public void AcosSingleTest(float value, float expectedResult, float variance) + { + Vector128 actualResult = Vector128.Acos(Vector128.Create(value)); + AssertEqual(Vector128.Create(expectedResult), actualResult, Vector128.Create(variance)); + } + [Theory] [MemberData(nameof(GenericMathTestMemberData.AsinDouble), MemberType = typeof(GenericMathTestMemberData))] public void AsinDoubleTest(double value, double expectedResult, double variance) diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs index 728a87900f314a..9b17733d2f5cd8 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs @@ -6481,6 +6481,22 @@ private static void TestCreateSequence(T start, T step) } } + [Theory] + [MemberData(nameof(GenericMathTestMemberData.AcosDouble), MemberType = typeof(GenericMathTestMemberData))] + public void AcosDoubleTest(double value, double expectedResult, double variance) + { + Vector256 actualResult = Vector256.Acos(Vector256.Create(value)); + AssertEqual(Vector256.Create(expectedResult), actualResult, Vector256.Create(variance)); + } + + [Theory] + [MemberData(nameof(GenericMathTestMemberData.AcosSingle), MemberType = typeof(GenericMathTestMemberData))] + public void AcosSingleTest(float value, float expectedResult, float variance) + { + Vector256 actualResult = Vector256.Acos(Vector256.Create(value)); + AssertEqual(Vector256.Create(expectedResult), actualResult, Vector256.Create(variance)); + } + [Theory] [MemberData(nameof(GenericMathTestMemberData.AsinDouble), MemberType = typeof(GenericMathTestMemberData))] public void AsinDoubleTest(double value, double expectedResult, double variance) diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs index d3c430020b5225..edd63125b77c9a 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs @@ -6264,6 +6264,22 @@ private static void TestCreateSequence(T start, T step) } } + [Theory] + [MemberData(nameof(GenericMathTestMemberData.AcosDouble), MemberType = typeof(GenericMathTestMemberData))] + public void AcosDoubleTest(double value, double expectedResult, double variance) + { + Vector512 actualResult = Vector512.Acos(Vector512.Create(value)); + AssertEqual(Vector512.Create(expectedResult), actualResult, Vector512.Create(variance)); + } + + [Theory] + [MemberData(nameof(GenericMathTestMemberData.AcosSingle), MemberType = typeof(GenericMathTestMemberData))] + public void AcosSingleTest(float value, float expectedResult, float variance) + { + Vector512 actualResult = Vector512.Acos(Vector512.Create(value)); + AssertEqual(Vector512.Create(expectedResult), actualResult, Vector512.Create(variance)); + } + [Theory] [MemberData(nameof(GenericMathTestMemberData.AsinDouble), MemberType = typeof(GenericMathTestMemberData))] public void AsinDoubleTest(double value, double expectedResult, double variance) diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs index be91c3325549fb..efbf1535db1b6b 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs @@ -4579,6 +4579,22 @@ private static void TestCreateSequence(T start, T step) } } + [Theory] + [MemberData(nameof(GenericMathTestMemberData.AcosDouble), MemberType = typeof(GenericMathTestMemberData))] + public void AcosDoubleTest(double value, double expectedResult, double variance) + { + Vector64 actualResult = Vector64.Acos(Vector64.Create(value)); + AssertEqual(Vector64.Create(expectedResult), actualResult, Vector64.Create(variance)); + } + + [Theory] + [MemberData(nameof(GenericMathTestMemberData.AcosSingle), MemberType = typeof(GenericMathTestMemberData))] + public void AcosSingleTest(float value, float expectedResult, float variance) + { + Vector64 actualResult = Vector64.Acos(Vector64.Create(value)); + AssertEqual(Vector64.Create(expectedResult), actualResult, Vector64.Create(variance)); + } + [Theory] [MemberData(nameof(GenericMathTestMemberData.AsinDouble), MemberType = typeof(GenericMathTestMemberData))] public void AsinDoubleTest(double value, double expectedResult, double variance) diff --git a/src/libraries/es-metadata.yml b/src/libraries/es-metadata.yml new file mode 100644 index 00000000000000..39ae7f272792cb --- /dev/null +++ b/src/libraries/es-metadata.yml @@ -0,0 +1,8 @@ +schemaVersion: 0.0.1 +isProduction: true +accountableOwners: + service: 7a9b52f6-7805-416c-9390-343168c0cdb3 +routing: + defaultAreaPath: + org: devdiv + path: DevDiv\NET Libraries diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs new file mode 100644 index 00000000000000..e8a8a237afc800 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs @@ -0,0 +1,22 @@ +// 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; + +public record struct DebuggerData(uint DefinesBitField, uint MDStructuresVersion); + +public interface IDebugger : IContract +{ + static string IContract.Name { get; } = nameof(Debugger); + + bool TryGetDebuggerData(out DebuggerData data) => throw new NotImplementedException(); + int GetAttachStateFlags() => throw new NotImplementedException(); + bool MetadataUpdatesApplied() => throw new NotImplementedException(); +} + +public readonly struct Debugger : IDebugger +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs new file mode 100644 index 00000000000000..58bd60ac94c5c7 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs @@ -0,0 +1,18 @@ +// 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; + +public sealed class DebuggerFactory : IContractFactory +{ + IDebugger IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new Debugger_1(target), + _ => default(Debugger), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs new file mode 100644 index 00000000000000..fab0ca1c91557b --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs @@ -0,0 +1,48 @@ +// 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.Contracts; + +internal readonly struct Debugger_1 : IDebugger +{ + private readonly Target _target; + + internal Debugger_1(Target target) + { + _target = target; + } + + bool IDebugger.TryGetDebuggerData(out DebuggerData data) + { + data = default; + TargetPointer debuggerPtrPtr = _target.ReadGlobalPointer(Constants.Globals.Debugger); + if (debuggerPtrPtr == TargetPointer.Null) + return false; + + TargetPointer debuggerPtr = _target.ReadPointer(debuggerPtrPtr); + if (debuggerPtr == TargetPointer.Null) + return false; + + Data.Debugger debugger = _target.ProcessedData.GetOrAdd(debuggerPtr); + if (debugger.LeftSideInitialized == 0) + return false; + + data = new DebuggerData(debugger.Defines, debugger.MDStructuresVersion); + return true; + } + + int IDebugger.GetAttachStateFlags() + { + TargetPointer addr = _target.ReadGlobalPointer(Constants.Globals.CLRJitAttachState); + return (int)_target.Read(addr.Value); + } + + bool IDebugger.MetadataUpdatesApplied() + { + if (_target.TryReadGlobalPointer(Constants.Globals.MetadataUpdatesApplied, out TargetPointer? addr)) + { + return _target.Read(addr.Value.Value) != 0; + } + return false; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ExceptionHandling.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ExceptionHandling.cs new file mode 100644 index 00000000000000..8f9c79fa6f1cdf --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ExceptionHandling.cs @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal partial class StackWalk_1 : IStackWalk +{ + /// + /// Flags from the ExceptionFlags class (exstatecommon.h). + /// These are bit flags stored in ExInfo.m_ExceptionFlags.m_flags. + /// + [Flags] + private enum ExceptionFlagsEnum : uint + { + // See Ex_UnwindHasStarted in src/coreclr/vm/exstatecommon.h + UnwindHasStarted = 0x00000004, + } + + /// + /// Given the CrawlFrame for a funclet frame, return the frame pointer of the enclosing funclet frame. + /// For filter funclet frames and normal method frames, this function returns a NULL StackFrame. + /// + /// + /// StackFrame.IsNull() - no skipping is necessary + /// StackFrame.IsMaxVal() - skip one frame and then ask again + /// Anything else - skip to the method frame indicated by the return value and ask again + /// + private TargetPointer FindParentStackFrameForStackWalk(StackDataFrameHandle handle, bool forGCReporting = false) + { + if (!forGCReporting && IsFilterFunclet(handle)) + { + return TargetPointer.Null; + } + else + { + return FindParentStackFrameHelper(handle, forGCReporting); + } + } + + private TargetPointer FindParentStackFrameHelper( + StackDataFrameHandle handle, + bool forGCReporting = false) + { + IPlatformAgnosticContext callerContext = handle.Context.Clone(); + callerContext.Unwind(_target); + TargetPointer callerStackFrame = callerContext.StackPointer; + + bool isFilterFunclet = IsFilterFunclet(handle); + + // Check for out-of-line finally funclets. Filter funclets can't be out-of-line. + if (!isFilterFunclet) + { + TargetPointer callerIp = callerContext.InstructionPointer; + + // In the runtime, on Windows, we check with that the IP is in the runtime + // TODO(stackref): make sure this difference doesn't matter + bool isCallerInVM = !IsManaged(callerIp, out CodeBlockHandle? _); + + if (!isCallerInVM) + { + if (!forGCReporting) + { + return TargetPointer.PlatformMaxValue(_target); + } + else + { + // ExInfo::GetCallerSPOfParentOfNonExceptionallyInvokedFunclet + IPlatformAgnosticContext callerCallerContext = callerContext.Clone(); + callerCallerContext.Unwind(_target); + return callerCallerContext.StackPointer; + } + } + } + + TargetPointer pExInfo = GetCurrentExceptionTracker(handle); + while (pExInfo != TargetPointer.Null) + { + Data.ExceptionInfo exInfo = _target.ProcessedData.GetOrAdd(pExInfo); + pExInfo = exInfo.PreviousNestedInfo; + + // ExInfo::StackRange::IsEmpty + if (exInfo.StackLowBound == TargetPointer.PlatformMaxValue(_target) && + exInfo.StackHighBound == TargetPointer.Null) + { + // This is ExInfo has just been created, skip it. + continue; + } + + if (callerStackFrame == exInfo.CSFEHClause) + { + return exInfo.CSFEnclosingClause; + } + } + + return TargetPointer.Null; + } + + + private bool IsFunclet(StackDataFrameHandle handle) + { + if (handle.State is StackWalkState.SW_FRAME or StackWalkState.SW_SKIPPED_FRAME) + { + return false; + } + + if (!IsManaged(handle.Context.InstructionPointer, out CodeBlockHandle? cbh)) + return false; + + return _eman.IsFunclet(cbh.Value); + } + + private bool IsFilterFunclet(StackDataFrameHandle handle) + { + if (handle.State is StackWalkState.SW_FRAME or StackWalkState.SW_SKIPPED_FRAME) + { + return false; + } + + if (!IsManaged(handle.Context.InstructionPointer, out CodeBlockHandle? cbh)) + return false; + + return _eman.IsFilterFunclet(cbh.Value); + } + + private TargetPointer GetCurrentExceptionTracker(StackDataFrameHandle handle) + { + Data.Thread thread = _target.ProcessedData.GetOrAdd(handle.ThreadData.ThreadAddress); + // ExceptionTracker is the address of the field on the Thread object. + // Dereference to get the actual ExInfo pointer. + return _target.ReadPointer(thread.ExceptionTracker); + } + + private bool HasFrameBeenUnwoundByAnyActiveException(IStackDataFrameHandle stackDataFrameHandle) + { + StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); + + TargetPointer callerStackPointer; + if (handle.State is StackWalkState.SW_FRAMELESS) + { + IPlatformAgnosticContext callerContext = handle.Context.Clone(); + callerContext.Unwind(_target); + callerStackPointer = callerContext.StackPointer; + } + else + { + callerStackPointer = handle.FrameAddress; + } + + TargetPointer pExInfo = GetCurrentExceptionTracker(handle); + while (pExInfo != TargetPointer.Null) + { + Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd(pExInfo); + pExInfo = exceptionInfo.PreviousNestedInfo; + + if (IsInStackRegionUnwoundBySpecifiedException(callerStackPointer, exceptionInfo)) + return true; + } + return false; + } + + private bool IsInStackRegionUnwoundBySpecifiedException(TargetPointer callerStackPointer, Data.ExceptionInfo exceptionInfo) + { + // The tracker must be in the second pass (unwind has started), and its stack range must not be empty. + if ((exceptionInfo.ExceptionFlags & (uint)ExceptionFlagsEnum.UnwindHasStarted) == 0) + return false; + + // Check for empty range + if (exceptionInfo.StackLowBound == TargetPointer.PlatformMaxValue(_target) + && exceptionInfo.StackHighBound == TargetPointer.Null) + { + return false; + } + + return (exceptionInfo.StackLowBound < callerStackPointer) && (callerStackPointer <= exceptionInfo.StackHighBound); + } + +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs new file mode 100644 index 00000000000000..184a875c908980 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class GcScanContext +{ + + private readonly Target _target; + public bool ResolveInteriorPointers { get; } + public List StackRefs { get; } = []; + public TargetPointer StackPointer { get; private set; } + public TargetPointer InstructionPointer { get; private set; } + public TargetPointer Frame { get; private set; } + + public GcScanContext(Target target, bool resolveInteriorPointers) + { + _target = target; + ResolveInteriorPointers = resolveInteriorPointers; + } + + public void UpdateScanContext(TargetPointer sp, TargetPointer ip, TargetPointer frame) + { + StackPointer = sp; + InstructionPointer = ip; + Frame = frame; + } + + public void GCEnumCallback(TargetPointer pObject, GcScanFlags flags, GcScanSlotLocation loc) + { + TargetPointer addr; + TargetPointer obj; + + if (loc.TargetPtr) + { + addr = pObject; + obj = _target.ReadPointer(addr); + } + else + { + addr = 0; + obj = pObject; + } + + if (flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR) && ResolveInteriorPointers) + { + // TODO(stackref): handle interior pointers + // https://github.com/dotnet/runtime/issues/125728 + throw new NotImplementedException(); + } + + StackRefData data = new() + { + HasRegisterInformation = true, + Register = loc.Reg, + Offset = loc.RegOffset, + Address = addr, + Object = obj, + Flags = flags, + StackPointer = StackPointer, + }; + + if (Frame != TargetPointer.Null) + { + data.SourceType = StackRefData.SourceTypes.StackSourceFrame; + data.Source = Frame; + } + else + { + data.SourceType = StackRefData.SourceTypes.StackSourceIP; + data.Source = InstructionPointer; + } + + StackRefs.Add(data); + } + + public void GCReportCallback(TargetPointer ppObj, GcScanFlags flags) + { + if (flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR) && ResolveInteriorPointers) + { + // TODO(stackref): handle interior pointers + // https://github.com/dotnet/runtime/issues/125728 + throw new NotImplementedException(); + } + + // Read the object pointer from the stack slot, matching legacy DAC behavior + // (DacStackReferenceWalker::GCReportCallback in daccess.cpp) + TargetPointer obj = _target.ReadPointer(ppObj); + + StackRefData data = new() + { + HasRegisterInformation = false, + Register = 0, + Offset = 0, + Address = ppObj, + Object = obj, + Flags = flags, + StackPointer = StackPointer, + }; + + if (Frame != TargetPointer.Null) + { + data.SourceType = StackRefData.SourceTypes.StackSourceFrame; + data.Source = Frame; + } + else + { + data.SourceType = StackRefData.SourceTypes.StackSourceIP; + data.Source = InstructionPointer; + } + + StackRefs.Add(data); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanFlags.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanFlags.cs new file mode 100644 index 00000000000000..0575b625d5b9d4 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanFlags.cs @@ -0,0 +1,14 @@ +// 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.StackWalkHelpers; + +[Flags] +internal enum GcScanFlags +{ + None = 0x0, + GC_CALL_INTERIOR = 0x1, + GC_CALL_PINNED = 0x2, +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanSlotLocation.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanSlotLocation.cs new file mode 100644 index 00000000000000..e9829ab4bceba0 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanSlotLocation.cs @@ -0,0 +1,6 @@ +// 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.Contracts.StackWalkHelpers; + +internal readonly record struct GcScanSlotLocation(int Reg, int RegOffset, bool TargetPtr); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs new file mode 100644 index 00000000000000..fa72eb606fad75 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class GcScanner +{ + private readonly Target _target; + private readonly IExecutionManager _eman; + private readonly IGCInfo _gcInfo; + + internal GcScanner(Target target) + { + _target = target; + _eman = target.Contracts.ExecutionManager; + _gcInfo = target.Contracts.GCInfo; + } + + public bool EnumGcRefs( + IPlatformAgnosticContext context, + CodeBlockHandle cbh, + CodeManagerFlags flags, + GcScanContext scanContext) + { + TargetNUInt relativeOffset = _eman.GetRelativeOffset(cbh); + _eman.GetGCInfo(cbh, out TargetPointer gcInfoAddr, out uint gcVersion); + + if (_eman.IsFilterFunclet(cbh)) + flags |= CodeManagerFlags.NoReportUntracked; + + IGCInfoHandle handle = _gcInfo.DecodePlatformSpecificGCInfo(gcInfoAddr, gcVersion); + if (handle is not IGCInfoDecoder decoder) + return false; + + uint stackBaseRegister = decoder.StackBaseRegister; + + // Lazily compute the caller SP for GC_CALLER_SP_REL slots. + // The native code uses GET_CALLER_SP(pRD) which comes from EnsureCallerContextIsValid. + TargetPointer? callerSP = null; + + return decoder.EnumerateLiveSlots( + (uint)relativeOffset.Value, + flags, + (bool isRegister, uint registerNumber, int spOffset, uint spBase, uint gcFlags) => + { + GcScanFlags scanFlags = GcScanFlags.None; + if ((gcFlags & 0x1) != 0) // GC_SLOT_INTERIOR + scanFlags |= GcScanFlags.GC_CALL_INTERIOR; + if ((gcFlags & 0x2) != 0) // GC_SLOT_PINNED + scanFlags |= GcScanFlags.GC_CALL_PINNED; + + if (isRegister) + { + TargetPointer regValue = ReadRegisterValue(context, (int)registerNumber); + GcScanSlotLocation loc = new((int)registerNumber, 0, false); + scanContext.GCEnumCallback(regValue, scanFlags, loc); + } + else + { + int spReg = context.StackPointerRegister; + int reg = spBase switch + { + 1 => spReg, // GC_SP_REL → SP register number + 2 => (int)stackBaseRegister, // GC_FRAMEREG_REL → frame base register + 0 => -(spReg + 1), // GC_CALLER_SP_REL → -(SP + 1) + _ => throw new InvalidOperationException($"Unknown stack slot base: {spBase}"), + }; + TargetPointer baseAddr = spBase switch + { + 1 => context.StackPointer, // GC_SP_REL + 2 => ReadRegisterValue(context, (int)stackBaseRegister), // GC_FRAMEREG_REL + 0 => GetCallerSP(context, ref callerSP), // GC_CALLER_SP_REL + _ => throw new InvalidOperationException($"Unknown stack slot base: {spBase}"), + }; + + TargetPointer addr = new(baseAddr.Value + (ulong)(long)spOffset); + GcScanSlotLocation loc = new(reg, spOffset, true); + scanContext.GCEnumCallback(addr, scanFlags, loc); + } + }); + } + + /// + /// Compute the caller's SP by unwinding the current context one frame. + /// Cached in to avoid repeated unwinds for the same frame. + /// + private TargetPointer GetCallerSP(IPlatformAgnosticContext context, ref TargetPointer? cached) + { + if (cached is null) + { + IPlatformAgnosticContext callerContext = context.Clone(); + callerContext.Unwind(_target); + cached = callerContext.StackPointer; + } + return cached.Value; + } + + private static TargetPointer ReadRegisterValue(IPlatformAgnosticContext context, int registerNumber) + { + if (!context.TryReadRegister(registerNumber, out TargetNUInt value)) + throw new ArgumentOutOfRangeException(nameof(registerNumber), $"Register number {registerNumber} not found"); + + return new TargetPointer(value.Value); + } + +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.cs new file mode 100644 index 00000000000000..46e5bac46f6431 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.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. + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class StackRefData +{ + public enum SourceTypes + { + StackSourceIP = 0, + StackSourceFrame = 1, + } + + public bool HasRegisterInformation { get; set; } + public int Register { get; set; } + public int Offset { get; set; } + public TargetPointer Address { get; set; } + public TargetPointer Object { get; set; } + public GcScanFlags Flags { get; set; } + public SourceTypes SourceType { get; set; } + public TargetPointer Source { get; set; } + public TargetPointer StackPointer { get; set; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs new file mode 100644 index 00000000000000..3165d480333a8e --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.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. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class Debugger : IData +{ + static Debugger IData.Create(Target target, TargetPointer address) + => new Debugger(target, address); + + public Debugger(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Debugger); + + LeftSideInitialized = target.Read(address + (ulong)type.Fields[nameof(LeftSideInitialized)].Offset); + Defines = target.Read(address + (ulong)type.Fields[nameof(Defines)].Offset); + MDStructuresVersion = target.Read(address + (ulong)type.Fields[nameof(MDStructuresVersion)].Offset); + } + + public int LeftSideInitialized { get; init; } + public uint Defines { get; init; } + public uint MDStructuresVersion { 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 new file mode 100644 index 00000000000000..ce9e28ca54aa36 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -0,0 +1,1274 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +[GeneratedComClass] +public sealed unsafe partial class DacDbiImpl : IDacDbiInterface +{ + private readonly Target _target; + private readonly IDacDbiInterface? _legacy; + + // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: + // virtual HRESULT AssignCopy(const WCHAR* psz) = 0; + // The nint we receive is a pointer to the object, whose first field is the vtable pointer. + // The vtable has a single entry: a function pointer for AssignCopy. + // Use Thiscall because this is a C++ virtual method (thiscall on x86, no-op on x64/arm64). + private delegate* unmanaged[Thiscall] GetAssignCopyFnPtr(nint stringHolder) + { + // stringHolder -> vtable ptr -> first slot is AssignCopy + nint vtable = *(nint*)stringHolder; + return (delegate* unmanaged[Thiscall])(*(nint*)vtable); + } + + private int StringHolderAssignCopy(nint stringHolder, string str) + { + fixed (char* pStr = str) + { + return GetAssignCopyFnPtr(stringHolder)(stringHolder, pStr); + } + } + + public DacDbiImpl(Target target, object? legacyObj) + { + _target = target; + _legacy = legacyObj as IDacDbiInterface; + } + + public int CheckDbiVersion(DbiVersion* pVersion) + => _legacy is not null ? _legacy.CheckDbiVersion(pVersion) : HResults.E_NOTIMPL; + + public int FlushCache() + { + _target.Flush(); + return _legacy is not null ? _legacy.FlushCache() : HResults.S_OK; + } + + public int DacSetTargetConsistencyChecks(Interop.BOOL fEnableAsserts) + => _legacy is not null ? _legacy.DacSetTargetConsistencyChecks(fEnableAsserts) : HResults.E_NOTIMPL; + + public int IsLeftSideInitialized(Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + *pResult = _target.Contracts.Debugger.TryGetDebuggerData(out _) ? 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.IsLeftSideInitialized(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal); + } +#endif + + return hr; + } + + public int GetAppDomainFromId(uint appdomainId, ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + if (appdomainId != _target.ReadGlobal(Constants.Globals.DefaultADID)) + throw new ArgumentException(null, nameof(appdomainId)); + TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + *pRetVal = _target.ReadPointer(appDomainPtr); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetAppDomainFromId(appdomainId, &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 GetAppDomainId(ulong vmAppDomain, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + *pRetVal = vmAppDomain == 0 ? 0u : _target.ReadGlobal(Constants.Globals.DefaultADID); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.GetAppDomainId(vmAppDomain, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetAppDomainObject(ulong vmAppDomain, ulong* pRetVal) + => _legacy is not null ? _legacy.GetAppDomainObject(vmAppDomain, pRetVal) : HResults.E_NOTIMPL; + + public int GetAssemblyFromDomainAssembly(ulong vmDomainAssembly, ulong* vmAssembly) + => _legacy is not null ? _legacy.GetAssemblyFromDomainAssembly(vmDomainAssembly, vmAssembly) : HResults.E_NOTIMPL; + + public int IsAssemblyFullyTrusted(ulong vmDomainAssembly, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.TRUE; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.IsAssemblyFullyTrusted(vmDomainAssembly, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int GetAppDomainFullName(ulong vmAppDomain, nint pStrName) + { + int hr = HResults.S_OK; + try + { + string name = _target.Contracts.Loader.GetAppDomainFriendlyName(); + hr = StringHolderAssignCopy(pStrName, name); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + int hrLocal = _legacy.GetAppDomainFullName(vmAppDomain, pStrName); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + return hr; + } + + public int GetModuleSimpleName(ulong vmModule, nint pStrFilename) + => _legacy is not null ? _legacy.GetModuleSimpleName(vmModule, pStrFilename) : HResults.E_NOTIMPL; + + public int GetAssemblyPath(ulong vmAssembly, nint pStrFilename, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); + string path = loader.GetPath(handle); + if (string.IsNullOrEmpty(path)) + { + *pResult = Interop.BOOL.FALSE; + } + else + { + hr = StringHolderAssignCopy(pStrFilename, path); + *pResult = Interop.BOOL.TRUE; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.GetAssemblyPath(vmAssembly, pStrFilename, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int ResolveTypeReference(DacDbiTypeRefData* pTypeRefInfo, DacDbiTypeRefData* pTargetRefInfo) + => _legacy is not null ? _legacy.ResolveTypeReference(pTypeRefInfo, pTargetRefInfo) : HResults.E_NOTIMPL; + + public int GetModulePath(ulong vmModule, nint pStrFilename, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); + string path = loader.GetPath(handle); + if (string.IsNullOrEmpty(path)) + { + *pResult = Interop.BOOL.FALSE; + } + else + { + hr = StringHolderAssignCopy(pStrFilename, path); + *pResult = Interop.BOOL.TRUE; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.GetModulePath(vmModule, pStrFilename, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int GetMetadata(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer) + => _legacy is not null ? _legacy.GetMetadata(vmModule, pTargetBuffer) : HResults.E_NOTIMPL; + + public int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, int* pSymbolFormat) + => _legacy is not null ? _legacy.GetSymbolsBuffer(vmModule, pTargetBuffer, pSymbolFormat) : HResults.E_NOTIMPL; + + public int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData) + => _legacy is not null ? _legacy.GetModuleData(vmModule, pData) : HResults.E_NOTIMPL; + + public int GetDomainAssemblyData(ulong vmDomainAssembly, DacDbiDomainAssemblyInfo* pData) + => _legacy is not null ? _legacy.GetDomainAssemblyData(vmDomainAssembly, pData) : HResults.E_NOTIMPL; + + public int GetModuleForDomainAssembly(ulong vmDomainAssembly, ulong* pModule) + => _legacy is not null ? _legacy.GetModuleForDomainAssembly(vmDomainAssembly, pModule) : HResults.E_NOTIMPL; + + public int GetAddressType(ulong address, int* pRetVal) + => _legacy is not null ? _legacy.GetAddressType(address, pRetVal) : HResults.E_NOTIMPL; + + public int IsTransitionStub(ulong address, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsTransitionStub(address, pResult) : HResults.E_NOTIMPL; + + public int GetCompilerFlags(ulong vmDomainAssembly, Interop.BOOL* pfAllowJITOpts, Interop.BOOL* pfEnableEnC) + => _legacy is not null ? _legacy.GetCompilerFlags(vmDomainAssembly, pfAllowJITOpts, pfEnableEnC) : HResults.E_NOTIMPL; + + public int SetCompilerFlags(ulong vmDomainAssembly, Interop.BOOL fAllowJitOpts, Interop.BOOL fEnableEnC) + => _legacy is not null ? _legacy.SetCompilerFlags(vmDomainAssembly, fAllowJitOpts, fEnableEnC) : HResults.E_NOTIMPL; + + public int EnumerateAppDomains(nint fpCallback, nint pUserData) + { + int hr = HResults.S_OK; + try + { + TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = _target.ReadPointer(appDomainPtr); + var callback = (delegate* unmanaged)fpCallback; + callback(appDomain, pUserData); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + return hr; + } + + public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, nint fpCallback, nint pUserData) + => _legacy is not null ? _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, fpCallback, pUserData) : HResults.E_NOTIMPL; + + public int EnumerateModulesInAssembly(ulong vmAssembly, nint fpCallback, nint pUserData) + => _legacy is not null ? _legacy.EnumerateModulesInAssembly(vmAssembly, fpCallback, pUserData) : HResults.E_NOTIMPL; + + public int RequestSyncAtEvent() + => _legacy is not null ? _legacy.RequestSyncAtEvent() : HResults.E_NOTIMPL; + + public int SetSendExceptionsOutsideOfJMC(Interop.BOOL sendExceptionsOutsideOfJMC) + => _legacy is not null ? _legacy.SetSendExceptionsOutsideOfJMC(sendExceptionsOutsideOfJMC) : HResults.E_NOTIMPL; + + public int MarkDebuggerAttachPending() + => _legacy is not null ? _legacy.MarkDebuggerAttachPending() : HResults.E_NOTIMPL; + + public int MarkDebuggerAttached(Interop.BOOL fAttached) + => _legacy is not null ? _legacy.MarkDebuggerAttached(fAttached) : HResults.E_NOTIMPL; + + public int Hijack(ulong vmThread, uint dwThreadId, nint pRecord, nint pOriginalContext, uint cbSizeContext, int reason, nint pUserData, ulong* pRemoteContextAddr) + => _legacy is not null ? _legacy.Hijack(vmThread, dwThreadId, pRecord, pOriginalContext, cbSizeContext, reason, pUserData, pRemoteContextAddr) : HResults.E_NOTIMPL; + + public int EnumerateThreads(nint fpCallback, nint pUserData) + { + int hr = HResults.S_OK; +#if DEBUG + List? cdacThreads = _legacy is not null ? new() : null; +#endif + try + { + Contracts.IThread threadContract = _target.Contracts.Thread; + Contracts.ThreadStoreData threadStore = threadContract.GetThreadStoreData(); + var callback = (delegate* unmanaged)fpCallback; + TargetPointer currentThread = threadStore.FirstThread; + while (currentThread != TargetPointer.Null) + { + Contracts.ThreadData threadData = threadContract.GetThreadData(currentThread); + // Match native: skip dead and unstarted threads + if ((threadData.State & (Contracts.ThreadState.Dead | Contracts.ThreadState.Unstarted)) == 0) + { + callback(currentThread.Value, pUserData); +#if DEBUG + cdacThreads?.Add(currentThread.Value); +#endif + } + currentThread = threadData.NextThread; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + List dacThreads = new(); + GCHandle dacHandle = GCHandle.Alloc(dacThreads); + int hrLocal = _legacy.EnumerateThreads( + (nint)(delegate* unmanaged)&CollectThreadCallback, + GCHandle.ToIntPtr(dacHandle)); + dacHandle.Free(); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert( + cdacThreads!.SequenceEqual(dacThreads), + $"Thread enumeration mismatch - cDAC: [{string.Join(",", cdacThreads!.Select(t => $"0x{t:x}"))}], DAC: [{string.Join(",", dacThreads.Select(t => $"0x{t:x}"))}]"); + } + } +#endif + return hr; + } + +#if DEBUG + [UnmanagedCallersOnly] + private static void CollectThreadCallback(ulong value, nint pUserData) + { + GCHandle handle = GCHandle.FromIntPtr(pUserData); + ((List)handle.Target!).Add(value); + } +#endif + + public int IsThreadMarkedDead(ulong vmThread, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + *pResult = (threadData.State & Contracts.ThreadState.Dead) != 0 ? 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.IsThreadMarkedDead(vmThread, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int GetThreadHandle(ulong vmThread, nint pRetVal) + => _legacy is not null ? _legacy.GetThreadHandle(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetThreadObject(ulong vmThread, ulong* pRetVal) + => _legacy is not null ? _legacy.GetThreadObject(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetThreadAllocInfo(ulong vmThread, DacDbiThreadAllocInfo* pThreadAllocInfo) + => _legacy is not null ? _legacy.GetThreadAllocInfo(vmThread, pThreadAllocInfo) : HResults.E_NOTIMPL; + + public int SetDebugState(ulong vmThread, int debugState) + => _legacy is not null ? _legacy.SetDebugState(vmThread, debugState) : HResults.E_NOTIMPL; + + public int HasUnhandledException(ulong vmThread, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.HasUnhandledException(vmThread, pResult) : HResults.E_NOTIMPL; + + public int GetUserState(ulong vmThread, int* pRetVal) + => _legacy is not null ? _legacy.GetUserState(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetPartialUserState(ulong vmThread, int* pRetVal) + => _legacy is not null ? _legacy.GetPartialUserState(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetConnectionID(ulong vmThread, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.GetConnectionID(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetTaskID(ulong vmThread, ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetTaskID(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int TryGetVolatileOSThreadID(ulong vmThread, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + uint osId = (uint)threadData.OSId.Value; + // Match native: SWITCHED_OUT_FIBER_OSID (0xbaadf00d) means thread is switched out + const uint SWITCHED_OUT_FIBER_OSID = 0xbaadf00d; + *pRetVal = osId == SWITCHED_OUT_FIBER_OSID ? 0 : osId; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.TryGetVolatileOSThreadID(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetUniqueThreadID(ulong vmThread, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); + *pRetVal = (uint)threadData.OSId.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.GetUniqueThreadID(vmThread, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetCurrentException(ulong vmThread, ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + TargetPointer throwable = _target.Contracts.Thread.GetThrowableObject(new TargetPointer(vmThread)); + *pRetVal = throwable.Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetCurrentException(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 GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) + => _legacy is not null ? _legacy.GetObjectForCCW(ccwPtr, pRetVal) : HResults.E_NOTIMPL; + + public int GetCurrentCustomDebuggerNotification(ulong vmThread, ulong* pRetVal) + => _legacy is not null ? _legacy.GetCurrentCustomDebuggerNotification(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetCurrentAppDomain(ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + *pRetVal = _target.ReadPointer(appDomainPtr); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetCurrentAppDomain(&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 ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal) + => _legacy is not null ? _legacy.ResolveAssembly(vmScope, tkAssemblyRef, pRetVal) : HResults.E_NOTIMPL; + + public int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, nint pNativeVarData, nint pSequencePoints) + => _legacy is not null ? _legacy.GetNativeCodeSequencePointsAndVarInfo(vmMethodDesc, startAddress, fCodeAvailable, pNativeVarData, pSequencePoints) : HResults.E_NOTIMPL; + + public int GetManagedStoppedContext(ulong vmThread, ulong* pRetVal) + => _legacy is not null ? _legacy.GetManagedStoppedContext(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int CreateStackWalk(ulong vmThread, nint pInternalContextBuffer, nuint* ppSFIHandle) + => _legacy is not null ? _legacy.CreateStackWalk(vmThread, pInternalContextBuffer, ppSFIHandle) : HResults.E_NOTIMPL; + + public int DeleteStackWalk(nuint ppSFIHandle) + => _legacy is not null ? _legacy.DeleteStackWalk(ppSFIHandle) : HResults.E_NOTIMPL; + + public int GetStackWalkCurrentContext(nuint pSFIHandle, nint pContext) + => _legacy is not null ? _legacy.GetStackWalkCurrentContext(pSFIHandle, pContext) : HResults.E_NOTIMPL; + + public int SetStackWalkCurrentContext(ulong vmThread, nuint pSFIHandle, int flag, nint pContext) + => _legacy is not null ? _legacy.SetStackWalkCurrentContext(vmThread, pSFIHandle, flag, pContext) : HResults.E_NOTIMPL; + + public int UnwindStackWalkFrame(nuint pSFIHandle, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.UnwindStackWalkFrame(pSFIHandle, pResult) : HResults.E_NOTIMPL; + + public int CheckContext(ulong vmThread, nint pContext) + => _legacy is not null ? _legacy.CheckContext(vmThread, pContext) : HResults.E_NOTIMPL; + + public int GetStackWalkCurrentFrameInfo(nuint pSFIHandle, nint pFrameData, int* pRetVal) + => _legacy is not null ? _legacy.GetStackWalkCurrentFrameInfo(pSFIHandle, pFrameData, pRetVal) : HResults.E_NOTIMPL; + + public int GetCountOfInternalFrames(ulong vmThread, uint* pRetVal) + => _legacy is not null ? _legacy.GetCountOfInternalFrames(vmThread, pRetVal) : HResults.E_NOTIMPL; + + public int EnumerateInternalFrames(ulong vmThread, nint fpCallback, nint pUserData) + => _legacy is not null ? _legacy.EnumerateInternalFrames(vmThread, fpCallback, pUserData) : HResults.E_NOTIMPL; + + public int IsMatchingParentFrame(ulong fpToCheck, ulong fpParent, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsMatchingParentFrame(fpToCheck, fpParent, pResult) : HResults.E_NOTIMPL; + + public int GetStackParameterSize(ulong controlPC, uint* pRetVal) + => _legacy is not null ? _legacy.GetStackParameterSize(controlPC, pRetVal) : HResults.E_NOTIMPL; + + public int GetFramePointer(nuint pSFIHandle, ulong* pRetVal) + => _legacy is not null ? _legacy.GetFramePointer(pSFIHandle, pRetVal) : HResults.E_NOTIMPL; + + public int IsLeafFrame(ulong vmThread, nint pContext, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsLeafFrame(vmThread, pContext, pResult) : HResults.E_NOTIMPL; + + public int GetContext(ulong vmThread, nint pContextBuffer) + => _legacy is not null ? _legacy.GetContext(vmThread, pContextBuffer) : HResults.E_NOTIMPL; + + public int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Interop.BOOL fActive) + => _legacy is not null ? _legacy.ConvertContextToDebuggerRegDisplay(pInContext, pOutDRD, fActive) : HResults.E_NOTIMPL; + + public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal) + => _legacy is not null ? _legacy.IsDiagnosticsHiddenOrLCGMethod(vmMethodDesc, pRetVal) : HResults.E_NOTIMPL; + + public int GetVarArgSig(ulong VASigCookieAddr, ulong* pArgBase, DacDbiTargetBuffer* pRetVal) + => _legacy is not null ? _legacy.GetVarArgSig(VASigCookieAddr, pArgBase, pRetVal) : HResults.E_NOTIMPL; + + public int RequiresAlign8(ulong thExact, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.RequiresAlign8(thExact, pResult) : HResults.E_NOTIMPL; + + public int ResolveExactGenericArgsToken(uint dwExactGenericArgsTokenIndex, ulong rawToken, ulong* pRetVal) + => _legacy is not null ? _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, pRetVal) : HResults.E_NOTIMPL; + + public int GetILCodeAndSig(ulong vmDomainAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken) + => _legacy is not null ? _legacy.GetILCodeAndSig(vmDomainAssembly, functionToken, pTargetBuffer, pLocalSigToken) : HResults.E_NOTIMPL; + + public int GetNativeCodeInfo(ulong vmDomainAssembly, uint functionToken, nint pJitManagerList) + => _legacy is not null ? _legacy.GetNativeCodeInfo(vmDomainAssembly, functionToken, pJitManagerList) : HResults.E_NOTIMPL; + + public int GetNativeCodeInfoForAddr(ulong codeAddress, nint pCodeInfo, ulong* pVmModule, uint* pFunctionToken) + => _legacy is not null ? _legacy.GetNativeCodeInfoForAddr(codeAddress, pCodeInfo, pVmModule, pFunctionToken) : HResults.E_NOTIMPL; + + public int IsValueType(ulong vmTypeHandle, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + Contracts.TypeHandle th = rts.GetTypeHandle(new TargetPointer(vmTypeHandle)); + CorElementType corType = rts.GetSignatureCorElementType(th); + *pResult = corType == CorElementType.ValueType ? 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.IsValueType(vmTypeHandle, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int HasTypeParams(ulong vmTypeHandle, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.HasTypeParams(vmTypeHandle, pResult) : HResults.E_NOTIMPL; + + public int GetClassInfo(ulong vmAppDomain, ulong thExact, nint pData) + => _legacy is not null ? _legacy.GetClassInfo(vmAppDomain, thExact, pData) : HResults.E_NOTIMPL; + + public int GetInstantiationFieldInfo(ulong vmDomainAssembly, ulong vmTypeHandle, ulong vmExactMethodTable, nint pFieldList, nuint* pObjectSize) + => _legacy is not null ? _legacy.GetInstantiationFieldInfo(vmDomainAssembly, vmTypeHandle, vmExactMethodTable, pFieldList, pObjectSize) : HResults.E_NOTIMPL; + + public int TypeHandleToExpandedTypeInfo(int boxed, ulong vmAppDomain, ulong vmTypeHandle, nint pData) + => _legacy is not null ? _legacy.TypeHandleToExpandedTypeInfo(boxed, vmAppDomain, vmTypeHandle, pData) : HResults.E_NOTIMPL; + + public int GetObjectExpandedTypeInfo(int boxed, ulong vmAppDomain, ulong addr, nint pTypeInfo) + => _legacy is not null ? _legacy.GetObjectExpandedTypeInfo(boxed, vmAppDomain, addr, pTypeInfo) : HResults.E_NOTIMPL; + + public int GetObjectExpandedTypeInfoFromID(int boxed, ulong vmAppDomain, COR_TYPEID id, nint pTypeInfo) + => _legacy is not null ? _legacy.GetObjectExpandedTypeInfoFromID(boxed, vmAppDomain, id, pTypeInfo) : HResults.E_NOTIMPL; + + public int GetTypeHandle(ulong vmModule, uint metadataToken, ulong* pRetVal) + => _legacy is not null ? _legacy.GetTypeHandle(vmModule, metadataToken, pRetVal) : HResults.E_NOTIMPL; + + public int GetApproxTypeHandle(nint pTypeData, ulong* pRetVal) + => _legacy is not null ? _legacy.GetApproxTypeHandle(pTypeData, pRetVal) : HResults.E_NOTIMPL; + + public int GetExactTypeHandle(nint pTypeData, nint pArgInfo, ulong* pVmTypeHandle) + => _legacy is not null ? _legacy.GetExactTypeHandle(pTypeData, pArgInfo, pVmTypeHandle) : HResults.E_NOTIMPL; + + public int GetMethodDescParams(ulong vmAppDomain, ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams, nint pGenericTypeParams) + => _legacy is not null ? _legacy.GetMethodDescParams(vmAppDomain, vmMethodDesc, genericsToken, pcGenericClassTypeParams, pGenericTypeParams) : HResults.E_NOTIMPL; + + public int GetThreadStaticAddress(ulong vmField, ulong vmRuntimeThread, ulong* pRetVal) + => _legacy is not null ? _legacy.GetThreadStaticAddress(vmField, vmRuntimeThread, pRetVal) : HResults.E_NOTIMPL; + + public int GetCollectibleTypeStaticAddress(ulong vmField, ulong vmAppDomain, ulong* pRetVal) + => _legacy is not null ? _legacy.GetCollectibleTypeStaticAddress(vmField, vmAppDomain, pRetVal) : HResults.E_NOTIMPL; + + public int GetEnCHangingFieldInfo(nint pEnCFieldInfo, nint pFieldData, Interop.BOOL* pfStatic) + => _legacy is not null ? _legacy.GetEnCHangingFieldInfo(pEnCFieldInfo, pFieldData, pfStatic) : HResults.E_NOTIMPL; + + public int GetTypeHandleParams(ulong vmAppDomain, ulong vmTypeHandle, nint pParams) + => _legacy is not null ? _legacy.GetTypeHandleParams(vmAppDomain, vmTypeHandle, pParams) : HResults.E_NOTIMPL; + + public int GetSimpleType(ulong vmAppDomain, int simpleType, uint* pMetadataToken, ulong* pVmModule, ulong* pVmDomainAssembly) + => _legacy is not null ? _legacy.GetSimpleType(vmAppDomain, simpleType, pMetadataToken, pVmModule, pVmDomainAssembly) : HResults.E_NOTIMPL; + + public int IsExceptionObject(ulong vmObject, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsExceptionObject(vmObject, pResult) : HResults.E_NOTIMPL; + + public int GetStackFramesFromException(ulong vmObject, nint pDacStackFrames) + => _legacy is not null ? _legacy.GetStackFramesFromException(vmObject, pDacStackFrames) : HResults.E_NOTIMPL; + + public int IsRcw(ulong vmObject, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsRcw(vmObject, pResult) : HResults.E_NOTIMPL; + + public int GetRcwCachedInterfaceTypes(ulong vmObject, ulong vmAppDomain, Interop.BOOL bIInspectableOnly, nint pDacInterfaces) + => _legacy is not null ? _legacy.GetRcwCachedInterfaceTypes(vmObject, vmAppDomain, bIInspectableOnly, pDacInterfaces) : HResults.E_NOTIMPL; + + public int GetRcwCachedInterfacePointers(ulong vmObject, Interop.BOOL bIInspectableOnly, nint pDacItfPtrs) + => _legacy is not null ? _legacy.GetRcwCachedInterfacePointers(vmObject, bIInspectableOnly, pDacItfPtrs) : HResults.E_NOTIMPL; + + public int GetCachedWinRTTypesForIIDs(ulong vmAppDomain, nint pIids, nint pTypes) + => _legacy is not null ? _legacy.GetCachedWinRTTypesForIIDs(vmAppDomain, pIids, pTypes) : HResults.E_NOTIMPL; + + public int GetCachedWinRTTypes(ulong vmAppDomain, nint piids, nint pTypes) + => _legacy is not null ? _legacy.GetCachedWinRTTypes(vmAppDomain, piids, pTypes) : HResults.E_NOTIMPL; + + public int GetTypedByRefInfo(ulong pTypedByRef, ulong vmAppDomain, nint pObjectData) + => _legacy is not null ? _legacy.GetTypedByRefInfo(pTypedByRef, vmAppDomain, pObjectData) : HResults.E_NOTIMPL; + + public int GetStringData(ulong objectAddress, nint pObjectData) + => _legacy is not null ? _legacy.GetStringData(objectAddress, pObjectData) : HResults.E_NOTIMPL; + + public int GetArrayData(ulong objectAddress, nint pObjectData) + => _legacy is not null ? _legacy.GetArrayData(objectAddress, pObjectData) : HResults.E_NOTIMPL; + + public int GetBasicObjectInfo(ulong objectAddress, int type, ulong vmAppDomain, nint pObjectData) + => _legacy is not null ? _legacy.GetBasicObjectInfo(objectAddress, type, vmAppDomain, pObjectData) : HResults.E_NOTIMPL; + + public int TestCrst(ulong vmCrst) + => _legacy is not null ? _legacy.TestCrst(vmCrst) : HResults.E_NOTIMPL; + + public int TestRWLock(ulong vmRWLock) + => _legacy is not null ? _legacy.TestRWLock(vmRWLock) : HResults.E_NOTIMPL; + + public int GetDebuggerControlBlockAddress(ulong* pRetVal) + => _legacy is not null ? _legacy.GetDebuggerControlBlockAddress(pRetVal) : HResults.E_NOTIMPL; + + public int GetObjectFromRefPtr(ulong ptr, ulong* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + *pRetVal = _target.ReadPointer(new TargetPointer(ptr)).Value; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetObjectFromRefPtr(ptr, &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 GetObject(ulong ptr, ulong* pRetVal) + { + // Native GetObject wraps the address directly in a VMPTR_Object without dereferencing. + *pRetVal = ptr; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetObject(ptr, &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 EnableNGENPolicy(int ePolicy) + => HResults.E_NOTIMPL; + + public int SetNGENCompilerFlags(uint dwFlags) + => _legacy is not null ? _legacy.SetNGENCompilerFlags(dwFlags) : HResults.E_NOTIMPL; + + public int GetNGENCompilerFlags(uint* pdwFlags) + => _legacy is not null ? _legacy.GetNGENCompilerFlags(pdwFlags) : HResults.E_NOTIMPL; + + public int GetVmObjectHandle(ulong handleAddress, ulong* pRetVal) + { + *pRetVal = handleAddress; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetVmObjectHandle(handleAddress, &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 IsVmObjectHandleValid(ulong vmHandle, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + TargetPointer obj = _target.ReadPointer(new TargetPointer(vmHandle)); + *pResult = obj != TargetPointer.Null ? 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.IsVmObjectHandleValid(vmHandle, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int IsWinRTModule(ulong vmModule, Interop.BOOL* isWinRT) + { + *isWinRT = Interop.BOOL.FALSE; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL isWinRTLocal; + int hrLocal = _legacy.IsWinRTModule(vmModule, &isWinRTLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*isWinRT == isWinRTLocal, $"cDAC: {*isWinRT}, DAC: {isWinRTLocal}"); + } +#endif + return hr; + } + + public int GetAppDomainIdFromVmObjectHandle(ulong vmHandle, uint* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + // In modern .NET there is only one AppDomain (id=1). + // Return 0 for null handles to match native behavior. + *pRetVal = vmHandle != 0 ? 1u : 0u; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint retValLocal; + int hrLocal = _legacy.GetAppDomainIdFromVmObjectHandle(vmHandle, &retValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); + } +#endif + return hr; + } + + public int GetHandleAddressFromVmHandle(ulong vmHandle, ulong* pRetVal) + { + *pRetVal = vmHandle; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + ulong retValLocal; + int hrLocal = _legacy.GetHandleAddressFromVmHandle(vmHandle, &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 GetObjectContents(ulong obj, DacDbiTargetBuffer* pRetVal) + => _legacy is not null ? _legacy.GetObjectContents(obj, pRetVal) : HResults.E_NOTIMPL; + + public int GetThreadOwningMonitorLock(ulong vmObject, DacDbiMonitorLockInfo* pRetVal) + => _legacy is not null ? _legacy.GetThreadOwningMonitorLock(vmObject, pRetVal) : HResults.E_NOTIMPL; + + public int EnumerateMonitorEventWaitList(ulong vmObject, nint fpCallback, nint pUserData) + => _legacy is not null ? _legacy.EnumerateMonitorEventWaitList(vmObject, fpCallback, pUserData) : HResults.E_NOTIMPL; + + public int GetAttachStateFlags(int* pRetVal) + { + *pRetVal = 0; + int hr = HResults.S_OK; + try + { + *pRetVal = _target.Contracts.Debugger.GetAttachStateFlags(); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + int resultLocal; + int hrLocal = _legacy.GetAttachStateFlags(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == resultLocal); + } +#endif + + return hr; + } + + public int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.GetMetaDataFileInfoFromPEFile(vmPEAssembly, dwTimeStamp, dwImageSize, pStrFilename, pResult) : HResults.E_NOTIMPL; + + public int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsThreadSuspendedOrHijacked(vmThread, pResult) : HResults.E_NOTIMPL; + + public int AreGCStructuresValid(Interop.BOOL* pResult) + { + // Native DacDbiInterfaceImpl::AreGCStructuresValid always returns TRUE. + // DacDbi callers assume the runtime is suspended, so GC structures are always valid. + *pResult = Interop.BOOL.TRUE; + int hr = HResults.S_OK; +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.AreGCStructuresValid(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } + + public int CreateHeapWalk(nuint* pHandle) + => _legacy is not null ? _legacy.CreateHeapWalk(pHandle) : HResults.E_NOTIMPL; + + public int DeleteHeapWalk(nuint handle) + => _legacy is not null ? _legacy.DeleteHeapWalk(handle) : HResults.E_NOTIMPL; + + public int WalkHeap(nuint handle, uint count, COR_HEAPOBJECT* objects, uint* fetched) + => _legacy is not null ? _legacy.WalkHeap(handle, count, objects, fetched) : HResults.E_NOTIMPL; + + public int GetHeapSegments(nint pSegments) + => _legacy is not null ? _legacy.GetHeapSegments(pSegments) : HResults.E_NOTIMPL; + + public int IsValidObject(ulong obj, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsValidObject(obj, pResult) : HResults.E_NOTIMPL; + + public int GetAppDomainForObject(ulong obj, ulong* pApp, ulong* pModule, ulong* pDomainAssembly, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.GetAppDomainForObject(obj, pApp, pModule, pDomainAssembly, pResult) : HResults.E_NOTIMPL; + + public int CreateRefWalk(nuint* pHandle, Interop.BOOL walkStacks, Interop.BOOL walkFQ, uint handleWalkMask) + => _legacy is not null ? _legacy.CreateRefWalk(pHandle, walkStacks, walkFQ, handleWalkMask) : HResults.E_NOTIMPL; + + public int DeleteRefWalk(nuint handle) + => _legacy is not null ? _legacy.DeleteRefWalk(handle) : HResults.E_NOTIMPL; + + public int WalkRefs(nuint handle, uint count, nint refs, uint* pFetched) + => _legacy is not null ? _legacy.WalkRefs(handle, count, refs, pFetched) : HResults.E_NOTIMPL; + + public int GetTypeID(ulong obj, COR_TYPEID* pType) + { + *pType = default; + int hr = HResults.S_OK; + try + { + TargetPointer mt = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(obj)); + pType->token1 = mt.Value; + pType->token2 = 0; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + COR_TYPEID resultLocal; + int hrLocal = _legacy.GetTypeID(obj, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pType->token1 == resultLocal.token1); + Debug.Assert(pType->token2 == resultLocal.token2); + } + } +#endif + + return hr; + } + + public int GetTypeIDForType(ulong vmTypeHandle, COR_TYPEID* pId) + { + *pId = default; + int hr = HResults.S_OK; + try + { + pId->token1 = vmTypeHandle; + pId->token2 = 0; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + COR_TYPEID resultLocal; + int hrLocal = _legacy.GetTypeIDForType(vmTypeHandle, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pId->token1 == resultLocal.token1); + Debug.Assert(pId->token2 == resultLocal.token2); + } + } +#endif + + return hr; + } + + public int GetObjectFields(nint id, uint celt, COR_FIELD* layout, uint* pceltFetched) + => _legacy is not null ? _legacy.GetObjectFields(id, celt, layout, pceltFetched) : HResults.E_NOTIMPL; + + public int GetTypeLayout(nint id, COR_TYPE_LAYOUT* pLayout) + => _legacy is not null ? _legacy.GetTypeLayout(id, pLayout) : HResults.E_NOTIMPL; + + public int GetArrayLayout(nint id, nint pLayout) + => _legacy is not null ? _legacy.GetArrayLayout(id, pLayout) : HResults.E_NOTIMPL; + + public int GetGCHeapInformation(COR_HEAPINFO* pHeapInfo) + { + *pHeapInfo = default; + int hr = HResults.S_OK; + try + { + Contracts.IGC gc = _target.Contracts.GC; + pHeapInfo->areGCStructuresValid = gc.GetGCStructuresValid() ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + pHeapInfo->numHeaps = gc.GetGCHeapCount(); + pHeapInfo->pointerSize = (uint)_target.PointerSize; + + string[] identifiers = gc.GetGCIdentifiers(); + bool isServer = identifiers.Contains(GCIdentifiers.Server); + pHeapInfo->gcType = isServer ? 1 : 0; // CorDebugServerGC = 1, CorDebugWorkstationGC = 0 + pHeapInfo->concurrent = identifiers.Contains(GCIdentifiers.Background) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + COR_HEAPINFO resultLocal; + int hrLocal = _legacy.GetGCHeapInformation(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pHeapInfo->areGCStructuresValid == resultLocal.areGCStructuresValid); + Debug.Assert(pHeapInfo->numHeaps == resultLocal.numHeaps); + Debug.Assert(pHeapInfo->pointerSize == resultLocal.pointerSize); + Debug.Assert(pHeapInfo->gcType == resultLocal.gcType); + Debug.Assert(pHeapInfo->concurrent == resultLocal.concurrent); + } + } +#endif + + return hr; + } + + public int GetPEFileMDInternalRW(ulong vmPEAssembly, ulong* pAddrMDInternalRW) + => _legacy is not null ? _legacy.GetPEFileMDInternalRW(vmPEAssembly, pAddrMDInternalRW) : HResults.E_NOTIMPL; + + public int GetReJitInfo(ulong vmModule, uint methodTk, ulong* pReJitInfo) + => _legacy is not null ? _legacy.GetReJitInfo(vmModule, methodTk, pReJitInfo) : HResults.E_NOTIMPL; + + public int GetReJitInfoByAddress(ulong vmMethod, ulong codeStartAddress, ulong* pReJitInfo) + => _legacy is not null ? _legacy.GetReJitInfoByAddress(vmMethod, codeStartAddress, pReJitInfo) : HResults.E_NOTIMPL; + + public int GetSharedReJitInfo(ulong vmReJitInfo, ulong* pSharedReJitInfo) + => _legacy is not null ? _legacy.GetSharedReJitInfo(vmReJitInfo, pSharedReJitInfo) : HResults.E_NOTIMPL; + + public int GetSharedReJitInfoData(ulong sharedReJitInfo, DacDbiSharedReJitInfo* pData) + => _legacy is not null ? _legacy.GetSharedReJitInfoData(sharedReJitInfo, pData) : HResults.E_NOTIMPL; + + public int AreOptimizationsDisabled(ulong vmModule, uint methodTk, Interop.BOOL* pOptimizationsDisabled) + => _legacy is not null ? _legacy.AreOptimizationsDisabled(vmModule, methodTk, pOptimizationsDisabled) : HResults.E_NOTIMPL; + + public int GetDefinesBitField(uint* pDefines) + { + *pDefines = 0; + int hr = HResults.S_OK; + try + { + if (!_target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!; + *pDefines = data.DefinesBitField; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + uint resultLocal; + int hrLocal = _legacy.GetDefinesBitField(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pDefines == resultLocal); + } +#endif + + return hr; + } + + public int GetMDStructuresVersion(uint* pMDStructuresVersion) + { + *pMDStructuresVersion = 0; + int hr = HResults.S_OK; + try + { + if (!_target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)) + throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!; + *pMDStructuresVersion = data.MDStructuresVersion; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacy is not null) + { + uint resultLocal; + int hrLocal = _legacy.GetMDStructuresVersion(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pMDStructuresVersion == resultLocal); + } +#endif + + return hr; + } + + public int GetActiveRejitILCodeVersionNode(ulong vmModule, uint methodTk, ulong* pVmILCodeVersionNode) + => _legacy is not null ? _legacy.GetActiveRejitILCodeVersionNode(vmModule, methodTk, pVmILCodeVersionNode) : HResults.E_NOTIMPL; + + public int GetNativeCodeVersionNode(ulong vmMethod, ulong codeStartAddress, ulong* pVmNativeCodeVersionNode) + => _legacy is not null ? _legacy.GetNativeCodeVersionNode(vmMethod, codeStartAddress, pVmNativeCodeVersionNode) : HResults.E_NOTIMPL; + + public int GetILCodeVersionNode(ulong vmNativeCodeVersionNode, ulong* pVmILCodeVersionNode) + => _legacy is not null ? _legacy.GetILCodeVersionNode(vmNativeCodeVersionNode, pVmILCodeVersionNode) : HResults.E_NOTIMPL; + + public int GetILCodeVersionNodeData(ulong ilCodeVersionNode, DacDbiSharedReJitInfo* pData) + => _legacy is not null ? _legacy.GetILCodeVersionNodeData(ilCodeVersionNode, pData) : HResults.E_NOTIMPL; + + public int EnableGCNotificationEvents(Interop.BOOL fEnable) + => _legacy is not null ? _legacy.EnableGCNotificationEvents(fEnable) : HResults.E_NOTIMPL; + + public int IsDelegate(ulong vmObject, Interop.BOOL* pResult) + => _legacy is not null ? _legacy.IsDelegate(vmObject, pResult) : HResults.E_NOTIMPL; + + public int GetDelegateType(ulong delegateObject, int* delegateType) + => _legacy is not null ? _legacy.GetDelegateType(delegateObject, delegateType) : HResults.E_NOTIMPL; + + public int GetDelegateFunctionData(int delegateType, ulong delegateObject, ulong* ppFunctionDomainAssembly, uint* pMethodDef) + => _legacy is not null ? _legacy.GetDelegateFunctionData(delegateType, delegateObject, ppFunctionDomainAssembly, pMethodDef) : HResults.E_NOTIMPL; + + public int GetDelegateTargetObject(int delegateType, ulong delegateObject, ulong* ppTargetObj, ulong* ppTargetAppDomain) + => _legacy is not null ? _legacy.GetDelegateTargetObject(delegateType, delegateObject, ppTargetObj, ppTargetAppDomain) : HResults.E_NOTIMPL; + + public int GetLoaderHeapMemoryRanges(nint pRanges) + => _legacy is not null ? _legacy.GetLoaderHeapMemoryRanges(pRanges) : HResults.E_NOTIMPL; + + public int IsModuleMapped(ulong pModule, int* isModuleMapped) + => _legacy is not null ? _legacy.IsModuleMapped(pModule, isModuleMapped) : HResults.E_NOTIMPL; + + public int MetadataUpdatesApplied(Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + *pResult = _target.Contracts.Debugger.MetadataUpdatesApplied() ? 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.MetadataUpdatesApplied(&resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal); + } +#endif + + return hr; + } + + public int GetDomainAssemblyFromModule(ulong vmModule, ulong* pVmDomainAssembly) + => _legacy is not null ? _legacy.GetDomainAssemblyFromModule(vmModule, pVmDomainAssembly) : HResults.E_NOTIMPL; + + public int ParseContinuation(ulong continuationAddress, ulong* pDiagnosticIP, ulong* pNextContinuation, uint* pState) + => _legacy is not null ? _legacy.ParseContinuation(continuationAddress, pDiagnosticIP, pNextContinuation, pState) : HResults.E_NOTIMPL; + + public int GetAsyncLocals(ulong vmMethod, ulong codeAddr, uint state, nint pAsyncLocals) + => _legacy is not null ? _legacy.GetAsyncLocals(vmMethod, codeAddr, state, pAsyncLocals) : HResults.E_NOTIMPL; + + public int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex) + => _legacy is not null ? _legacy.GetGenericArgTokenIndex(vmMethod, pIndex) : HResults.E_NOTIMPL; +} diff --git a/src/native/managed/cdac/tests/DebuggerTests.cs b/src/native/managed/cdac/tests/DebuggerTests.cs new file mode 100644 index 00000000000000..3ad7c18f6e507f --- /dev/null +++ b/src/native/managed/cdac/tests/DebuggerTests.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class DebuggerTests +{ + private static TargetTestHelpers.LayoutResult GetDebuggerLayout(TargetTestHelpers helpers) + { + return helpers.LayoutFields( + [ + new(nameof(Data.Debugger.LeftSideInitialized), DataType.int32), + new(nameof(Data.Debugger.Defines), DataType.uint32), + new(nameof(Data.Debugger.MDStructuresVersion), DataType.uint32), + ]); + } + + private static TestPlaceholderTarget BuildTarget( + MockTarget.Architecture arch, + int leftSideInitialized, + uint defines, + uint mdStructuresVersion, + int? attachStateFlags = null, + byte? metadataUpdatesApplied = null) + { + TargetTestHelpers helpers = new(arch); + var builder = new TestPlaceholderTarget.Builder(arch); + MockMemorySpace.Builder memBuilder = builder.MemoryBuilder; + MockMemorySpace.BumpAllocator allocator = memBuilder.CreateAllocator(0x1_0000, 0x2_0000); + + TargetTestHelpers.LayoutResult debuggerLayout = GetDebuggerLayout(helpers); + builder.AddTypes(new() { [DataType.Debugger] = new Target.TypeInfo() { Fields = debuggerLayout.Fields, Size = debuggerLayout.Stride } }); + + // Allocate and populate the Debugger struct + MockMemorySpace.HeapFragment debuggerFrag = allocator.Allocate(debuggerLayout.Stride, "Debugger"); + helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.LeftSideInitialized)].Offset, sizeof(int)), leftSideInitialized); + helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.Defines)].Offset, sizeof(uint)), defines); + helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.MDStructuresVersion)].Offset, sizeof(uint)), mdStructuresVersion); + memBuilder.AddHeapFragment(debuggerFrag); + + // g_pDebugger is a pointer-to-Debugger. The global stores the address of g_pDebugger, + // so ReadGlobalPointer returns the location, and ReadPointer dereferences it. + MockMemorySpace.HeapFragment debuggerPtrFrag = allocator.Allocate((ulong)helpers.PointerSize, "g_pDebugger"); + helpers.WritePointer(debuggerPtrFrag.Data, debuggerFrag.Address); + memBuilder.AddHeapFragment(debuggerPtrFrag); + builder.AddGlobals((Constants.Globals.Debugger, debuggerPtrFrag.Address)); + + if (attachStateFlags.HasValue) + { + MockMemorySpace.HeapFragment attachFrag = allocator.Allocate(sizeof(uint), "CLRJitAttachState"); + helpers.Write(attachFrag.Data.AsSpan(0, sizeof(uint)), (uint)attachStateFlags.Value); + memBuilder.AddHeapFragment(attachFrag); + builder.AddGlobals((Constants.Globals.CLRJitAttachState, attachFrag.Address)); + } + + if (metadataUpdatesApplied.HasValue) + { + MockMemorySpace.HeapFragment metadataFrag = allocator.Allocate(1, "MetadataUpdatesApplied"); + helpers.Write(metadataFrag.Data.AsSpan(0, 1), metadataUpdatesApplied.Value); + memBuilder.AddHeapFragment(metadataFrag); + builder.AddGlobals((Constants.Globals.MetadataUpdatesApplied, metadataFrag.Address)); + } + + builder.AddContract(target => ((IContractFactory)new DebuggerFactory()).CreateContract(target, 1)); + + return builder.Build(); + } + + private static TestPlaceholderTarget BuildNullDebuggerTarget(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + var builder = new TestPlaceholderTarget.Builder(arch); + MockMemorySpace.Builder memBuilder = builder.MemoryBuilder; + MockMemorySpace.BumpAllocator allocator = memBuilder.CreateAllocator(0x1_0000, 0x2_0000); + + // g_pDebugger is a pointer-to-Debugger that contains null. + MockMemorySpace.HeapFragment debuggerPtrFrag = allocator.Allocate((ulong)helpers.PointerSize, "g_pDebugger"); + helpers.WritePointer(debuggerPtrFrag.Data, 0); + memBuilder.AddHeapFragment(debuggerPtrFrag); + builder.AddGlobals((Constants.Globals.Debugger, debuggerPtrFrag.Address)); + builder.AddContract(target => ((IContractFactory)new DebuggerFactory()).CreateContract(target, 1)); + + return builder.Build(); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetDebuggerData_ReturnsTrue_WhenInitialized(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0xDEADBEEF, mdStructuresVersion: 42); + IDebugger debugger = target.Contracts.Debugger; + + Assert.True(debugger.TryGetDebuggerData(out DebuggerData data)); + Assert.Equal(0xDEADBEEFu, data.DefinesBitField); + Assert.Equal(42u, data.MDStructuresVersion); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetDebuggerData_ReturnsFalse_WhenNotInitialized(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 0, defines: 0, mdStructuresVersion: 0); + IDebugger debugger = target.Contracts.Debugger; + + Assert.False(debugger.TryGetDebuggerData(out _)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TryGetDebuggerData_ReturnsFalse_WhenDebuggerNull(MockTarget.Architecture arch) + { + Target target = BuildNullDebuggerTarget(arch); + IDebugger debugger = target.Contracts.Debugger; + + Assert.False(debugger.TryGetDebuggerData(out _)); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetAttachStateFlags_ReturnsValue(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, attachStateFlags: 0x42); + IDebugger debugger = target.Contracts.Debugger; + + Assert.Equal(0x42, debugger.GetAttachStateFlags()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetAttachStateFlags_ReturnsZero_WhenValueIsZero(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, attachStateFlags: 0); + IDebugger debugger = target.Contracts.Debugger; + + Assert.Equal(0, debugger.GetAttachStateFlags()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MetadataUpdatesApplied_ReturnsTrue_WhenSet(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, metadataUpdatesApplied: 1); + IDebugger debugger = target.Contracts.Debugger; + + Assert.True(debugger.MetadataUpdatesApplied()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MetadataUpdatesApplied_ReturnsFalse_WhenNotSet(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, metadataUpdatesApplied: 0); + IDebugger debugger = target.Contracts.Debugger; + + Assert.False(debugger.MetadataUpdatesApplied()); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MetadataUpdatesApplied_ReturnsFalse_WhenGlobalMissing(MockTarget.Architecture arch) + { + Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0); + IDebugger debugger = target.Contracts.Debugger; + + Assert.False(debugger.MetadataUpdatesApplied()); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs new file mode 100644 index 00000000000000..feb10a49b2c69b --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl AppDomain, misc policy, and simple thread +/// property methods. Uses the BasicThreads debuggee (heap dump). +/// +public class DacDbiAppDomainDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainId_ReturnsOneForValidAppDomain(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = Target.ReadPointer(appDomainPtr); + + uint id; + int hr = dbi.GetAppDomainId(appDomain, &id); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(1u, id); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainId_ReturnsZeroForNull(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint id; + int hr = dbi.GetAppDomainId(0, &id); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(0u, id); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainFromId_ReturnsAppDomain(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong appDomain; + int hr = dbi.GetAppDomainFromId(1, &appDomain); + Assert.Equal(System.HResults.S_OK, hr); + Assert.NotEqual(0UL, appDomain); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainFromId_FailsForInvalidId(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong appDomain; + int hr = dbi.GetAppDomainFromId(99, &appDomain); + Assert.True(hr < 0, "Expected failure HRESULT for invalid AppDomain ID"); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetCurrentAppDomain_ReturnsNonNull(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong appDomain; + int hr = dbi.GetCurrentAppDomain(&appDomain); + Assert.Equal(System.HResults.S_OK, hr); + Assert.NotEqual(0UL, appDomain); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetCurrentAppDomain_MatchesGetAppDomainFromId(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong currentAD; + int hr1 = dbi.GetCurrentAppDomain(¤tAD); + Assert.Equal(System.HResults.S_OK, hr1); + + ulong fromId; + int hr2 = dbi.GetAppDomainFromId(1, &fromId); + Assert.Equal(System.HResults.S_OK, hr2); + + Assert.Equal(currentAD, fromId); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void EnumerateAppDomains_CallsCallbackOnce(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + int count = 0; + delegate* unmanaged callback = &CountCallback; + int hr = dbi.EnumerateAppDomains((nint)callback, (nint)(&count)); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(1, count); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsAssemblyFullyTrusted_ReturnsTrue(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ILoader loader = Target.Contracts.Loader; + TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = Target.ReadPointer(appDomainPtr); + IEnumerable modules = loader.GetModuleHandles(new TargetPointer(appDomain), + AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); + + foreach (ModuleHandle module in modules) + { + TargetPointer moduleAddr = loader.GetModule(module); + Interop.BOOL result; + int hr = dbi.IsAssemblyFullyTrusted(moduleAddr, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(Interop.BOOL.TRUE, result); + break; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetConnectionID_ReturnsZero(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + uint connId; + int hr = dbi.GetConnectionID(storeData.FirstThread, &connId); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(0u, connId); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetTaskID_ReturnsZero(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + ulong taskId; + int hr = dbi.GetTaskID(storeData.FirstThread, &taskId); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(0UL, taskId); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsWinRTModule_ReturnsFalse(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ILoader loader = Target.Contracts.Loader; + TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = Target.ReadPointer(appDomainPtr); + IEnumerable modules = loader.GetModuleHandles(new TargetPointer(appDomain), + AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); + + foreach (ModuleHandle module in modules) + { + TargetPointer moduleAddr = loader.GetModule(module); + Interop.BOOL isWinRT; + int hr = dbi.IsWinRTModule(moduleAddr, &isWinRT); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(Interop.BOOL.FALSE, isWinRT); + break; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void EnableNGENPolicy_ReturnsENotImpl(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + int hr = dbi.EnableNGENPolicy(0); + Assert.Equal(System.HResults.E_NOTIMPL, hr); + } + + [UnmanagedCallersOnly] + private static unsafe void CountCallback(ulong addr, nint userData) + { + (*(int*)userData)++; + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs new file mode 100644 index 00000000000000..718f1a9273a3a8 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl IDebugger contract methods. +/// Uses the BasicThreads debuggee (heap dump). +/// +public class DacDbiDebuggerDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsLeftSideInitialized_ReturnsNonZero(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + Interop.BOOL result; + int hr = dbi.IsLeftSideInitialized(&result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.NotEqual(Interop.BOOL.FALSE, result); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAttachStateFlags_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + int flags; + int hr = dbi.GetAttachStateFlags(&flags); + Assert.Equal(System.HResults.S_OK, hr); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetDefinesBitField_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint defines; + int hr = dbi.GetDefinesBitField(&defines); + Assert.Equal(System.HResults.S_OK, hr); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetMDStructuresVersion_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint version; + int hr = dbi.GetMDStructuresVersion(&version); + Assert.Equal(System.HResults.S_OK, hr); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void MetadataUpdatesApplied_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + Interop.BOOL result; + int hr = dbi.MetadataUpdatesApplied(&result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.True(result == Interop.BOOL.TRUE || result == Interop.BOOL.FALSE); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsLeftSideInitialized_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + Interop.BOOL dbiResult; + int hr = dbi.IsLeftSideInitialized(&dbiResult); + Assert.Equal(System.HResults.S_OK, hr); + + bool contractResult = Target.Contracts.Debugger.TryGetDebuggerData(out _); + Assert.Equal(contractResult, dbiResult != Interop.BOOL.FALSE); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetDefinesBitField_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint dbiResult; + int hr = dbi.GetDefinesBitField(&dbiResult); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.True(Target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)); + Assert.Equal(data.DefinesBitField, dbiResult); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetMDStructuresVersion_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint dbiResult; + int hr = dbi.GetMDStructuresVersion(&dbiResult); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.True(Target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)); + Assert.Equal(data.MDStructuresVersion, dbiResult); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs new file mode 100644 index 00000000000000..55de104c4c978c --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl GC methods. +/// Uses the BasicThreads debuggee (heap dump). +/// +public class DacDbiGCDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void AreGCStructuresValid_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + Interop.BOOL result; + int hr = dbi.AreGCStructuresValid(&result); + Assert.Equal(System.HResults.S_OK, hr); + + bool contractResult = Target.Contracts.GC.GetGCStructuresValid(); + Assert.Equal(contractResult, result == Interop.BOOL.TRUE); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetGCHeapInformation_Succeeds(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + COR_HEAPINFO heapInfo; + int hr = dbi.GetGCHeapInformation(&heapInfo); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.True(heapInfo.pointerSize == 4 || heapInfo.pointerSize == 8); + Assert.True(heapInfo.numHeaps >= 1); + Assert.True(heapInfo.areGCStructuresValid == Interop.BOOL.TRUE || heapInfo.areGCStructuresValid == Interop.BOOL.FALSE); + Assert.True(heapInfo.concurrent == Interop.BOOL.TRUE || heapInfo.concurrent == Interop.BOOL.FALSE); + Assert.True(heapInfo.gcType == 0 || heapInfo.gcType == 1); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetGCHeapInformation_MatchesPointerSize(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + COR_HEAPINFO heapInfo; + int hr = dbi.GetGCHeapInformation(&heapInfo); + Assert.Equal(System.HResults.S_OK, hr); + + Assert.Equal((uint)Target.PointerSize, heapInfo.pointerSize); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetGCHeapInformation_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + COR_HEAPINFO heapInfo; + int hr = dbi.GetGCHeapInformation(&heapInfo); + Assert.Equal(System.HResults.S_OK, hr); + + IGC gc = Target.Contracts.GC; + bool contractValid = gc.GetGCStructuresValid(); + Assert.Equal(contractValid, heapInfo.areGCStructuresValid == Interop.BOOL.TRUE); + + uint heapCount = gc.GetGCHeapCount(); + Assert.Equal(heapCount, heapInfo.numHeaps); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs new file mode 100644 index 00000000000000..a24ac2cb9a6768 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl loader, assembly, and module methods. +/// Uses the MultiModule debuggee (full dump), which loads assemblies from multiple ALCs. +/// +public class DacDbiLoaderDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "MultiModule"; + protected override string DumpType => "full"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void GetAppDomainFullName_ReturnsNonEmpty(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); + ulong appDomain = Target.ReadPointer(appDomainPtr); + + using var holder = new NativeStringHolder(); + int hr = dbi.GetAppDomainFullName(appDomain, holder.Ptr); + Assert.Equal(System.HResults.S_OK, hr); + Assert.False(string.IsNullOrEmpty(holder.Value), "AppDomain name should not be empty"); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs new file mode 100644 index 00000000000000..e47021a41484e2 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl object handle methods. +/// Uses the BasicThreads debuggee (heap dump). +/// +public class DacDbiObjectDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetVmObjectHandle_IsIdentity(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong testAddr = 0x12345678; + ulong result; + int hr = dbi.GetVmObjectHandle(testAddr, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(testAddr, result); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetHandleAddressFromVmHandle_IsIdentity(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ulong testAddr = 0xABCDEF00; + ulong result; + int hr = dbi.GetHandleAddressFromVmHandle(testAddr, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(testAddr, result); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainIdFromVmObjectHandle_ReturnsOneForNonZero(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint id; + int hr = dbi.GetAppDomainIdFromVmObjectHandle(0x12345678, &id); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(1u, id); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetAppDomainIdFromVmObjectHandle_ReturnsZeroForNull(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + uint id; + int hr = dbi.GetAppDomainIdFromVmObjectHandle(0, &id); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(0u, id); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs new file mode 100644 index 00000000000000..11017bf43c1be6 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs @@ -0,0 +1,168 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Microsoft.DotNet.XUnitExtensions; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl thread methods. +/// Uses the BasicThreads debuggee (heap dump), which spawns 5 named threads then crashes. +/// +public class DacDbiThreadDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "BasicThreads"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void EnumerateThreads_CountMatchesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + int dbiCount = 0; + delegate* unmanaged callback = &CountThreadCallback; + int hr = dbi.EnumerateThreads((nint)callback, (nint)(&dbiCount)); + Assert.Equal(System.HResults.S_OK, hr); + + int expectedCount = 0; + TargetPointer current = storeData.FirstThread; + while (current != TargetPointer.Null) + { + ThreadData data = threadContract.GetThreadData(current); + bool isDead = (data.State & ThreadState.Dead) != 0; + bool isUnstarted = (data.State & ThreadState.Unstarted) != 0; + if (!isDead && !isUnstarted) + { + expectedCount++; + } + + current = data.NextThread; + } + + Assert.Equal(expectedCount, dbiCount); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsThreadMarkedDead_CrossValidateWithContract(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 isDead; + int hr = dbi.IsThreadMarkedDead(current, &isDead); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + bool contractSaysDead = (data.State & ThreadState.Dead) != 0; + Assert.Equal(contractSaysDead, isDead == Interop.BOOL.TRUE); + + current = data.NextThread; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void TryGetVolatileOSThreadID_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) + { + uint osId; + int hr = dbi.TryGetVolatileOSThreadID(current, &osId); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + // DacDbi normalizes SWITCHED_OUT_FIBER_OSID (0xbaadf00d) to 0 + const uint SWITCHED_OUT_FIBER_OSID = 0xbaadf00d; + uint expectedOsId = (uint)data.OSId.Value; + if (expectedOsId == SWITCHED_OUT_FIBER_OSID) + expectedOsId = 0; + Assert.Equal(expectedOsId, osId); + + current = data.NextThread; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetUniqueThreadID_MatchesContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + HashSet seenIds = new(); + + while (current != TargetPointer.Null) + { + uint uniqueId; + int hr = dbi.GetUniqueThreadID(current, &uniqueId); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + Assert.Equal((uint)data.OSId.Value, uniqueId); + Assert.True(seenIds.Add(uniqueId), $"Duplicate thread ID: {uniqueId}"); + + current = data.NextThread; + } + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void GetCurrentException_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + 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 + { + throw new SkipTestException("GetThrowableObject not yet implemented in Thread contract"); + } + + Assert.Equal(System.HResults.S_OK, hr); + } + + [UnmanagedCallersOnly] + private static unsafe void CountThreadCallback(ulong addr, nint userData) + { + (*(int*)userData)++; + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs new file mode 100644 index 00000000000000..8bca8e70b1bb24 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Creates a native-memory object that mimics the C++ IStringHolder vtable layout. +/// DacDbiImpl.StringHolderAssignCopy reads: objPtr -> vtable -> AssignCopy fn ptr. +/// This helper allocates that exact structure in unmanaged memory so we can test +/// string-returning DacDbiImpl methods directly (without COM marshaling). +/// +internal sealed class NativeStringHolder : IDisposable +{ + // Layout in native memory: + // _objectPtr -> [vtablePtr] (nint) + // _vtablePtr -> [fnPtr] (nint, the AssignCopy function pointer) + private readonly IntPtr _objectPtr; + private readonly IntPtr _vtablePtr; + private readonly GCHandle _delegateHandle; + private bool _disposed; + + // Delegate matching the native IStringHolder::AssignCopy(this, const WCHAR* psz) signature. + // Use ThisCall to match the C++ virtual method calling convention (thiscall on x86, no-op on x64/arm64). + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate int AssignCopyDelegate(IntPtr thisPtr, IntPtr psz); + + public string? Value { get; private set; } + + public NativeStringHolder() + { + AssignCopyDelegate assignCopy = AssignCopyImpl; + _delegateHandle = GCHandle.Alloc(assignCopy); + IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(assignCopy); + + // Allocate vtable (one slot: AssignCopy) + _vtablePtr = Marshal.AllocHGlobal(IntPtr.Size); + Marshal.WriteIntPtr(_vtablePtr, fnPtr); + + // Allocate object (one field: vtable pointer) + _objectPtr = Marshal.AllocHGlobal(IntPtr.Size); + Marshal.WriteIntPtr(_objectPtr, _vtablePtr); + } + + /// + /// The native pointer to pass as the nint IStringHolder parameter. + /// + public nint Ptr => _objectPtr; + + private int AssignCopyImpl(IntPtr thisPtr, IntPtr psz) + { + Value = Marshal.PtrToStringUni(psz); + return System.HResults.S_OK; + } + + public void Dispose() + { + if (!_disposed) + { + Marshal.FreeHGlobal(_objectPtr); + Marshal.FreeHGlobal(_vtablePtr); + _delegateHandle.Free(); + _disposed = true; + } + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/Program.cs new file mode 100644 index 00000000000000..b6fe4395d5bef9 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/Program.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +/// +/// Debuggee for cDAC dump tests — exercises stack reference enumeration. +/// Creates objects on the stack that should be reported as GC references, +/// then crashes with them still live. The test walks the stack and verifies +/// the expected references are found. +/// +internal static class Program +{ + /// + /// Marker string that tests can search for in the reported GC references + /// to verify that stack refs are being enumerated correctly. + /// + public const string MarkerValue = "cDAC-StackRefs-Marker-12345"; + + private static void Main() + { + MethodWithStackRefs(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void MethodWithStackRefs() + { + // These locals will be GC-tracked in the JIT's GCInfo. + // The string has a known value we can find in the dump. + string marker = MarkerValue; + int[] array = [1, 2, 3, 4, 5]; + object boxed = 42; + + // Force the JIT to keep them alive at the FailFast call site. + GC.KeepAlive(marker); + GC.KeepAlive(array); + GC.KeepAlive(boxed); + + Environment.FailFast("cDAC dump test: StackRefs debuggee intentional crash"); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/StackRefs.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/StackRefs.csproj new file mode 100644 index 00000000000000..bb776824769fe6 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/StackRefs.csproj @@ -0,0 +1,5 @@ + + + Full + + diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTestStackWalker.cs b/src/native/managed/cdac/tests/DumpTests/DumpTestStackWalker.cs new file mode 100644 index 00000000000000..bff72c2b2f3b08 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DumpTestStackWalker.cs @@ -0,0 +1,302 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// A single resolved stack frame, carrying both the method name and the raw +/// MethodDesc pointer so that callers can perform ad-hoc assertions. +/// +internal readonly record struct ResolvedFrame(string? Name, TargetPointer MethodDescPtr); + +/// +/// Encapsulates a resolved stack walk for a thread, providing a builder-pattern +/// API to assert frame names, ordering, adjacency, and ad-hoc predicates. +/// +/// +/// +/// Inner-to-outer (callee -> caller, default): +/// +/// DumpTestStackWalker.Walk(Target, threadData) +/// .InnerToOuter() +/// .ExpectFrame("MethodC") +/// .ExpectFrame("MethodB") +/// .ExpectFrame("Main") +/// .Verify(); +/// +/// +/// +/// Outer-to-inner (caller -> callee): +/// +/// DumpTestStackWalker.Walk(Target, threadData) +/// .OuterToInner() +/// .ExpectFrame("Main") +/// .ExpectFrame("MethodA") +/// .ExpectFrame("MethodC") +/// .Verify(); +/// +/// +/// +/// Strict adjacency (no gaps allowed between the two frames): +/// +/// .ExpectFrame("CrashInVarargPInvoke") +/// .ExpectAdjacentFrame("Main") +/// +/// +/// +/// Ad-hoc predicate on matched frame: +/// +/// .ExpectFrame("IL_STUB_PInvoke", frame => +/// { +/// var md = rts.GetMethodDescHandle(frame.MethodDescPtr); +/// Assert.True(rts.IsILStub(md)); +/// }) +/// +/// +/// +internal sealed class DumpTestStackWalker +{ + private readonly ContractDescriptorTarget _target; + private readonly List _frames; + private readonly List _expectations = []; + private bool _outerToInner; + + private DumpTestStackWalker(ContractDescriptorTarget target, List frames) + { + _target = target; + _frames = frames; + } + + /// The cDAC target the stack was walked from. + public ContractDescriptorTarget Target => _target; + + /// + /// The fully resolved call stack in inner-to-outer order (callee -> caller). + /// + public IReadOnlyList Frames => _frames; + + /// + /// Creates a by walking the stack for + /// and resolving every frame's method name. + /// + public static DumpTestStackWalker Walk(ContractDescriptorTarget target, ThreadData threadData) + { + IStackWalk stackWalk = target.Contracts.StackWalk; + List frames = []; + + foreach (IStackDataFrameHandle frame in stackWalk.CreateStackWalk(threadData)) + { + TargetPointer methodDescPtr = stackWalk.GetMethodDescPtr(frame); + string? name = DumpTestHelpers.GetMethodName(target, methodDescPtr); + frames.Add(new ResolvedFrame(name, methodDescPtr)); + } + + return new DumpTestStackWalker(target, frames); + } + + /// + /// Sets the expectation direction to inner-to-outer (callee -> caller). + /// This is the default and matches the natural stack walk order. + /// + public DumpTestStackWalker InnerToOuter() + { + _outerToInner = false; + return this; + } + + /// + /// Sets the expectation direction to outer-to-inner (caller -> callee). + /// Expectations are matched starting from the outermost frame (e.g. Main) + /// toward the innermost (e.g. the crash site). + /// + public DumpTestStackWalker OuterToInner() + { + _outerToInner = true; + return this; + } + + /// + /// Prints the resolved call stack to the provided , + /// or to if no writer is given. + /// Useful for debugging test failures. + /// + public DumpTestStackWalker Print(Action? writer = null) + { + writer ??= Console.WriteLine; + writer($"Call stack ({_frames.Count} frames, {(_outerToInner ? "outer->inner" : "inner->outer")}):"); + + for (int i = 0; i < _frames.Count; i++) + { + int frameIndex = _outerToInner ? _frames.Count - 1 - i : i; + ResolvedFrame f = _frames[frameIndex]; + string name = f.Name ?? ""; + string md = f.MethodDescPtr != TargetPointer.Null + ? $"0x{(ulong)f.MethodDescPtr:X}" + : "null"; + writer($" [{i}] {name} (MethodDesc: {md})"); + } + + return this; + } + + /// + /// Expects a frame with after the previous expectation. + /// Gaps (unmatched frames) between this and the previous expectation are allowed. + /// + public DumpTestStackWalker ExpectFrame(string methodName, Action? assert = null) + { + _expectations.Add(new Expectation(methodName, adjacent: false, assert)); + return this; + } + + /// + /// Expects a frame with immediately after the + /// previously matched frame (no gaps allowed). + /// + public DumpTestStackWalker ExpectAdjacentFrame(string methodName, Action? assert = null) + { + Assert.True(_expectations.Count > 0, + "ExpectAdjacentFrame must follow a prior ExpectFrame or ExpectAdjacentFrame."); + _expectations.Add(new Expectation(methodName, adjacent: true, assert)); + return this; + } + + /// + /// Expects the next frame (at the current search position) to satisfy + /// , without matching by name. + /// Gaps before this frame are allowed. + /// + public DumpTestStackWalker ExpectFrameWhere(Func predicate, string description, Action? assert = null) + { + _expectations.Add(new Expectation(predicate, description, adjacent: false, assert)); + return this; + } + + /// + /// Expects the frame immediately after the previously matched frame to satisfy + /// (no gaps allowed). + /// + public DumpTestStackWalker ExpectAdjacentFrameWhere(Func predicate, string description, Action? assert = null) + { + Assert.True(_expectations.Count > 0, + "ExpectAdjacentFrameWhere must follow a prior expectation."); + _expectations.Add(new Expectation(predicate, description, adjacent: true, assert)); + return this; + } + + /// + /// Asserts that the call stack contains a frame with the given + /// , regardless of position or order. + /// + public DumpTestStackWalker AssertHasFrame(string methodName) + { + Assert.True(_frames.Any(f => string.Equals(f.Name, methodName, StringComparison.Ordinal)), + $"Expected frame '{methodName}' not found. Call stack: [{FormatCallStack(_frames)}]"); + return this; + } + + /// + /// Verifies all expectations added via , + /// , and . + /// + public void Verify() + { + // When outer-to-inner, reverse so expectations match caller -> callee order. + List ordered = _outerToInner + ? new List(Enumerable.Reverse(_frames)) + : _frames; + + int searchStart = 0; + int previousMatchIndex = -1; + + foreach (Expectation expectation in _expectations) + { + if (expectation.Adjacent) + { + int requiredIndex = previousMatchIndex + 1; + Assert.True(requiredIndex < ordered.Count, + $"Expected adjacent frame '{expectation.Description}' but stack ended at index {previousMatchIndex}. " + + $"Call stack: [{FormatCallStack(ordered)}]"); + + Assert.True(expectation.Matches(ordered[requiredIndex]), + $"Expected adjacent frame '{expectation.Description}' at index {requiredIndex}, " + + $"but found '{ordered[requiredIndex].Name ?? ""}'. " + + $"Call stack: [{FormatCallStack(ordered)}]"); + + expectation.RunAssert(ordered[requiredIndex]); + previousMatchIndex = requiredIndex; + searchStart = requiredIndex + 1; + } + else + { + int foundIndex = -1; + for (int i = searchStart; i < ordered.Count; i++) + { + if (expectation.Matches(ordered[i])) + { + foundIndex = i; + break; + } + } + + Assert.True(foundIndex >= 0, + $"Expected frame '{expectation.Description}' not found (searching from index {searchStart}). " + + $"Call stack: [{FormatCallStack(ordered)}]"); + + expectation.RunAssert(ordered[foundIndex]); + previousMatchIndex = foundIndex; + searchStart = foundIndex + 1; + } + } + } + + private static string FormatCallStack(List frames) + => string.Join(", ", frames.Select(f => f.Name ?? "")); + + private sealed class Expectation + { + private readonly string? _name; + private readonly Func? _predicate; + private readonly Action? _assert; + + public bool Adjacent { get; } + public string Description { get; } + + public Expectation(string name, bool adjacent, Action? assert) + { + _name = name; + _predicate = null; + _assert = assert; + Adjacent = adjacent; + Description = name; + } + + public Expectation(Func predicate, string description, bool adjacent, Action? assert) + { + _name = null; + _predicate = predicate; + _assert = assert; + Adjacent = adjacent; + Description = description; + } + + public bool Matches(ResolvedFrame frame) + { + if (_predicate is not null) + return _predicate(frame); + + return string.Equals(frame.Name, _name, StringComparison.Ordinal); + } + + public void RunAssert(ResolvedFrame frame) + { + _assert?.Invoke(frame); + } + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/SkipOnArchAttribute.cs b/src/native/managed/cdac/tests/DumpTests/SkipOnArchAttribute.cs new file mode 100644 index 00000000000000..86a28c2fd27707 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/SkipOnArchAttribute.cs @@ -0,0 +1,25 @@ +// 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.DumpTests; + +/// +/// When applied to a test method, causes the test to be skipped when +/// the dump architecture matches the specified value. Checked by +/// before each test runs. +/// Multiple attributes can be stacked on a single method. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class SkipOnArchAttribute : Attribute +{ + public string Arch { get; } + public string Reason { get; } + + public SkipOnArchAttribute(string arch, string reason) + { + Arch = arch; + Reason = reason; + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs new file mode 100644 index 00000000000000..952b361636a6f8 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs @@ -0,0 +1,213 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for WalkStackReferences. +/// Uses the InitializeDumpTest overload to target different debuggees per test. +/// +public class StackReferenceDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "StackWalk"; + protected override string DumpType => "full"; + + // --- StackWalk debuggee: basic stack walk --- + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public void WalkStackReferences_ReturnsWithoutThrowing(TestConfiguration config) + { + InitializeDumpTest(config, "StackWalk", "full"); + IStackWalk stackWalk = Target.Contracts.StackWalk; + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + + IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); + Assert.NotNull(refs); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public void WalkStackReferences_RefsHaveValidSourceInfo(TestConfiguration config) + { + InitializeDumpTest(config, "StackWalk", "full"); + IStackWalk stackWalk = Target.Contracts.StackWalk; + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + + IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); + foreach (StackReferenceData r in refs) + { + Assert.True(r.Source != TargetPointer.Null, "Stack reference should have a non-null Source (IP or Frame address)"); + Assert.True(r.StackPointer != TargetPointer.Null, "Stack reference should have a non-null StackPointer"); + } + } + + // --- GCRoots debuggee: objects kept alive on stack --- + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + [SkipOnArch("x86", "GCInfo decoder does not support x86")] + public void GCRoots_WalkStackReferences_FindsRefs(TestConfiguration config) + { + InitializeDumpTest(config, "GCRoots", "full"); + IStackWalk stackWalk = Target.Contracts.StackWalk; + + ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "Main"); + + IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); + Assert.NotNull(refs); + Assert.True(refs.Count > 0, + "Expected GCRoots Main thread to have at least one stack reference (objects kept alive via GC.KeepAlive)"); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + [SkipOnArch("x86", "GCInfo decoder does not support x86")] + public void GCRoots_RefsPointToValidObjects(TestConfiguration config) + { + InitializeDumpTest(config, "GCRoots", "full"); + IStackWalk stackWalk = Target.Contracts.StackWalk; + + ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "Main"); + + IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); + + int validObjectCount = 0; + foreach (StackReferenceData r in refs) + { + if (r.Object == TargetPointer.Null) + continue; + + try + { + TargetPointer methodTable = Target.ReadPointer(r.Object); + if (methodTable != TargetPointer.Null) + validObjectCount++; + } + catch + { + // Some refs may be interior pointers or otherwise unreadable + } + } + + Assert.True(validObjectCount > 0, + $"Expected at least one stack ref pointing to a valid object (total refs: {refs.Count})"); + } + + // --- StackRefs debuggee: known objects on stack with verifiable content --- + // These tests require Frame-based GC root scanning (ScanFrameRoots) which is not yet implemented. + + [Theory(Skip = "Requires Frame-based GC root scanning (ScanFrameRoots) — not yet implemented")] + [MemberData(nameof(TestConfigurations))] + public void StackRefs_FindsMarkerString(TestConfiguration config) + { + InitializeDumpTest(config, "StackRefs", "full"); + IStackWalk stackWalk = Target.Contracts.StackWalk; + IObject objectContract = Target.Contracts.Object; + + ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "MethodWithStackRefs"); + + IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); + Assert.True(refs.Count > 0, "Expected at least one stack reference from MethodWithStackRefs"); + + bool foundMarker = false; + string expectedMarker = "cDAC-StackRefs-Marker-12345"; + + foreach (StackReferenceData r in refs) + { + if (r.Object == TargetPointer.Null) + continue; + + try + { + string value = objectContract.GetStringValue(r.Object); + if (value == expectedMarker) + { + foundMarker = true; + break; + } + } + catch + { + // Not a string or not readable — skip + } + } + + Assert.True(foundMarker, + $"Expected to find marker string '{expectedMarker}' among {refs.Count} stack references"); + } + + [Theory(Skip = "Requires Frame-based GC root scanning (ScanFrameRoots) — not yet implemented")] + [MemberData(nameof(TestConfigurations))] + public void StackRefs_FindsArrayReference(TestConfiguration config) + { + InitializeDumpTest(config, "StackRefs", "full"); + IStackWalk stackWalk = Target.Contracts.StackWalk; + IObject objectContract = Target.Contracts.Object; + + ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "MethodWithStackRefs"); + + IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); + Assert.True(refs.Count > 0, "Expected at least one stack reference from MethodWithStackRefs"); + + // Look for the int[] { 1, 2, 3, 4, 5 } array using the Object contract. + bool foundArray = false; + + foreach (StackReferenceData r in refs) + { + if (r.Object == TargetPointer.Null) + continue; + + try + { + TargetPointer dataStart = objectContract.GetArrayData(r.Object, out uint count, out _, out _); + if (count != 5) + continue; + + int elem0 = Target.Read(dataStart + sizeof(int) * 0); + int elem1 = Target.Read(dataStart + sizeof(int) * 1); + int elem2 = Target.Read(dataStart + sizeof(int) * 2); + + if (elem0 == 1 && elem1 == 2 && elem2 == 3) + { + foundArray = true; + break; + } + } + catch + { + // Not an array or not readable — skip + } + } + + Assert.True(foundArray, + $"Expected to find int[]{{1,2,3,4,5}} among {refs.Count} stack references"); + } + + // --- PInvokeStub debuggee: Frame-based path --- + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + [SkipOnOS(IncludeOnly = "windows", Reason = "PInvokeStub debuggee uses msvcrt.dll (Windows only)")] + public void PInvoke_WalkStackReferences_ReturnsWithoutThrowing(TestConfiguration config) + { + InitializeDumpTest(config, "PInvokeStub", "full"); + IStackWalk stackWalk = Target.Contracts.StackWalk; + + ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "Main"); + + IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); + Assert.NotNull(refs); + } +} diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/BasicAlloc.csproj b/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/BasicAlloc.csproj new file mode 100644 index 00000000000000..6b512ec9245ec3 --- /dev/null +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/BasicAlloc.csproj @@ -0,0 +1 @@ + diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/Program.cs b/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/Program.cs new file mode 100644 index 00000000000000..f886c0ef72cefe --- /dev/null +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/Program.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +/// +/// Exercises basic object allocation patterns: objects, strings, arrays. +/// +internal static class Program +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static object AllocAndHold() + { + object o = new object(); + string s = "hello world"; + int[] arr = new int[] { 1, 2, 3 }; + byte[] buf = new byte[256]; + GC.KeepAlive(o); + GC.KeepAlive(s); + GC.KeepAlive(arr); + GC.KeepAlive(buf); + return o; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void ManyLiveRefs() + { + object r0 = new object(); + object r1 = new object(); + object r2 = new object(); + object r3 = new object(); + object r4 = new object(); + object r5 = new object(); + object r6 = new object(); + object r7 = new object(); + string r8 = "live-string"; + int[] r9 = new int[10]; + + GC.KeepAlive(r0); GC.KeepAlive(r1); + GC.KeepAlive(r2); GC.KeepAlive(r3); + GC.KeepAlive(r4); GC.KeepAlive(r5); + GC.KeepAlive(r6); GC.KeepAlive(r7); + GC.KeepAlive(r8); GC.KeepAlive(r9); + } + + static int Main() + { + for (int i = 0; i < 2; i++) + { + AllocAndHold(); + ManyLiveRefs(); + } + return 100; + } +} diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Comprehensive.csproj b/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Comprehensive.csproj new file mode 100644 index 00000000000000..6b512ec9245ec3 --- /dev/null +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Comprehensive.csproj @@ -0,0 +1 @@ + diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Program.cs b/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Program.cs new file mode 100644 index 00000000000000..6a2f26f146ef0f --- /dev/null +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Program.cs @@ -0,0 +1,253 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +/// +/// All-in-one comprehensive debuggee that exercises every scenario +/// in a single run: allocations, exceptions, generics, P/Invoke, threading. +/// +internal static class Program +{ + interface IKeepAlive { object GetRef(); } + class BoxHolder : IKeepAlive + { + object _value; + public BoxHolder() { _value = new object(); } + public BoxHolder(object v) { _value = v; } + [MethodImpl(MethodImplOptions.NoInlining)] + public object GetRef() => _value; + } + + struct LargeStruct { public object A, B, C, D; } + + [MethodImpl(MethodImplOptions.NoInlining)] + static object AllocAndHold() + { + object o = new object(); + string s = "hello world"; + int[] arr = new int[] { 1, 2, 3 }; + GC.KeepAlive(o); + GC.KeepAlive(s); + GC.KeepAlive(arr); + return o; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void NestedCall(int depth) + { + object o = new object(); + if (depth > 0) + NestedCall(depth - 1); + GC.KeepAlive(o); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void TryCatchScenario() + { + object before = new object(); + try + { + throw new InvalidOperationException("test"); + } + catch (InvalidOperationException ex) + { + object inCatch = new object(); + GC.KeepAlive(ex); + GC.KeepAlive(inCatch); + } + GC.KeepAlive(before); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void TryFinallyScenario() + { + object outerRef = new object(); + try + { + object innerRef = new object(); + GC.KeepAlive(innerRef); + } + finally + { + object finallyRef = new object(); + GC.KeepAlive(finallyRef); + } + GC.KeepAlive(outerRef); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void NestedExceptionScenario() + { + object a = new object(); + try + { + try + { + throw new ArgumentException("inner"); + } + catch (ArgumentException ex1) + { + GC.KeepAlive(ex1); + throw new InvalidOperationException("outer", ex1); + } + } + catch (InvalidOperationException ex2) + { + GC.KeepAlive(ex2); + } + GC.KeepAlive(a); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void FilterExceptionScenario() + { + object holder = new object(); + try + { + throw new ArgumentException("filter-test"); + } + catch (ArgumentException ex) when (FilterCheck(ex)) + { + GC.KeepAlive(ex); + } + GC.KeepAlive(holder); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool FilterCheck(Exception ex) + { + object filterLocal = new object(); + GC.KeepAlive(filterLocal); + return ex.Message.Contains("filter"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static T GenericAlloc() where T : new() + { + T val = new T(); + object marker = new object(); + GC.KeepAlive(marker); + return val; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void InterfaceDispatchScenario() + { + IKeepAlive holder = new BoxHolder(new int[] { 42, 43 }); + object r = holder.GetRef(); + GC.KeepAlive(holder); + GC.KeepAlive(r); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void DelegateScenario() + { + object captured = new object(); + Func fn = () => { GC.KeepAlive(captured); return new object(); }; + object result = fn(); + GC.KeepAlive(result); + GC.KeepAlive(fn); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void StructWithRefsScenario() + { + LargeStruct ls; + ls.A = new object(); + ls.B = "struct-string"; + ls.C = new int[] { 10, 20 }; + ls.D = new BoxHolder(ls.A); + GC.KeepAlive(ls.A); + GC.KeepAlive(ls.B); + GC.KeepAlive(ls.C); + GC.KeepAlive(ls.D); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void PinnedScenario() + { + byte[] buffer = new byte[64]; + GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + object other = new object(); + GC.KeepAlive(other); + GC.KeepAlive(buffer); + } + finally + { + pin.Free(); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void MultiThreadScenario() + { + ManualResetEventSlim ready = new ManualResetEventSlim(false); + ManualResetEventSlim go = new ManualResetEventSlim(false); + Thread t = new Thread(() => + { + object threadLocal = new object(); + ready.Set(); + go.Wait(); + NestedCall(5); + GC.KeepAlive(threadLocal); + }); + t.Start(); + ready.Wait(); + go.Set(); + NestedCall(3); + t.Join(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void RethrowScenario() + { + object outerRef = new object(); + try + { + try + { + throw new ApplicationException("rethrow-test"); + } + catch (ApplicationException) + { + object catchRef = new object(); + GC.KeepAlive(catchRef); + throw; + } + } + catch (ApplicationException ex) + { + GC.KeepAlive(ex); + } + GC.KeepAlive(outerRef); + } + + static int Main() + { + for (int i = 0; i < 2; i++) + { + AllocAndHold(); + NestedCall(5); + TryCatchScenario(); + TryFinallyScenario(); + NestedExceptionScenario(); + FilterExceptionScenario(); + GenericAlloc(); + GenericAlloc>(); + InterfaceDispatchScenario(); + DelegateScenario(); + StructWithRefsScenario(); + PinnedScenario(); + MultiThreadScenario(); + RethrowScenario(); + } + return 100; + } +} diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/Directory.Build.props b/src/native/managed/cdac/tests/StressTests/Debuggees/Directory.Build.props new file mode 100644 index 00000000000000..e0bc33ad142047 --- /dev/null +++ b/src/native/managed/cdac/tests/StressTests/Debuggees/Directory.Build.props @@ -0,0 +1,15 @@ + + + + + Exe + $(NetCoreAppToolCurrent) + true + enable + $(ArtifactsBinDir)StressTests\$(MSBuildProjectName)\$(Configuration)\ + true + + false + $(NoWarn);SA1400;IDE0059;SYSLIB1054;CA1852;CA1861 + + diff --git a/src/native/managed/cdac/tests/StressTests/README.md b/src/native/managed/cdac/tests/StressTests/README.md new file mode 100644 index 00000000000000..c5bcde5675b3f0 --- /dev/null +++ b/src/native/managed/cdac/tests/StressTests/README.md @@ -0,0 +1,108 @@ +# cDAC Stress Tests + +This folder contains stress tests that verify the cDAC's stack reference +enumeration against the runtime's GC root scanning. The tests run managed +debuggee applications under `corerun` with cDAC stress flags enabled, +triggering verification at allocation points, GC points, or instruction-level +GC stress points. + +## Quick Start + +```powershell +# Prerequisites: build CoreCLR Checked and generate core_root +# build.cmd clr+libs -rc Checked -lc Release +# src\tests\build.cmd Checked generatelayoutonly /p:LibrariesConfiguration=Release + +# Run all debuggees (allocation-point verification, no GCStress) +./RunStressTests.ps1 -SkipBuild + +# Run a single debuggee +./RunStressTests.ps1 -SkipBuild -Debuggee BasicAlloc + +# Run with instruction-level GCStress (slower, more thorough) +./RunStressTests.ps1 -SkipBuild -CdacStress 0x14 -GCStress 0x4 + +# Full comparison including walk parity and DAC cross-check +./RunStressTests.ps1 -SkipBuild -CdacStress 0x74 -GCStress 0x4 +``` + +## How It Works + +### DOTNET_CdacStress Flags + +The `DOTNET_CdacStress` environment variable is a bitmask that controls +**where** and **what** the runtime verifies: + +| Bit | Flag | Description | +|-----|------|-------------| +| 0x1 | ALLOC | Verify at managed allocation points | +| 0x2 | GC | Verify at GC collection points | +| 0x4 | INSTR | Verify at instruction-level GC stress points (requires `DOTNET_GCStress`) | +| 0x10 | REFS | Compare GC stack references (cDAC vs runtime) | +| 0x20 | WALK | Compare stack walk frame ordering (cDAC vs DAC) | +| 0x40 | USE_DAC | Also compare GC refs against the legacy DAC | +| 0x100 | UNIQUE | Only verify each instruction pointer once | + +Common combinations: +- `0x11` — ALLOC + REFS (fast, default) +- `0x14` — INSTR + REFS (thorough, requires `DOTNET_GCStress=0x4`) +- `0x31` — ALLOC + REFS + WALK (fast with walk parity check) +- `0x74` — INSTR + REFS + WALK + USE_DAC (full comparison) + +### Verification Flow + +At each stress point, the native hook (`cdacstress.cpp`) in the runtime: + +1. Suspends the current thread's context +2. Calls the cDAC's `GetStackReferences` to enumerate GC roots +3. Compares against the runtime's own GC root enumeration +4. Optionally compares against the legacy DAC's enumeration +5. Optionally compares stack walk frame ordering +6. Logs `[PASS]` or `[FAIL]` per verification point + +The script collects these results and reports aggregate pass/fail counts. + +## Debuggees + +Each debuggee is a standalone console application under `Debuggees/`: + +| Debuggee | Scenarios | +|----------|-----------| +| **BasicAlloc** | Object allocation, strings, arrays, many live refs | +| **Comprehensive** | All-in-one: allocations, deep stacks, exceptions, generics, P/Invoke, threading | + +All debuggees return exit code 100 on success. + +### Adding a New Debuggee + +1. Create a new folder under `Debuggees/` (e.g., `Debuggees/MyScenario/`) +2. Add a minimal `.csproj`: + ```xml + + ``` + The `Directory.Build.props` provides all common settings. +3. Add a `Program.cs` with a `Main()` that returns 100 +4. Use `[MethodImpl(MethodImplOptions.NoInlining)]` and `GC.KeepAlive()` + to prevent the JIT from optimizing away allocations and references + +The script auto-discovers all debuggees by scanning for `.csproj` files. + +## Script Parameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `-Configuration` | `Checked` | Runtime build configuration | +| `-CdacStress` | `0x11` | Hex bitmask for `DOTNET_CdacStress` | +| `-GCStress` | _(empty)_ | Hex value for `DOTNET_GCStress` (e.g., `0x4`) | +| `-Debuggee` | _(all)_ | Which debuggee(s) to run | +| `-SkipBuild` | off | Skip CoreCLR/cDAC build step | +| `-SkipBaseline` | off | Skip baseline (no-stress) verification | + +## Expected Results + +Most runs achieve >99.5% pass rate. A small number of failures (~0.2%) +are expected due to the ScanFrameRoots gap — the cDAC does not yet enumerate +GC roots from explicit frame stub data (e.g., `StubDispatchFrame`, +`PInvokeCalliFrame`). These are tracked in [known-issues.md](known-issues.md). + +Walk parity (`WALK` flag) should show 0 mismatches. diff --git a/src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 b/src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 new file mode 100644 index 00000000000000..f51933048c5ac4 --- /dev/null +++ b/src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 @@ -0,0 +1,309 @@ +<# +.SYNOPSIS + Build and test the cDAC stress verification mode. + +.DESCRIPTION + This script: + 1. Builds CoreCLR native + cDAC tools (incremental) + 2. Generates core_root layout + 3. Builds debuggee test apps from the Debuggees/ folder + 4. Runs each debuggee under corerun with configurable cDAC stress flags + + Supports Windows, Linux, and macOS. + + The DOTNET_CdacStress environment variable controls WHERE and WHAT is verified: + WHERE (low nibble): + 0x1 = ALLOC — verify at allocation points + 0x2 = GC — verify at GC points + 0x4 = INSTR — verify at instruction-level GC stress points (requires DOTNET_GCStress) + WHAT (high nibble): + 0x10 = REFS — compare GC stack references (cDAC vs runtime) + 0x20 = WALK — compare stack walk frames (cDAC vs DAC) + 0x40 = USE_DAC — also compare GC refs against DAC + MODIFIER: + 0x100 = UNIQUE — only verify each IP once + +.PARAMETER Configuration + Runtime configuration: Checked (default) or Debug. + +.PARAMETER CdacStress + Hex value for DOTNET_CdacStress flags. Default: 0x11 (ALLOC|REFS). + Common values: + 0x11 = ALLOC|REFS (fast, allocation points only) + 0x14 = INSTR|REFS (thorough, requires GCStress) + 0x74 = INSTR|REFS|WALK|USE_DAC (full comparison, slow) + +.PARAMETER GCStress + Hex value for DOTNET_GCStress. Default: empty (disabled). + Set to 0x4 for instruction-level stress. + +.PARAMETER Debuggee + Which debuggee(s) to run. Default: All. + Auto-discovered from the Debuggees directory. + +.PARAMETER SkipBuild + Skip the CoreCLR/cDAC build step (use existing artifacts). + +.PARAMETER SkipBaseline + Skip baseline verification steps. + +.EXAMPLE + ./RunStressTests.ps1 -SkipBuild + ./RunStressTests.ps1 -Debuggee BasicAlloc -SkipBuild + ./RunStressTests.ps1 -CdacStress 0x74 -GCStress 0x4 # Full comparison with GCStress + ./RunStressTests.ps1 -CdacStress 0x114 -SkipBuild # Unique IPs only +#> +param( + [ValidateSet("Checked", "Debug")] + [string]$Configuration = "Checked", + + [string]$CdacStress = "0x11", + + [string]$GCStress = "", + + [string[]]$Debuggee = @(), + + [switch]$SkipBuild, + + [switch]$SkipBaseline +) + +$ErrorActionPreference = "Stop" +$scriptDir = $PSScriptRoot +$repoRoot = $scriptDir + +# Resolve repo root — walk up from script location to find build script +$buildScript = if ($IsWindows -or $env:OS -eq "Windows_NT") { "build.cmd" } else { "build.sh" } +while ($repoRoot -and !(Test-Path (Join-Path $repoRoot $buildScript))) { + $parent = Split-Path $repoRoot -Parent + if ($parent -eq $repoRoot) { $repoRoot = $null; break } + $repoRoot = $parent +} +if (-not $repoRoot) { + Write-Error "Could not find repo root ($buildScript). Place this script inside the runtime repo." + exit 1 +} + +# Detect platform +$isWin = ($IsWindows -or $env:OS -eq "Windows_NT") +$osName = if ($isWin) { "windows" } elseif ($IsMacOS) { "osx" } else { "linux" } +$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() +$arch = switch ($arch) { + "x64" { "x64" } + "arm64" { "arm64" } + "arm" { "arm" } + "x86" { "x86" } + default { throw "Unsupported architecture: $arch" } +} + +$platformId = "$osName.$arch" +$coreRoot = Join-Path $repoRoot "artifacts" "tests" "coreclr" "$platformId.$Configuration" "Tests" "Core_Root" +$buildCmd = Join-Path $repoRoot $buildScript +$dotnetName = if ($isWin) { "dotnet.exe" } else { "dotnet" } +$corerunName = if ($isWin) { "corerun.exe" } else { "corerun" } +$dotnetExe = Join-Path $repoRoot ".dotnet" $dotnetName +$corerunExe = Join-Path $coreRoot $corerunName +$cdacDll = if ($isWin) { "mscordaccore_universal.dll" } elseif ($IsMacOS) { "libmscordaccore_universal.dylib" } else { "libmscordaccore_universal.so" } +$debuggeesDir = Join-Path $scriptDir "Debuggees" + +# Discover available debuggees +$allDebuggees = Get-ChildItem $debuggeesDir -Directory | Where-Object { Test-Path (Join-Path $_.FullName "*.csproj") } | ForEach-Object { $_.Name } + +# Resolve which debuggees to run +if ($Debuggee.Count -eq 0) { + $selectedDebuggees = $allDebuggees +} else { + $selectedDebuggees = $Debuggee + foreach ($d in $selectedDebuggees) { + if ($d -notin $allDebuggees) { + Write-Error "Unknown debuggee '$d'. Available: $($allDebuggees -join ', ')" + exit 1 + } + } +} + +Write-Host "=== cDAC Stress Test ===" -ForegroundColor Cyan +Write-Host " Repo root: $repoRoot" +Write-Host " Platform: $platformId" +Write-Host " Configuration: $Configuration" +Write-Host " CdacStress: $CdacStress" +Write-Host " GCStress: $(if ($GCStress) { $GCStress } else { '(disabled)' })" +Write-Host " Debuggees: $($selectedDebuggees -join ', ')" +Write-Host "" + +# --------------------------------------------------------------------------- +# Step 1: Build CoreCLR + cDAC +# --------------------------------------------------------------------------- +if (-not $SkipBuild) { + Write-Host ">>> Step 1: Building CoreCLR native + cDAC tools ($Configuration)..." -ForegroundColor Yellow + Push-Location $repoRoot + try { + $buildArgs = @("-subset", "clr.native+tools.cdac", "-c", $Configuration, "-rc", $Configuration, "-lc", "Release", "-bl") + & $buildCmd @buildArgs + if ($LASTEXITCODE -ne 0) { Write-Error "Build failed with exit code $LASTEXITCODE"; exit 1 } + } finally { + Pop-Location + } + + Write-Host ">>> Step 1b: Generating core_root layout..." -ForegroundColor Yellow + $testBuildScript = if ($isWin) { + Join-Path $repoRoot "src" "tests" "build.cmd" + } else { + Join-Path $repoRoot "src" "tests" "build.sh" + } + & $testBuildScript $Configuration generatelayoutonly -SkipRestorePackages /p:LibrariesConfiguration=Release + if ($LASTEXITCODE -ne 0) { Write-Error "Core_root generation failed"; exit 1 } +} else { + Write-Host ">>> Step 1: Skipping build (-SkipBuild)" -ForegroundColor DarkGray + if (!(Test-Path $corerunExe)) { + Write-Error "Core_root not found at $coreRoot. Run without -SkipBuild first." + exit 1 + } +} + +# Verify cDAC library exists +if (!(Test-Path (Join-Path $coreRoot $cdacDll))) { + Write-Error "$cdacDll not found in core_root. Ensure cDAC was built." + exit 1 +} + +# --------------------------------------------------------------------------- +# Step 2: Build debuggees +# --------------------------------------------------------------------------- +Write-Host ">>> Step 2: Building debuggees..." -ForegroundColor Yellow +foreach ($d in $selectedDebuggees) { + $csproj = Get-ChildItem (Join-Path $debuggeesDir $d) -Filter "*.csproj" | Select-Object -First 1 + & $dotnetExe build $csproj.FullName -c Release --nologo -v q + if ($LASTEXITCODE -ne 0) { Write-Error "Failed to build debuggee '$d'"; exit 1 } + Write-Host " Built $d" -ForegroundColor DarkGray +} + +# Helper: find the debuggee DLL in the build output +function Find-DebuggeeDll([string]$name) { + $binDir = Join-Path $repoRoot "artifacts" "bin" "StressTests" $name "Release" + if (!(Test-Path $binDir)) { + # Fall back to checking the project output directly + $projDir = Join-Path $debuggeesDir $name + $binDir = Join-Path $projDir "bin" "Release" + } + $dll = Get-ChildItem $binDir -Recurse -Filter "$name.dll" | Select-Object -First 1 + if (-not $dll) { + Write-Error "Could not find $name.dll in $binDir" + exit 1 + } + return $dll.FullName +} + +# Helper: clear stress environment variables +function Clear-StressEnv { + Remove-Item Env:\DOTNET_GCStress -ErrorAction SilentlyContinue + Remove-Item Env:\DOTNET_CdacStress -ErrorAction SilentlyContinue + Remove-Item Env:\DOTNET_CdacStressLogFile -ErrorAction SilentlyContinue + Remove-Item Env:\DOTNET_ContinueOnAssert -ErrorAction SilentlyContinue +} + +# Helper: run a debuggee with corerun and return exit code +function Invoke-Debuggee([string]$dllPath) { + $env:CORE_ROOT = $coreRoot + & $corerunExe $dllPath + return $LASTEXITCODE +} + +# --------------------------------------------------------------------------- +# Step 3: Run baseline (optional) +# --------------------------------------------------------------------------- +if (-not $SkipBaseline) { + Write-Host ">>> Step 3: Running baseline (no stress)..." -ForegroundColor Yellow + Clear-StressEnv + foreach ($d in $selectedDebuggees) { + $dll = Find-DebuggeeDll $d + $ec = Invoke-Debuggee $dll + if ($ec -ne 100) { + Write-Error "Baseline failed for '$d' (exit code $ec, expected 100)" + exit 1 + } + Write-Host " $d — baseline passed" -ForegroundColor DarkGray + } + Write-Host " All baselines passed." -ForegroundColor Green +} else { + Write-Host ">>> Skipping baseline (-SkipBaseline)" -ForegroundColor DarkGray +} + +# --------------------------------------------------------------------------- +# Step 4: Run with cDAC stress +# --------------------------------------------------------------------------- +Write-Host ">>> Step 4: Running with CdacStress=$CdacStress$(if ($GCStress) { " GCStress=$GCStress" })..." -ForegroundColor Yellow +$logDir = Join-Path $repoRoot "artifacts" "tests" "coreclr" "$platformId.$Configuration" "Tests" "cdacstresslogs" +New-Item -ItemType Directory -Force $logDir | Out-Null + +$totalPasses = 0 +$totalFails = 0 +$totalWalkOK = 0 +$totalWalkMM = 0 +$failedDebuggees = @() +$sw = [System.Diagnostics.Stopwatch]::StartNew() + +foreach ($d in $selectedDebuggees) { + $dll = Find-DebuggeeDll $d + $logFile = Join-Path $logDir "$d.log" + + Clear-StressEnv + $env:CORE_ROOT = $coreRoot + $env:DOTNET_CdacStress = $CdacStress + $env:DOTNET_CdacStressLogFile = $logFile + $env:DOTNET_ContinueOnAssert = "1" + if ($GCStress) { + $env:DOTNET_GCStress = $GCStress + } + + $dSw = [System.Diagnostics.Stopwatch]::StartNew() + & $corerunExe $dll + $ec = $LASTEXITCODE + $dSw.Stop() + + # Parse results + $passes = 0; $fails = 0; $walkOK = 0; $walkMM = 0 + if (Test-Path $logFile) { + $logContent = Get-Content $logFile + $passes = ($logContent | Select-String "^\[PASS\]").Count + $fails = ($logContent | Select-String "^\[FAIL\]").Count + $walkOK = ($logContent | Select-String "WALK_OK").Count + $walkMM = ($logContent | Select-String "WALK_MISMATCH").Count + } + + $totalPasses += $passes + $totalFails += $fails + $totalWalkOK += $walkOK + $totalWalkMM += $walkMM + + $status = if ($ec -eq 100) { "PASS" } else { "FAIL"; $failedDebuggees += $d } + $color = if ($ec -eq 100 -and $fails -eq 0) { "Green" } elseif ($ec -eq 100) { "Yellow" } else { "Red" } + $detail = "refs=$passes/$($passes+$fails)" + if ($walkOK -gt 0 -or $walkMM -gt 0) { $detail += " walk=$walkOK/$($walkOK+$walkMM)" } + Write-Host " $d — $status ($detail) [$($dSw.Elapsed.ToString('mm\:ss'))]" -ForegroundColor $color +} + +$sw.Stop() +Clear-StressEnv + +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- +Write-Host "" +Write-Host "=== Summary ===" -ForegroundColor Cyan +Write-Host " Elapsed: $($sw.Elapsed.ToString('mm\:ss'))" +Write-Host " Stress refs: $totalPasses PASS / $totalFails FAIL" -ForegroundColor $(if ($totalFails -eq 0) { "Green" } else { "Yellow" }) +if ($totalWalkOK -gt 0 -or $totalWalkMM -gt 0) { + Write-Host " Walk parity: $totalWalkOK OK / $totalWalkMM MISMATCH" -ForegroundColor $(if ($totalWalkMM -eq 0) { "Green" } else { "Yellow" }) +} +Write-Host " Logs: $logDir" + +if ($failedDebuggees.Count -gt 0) { + Write-Host "" + Write-Host "=== FAILED: $($failedDebuggees -join ', ') ===" -ForegroundColor Red + exit 1 +} else { + Write-Host "" + Write-Host "=== ALL PASSED ===" -ForegroundColor Green + exit 0 +} diff --git a/src/native/managed/cdac/tests/StressTests/known-issues.md b/src/native/managed/cdac/tests/StressTests/known-issues.md new file mode 100644 index 00000000000000..6445d255b67362 --- /dev/null +++ b/src/native/managed/cdac/tests/StressTests/known-issues.md @@ -0,0 +1,57 @@ +# cDAC Stack Reference Walking — Known Issues + +This document tracks known gaps between the cDAC's stack reference enumeration +and the legacy DAC's `GetStackReferences`. + +## Current Test Results + +Using `DOTNET_CdacStress` with cDAC-vs-DAC comparison: + +| Mode | Non-EH debuggees (6) | ExceptionHandling | +|------|-----------------------|-------------------| +| INSTR (0x4 + GCStress=0x4, step=10) | 0 failures | 0-2 failures | +| ALLOC+UNIQUE (0x101) | 0 failures | 4 failures | +| Walk comparison (0x20, IP+SP) | 0 mismatches | N/A | + +## Known Issue: cDAC Cannot Unwind Through Native Frames + +**Severity**: Low — only affects live-process stress testing during active +exception first-pass dispatch. Does not affect dump analysis where the thread +is suspended with a consistent Frame chain. + +**Pattern**: `cDAC < DAC` (cDAC reports 4 refs, DAC reports 10-13). +ExceptionHandling debuggee only, 4 deterministic occurrences per run. + +**Root cause**: The cDAC's `AMD64Unwinder.Unwind` (and equivalents for other +architectures) can only unwind **managed** frames — it checks +`ExecutionManager.GetCodeBlockHandle(IP)` first and returns false if the IP +is not in a managed code range. This means it cannot unwind through native +runtime frames (allocation helpers, EH dispatch code, etc.). + +When the allocation stress point fires during exception first-pass dispatch: + +1. The thread's `m_pFrame` is `FRAME_TOP` (no explicit Frames in the chain + because the InlinedCallFrame/SoftwareExceptionFrame have been popped or + not yet pushed at that point in the EH dispatch sequence) +2. The initial IP is in native code (allocation helper) +3. The cDAC attempts to unwind through native frames but + `GetCodeBlockHandle` returns null for native IPs → unwind fails +4. With no Frames and no ability to unwind, the walk stops early + +The legacy DAC's `DacStackReferenceWalker::WalkStack` succeeds because +`StackWalkFrames` calls `VirtualUnwindToFirstManagedCallFrame` which uses +OS-level unwind (`RtlVirtualUnwind` on Windows, `PAL_VirtualUnwind` on Unix) +that can unwind ANY native frame using PE `.pdata`/`.xdata` sections. + +**Possible fixes**: +1. **Ensure Frames are always available** — change the runtime to keep + an explicit Frame pushed during allocation points within EH dispatch. + The cDAC cannot do OS-level native unwind (it operates on dumps where + `RtlVirtualUnwind` is not available). The Frame chain is the only + mechanism the cDAC has for transitioning through native code to reach + managed frames. If `m_pFrame = FRAME_TOP` when the IP is native, the + cDAC cannot proceed. +2. **Accept as known limitation** — these failures only occur during + live-process stress testing at a narrow window during EH first-pass + dispatch. In dumps, the exception state is frozen and the Frame chain + is consistent. diff --git a/src/tests/restore.proj b/src/tests/restore.proj new file mode 100644 index 00000000000000..7b995a4ffcf9f0 --- /dev/null +++ b/src/tests/restore.proj @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/tests/restore.projects.props b/src/tests/restore.projects.props new file mode 100644 index 00000000000000..285dd6d6cb4d55 --- /dev/null +++ b/src/tests/restore.projects.props @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + From 548d0003f61337516bc83f23ca68f960a3589a19 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 18:59:57 +0000 Subject: [PATCH 3/7] Remove unrelated files accidentally included from main branch checkout Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e622ed4f-7db6-4d3e-a09c-d121e640f0fe Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- docs/design/datacontracts/Debugger.md | 68 - src/coreclr/vm/cdacstress.cpp | 1211 ---------------- src/coreclr/vm/cdacstress.h | 125 -- .../Fuzzers/SslStreamClientHelloFuzzer.cs | 48 - .../Fuzzing/DotnetFuzzing/SR.Fake.cs | 5 - src/libraries/es-metadata.yml | 8 - .../Contracts/IDebugger.cs | 22 - .../Contracts/DebuggerFactory.cs | 18 - .../Contracts/Debugger_1.cs | 48 - .../Contracts/StackWalk/ExceptionHandling.cs | 182 --- .../Contracts/StackWalk/GC/GcScanContext.cs | 117 -- .../Contracts/StackWalk/GC/GcScanFlags.cs | 14 - .../StackWalk/GC/GcScanSlotLocation.cs | 6 - .../Contracts/StackWalk/GC/GcScanner.cs | 109 -- .../Contracts/StackWalk/GC/StackRefData.cs | 23 - .../Data/Debugger.cs | 23 - .../Dbi/DacDbiImpl.cs | 1274 ----------------- .../managed/cdac/tests/DebuggerTests.cs | 171 --- .../DacDbi/DacDbiAppDomainDumpTests.cs | 217 --- .../DacDbi/DacDbiDebuggerDumpTests.cs | 126 -- .../DumpTests/DacDbi/DacDbiGCDumpTests.cs | 85 -- .../DumpTests/DacDbi/DacDbiLoaderDumpTests.cs | 36 - .../DumpTests/DacDbi/DacDbiObjectDumpTests.cs | 72 - .../DumpTests/DacDbi/DacDbiThreadDumpTests.cs | 168 --- .../DumpTests/DacDbi/NativeStringHolder.cs | 68 - .../DumpTests/Debuggees/StackRefs/Program.cs | 42 - .../Debuggees/StackRefs/StackRefs.csproj | 5 - .../tests/DumpTests/DumpTestStackWalker.cs | 302 ---- .../tests/DumpTests/SkipOnArchAttribute.cs | 25 - .../DumpTests/StackReferenceDumpTests.cs | 213 --- .../Debuggees/BasicAlloc/BasicAlloc.csproj | 1 - .../Debuggees/BasicAlloc/Program.cs | 56 - .../Comprehensive/Comprehensive.csproj | 1 - .../Debuggees/Comprehensive/Program.cs | 253 ---- .../Debuggees/Directory.Build.props | 15 - .../managed/cdac/tests/StressTests/README.md | 108 -- .../cdac/tests/StressTests/RunStressTests.ps1 | 309 ---- .../cdac/tests/StressTests/known-issues.md | 57 - src/tests/restore.proj | 7 - src/tests/restore.projects.props | 17 - 40 files changed, 5655 deletions(-) delete mode 100644 docs/design/datacontracts/Debugger.md delete mode 100644 src/coreclr/vm/cdacstress.cpp delete mode 100644 src/coreclr/vm/cdacstress.h delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/SslStreamClientHelloFuzzer.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/SR.Fake.cs delete mode 100644 src/libraries/es-metadata.yml delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ExceptionHandling.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanFlags.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanSlotLocation.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs delete mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs delete mode 100644 src/native/managed/cdac/tests/DebuggerTests.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/Program.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/StackRefs.csproj delete mode 100644 src/native/managed/cdac/tests/DumpTests/DumpTestStackWalker.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/SkipOnArchAttribute.cs delete mode 100644 src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs delete mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/BasicAlloc.csproj delete mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/Program.cs delete mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Comprehensive.csproj delete mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Program.cs delete mode 100644 src/native/managed/cdac/tests/StressTests/Debuggees/Directory.Build.props delete mode 100644 src/native/managed/cdac/tests/StressTests/README.md delete mode 100644 src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 delete mode 100644 src/native/managed/cdac/tests/StressTests/known-issues.md delete mode 100644 src/tests/restore.proj delete mode 100644 src/tests/restore.projects.props diff --git a/docs/design/datacontracts/Debugger.md b/docs/design/datacontracts/Debugger.md deleted file mode 100644 index 03ee1ebae704d2..00000000000000 --- a/docs/design/datacontracts/Debugger.md +++ /dev/null @@ -1,68 +0,0 @@ -# Contract Debugger - -This contract is for reading debugger state from the target process, including initialization status, configuration flags, metadata update state, and JIT attach state. - -## APIs of contract - -```csharp -record struct DebuggerData(uint DefinesBitField, uint MDStructuresVersion); -``` - -```csharp -bool TryGetDebuggerData(out DebuggerData data); -int GetAttachStateFlags(); -bool MetadataUpdatesApplied(); -``` - -## Version 1 - -The contract depends on the following globals - -| Global Name | Type | Description | -| --- | --- | --- | -| `Debugger` | TargetPointer | Address of the pointer to the Debugger instance (`&g_pDebugger`) | -| `CLRJitAttachState` | TargetPointer | Pointer to the CLR JIT attach state flags | -| `MetadataUpdatesApplied` | TargetPointer | Pointer to the g_metadataUpdatesApplied flag | - -The contract additionally depends on these data descriptors - -| Data Descriptor Name | Field | Meaning | -| --- | --- | --- | -| `Debugger` | `LeftSideInitialized` | Whether the left-side debugger infrastructure is initialized | -| `Debugger` | `Defines` | Bitfield of compile-time debugger feature defines | -| `Debugger` | `MDStructuresVersion` | Version of metadata data structures | - -```csharp -bool TryGetDebuggerData(out DebuggerData data) -{ - data = default; - // The Debugger global points to g_pDebugger (a pointer-to-pointer). - // First read gets the address of g_pDebugger, second dereferences it. - TargetPointer debuggerPtrPtr = target.ReadGlobalPointer("Debugger"); - if (debuggerPtrPtr == TargetPointer.Null) - return false; - TargetPointer debuggerPtr = target.ReadPointer(debuggerPtrPtr); - if (debuggerPtr == TargetPointer.Null) - return false; - int leftSideInitialized = target.Read(debuggerPtr + /* Debugger::LeftSideInitialized offset */); - if (leftSideInitialized == 0) - return false; - data = new DebuggerData( - DefinesBitField: target.Read(debuggerPtr + /* Debugger::Defines offset */), - MDStructuresVersion: target.Read(debuggerPtr + /* Debugger::MDStructuresVersion offset */)); - return true; -} - -int GetAttachStateFlags() -{ - TargetPointer addr = target.ReadGlobalPointer("CLRJitAttachState"); - return (int)target.Read(addr); -} - -bool MetadataUpdatesApplied() -{ - if (target.TryReadGlobalPointer("MetadataUpdatesApplied", out TargetPointer addr)) - return target.Read(addr) != 0; - return false; -} -``` diff --git a/src/coreclr/vm/cdacstress.cpp b/src/coreclr/vm/cdacstress.cpp deleted file mode 100644 index 81564b0b37a6ec..00000000000000 --- a/src/coreclr/vm/cdacstress.cpp +++ /dev/null @@ -1,1211 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// -// CdacStress.cpp -// -// Implements in-process cDAC loading and stack reference verification. -// Enabled via DOTNET_CdacStress (bit flags) or legacy DOTNET_GCStress=0x20. -// At each enabled stress point we: -// 1. Ask the cDAC to enumerate stack GC references via ISOSDacInterface::GetStackReferences -// 2. Ask the runtime to enumerate stack GC references via StackWalkFrames + GcInfoDecoder -// 3. Compare the two sets and report any mismatches -// - -#include "common.h" - -#ifdef HAVE_GCCOVER - -#include "cdacstress.h" -#include "../../native/managed/cdac/inc/cdac_reader.h" -#include "../../debug/datadescriptor-shared/inc/contract-descriptor.h" -#include -#include -#include "threads.h" -#include "eeconfig.h" -#include "gccover.h" -#include "sstring.h" -#include "exinfo.h" - -// Forward-declare the 3-param GcEnumObject used as a GCEnumCallback. -// Defined in gcenv.ee.common.cpp; not exposed in any header. -extern void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags); - -#define CDAC_LIB_NAME MAKEDLLNAME_W(W("mscordaccore_universal")) - -// Represents a single GC stack reference for comparison purposes. -struct StackRef -{ - CLRDATA_ADDRESS Address; // Location on stack holding the ref - CLRDATA_ADDRESS Object; // The object pointer value - unsigned int Flags; // SOSRefFlags (interior, pinned) - CLRDATA_ADDRESS Source; // IP or Frame that owns this ref - int SourceType; // SOS_StackSourceIP or SOS_StackSourceFrame - int Register; // Register number (cDAC only) - int Offset; // Register offset (cDAC only) - CLRDATA_ADDRESS StackPointer; // Stack pointer at this ref (cDAC only) -}; - -// Fixed-size buffer for collecting refs during stack walk. -// No heap allocation inside the promote callback — we're under NOTHROW contracts. -static const int MAX_COLLECTED_REFS = 4096; - -// Static state — cDAC -static HMODULE s_cdacModule = NULL; -static intptr_t s_cdacHandle = 0; -static IUnknown* s_cdacSosInterface = nullptr; -static IXCLRDataProcess* s_cdacProcess = nullptr; // Cached QI result for Flush() -static ISOSDacInterface* s_cdacSosDac = nullptr; // Cached QI result for GetStackReferences() - -// Static state — legacy DAC (for three-way comparison) -static HMODULE s_dacModule = NULL; -static ISOSDacInterface* s_dacSosDac = nullptr; -static IXCLRDataProcess* s_dacProcess = nullptr; - -// Static state — common -static bool s_initialized = false; -static bool s_failFast = true; -static DWORD s_step = 1; // Verify every Nth stress point (1=every point) -static DWORD s_cdacStressLevel = 0; // Resolved CdacStressFlags -static FILE* s_logFile = nullptr; -static CrstStatic s_cdacLock; // Serializes cDAC access from concurrent GC stress threads - -// Unique-stack filtering: hash set of previously seen stack traces. -// Protected by s_cdacLock (already held during VerifyAtStressPoint). - -static SHash>>* s_seenStacks = nullptr; - -// Thread-local reentrancy guard — prevents infinite recursion when -// allocations inside VerifyAtStressPoint trigger VerifyAtAllocPoint. -thread_local bool t_inVerification = false; - -// Verification counters (reported at shutdown) -static volatile LONG s_verifyCount = 0; -static volatile LONG s_verifyPass = 0; -static volatile LONG s_verifyFail = 0; -static volatile LONG s_verifySkip = 0; - -// Thread-local storage for the current thread context at the stress point. -static thread_local PCONTEXT s_currentContext = nullptr; -static thread_local DWORD s_currentThreadId = 0; - -// Extern declaration for the contract descriptor symbol exported from coreclr. -extern "C" struct ContractDescriptor DotNetRuntimeContractDescriptor; - -//----------------------------------------------------------------------------- -// In-process callbacks for the cDAC reader. -// These allow the cDAC to read memory from the current process. -//----------------------------------------------------------------------------- - -// Helper for ReadFromTargetCallback — AVInRuntimeImplOkayHolder cannot be -// directly inside PAL_TRY scope (see controller.cpp:109). -static void ReadFromTargetHelper(void* src, uint8_t* dest, uint32_t count) -{ - AVInRuntimeImplOkayHolder AVOkay; - memcpy(dest, src, count); -} - -static int ReadFromTargetCallback(uint64_t addr, uint8_t* dest, uint32_t count, void* context) -{ - void* src = reinterpret_cast(static_cast(addr)); - struct Param { void* src; uint8_t* dest; uint32_t count; } param; - param.src = src; param.dest = dest; param.count = count; - PAL_TRY(Param *, pParam, ¶m) - { - ReadFromTargetHelper(pParam->src, pParam->dest, pParam->count); - } - PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) - { - return E_FAIL; - } - PAL_ENDTRY - return S_OK; -} - -static int WriteToTargetCallback(uint64_t addr, const uint8_t* buff, uint32_t count, void* context) -{ - return E_NOTIMPL; -} - -static int ReadThreadContextCallback(uint32_t threadId, uint32_t contextFlags, uint32_t contextBufferSize, uint8_t* contextBuffer, void* context) -{ - // Return the thread context that was stored by VerifyAtStressPoint. - if (s_currentContext != nullptr && s_currentThreadId == threadId) - { - DWORD copySize = min(contextBufferSize, (uint32_t)sizeof(CONTEXT)); - memcpy(contextBuffer, s_currentContext, copySize); - return S_OK; - } - - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: ReadThreadContext mismatch: requested=%u stored=%u\n", - threadId, s_currentThreadId)); - return E_FAIL; -} - -//----------------------------------------------------------------------------- -// Minimal ICLRDataTarget implementation for loading the legacy DAC in-process. -// Routes ReadVirtual/GetThreadContext to the same callbacks as the cDAC. -//----------------------------------------------------------------------------- -class InProcessDataTarget : public ICLRDataTarget, public ICLRRuntimeLocator -{ - volatile LONG m_refCount; -public: - InProcessDataTarget() : m_refCount(1) {} - virtual ~InProcessDataTarget() = default; - - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppObj) override - { - if (riid == IID_IUnknown || riid == __uuidof(ICLRDataTarget)) - { - *ppObj = static_cast(this); - AddRef(); - return S_OK; - } - if (riid == __uuidof(ICLRRuntimeLocator)) - { - *ppObj = static_cast(this); - AddRef(); - return S_OK; - } - *ppObj = nullptr; - return E_NOINTERFACE; - } - ULONG STDMETHODCALLTYPE AddRef() override { return InterlockedIncrement(&m_refCount); } - ULONG STDMETHODCALLTYPE Release() override - { - ULONG c = InterlockedDecrement(&m_refCount); - if (c == 0) delete this; - return c; - } - - // ICLRRuntimeLocator — provides the CLR base address directly so the DAC - // does not fall back to GetImageBase (which needs GetModuleHandleW, unavailable on Linux). - HRESULT STDMETHODCALLTYPE GetRuntimeBase(CLRDATA_ADDRESS* baseAddress) override - { - *baseAddress = (CLRDATA_ADDRESS)GetCurrentModuleBase(); - return S_OK; - } - - HRESULT STDMETHODCALLTYPE GetMachineType(ULONG32* machineType) override - { -#ifdef TARGET_AMD64 - *machineType = IMAGE_FILE_MACHINE_AMD64; -#elif defined(TARGET_ARM64) - *machineType = IMAGE_FILE_MACHINE_ARM64; -#elif defined(TARGET_X86) - *machineType = IMAGE_FILE_MACHINE_I386; -#else - return E_NOTIMPL; -#endif - return S_OK; - } - - HRESULT STDMETHODCALLTYPE GetPointerSize(ULONG32* pointerSize) override - { - *pointerSize = sizeof(void*); - return S_OK; - } - - HRESULT STDMETHODCALLTYPE GetImageBase(LPCWSTR imagePath, CLRDATA_ADDRESS* baseAddress) override - { - // Not needed — the DAC uses ICLRRuntimeLocator::GetRuntimeBase() instead. - return E_NOTIMPL; - } - - HRESULT STDMETHODCALLTYPE ReadVirtual(CLRDATA_ADDRESS address, BYTE* buffer, ULONG32 bytesRequested, ULONG32* bytesRead) override - { - int hr = ReadFromTargetCallback((uint64_t)address, buffer, bytesRequested, nullptr); - if (hr == S_OK && bytesRead != nullptr) - *bytesRead = bytesRequested; - return hr; - } - - HRESULT STDMETHODCALLTYPE WriteVirtual(CLRDATA_ADDRESS, BYTE*, ULONG32, ULONG32*) override { return E_NOTIMPL; } - - HRESULT STDMETHODCALLTYPE GetTLSValue(ULONG32 threadId, ULONG32 index, CLRDATA_ADDRESS* value) override { return E_NOTIMPL; } - HRESULT STDMETHODCALLTYPE SetTLSValue(ULONG32 threadId, ULONG32 index, CLRDATA_ADDRESS value) override { return E_NOTIMPL; } - HRESULT STDMETHODCALLTYPE GetCurrentThreadID(ULONG32* threadId) override - { - *threadId = ::GetCurrentThreadId(); - return S_OK; - } - - HRESULT STDMETHODCALLTYPE GetThreadContext(ULONG32 threadId, ULONG32 contextFlags, ULONG32 contextSize, BYTE* contextBuffer) override - { - return ReadThreadContextCallback(threadId, contextFlags, contextSize, contextBuffer, nullptr); - } - - HRESULT STDMETHODCALLTYPE SetThreadContext(ULONG32, ULONG32, BYTE*) override { return E_NOTIMPL; } - HRESULT STDMETHODCALLTYPE Request(ULONG32, ULONG32, BYTE*, ULONG32, BYTE*) override { return E_NOTIMPL; } -}; - -//----------------------------------------------------------------------------- -// Initialization / Shutdown -//----------------------------------------------------------------------------- - -bool CdacStress::IsEnabled() -{ - // Check DOTNET_CdacStress first (new config) - DWORD cdacStress = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStress); - if (cdacStress != 0) - return true; - - // Fall back to legacy DOTNET_GCStress=0x20 - return (g_pConfig->GetGCStressLevel() & EEConfig::GCSTRESS_CDAC) != 0; -} - -bool CdacStress::IsInitialized() -{ - return s_initialized; -} - -DWORD GetCdacStressLevel() -{ - return s_cdacStressLevel; -} - -bool CdacStress::IsUniqueEnabled() -{ - return (s_cdacStressLevel & CDACSTRESS_UNIQUE) != 0; -} - -bool CdacStress::Initialize() -{ - if (!IsEnabled()) - return false; - - // Resolve the stress level from DOTNET_CdacStress or legacy GCSTRESS_CDAC - DWORD cdacStress = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStress); - if (cdacStress != 0) - { - s_cdacStressLevel = cdacStress; - } - else - { - // Legacy: GCSTRESS_CDAC maps to allocation-point + reference verification - s_cdacStressLevel = CDACSTRESS_ALLOC | CDACSTRESS_REFS; - } - - // Load mscordaccore_universal from next to coreclr - PathString path; - if (WszGetModuleFileName(reinterpret_cast(GetCurrentModuleBase()), path) == 0) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to get module file name\n")); - return false; - } - - SString::Iterator iter = path.End(); - if (!path.FindBack(iter, DIRECTORY_SEPARATOR_CHAR_W)) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to find directory separator\n")); - return false; - } - - iter++; - path.Truncate(iter); - path.Append(CDAC_LIB_NAME); - - s_cdacModule = CLRLoadLibrary(path.GetUnicode()); - if (s_cdacModule == NULL) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to load %S\n", path.GetUnicode())); - return false; - } - - // Resolve cdac_reader_init - auto init = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_init")); - if (init == nullptr) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to resolve cdac_reader_init\n")); - ::FreeLibrary(s_cdacModule); - s_cdacModule = NULL; - return false; - } - - // Get the address of the contract descriptor in our own process - uint64_t descriptorAddr = reinterpret_cast(&DotNetRuntimeContractDescriptor); - - // Initialize the cDAC reader with in-process callbacks - if (init(descriptorAddr, &ReadFromTargetCallback, &WriteToTargetCallback, &ReadThreadContextCallback, nullptr, &s_cdacHandle) != 0) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: cdac_reader_init failed\n")); - ::FreeLibrary(s_cdacModule); - s_cdacModule = NULL; - return false; - } - - // Create the SOS interface - auto createSos = reinterpret_cast( - ::GetProcAddress(s_cdacModule, "cdac_reader_create_sos_interface")); - if (createSos == nullptr) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to resolve cdac_reader_create_sos_interface\n")); - auto freeFn = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_free")); - if (freeFn != nullptr) - freeFn(s_cdacHandle); - ::FreeLibrary(s_cdacModule); - s_cdacModule = NULL; - s_cdacHandle = 0; - return false; - } - - if (createSos(s_cdacHandle, nullptr, &s_cdacSosInterface) != 0) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: cdac_reader_create_sos_interface failed\n")); - auto freeFn = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_free")); - if (freeFn != nullptr) - freeFn(s_cdacHandle); - ::FreeLibrary(s_cdacModule); - s_cdacModule = NULL; - s_cdacHandle = 0; - return false; - } - - // Read configuration for fail-fast behavior - s_failFast = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStressFailFast) != 0; - - // Read step interval for throttling verifications - s_step = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStressStep); - if (s_step == 0) - s_step = 1; - - // Cache QI results so we don't QI on every stress point - { - HRESULT hr = s_cdacSosInterface->QueryInterface(__uuidof(IXCLRDataProcess), reinterpret_cast(&s_cdacProcess)); - if (FAILED(hr) || s_cdacProcess == nullptr) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to QI for IXCLRDataProcess (hr=0x%08x)\n", hr)); - } - - hr = s_cdacSosInterface->QueryInterface(__uuidof(ISOSDacInterface), reinterpret_cast(&s_cdacSosDac)); - if (FAILED(hr) || s_cdacSosDac == nullptr) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Failed to QI for ISOSDacInterface (hr=0x%08x) - cannot verify\n", hr)); - if (s_cdacProcess != nullptr) - { - s_cdacProcess->Release(); - s_cdacProcess = nullptr; - } - auto freeFn = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_free")); - if (freeFn != nullptr) - freeFn(s_cdacHandle); - ::FreeLibrary(s_cdacModule); - s_cdacModule = NULL; - s_cdacHandle = 0; - return false; - } - } - - // Open log file if configured - CLRConfigStringHolder logFilePath(CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CdacStressLogFile)); - if (logFilePath != nullptr) - { - SString sLogPath(logFilePath); - fopen_s(&s_logFile, sLogPath.GetUTF8(), "w"); - if (s_logFile != nullptr) - { - fprintf(s_logFile, "=== cDAC GC Stress Verification Log ===\n"); - fprintf(s_logFile, "FailFast: %s\n", s_failFast ? "true" : "false"); - fprintf(s_logFile, "Step: %u (verify every %u stress points)\n\n", s_step, s_step); - } - } - - s_cdacLock.Init(CrstGCCover, CRST_DEFAULT); - - if (IsUniqueEnabled()) - { - s_seenStacks = new SHash>>(); - } - - // Load the legacy DAC for three-way comparison (optional — non-fatal if it fails). - { - PathString dacPath; - if (WszGetModuleFileName(reinterpret_cast(GetCurrentModuleBase()), dacPath) != 0) - { - SString::Iterator dacIter = dacPath.End(); - if (dacPath.FindBack(dacIter, DIRECTORY_SEPARATOR_CHAR_W)) - { - dacIter++; - dacPath.Truncate(dacIter); - dacPath.Append(W("mscordaccore.dll")); - - s_dacModule = CLRLoadLibrary(dacPath.GetUnicode()); - if (s_dacModule != NULL) - { - typedef HRESULT (STDAPICALLTYPE *PFN_CLRDataCreateInstance)(REFIID, ICLRDataTarget*, void**); - auto pfnCreate = reinterpret_cast( - ::GetProcAddress(s_dacModule, "CLRDataCreateInstance")); - if (pfnCreate != nullptr) - { - InProcessDataTarget* pTarget = new (nothrow) InProcessDataTarget(); - if (pTarget != nullptr) - { - IUnknown* pDacUnk = nullptr; - HRESULT hr = pfnCreate(__uuidof(IUnknown), pTarget, (void**)&pDacUnk); - pTarget->Release(); - if (SUCCEEDED(hr) && pDacUnk != nullptr) - { - pDacUnk->QueryInterface(__uuidof(ISOSDacInterface), (void**)&s_dacSosDac); - pDacUnk->QueryInterface(__uuidof(IXCLRDataProcess), (void**)&s_dacProcess); - pDacUnk->Release(); - } - } - } - if (s_dacSosDac == nullptr) - { - LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Legacy DAC loaded but QI for ISOSDacInterface failed\n")); - } - } - else - { - LOG((LF_GCROOTS, LL_INFO10, "CDAC GC Stress: Legacy DAC not found (three-way comparison disabled)\n")); - } - } - } - } - - s_initialized = true; - LOG((LF_GCROOTS, LL_INFO10, "CDAC GC Stress: Initialized successfully (failFast=%d, logFile=%s)\n", - s_failFast, s_logFile != nullptr ? "yes" : "no")); - return true; -} - -void CdacStress::Shutdown() -{ - if (!s_initialized) - return; - - // Print summary to stderr so results are always visible - LONG actualVerifications = s_verifyPass + s_verifyFail + s_verifySkip; - fprintf(stderr, "CDAC GC Stress: %ld stress points, %ld verifications (%ld pass / %ld fail, %ld skipped)\n", - (long)s_verifyCount, (long)actualVerifications, (long)s_verifyPass, (long)s_verifyFail, (long)s_verifySkip); - STRESS_LOG3(LF_GCROOTS, LL_ALWAYS, - "CDAC GC Stress shutdown: %d verifications (%d pass / %d fail)\n", - (int)actualVerifications, (int)s_verifyPass, (int)s_verifyFail); - - if (s_logFile != nullptr) - { - fprintf(s_logFile, "\n=== Summary ===\n"); - fprintf(s_logFile, "Total stress points: %ld\n", (long)s_verifyCount); - fprintf(s_logFile, "Total verifications: %ld\n", (long)actualVerifications); - fprintf(s_logFile, " Passed: %ld\n", (long)s_verifyPass); - fprintf(s_logFile, " Failed: %ld\n", (long)s_verifyFail); - fprintf(s_logFile, " Skipped: %ld\n", (long)s_verifySkip); - fclose(s_logFile); - s_logFile = nullptr; - } - - if (s_cdacSosDac != nullptr) - { - s_cdacSosDac->Release(); - s_cdacSosDac = nullptr; - } - - if (s_cdacProcess != nullptr) - { - s_cdacProcess->Release(); - s_cdacProcess = nullptr; - } - - if (s_cdacSosInterface != nullptr) - { - s_cdacSosInterface->Release(); - s_cdacSosInterface = nullptr; - } - - if (s_cdacHandle != 0) - { - auto freeFn = reinterpret_cast(::GetProcAddress(s_cdacModule, "cdac_reader_free")); - if (freeFn != nullptr) - freeFn(s_cdacHandle); - s_cdacHandle = 0; - } - - // Legacy DAC cleanup - if (s_dacSosDac != nullptr) { s_dacSosDac->Release(); s_dacSosDac = nullptr; } - if (s_dacProcess != nullptr) { s_dacProcess->Release(); s_dacProcess = nullptr; } - - if (s_seenStacks != nullptr) - { - delete s_seenStacks; - s_seenStacks = nullptr; - } - - s_initialized = false; - LOG((LF_GCROOTS, LL_INFO10, "CDAC GC Stress: Shutdown complete\n")); -} - -//----------------------------------------------------------------------------- -// Collect stack refs from the cDAC -//----------------------------------------------------------------------------- - -static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray* pRefs) -{ - if (pSosDac == nullptr) - return false; - - ISOSStackRefEnum* pEnum = nullptr; - HRESULT hr = pSosDac->GetStackReferences(osThreadId, &pEnum); - - if (FAILED(hr) || pEnum == nullptr) - return false; - - SOSStackRefData refData; - unsigned int fetched = 0; - while (true) - { - hr = pEnum->Next(1, &refData, &fetched); - if (FAILED(hr) || fetched == 0) - break; - - StackRef ref; - ref.Address = refData.Address; - ref.Object = refData.Object; - ref.Flags = refData.Flags; - ref.Source = refData.Source; - ref.SourceType = refData.SourceType; - ref.Register = refData.Register; - ref.Offset = refData.Offset; - ref.StackPointer = refData.StackPointer; - pRefs->Append(ref); - } - - // Release twice: once for the normal ref, and once for the extra ref-count - // leaked by SOSDacImpl.GetStackReferences for COM compat (see ConvertToUnmanaged call). - pEnum->Release(); - pEnum->Release(); - return true; -} - -//----------------------------------------------------------------------------- -// Collect stack refs from the runtime's own GC scanning -//----------------------------------------------------------------------------- - -struct RuntimeRefCollectionContext -{ - StackRef refs[MAX_COLLECTED_REFS]; - int count; - bool overflow; -}; - -static void CollectRuntimeRefsPromoteFunc(PTR_PTR_Object ppObj, ScanContext* sc, uint32_t flags) -{ - RuntimeRefCollectionContext* ctx = reinterpret_cast(sc->_unused1); - if (ctx == nullptr) - return; - if (ctx->count >= MAX_COLLECTED_REFS) - { - ctx->overflow = true; - return; - } - - StackRef& ref = ctx->refs[ctx->count++]; - - // Always report the real ppObj address. For register-based refs, ppObj points - // into the REGDISPLAY/CONTEXT on the native stack — we can't reliably distinguish - // these from managed stack slots on the runtime side. The comparison logic handles - // this by matching register refs (cDAC Address=0) by (Object, Flags) only. - ref.Address = reinterpret_cast(ppObj); - ref.Object = reinterpret_cast(*ppObj); - - ref.Flags = 0; - if (flags & GC_CALL_INTERIOR) - ref.Flags |= SOSRefInterior; - if (flags & GC_CALL_PINNED) - ref.Flags |= SOSRefPinned; - ref.Source = 0; - ref.SourceType = 0; -} - -static bool CollectRuntimeStackRefs(Thread* pThread, PCONTEXT regs, StackRef* outRefs, int* outCount) -{ - RuntimeRefCollectionContext collectCtx; - collectCtx.count = 0; - collectCtx.overflow = false; - - GCCONTEXT gcctx = {}; - - // Set up ScanContext the same way ScanStackRoots does — the stack_limit and - // thread_under_crawl fields are required for PromoteCarefully/IsAddressInStack. - ScanContext sc; - sc.promotion = TRUE; - sc.thread_under_crawl = pThread; - sc._unused1 = &collectCtx; - - Frame* pTopFrame = pThread->GetFrame(); - Object** topStack = (Object**)pTopFrame; - if (InlinedCallFrame::FrameHasActiveCall(pTopFrame)) - { - InlinedCallFrame* pInlinedFrame = dac_cast(pTopFrame); - topStack = (Object**)pInlinedFrame->GetCallSiteSP(); - } - sc.stack_limit = (uintptr_t)topStack; - - gcctx.f = CollectRuntimeRefsPromoteFunc; - gcctx.sc = ≻ - gcctx.cf = NULL; - - // Set FORBIDGC_LOADER_USE_ENABLED so MethodDesc::GetName uses NOTHROW - // instead of THROWS inside EECodeManager::EnumGcRefs. - GCForbidLoaderUseHolder forbidLoaderUse; - - unsigned flagsStackWalk = ALLOW_ASYNC_STACK_WALK | ALLOW_INVALID_OBJECTS; - flagsStackWalk |= GC_FUNCLET_REFERENCE_REPORTING; - - // Use a callback that matches DAC behavior (DacStackReferenceWalker::Callback): - // Only call EnumGcRefs for frameless frames and GcScanRoots for explicit frames. - // Deliberately skip the post-scan logic (LCG resolver promotion, - // GcReportLoaderAllocator, generic param context) that GcStackCrawlCallBack - // includes — the DAC's callback has that logic disabled (#if 0). - struct DiagContext { GCCONTEXT* gcctx; RuntimeRefCollectionContext* collectCtx; }; - DiagContext diagCtx = { &gcctx, &collectCtx }; - - auto dacLikeCallback = [](CrawlFrame* pCF, VOID* pData) -> StackWalkAction - { - DiagContext* dCtx = (DiagContext*)pData; - GCCONTEXT* gcctx = dCtx->gcctx; - - ResetPointerHolder rph(&gcctx->cf); - gcctx->cf = pCF; - - bool fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences(); - - if (fReportGCReferences) - { - if (pCF->IsFrameless()) - { - ICodeManager* pCM = pCF->GetCodeManager(); - _ASSERTE(pCM != NULL); - unsigned flags = pCF->GetCodeManagerFlags(); - pCM->EnumGcRefs(pCF->GetRegisterSet(), - pCF->GetCodeInfo(), - flags, - GcEnumObject, - gcctx); - } - else - { - Frame* pFrame = pCF->GetFrame(); - pFrame->GcScanRoots(gcctx->f, gcctx->sc); - } - } - - return SWA_CONTINUE; - }; - - pThread->StackWalkFrames(dacLikeCallback, &diagCtx, flagsStackWalk); - - // NOTE: ScanStackRoots also scans the separate GCFrame linked list - // (Thread::GetGCFrame), but the DAC's GetStackReferences / DacStackReferenceWalker - // does NOT include those. We intentionally omit GCFrame scanning here so our - // runtime-side collection matches what the cDAC is expected to produce. - - // Copy results out - *outCount = collectCtx.count; - memcpy(outRefs, collectCtx.refs, collectCtx.count * sizeof(StackRef)); - return !collectCtx.overflow; -} - -//----------------------------------------------------------------------------- -// Filter cDAC refs to match runtime PromoteCarefully behavior. -// The runtime's PromoteCarefully (siginfo.cpp) skips interior pointers whose -// object value is a stack address. The cDAC reports all GcInfo slots without -// this filter, so we apply it here before comparing against runtime refs. -//----------------------------------------------------------------------------- - -static int FilterInteriorStackRefs(StackRef* refs, int count, Thread* pThread, uintptr_t stackLimit) -{ - int writeIdx = 0; - for (int i = 0; i < count; i++) - { - bool isInterior = (refs[i].Flags & SOSRefInterior) != 0; - if (isInterior && - pThread->IsAddressInStack((void*)(size_t)refs[i].Object) && - (size_t)refs[i].Object >= stackLimit) - { - continue; - } - refs[writeIdx++] = refs[i]; - } - return writeIdx; -} - -//----------------------------------------------------------------------------- -// Deduplicate cDAC refs that have the same (Address, Object, Flags). -// The cDAC may walk the same managed frame at two different offsets due to -// Frames restoring context (e.g. InlinedCallFrame). The same stack slots -// get reported from both offsets. The runtime only walks each frame once, -// so we deduplicate to match. -//----------------------------------------------------------------------------- - -static int __cdecl CompareStackRefKey(const void* a, const void* b) -{ - const StackRef* refA = static_cast(a); - const StackRef* refB = static_cast(b); - if (refA->Address != refB->Address) - return (refA->Address < refB->Address) ? -1 : 1; - if (refA->Object != refB->Object) - return (refA->Object < refB->Object) ? -1 : 1; - if (refA->Flags != refB->Flags) - return (refA->Flags < refB->Flags) ? -1 : 1; - return 0; -} - -static int DeduplicateRefs(StackRef* refs, int count) -{ - if (count <= 1) - return count; - qsort(refs, count, sizeof(StackRef), CompareStackRefKey); - int writeIdx = 1; - for (int i = 1; i < count; i++) - { - // Only dedup stack-based refs (Address != 0). - // Register refs (Address == 0) are legitimately different entries - // even when Address/Object/Flags match (different registers). - if (refs[i].Address != 0 && - refs[i].Address == refs[i-1].Address && - refs[i].Object == refs[i-1].Object && - refs[i].Flags == refs[i-1].Flags) - { - continue; - } - refs[writeIdx++] = refs[i]; - } - return writeIdx; -} - -//----------------------------------------------------------------------------- -// Report mismatch -//----------------------------------------------------------------------------- - -static void ReportMismatch(const char* message, Thread* pThread, PCONTEXT regs) -{ - LOG((LF_GCROOTS, LL_ERROR, "CDAC GC Stress: %s (Thread=0x%x, IP=0x%p)\n", - message, pThread->GetOSThreadId(), (void*)GetIP(regs))); - - if (s_failFast) - { - _ASSERTE_MSG(false, message); - } -} - -//----------------------------------------------------------------------------- -// Compare IXCLRDataStackWalk frame-by-frame between cDAC and legacy DAC. -// Creates a stack walk on each, advances in lockstep, and compares -// GetContext + Request(FRAME_DATA) at each step. -//----------------------------------------------------------------------------- - -static void CompareStackWalks(Thread* pThread, PCONTEXT regs) -{ - if (s_cdacProcess == nullptr || s_dacProcess == nullptr) - return; - - DWORD osThreadId = pThread->GetOSThreadId(); - - // Get IXCLRDataTask for the thread from both processes - IXCLRDataTask* cdacTask = nullptr; - IXCLRDataTask* dacTask = nullptr; - - HRESULT hr1 = s_cdacProcess->GetTaskByOSThreadID(osThreadId, &cdacTask); - HRESULT hr2 = s_dacProcess->GetTaskByOSThreadID(osThreadId, &dacTask); - - if (FAILED(hr1) || cdacTask == nullptr || FAILED(hr2) || dacTask == nullptr) - { - if (cdacTask) cdacTask->Release(); - if (dacTask) dacTask->Release(); - return; - } - - // Create stack walks - IXCLRDataStackWalk* cdacWalk = nullptr; - IXCLRDataStackWalk* dacWalk = nullptr; - - hr1 = cdacTask->CreateStackWalk(0xF /* CLRDATA_SIMPFRAME_MANAGED_METHOD | ... */, &cdacWalk); - hr2 = dacTask->CreateStackWalk(0xF, &dacWalk); - - cdacTask->Release(); - dacTask->Release(); - - if (FAILED(hr1) || cdacWalk == nullptr || FAILED(hr2) || dacWalk == nullptr) - { - if (cdacWalk) cdacWalk->Release(); - if (dacWalk) dacWalk->Release(); - return; - } - - // Walk in lockstep comparing each frame - int frameIdx = 0; - bool mismatch = false; - while (frameIdx < 200) // safety limit - { - // Compare GetContext - BYTE cdacCtx[4096] = {}; - BYTE dacCtx[4096] = {}; - ULONG32 cdacCtxSize = 0, dacCtxSize = 0; - - hr1 = cdacWalk->GetContext(0, sizeof(cdacCtx), &cdacCtxSize, cdacCtx); - hr2 = dacWalk->GetContext(0, sizeof(dacCtx), &dacCtxSize, dacCtx); - - if (hr1 != hr2) - { - if (s_logFile) - fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: GetContext hr mismatch cDAC=0x%x DAC=0x%x\n", - frameIdx, hr1, hr2); - mismatch = true; - break; - } - if (hr1 != S_OK) - break; // both finished - - if (cdacCtxSize != dacCtxSize) - { - if (s_logFile) - fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: Context size differs cDAC=%u DAC=%u\n", - frameIdx, cdacCtxSize, dacCtxSize); - mismatch = true; - } - else if (cdacCtxSize >= sizeof(CONTEXT)) - { - // Compare IP and SP — these are what matter for stack walk parity. - // Other CONTEXT fields (floating-point, debug registers, xstate) may - // differ between cDAC and DAC without affecting the walk. - PCODE cdacIP = GetIP((CONTEXT*)cdacCtx); - PCODE dacIP = GetIP((CONTEXT*)dacCtx); - TADDR cdacSP = GetSP((CONTEXT*)cdacCtx); - TADDR dacSP = GetSP((CONTEXT*)dacCtx); - - if (cdacIP != dacIP || cdacSP != dacSP) - { - if (s_logFile) - fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: Context differs cDAC_IP=0x%llx cDAC_SP=0x%llx DAC_IP=0x%llx DAC_SP=0x%llx\n", - frameIdx, - (unsigned long long)cdacIP, (unsigned long long)cdacSP, - (unsigned long long)dacIP, (unsigned long long)dacSP); - mismatch = true; - } - } - - // Compare Request(FRAME_DATA) - ULONG64 cdacFrameAddr = 0, dacFrameAddr = 0; - hr1 = cdacWalk->Request(0xf0000000, 0, nullptr, sizeof(cdacFrameAddr), (BYTE*)&cdacFrameAddr); - hr2 = dacWalk->Request(0xf0000000, 0, nullptr, sizeof(dacFrameAddr), (BYTE*)&dacFrameAddr); - - if (hr1 == S_OK && hr2 == S_OK && cdacFrameAddr != dacFrameAddr) - { - if (s_logFile) - { - PCODE cdacIP = 0, dacIP = 0; - if (cdacCtxSize >= sizeof(CONTEXT)) - cdacIP = GetIP((CONTEXT*)cdacCtx); - if (dacCtxSize >= sizeof(CONTEXT)) - dacIP = GetIP((CONTEXT*)dacCtx); - fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: FrameAddr cDAC=0x%llx DAC=0x%llx (cDAC_IP=0x%llx DAC_IP=0x%llx)\n", - frameIdx, (unsigned long long)cdacFrameAddr, (unsigned long long)dacFrameAddr, - (unsigned long long)cdacIP, (unsigned long long)dacIP); - } - mismatch = true; - } - - // Advance both - hr1 = cdacWalk->Next(); - hr2 = dacWalk->Next(); - - if (hr1 != hr2) - { - if (s_logFile) - fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: Next hr mismatch cDAC=0x%x DAC=0x%x\n", - frameIdx, hr1, hr2); - mismatch = true; - break; - } - if (hr1 != S_OK) - break; // both finished - - frameIdx++; - } - - if (!mismatch && s_logFile) - fprintf(s_logFile, " [WALK_OK] %d frames matched between cDAC and DAC\n", frameIdx); - - cdacWalk->Release(); - dacWalk->Release(); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// Compare two ref sets using two-phase matching. -// Phase 1: Match stack refs (Address != 0) by exact (Address, Object, Flags). -// Phase 2: Match register refs (Address == 0) by (Object, Flags) only. -// Returns true if all refs in setA have a match in setB and counts are equal. -//----------------------------------------------------------------------------- - -static bool CompareRefSets(StackRef* refsA, int countA, StackRef* refsB, int countB) -{ - if (countA != countB) - return false; - if (countA == 0) - return true; - if (countA > MAX_COLLECTED_REFS) - return false; - - bool matched[MAX_COLLECTED_REFS] = {}; - - for (int i = 0; i < countA; i++) - { - if (refsA[i].Address == 0) - continue; - bool found = false; - for (int j = 0; j < countB; j++) - { - if (matched[j]) continue; - if (refsA[i].Address == refsB[j].Address && - refsA[i].Object == refsB[j].Object && - refsA[i].Flags == refsB[j].Flags) - { - matched[j] = true; - found = true; - break; - } - } - if (!found) return false; - } - - for (int i = 0; i < countA; i++) - { - if (refsA[i].Address != 0) - continue; - bool found = false; - for (int j = 0; j < countB; j++) - { - if (matched[j]) continue; - if (refsA[i].Object == refsB[j].Object && - refsA[i].Flags == refsB[j].Flags) - { - matched[j] = true; - found = true; - break; - } - } - if (!found) return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Filter interior stack pointers and deduplicate a ref set in place. -//----------------------------------------------------------------------------- - -static int FilterAndDedup(StackRef* refs, int count, Thread* pThread, uintptr_t stackLimit) -{ - count = FilterInteriorStackRefs(refs, count, pThread, stackLimit); - count = DeduplicateRefs(refs, count); - return count; -} - -//----------------------------------------------------------------------------- -// Main entry point: verify at a GC stress point -//----------------------------------------------------------------------------- - -bool CdacStress::ShouldSkipStressPoint() -{ - LONG count = InterlockedIncrement(&s_verifyCount); - - if (s_step <= 1) - return false; - - return (count % s_step) != 0; -} - -void CdacStress::VerifyAtAllocPoint() -{ - if (!s_initialized) - return; - - // Reentrancy guard: allocations inside VerifyAtStressPoint (e.g., SArray) - // would trigger this function again, causing deadlock on s_cdacLock. - if (t_inVerification) - return; - - Thread* pThread = GetThreadNULLOk(); - if (pThread == nullptr || !pThread->PreemptiveGCDisabled()) - return; - - CONTEXT ctx; - RtlCaptureContext(&ctx); - VerifyAtStressPoint(pThread, &ctx); -} - -void CdacStress::VerifyAtStressPoint(Thread* pThread, PCONTEXT regs) -{ - _ASSERTE(s_initialized); - _ASSERTE(pThread != nullptr); - _ASSERTE(regs != nullptr); - - // RAII guard: set t_inVerification=true on entry, false on exit. - // Prevents infinite recursion when allocations inside this function - // trigger VerifyAtAllocPoint again (which would deadlock on s_cdacLock). - struct ReentrancyGuard { - ReentrancyGuard() { t_inVerification = true; } - ~ReentrancyGuard() { t_inVerification = false; } - } reentrancyGuard; - - // Serialize cDAC access — the cDAC's ProcessedData cache and COM interfaces - // are not thread-safe, and GC stress can fire on multiple threads. - CrstHolder cdacLock(&s_cdacLock); - - // Unique-stack filtering: use IP + SP as a stack identity. - // This skips re-verification at the same code location with the same stack depth. - if (IsUniqueEnabled() && s_seenStacks != nullptr) - { - SIZE_T stackHash = GetIP(regs) ^ (GetSP(regs) * 2654435761u); - if (s_seenStacks->LookupPtr(stackHash) != nullptr) - return; - s_seenStacks->Add(stackHash); - } - - // Set the thread context for the cDAC's ReadThreadContext callback. - s_currentContext = regs; - s_currentThreadId = pThread->GetOSThreadId(); - - // Flush the cDAC's ProcessedData cache so it re-reads from the live process. - if (s_cdacProcess != nullptr) - { - s_cdacProcess->Flush(); - } - - // Flush the legacy DAC cache too. - if (s_dacProcess != nullptr) - { - s_dacProcess->Flush(); - } - - // Compare IXCLRDataStackWalk frame-by-frame between cDAC and legacy DAC. - if (s_cdacStressLevel & CDACSTRESS_WALK) - { - CompareStackWalks(pThread, regs); - } - - // Compare GC stack references. - if (!(s_cdacStressLevel & CDACSTRESS_REFS)) - { - s_currentContext = nullptr; - s_currentThreadId = 0; - return; - } - - // Step 1: Collect raw refs from cDAC (always) and DAC (if USE_DAC). - DWORD osThreadId = pThread->GetOSThreadId(); - - SArray cdacRefs; - bool haveCdac = CollectStackRefs(s_cdacSosDac, osThreadId, &cdacRefs); - - SArray dacRefs; - bool haveDac = false; - if (s_cdacStressLevel & CDACSTRESS_USE_DAC) - { - haveDac = (s_dacSosDac != nullptr) && CollectStackRefs(s_dacSosDac, osThreadId, &dacRefs); - } - - s_currentContext = nullptr; - s_currentThreadId = 0; - - StackRef runtimeRefsBuf[MAX_COLLECTED_REFS]; - int runtimeCount = 0; - bool haveRuntime = CollectRuntimeStackRefs(pThread, regs, runtimeRefsBuf, &runtimeCount); - - if (!haveCdac || !haveRuntime) - { - InterlockedIncrement(&s_verifySkip); - if (s_logFile != nullptr) - { - if (!haveCdac) - fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - cDAC GetStackReferences failed\n", - osThreadId, (void*)GetIP(regs)); - else - fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - runtime CollectRuntimeStackRefs overflowed\n", - osThreadId, (void*)GetIP(regs)); - } - return; - } - - // Step 2: Compare cDAC vs DAC raw (before any filtering). - int rawCdacCount = (int)cdacRefs.GetCount(); - int rawDacCount = haveDac ? (int)dacRefs.GetCount() : -1; - bool dacMatch = true; - if (haveDac) - { - StackRef* cdacBuf = cdacRefs.OpenRawBuffer(); - StackRef* dacBuf = dacRefs.OpenRawBuffer(); - dacMatch = CompareRefSets(cdacBuf, rawCdacCount, dacBuf, rawDacCount); - cdacRefs.CloseRawBuffer(); - dacRefs.CloseRawBuffer(); - } - - // Step 3: Filter cDAC refs and compare vs RT (always). - Frame* pTopFrame = pThread->GetFrame(); - Object** topStack = (Object**)pTopFrame; - if (InlinedCallFrame::FrameHasActiveCall(pTopFrame)) - { - InlinedCallFrame* pInlinedFrame = dac_cast(pTopFrame); - topStack = (Object**)pInlinedFrame->GetCallSiteSP(); - } - uintptr_t stackLimit = (uintptr_t)topStack; - - int filteredCdacCount = rawCdacCount; - if (filteredCdacCount > 0) - { - StackRef* cdacBuf = cdacRefs.OpenRawBuffer(); - filteredCdacCount = FilterAndDedup(cdacBuf, filteredCdacCount, pThread, stackLimit); - cdacRefs.CloseRawBuffer(); - } - runtimeCount = DeduplicateRefs(runtimeRefsBuf, runtimeCount); - - StackRef* cdacBuf = cdacRefs.OpenRawBuffer(); - bool rtMatch = CompareRefSets(cdacBuf, filteredCdacCount, runtimeRefsBuf, runtimeCount); - cdacRefs.CloseRawBuffer(); - - // Step 4: Pass requires cDAC vs RT match. - // DAC mismatch is logged separately but doesn't affect pass/fail. - bool pass = rtMatch; - - if (pass) - InterlockedIncrement(&s_verifyPass); - else - InterlockedIncrement(&s_verifyFail); - - // Step 5: Log results. - if (s_logFile != nullptr) - { - const char* label = pass ? "PASS" : "FAIL"; - if (pass && !dacMatch) - label = "DAC_MISMATCH"; - fprintf(s_logFile, "[%s] Thread=0x%x IP=0x%p cDAC=%d DAC=%d RT=%d\n", - label, osThreadId, (void*)GetIP(regs), - rawCdacCount, rawDacCount, runtimeCount); - - if (!pass || !dacMatch) - { - for (int i = 0; i < rawCdacCount; i++) - fprintf(s_logFile, " cDAC [%d]: Address=0x%llx Object=0x%llx Flags=0x%x Source=0x%llx SourceType=%d SP=0x%llx\n", - i, (unsigned long long)cdacRefs[i].Address, (unsigned long long)cdacRefs[i].Object, - cdacRefs[i].Flags, (unsigned long long)cdacRefs[i].Source, cdacRefs[i].SourceType, - (unsigned long long)cdacRefs[i].StackPointer); - if (haveDac) - { - for (int i = 0; i < rawDacCount; i++) - fprintf(s_logFile, " DAC [%d]: Address=0x%llx Object=0x%llx Flags=0x%x Source=0x%llx\n", - i, (unsigned long long)dacRefs[i].Address, (unsigned long long)dacRefs[i].Object, - dacRefs[i].Flags, (unsigned long long)dacRefs[i].Source); - } - for (int i = 0; i < runtimeCount; i++) - fprintf(s_logFile, " RT [%d]: Address=0x%llx Object=0x%llx Flags=0x%x\n", - i, (unsigned long long)runtimeRefsBuf[i].Address, (unsigned long long)runtimeRefsBuf[i].Object, - runtimeRefsBuf[i].Flags); - - fflush(s_logFile); - } - } -} - -#endif // HAVE_GCCOVER diff --git a/src/coreclr/vm/cdacstress.h b/src/coreclr/vm/cdacstress.h deleted file mode 100644 index b151155559e9c5..00000000000000 --- a/src/coreclr/vm/cdacstress.h +++ /dev/null @@ -1,125 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// -// CdacStress.h -// -// Infrastructure for verifying cDAC stack reference reporting against the -// runtime's own GC root enumeration at stress trigger points. -// -// Enabled via DOTNET_CdacStress (bit flags) or legacy DOTNET_GCStress=0x20. -// - -#ifndef _CDAC_STRESS_H_ -#define _CDAC_STRESS_H_ - -// Trigger points for cDAC stress verification. -enum cdac_trigger_points -{ - cdac_on_alloc, // Verify at allocation points - cdac_on_gc, // Verify at GC trigger points - cdac_on_instr, // Verify at instruction-level stress points (needs GCStress=0x4) -}; - -#ifdef HAVE_GCCOVER - -// Bit flags for DOTNET_CdacStress configuration. -// -// Low nibble: WHERE to trigger verification -// High nibble: WHAT to validate -// Modifier: HOW to filter -enum CdacStressFlags : DWORD -{ - // Trigger points (low nibble — where stress fires) - CDACSTRESS_ALLOC = 0x1, // Verify at allocation points - CDACSTRESS_GC = 0x2, // Verify at GC trigger points (future) - CDACSTRESS_INSTR = 0x4, // Verify at instruction stress points (needs GCStress=0x4) - - // Validation types (high nibble — what to check) - CDACSTRESS_REFS = 0x10, // Compare GC stack references - CDACSTRESS_WALK = 0x20, // Compare IXCLRDataStackWalk frame-by-frame - CDACSTRESS_USE_DAC = 0x40, // Also load legacy DAC and compare cDAC against it - - // Modifiers - CDACSTRESS_UNIQUE = 0x100, // Only verify on unique (IP, SP) pairs -}; - -// Forward declarations -class Thread; - -// Accessor for the resolved stress level — called by template specializations. -DWORD GetCdacStressLevel(); - -class CdacStress -{ -public: - static bool Initialize(); - static void Shutdown(); - static bool IsInitialized(); - - // Returns true if cDAC stress is enabled via DOTNET_CdacStress or legacy GCSTRESS_CDAC. - static bool IsEnabled(); - - // Template-based trigger point check, following the GCStress pattern. - template - static bool IsEnabled(); - - // Returns true if unique-stack filtering is active. - static bool IsUniqueEnabled(); - - // Verify at a stress point if the given trigger is enabled and not skipped. - // Follows the GCStress::MaybeTrigger pattern — call sites are one-liners. - template - FORCEINLINE static void MaybeVerify(Thread* pThread, PCONTEXT regs) - { - if (IsEnabled() && !ShouldSkipStressPoint()) - VerifyAtStressPoint(pThread, regs); - } - - // Allocation-point variant: captures thread context automatically. - template - FORCEINLINE static void MaybeVerify() - { - if (IsEnabled() && !ShouldSkipStressPoint()) - VerifyAtAllocPoint(); - } - - // Main entry point: verify cDAC stack refs match runtime stack refs. - static void VerifyAtStressPoint(Thread* pThread, PCONTEXT regs); - - // Verify at an allocation point. Captures current thread context. - static void VerifyAtAllocPoint(); - - // Returns true if this stress point should be skipped (step throttling). - static bool ShouldSkipStressPoint(); -}; - -template<> FORCEINLINE bool CdacStress::IsEnabled() -{ - return IsInitialized() && (GetCdacStressLevel() & CDACSTRESS_ALLOC) != 0; -} - -template<> FORCEINLINE bool CdacStress::IsEnabled() -{ - return IsInitialized() && (GetCdacStressLevel() & CDACSTRESS_GC) != 0; -} - -template<> FORCEINLINE bool CdacStress::IsEnabled() -{ - return IsInitialized() && (GetCdacStressLevel() & CDACSTRESS_INSTR) != 0; -} - -#else // !HAVE_GCCOVER - -// Stub when HAVE_GCCOVER is not defined — all calls compile to nothing. -class CdacStress -{ -public: - template - FORCEINLINE static void MaybeVerify(Thread* pThread, PCONTEXT regs) { } - template - FORCEINLINE static void MaybeVerify() { } -}; - -#endif // HAVE_GCCOVER -#endif // _CDAC_STRESS_H_ diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/SslStreamClientHelloFuzzer.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/SslStreamClientHelloFuzzer.cs deleted file mode 100644 index 9e75a0cebf12a0..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/SslStreamClientHelloFuzzer.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Net.Security; -using System.Security.Authentication; - -namespace DotnetFuzzing.Fuzzers -{ - internal sealed class SslStreamClientHelloFuzzer : IFuzzer - { - public string[] TargetAssemblies => ["System.Net.Security"]; - - public string[] TargetCoreLibPrefixes => []; - - public void FuzzTarget(ReadOnlySpan bytes) - { - byte[] buffer = ArrayPool.Shared.Rent(bytes.Length); - try - { - bytes.CopyTo(buffer); - using MemoryStream ms = new MemoryStream(buffer, 0, bytes.Length); - using SslStream sslStream = new SslStream(ms); - sslStream.AuthenticateAsServerAsync((stream, clientHelloInfo, b, token) => - { - // This callback should be called when ClientHello is - // received, Since we don't parse any other TLS messages, - // we can terminate the handshake here. Fuzzing the rest of - // the handshake would fuzz the platform TLS implementation - // which is not instrumented and is out of scope for this - // fuzzer. - throw new MyCustomException(); - }, null).GetAwaiter().GetResult(); - } - catch (Exception ex) when (ex is AuthenticationException or IOException or MyCustomException) - { - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } - - internal class MyCustomException : Exception - { - } - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/SR.Fake.cs b/src/libraries/Fuzzing/DotnetFuzzing/SR.Fake.cs deleted file mode 100644 index 22f4e066ee7153..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/SR.Fake.cs +++ /dev/null @@ -1,5 +0,0 @@ -internal static class SR -{ - public const string ObjectDisposed_StreamClosed = "Cannot access a closed Stream."; - public const string IO_SeekBeforeBegin = "An attempt was made to move the position before the beginning of the stream."; -} \ No newline at end of file diff --git a/src/libraries/es-metadata.yml b/src/libraries/es-metadata.yml deleted file mode 100644 index 39ae7f272792cb..00000000000000 --- a/src/libraries/es-metadata.yml +++ /dev/null @@ -1,8 +0,0 @@ -schemaVersion: 0.0.1 -isProduction: true -accountableOwners: - service: 7a9b52f6-7805-416c-9390-343168c0cdb3 -routing: - defaultAreaPath: - org: devdiv - path: DevDiv\NET Libraries diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs deleted file mode 100644 index e8a8a237afc800..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IDebugger.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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; - -public record struct DebuggerData(uint DefinesBitField, uint MDStructuresVersion); - -public interface IDebugger : IContract -{ - static string IContract.Name { get; } = nameof(Debugger); - - bool TryGetDebuggerData(out DebuggerData data) => throw new NotImplementedException(); - int GetAttachStateFlags() => throw new NotImplementedException(); - bool MetadataUpdatesApplied() => throw new NotImplementedException(); -} - -public readonly struct Debugger : IDebugger -{ - // Everything throws NotImplementedException -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs deleted file mode 100644 index 58bd60ac94c5c7..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebuggerFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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; - -public sealed class DebuggerFactory : IContractFactory -{ - IDebugger IContractFactory.CreateContract(Target target, int version) - { - return version switch - { - 1 => new Debugger_1(target), - _ => default(Debugger), - }; - } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs deleted file mode 100644 index fab0ca1c91557b..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Debugger_1.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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.Contracts; - -internal readonly struct Debugger_1 : IDebugger -{ - private readonly Target _target; - - internal Debugger_1(Target target) - { - _target = target; - } - - bool IDebugger.TryGetDebuggerData(out DebuggerData data) - { - data = default; - TargetPointer debuggerPtrPtr = _target.ReadGlobalPointer(Constants.Globals.Debugger); - if (debuggerPtrPtr == TargetPointer.Null) - return false; - - TargetPointer debuggerPtr = _target.ReadPointer(debuggerPtrPtr); - if (debuggerPtr == TargetPointer.Null) - return false; - - Data.Debugger debugger = _target.ProcessedData.GetOrAdd(debuggerPtr); - if (debugger.LeftSideInitialized == 0) - return false; - - data = new DebuggerData(debugger.Defines, debugger.MDStructuresVersion); - return true; - } - - int IDebugger.GetAttachStateFlags() - { - TargetPointer addr = _target.ReadGlobalPointer(Constants.Globals.CLRJitAttachState); - return (int)_target.Read(addr.Value); - } - - bool IDebugger.MetadataUpdatesApplied() - { - if (_target.TryReadGlobalPointer(Constants.Globals.MetadataUpdatesApplied, out TargetPointer? addr)) - { - return _target.Read(addr.Value.Value) != 0; - } - return false; - } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ExceptionHandling.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ExceptionHandling.cs deleted file mode 100644 index 8f9c79fa6f1cdf..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/ExceptionHandling.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Diagnostics; -using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -internal partial class StackWalk_1 : IStackWalk -{ - /// - /// Flags from the ExceptionFlags class (exstatecommon.h). - /// These are bit flags stored in ExInfo.m_ExceptionFlags.m_flags. - /// - [Flags] - private enum ExceptionFlagsEnum : uint - { - // See Ex_UnwindHasStarted in src/coreclr/vm/exstatecommon.h - UnwindHasStarted = 0x00000004, - } - - /// - /// Given the CrawlFrame for a funclet frame, return the frame pointer of the enclosing funclet frame. - /// For filter funclet frames and normal method frames, this function returns a NULL StackFrame. - /// - /// - /// StackFrame.IsNull() - no skipping is necessary - /// StackFrame.IsMaxVal() - skip one frame and then ask again - /// Anything else - skip to the method frame indicated by the return value and ask again - /// - private TargetPointer FindParentStackFrameForStackWalk(StackDataFrameHandle handle, bool forGCReporting = false) - { - if (!forGCReporting && IsFilterFunclet(handle)) - { - return TargetPointer.Null; - } - else - { - return FindParentStackFrameHelper(handle, forGCReporting); - } - } - - private TargetPointer FindParentStackFrameHelper( - StackDataFrameHandle handle, - bool forGCReporting = false) - { - IPlatformAgnosticContext callerContext = handle.Context.Clone(); - callerContext.Unwind(_target); - TargetPointer callerStackFrame = callerContext.StackPointer; - - bool isFilterFunclet = IsFilterFunclet(handle); - - // Check for out-of-line finally funclets. Filter funclets can't be out-of-line. - if (!isFilterFunclet) - { - TargetPointer callerIp = callerContext.InstructionPointer; - - // In the runtime, on Windows, we check with that the IP is in the runtime - // TODO(stackref): make sure this difference doesn't matter - bool isCallerInVM = !IsManaged(callerIp, out CodeBlockHandle? _); - - if (!isCallerInVM) - { - if (!forGCReporting) - { - return TargetPointer.PlatformMaxValue(_target); - } - else - { - // ExInfo::GetCallerSPOfParentOfNonExceptionallyInvokedFunclet - IPlatformAgnosticContext callerCallerContext = callerContext.Clone(); - callerCallerContext.Unwind(_target); - return callerCallerContext.StackPointer; - } - } - } - - TargetPointer pExInfo = GetCurrentExceptionTracker(handle); - while (pExInfo != TargetPointer.Null) - { - Data.ExceptionInfo exInfo = _target.ProcessedData.GetOrAdd(pExInfo); - pExInfo = exInfo.PreviousNestedInfo; - - // ExInfo::StackRange::IsEmpty - if (exInfo.StackLowBound == TargetPointer.PlatformMaxValue(_target) && - exInfo.StackHighBound == TargetPointer.Null) - { - // This is ExInfo has just been created, skip it. - continue; - } - - if (callerStackFrame == exInfo.CSFEHClause) - { - return exInfo.CSFEnclosingClause; - } - } - - return TargetPointer.Null; - } - - - private bool IsFunclet(StackDataFrameHandle handle) - { - if (handle.State is StackWalkState.SW_FRAME or StackWalkState.SW_SKIPPED_FRAME) - { - return false; - } - - if (!IsManaged(handle.Context.InstructionPointer, out CodeBlockHandle? cbh)) - return false; - - return _eman.IsFunclet(cbh.Value); - } - - private bool IsFilterFunclet(StackDataFrameHandle handle) - { - if (handle.State is StackWalkState.SW_FRAME or StackWalkState.SW_SKIPPED_FRAME) - { - return false; - } - - if (!IsManaged(handle.Context.InstructionPointer, out CodeBlockHandle? cbh)) - return false; - - return _eman.IsFilterFunclet(cbh.Value); - } - - private TargetPointer GetCurrentExceptionTracker(StackDataFrameHandle handle) - { - Data.Thread thread = _target.ProcessedData.GetOrAdd(handle.ThreadData.ThreadAddress); - // ExceptionTracker is the address of the field on the Thread object. - // Dereference to get the actual ExInfo pointer. - return _target.ReadPointer(thread.ExceptionTracker); - } - - private bool HasFrameBeenUnwoundByAnyActiveException(IStackDataFrameHandle stackDataFrameHandle) - { - StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); - - TargetPointer callerStackPointer; - if (handle.State is StackWalkState.SW_FRAMELESS) - { - IPlatformAgnosticContext callerContext = handle.Context.Clone(); - callerContext.Unwind(_target); - callerStackPointer = callerContext.StackPointer; - } - else - { - callerStackPointer = handle.FrameAddress; - } - - TargetPointer pExInfo = GetCurrentExceptionTracker(handle); - while (pExInfo != TargetPointer.Null) - { - Data.ExceptionInfo exceptionInfo = _target.ProcessedData.GetOrAdd(pExInfo); - pExInfo = exceptionInfo.PreviousNestedInfo; - - if (IsInStackRegionUnwoundBySpecifiedException(callerStackPointer, exceptionInfo)) - return true; - } - return false; - } - - private bool IsInStackRegionUnwoundBySpecifiedException(TargetPointer callerStackPointer, Data.ExceptionInfo exceptionInfo) - { - // The tracker must be in the second pass (unwind has started), and its stack range must not be empty. - if ((exceptionInfo.ExceptionFlags & (uint)ExceptionFlagsEnum.UnwindHasStarted) == 0) - return false; - - // Check for empty range - if (exceptionInfo.StackLowBound == TargetPointer.PlatformMaxValue(_target) - && exceptionInfo.StackHighBound == TargetPointer.Null) - { - return false; - } - - return (exceptionInfo.StackLowBound < callerStackPointer) && (callerStackPointer <= exceptionInfo.StackHighBound); - } - -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs deleted file mode 100644 index 184a875c908980..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; - -internal class GcScanContext -{ - - private readonly Target _target; - public bool ResolveInteriorPointers { get; } - public List StackRefs { get; } = []; - public TargetPointer StackPointer { get; private set; } - public TargetPointer InstructionPointer { get; private set; } - public TargetPointer Frame { get; private set; } - - public GcScanContext(Target target, bool resolveInteriorPointers) - { - _target = target; - ResolveInteriorPointers = resolveInteriorPointers; - } - - public void UpdateScanContext(TargetPointer sp, TargetPointer ip, TargetPointer frame) - { - StackPointer = sp; - InstructionPointer = ip; - Frame = frame; - } - - public void GCEnumCallback(TargetPointer pObject, GcScanFlags flags, GcScanSlotLocation loc) - { - TargetPointer addr; - TargetPointer obj; - - if (loc.TargetPtr) - { - addr = pObject; - obj = _target.ReadPointer(addr); - } - else - { - addr = 0; - obj = pObject; - } - - if (flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR) && ResolveInteriorPointers) - { - // TODO(stackref): handle interior pointers - // https://github.com/dotnet/runtime/issues/125728 - throw new NotImplementedException(); - } - - StackRefData data = new() - { - HasRegisterInformation = true, - Register = loc.Reg, - Offset = loc.RegOffset, - Address = addr, - Object = obj, - Flags = flags, - StackPointer = StackPointer, - }; - - if (Frame != TargetPointer.Null) - { - data.SourceType = StackRefData.SourceTypes.StackSourceFrame; - data.Source = Frame; - } - else - { - data.SourceType = StackRefData.SourceTypes.StackSourceIP; - data.Source = InstructionPointer; - } - - StackRefs.Add(data); - } - - public void GCReportCallback(TargetPointer ppObj, GcScanFlags flags) - { - if (flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR) && ResolveInteriorPointers) - { - // TODO(stackref): handle interior pointers - // https://github.com/dotnet/runtime/issues/125728 - throw new NotImplementedException(); - } - - // Read the object pointer from the stack slot, matching legacy DAC behavior - // (DacStackReferenceWalker::GCReportCallback in daccess.cpp) - TargetPointer obj = _target.ReadPointer(ppObj); - - StackRefData data = new() - { - HasRegisterInformation = false, - Register = 0, - Offset = 0, - Address = ppObj, - Object = obj, - Flags = flags, - StackPointer = StackPointer, - }; - - if (Frame != TargetPointer.Null) - { - data.SourceType = StackRefData.SourceTypes.StackSourceFrame; - data.Source = Frame; - } - else - { - data.SourceType = StackRefData.SourceTypes.StackSourceIP; - data.Source = InstructionPointer; - } - - StackRefs.Add(data); - } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanFlags.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanFlags.cs deleted file mode 100644 index 0575b625d5b9d4..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanFlags.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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.StackWalkHelpers; - -[Flags] -internal enum GcScanFlags -{ - None = 0x0, - GC_CALL_INTERIOR = 0x1, - GC_CALL_PINNED = 0x2, -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanSlotLocation.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanSlotLocation.cs deleted file mode 100644 index e9829ab4bceba0..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanSlotLocation.cs +++ /dev/null @@ -1,6 +0,0 @@ -// 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.Contracts.StackWalkHelpers; - -internal readonly record struct GcScanSlotLocation(int Reg, int RegOffset, bool TargetPtr); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs deleted file mode 100644 index fa72eb606fad75..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; - -internal class GcScanner -{ - private readonly Target _target; - private readonly IExecutionManager _eman; - private readonly IGCInfo _gcInfo; - - internal GcScanner(Target target) - { - _target = target; - _eman = target.Contracts.ExecutionManager; - _gcInfo = target.Contracts.GCInfo; - } - - public bool EnumGcRefs( - IPlatformAgnosticContext context, - CodeBlockHandle cbh, - CodeManagerFlags flags, - GcScanContext scanContext) - { - TargetNUInt relativeOffset = _eman.GetRelativeOffset(cbh); - _eman.GetGCInfo(cbh, out TargetPointer gcInfoAddr, out uint gcVersion); - - if (_eman.IsFilterFunclet(cbh)) - flags |= CodeManagerFlags.NoReportUntracked; - - IGCInfoHandle handle = _gcInfo.DecodePlatformSpecificGCInfo(gcInfoAddr, gcVersion); - if (handle is not IGCInfoDecoder decoder) - return false; - - uint stackBaseRegister = decoder.StackBaseRegister; - - // Lazily compute the caller SP for GC_CALLER_SP_REL slots. - // The native code uses GET_CALLER_SP(pRD) which comes from EnsureCallerContextIsValid. - TargetPointer? callerSP = null; - - return decoder.EnumerateLiveSlots( - (uint)relativeOffset.Value, - flags, - (bool isRegister, uint registerNumber, int spOffset, uint spBase, uint gcFlags) => - { - GcScanFlags scanFlags = GcScanFlags.None; - if ((gcFlags & 0x1) != 0) // GC_SLOT_INTERIOR - scanFlags |= GcScanFlags.GC_CALL_INTERIOR; - if ((gcFlags & 0x2) != 0) // GC_SLOT_PINNED - scanFlags |= GcScanFlags.GC_CALL_PINNED; - - if (isRegister) - { - TargetPointer regValue = ReadRegisterValue(context, (int)registerNumber); - GcScanSlotLocation loc = new((int)registerNumber, 0, false); - scanContext.GCEnumCallback(regValue, scanFlags, loc); - } - else - { - int spReg = context.StackPointerRegister; - int reg = spBase switch - { - 1 => spReg, // GC_SP_REL → SP register number - 2 => (int)stackBaseRegister, // GC_FRAMEREG_REL → frame base register - 0 => -(spReg + 1), // GC_CALLER_SP_REL → -(SP + 1) - _ => throw new InvalidOperationException($"Unknown stack slot base: {spBase}"), - }; - TargetPointer baseAddr = spBase switch - { - 1 => context.StackPointer, // GC_SP_REL - 2 => ReadRegisterValue(context, (int)stackBaseRegister), // GC_FRAMEREG_REL - 0 => GetCallerSP(context, ref callerSP), // GC_CALLER_SP_REL - _ => throw new InvalidOperationException($"Unknown stack slot base: {spBase}"), - }; - - TargetPointer addr = new(baseAddr.Value + (ulong)(long)spOffset); - GcScanSlotLocation loc = new(reg, spOffset, true); - scanContext.GCEnumCallback(addr, scanFlags, loc); - } - }); - } - - /// - /// Compute the caller's SP by unwinding the current context one frame. - /// Cached in to avoid repeated unwinds for the same frame. - /// - private TargetPointer GetCallerSP(IPlatformAgnosticContext context, ref TargetPointer? cached) - { - if (cached is null) - { - IPlatformAgnosticContext callerContext = context.Clone(); - callerContext.Unwind(_target); - cached = callerContext.StackPointer; - } - return cached.Value; - } - - private static TargetPointer ReadRegisterValue(IPlatformAgnosticContext context, int registerNumber) - { - if (!context.TryReadRegister(registerNumber, out TargetNUInt value)) - throw new ArgumentOutOfRangeException(nameof(registerNumber), $"Register number {registerNumber} not found"); - - return new TargetPointer(value.Value); - } - -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.cs deleted file mode 100644 index 46e5bac46f6431..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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.Contracts.StackWalkHelpers; - -internal class StackRefData -{ - public enum SourceTypes - { - StackSourceIP = 0, - StackSourceFrame = 1, - } - - public bool HasRegisterInformation { get; set; } - public int Register { get; set; } - public int Offset { get; set; } - public TargetPointer Address { get; set; } - public TargetPointer Object { get; set; } - public GcScanFlags Flags { get; set; } - public SourceTypes SourceType { get; set; } - public TargetPointer Source { get; set; } - public TargetPointer StackPointer { get; set; } -} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs deleted file mode 100644 index 3165d480333a8e..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Debugger.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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 Debugger : IData -{ - static Debugger IData.Create(Target target, TargetPointer address) - => new Debugger(target, address); - - public Debugger(Target target, TargetPointer address) - { - Target.TypeInfo type = target.GetTypeInfo(DataType.Debugger); - - LeftSideInitialized = target.Read(address + (ulong)type.Fields[nameof(LeftSideInitialized)].Offset); - Defines = target.Read(address + (ulong)type.Fields[nameof(Defines)].Offset); - MDStructuresVersion = target.Read(address + (ulong)type.Fields[nameof(MDStructuresVersion)].Offset); - } - - public int LeftSideInitialized { get; init; } - public uint Defines { get; init; } - public uint MDStructuresVersion { 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 deleted file mode 100644 index ce9e28ca54aa36..00000000000000 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ /dev/null @@ -1,1274 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.Marshalling; -using Microsoft.Diagnostics.DataContractReader.Contracts; - -namespace Microsoft.Diagnostics.DataContractReader.Legacy; - -[GeneratedComClass] -public sealed unsafe partial class DacDbiImpl : IDacDbiInterface -{ - private readonly Target _target; - private readonly IDacDbiInterface? _legacy; - - // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: - // virtual HRESULT AssignCopy(const WCHAR* psz) = 0; - // The nint we receive is a pointer to the object, whose first field is the vtable pointer. - // The vtable has a single entry: a function pointer for AssignCopy. - // Use Thiscall because this is a C++ virtual method (thiscall on x86, no-op on x64/arm64). - private delegate* unmanaged[Thiscall] GetAssignCopyFnPtr(nint stringHolder) - { - // stringHolder -> vtable ptr -> first slot is AssignCopy - nint vtable = *(nint*)stringHolder; - return (delegate* unmanaged[Thiscall])(*(nint*)vtable); - } - - private int StringHolderAssignCopy(nint stringHolder, string str) - { - fixed (char* pStr = str) - { - return GetAssignCopyFnPtr(stringHolder)(stringHolder, pStr); - } - } - - public DacDbiImpl(Target target, object? legacyObj) - { - _target = target; - _legacy = legacyObj as IDacDbiInterface; - } - - public int CheckDbiVersion(DbiVersion* pVersion) - => _legacy is not null ? _legacy.CheckDbiVersion(pVersion) : HResults.E_NOTIMPL; - - public int FlushCache() - { - _target.Flush(); - return _legacy is not null ? _legacy.FlushCache() : HResults.S_OK; - } - - public int DacSetTargetConsistencyChecks(Interop.BOOL fEnableAsserts) - => _legacy is not null ? _legacy.DacSetTargetConsistencyChecks(fEnableAsserts) : HResults.E_NOTIMPL; - - public int IsLeftSideInitialized(Interop.BOOL* pResult) - { - *pResult = Interop.BOOL.FALSE; - int hr = HResults.S_OK; - try - { - *pResult = _target.Contracts.Debugger.TryGetDebuggerData(out _) ? 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.IsLeftSideInitialized(&resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal); - } -#endif - - return hr; - } - - public int GetAppDomainFromId(uint appdomainId, ulong* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - if (appdomainId != _target.ReadGlobal(Constants.Globals.DefaultADID)) - throw new ArgumentException(null, nameof(appdomainId)); - TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); - *pRetVal = _target.ReadPointer(appDomainPtr); - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - ulong retValLocal; - int hrLocal = _legacy.GetAppDomainFromId(appdomainId, &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 GetAppDomainId(ulong vmAppDomain, uint* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - *pRetVal = vmAppDomain == 0 ? 0u : _target.ReadGlobal(Constants.Globals.DefaultADID); - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - uint retValLocal; - int hrLocal = _legacy.GetAppDomainId(vmAppDomain, &retValLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); - } -#endif - return hr; - } - - public int GetAppDomainObject(ulong vmAppDomain, ulong* pRetVal) - => _legacy is not null ? _legacy.GetAppDomainObject(vmAppDomain, pRetVal) : HResults.E_NOTIMPL; - - public int GetAssemblyFromDomainAssembly(ulong vmDomainAssembly, ulong* vmAssembly) - => _legacy is not null ? _legacy.GetAssemblyFromDomainAssembly(vmDomainAssembly, vmAssembly) : HResults.E_NOTIMPL; - - public int IsAssemblyFullyTrusted(ulong vmDomainAssembly, Interop.BOOL* pResult) - { - *pResult = Interop.BOOL.TRUE; - int hr = HResults.S_OK; -#if DEBUG - if (_legacy is not null) - { - Interop.BOOL resultLocal; - int hrLocal = _legacy.IsAssemblyFullyTrusted(vmDomainAssembly, &resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); - } -#endif - return hr; - } - - public int GetAppDomainFullName(ulong vmAppDomain, nint pStrName) - { - int hr = HResults.S_OK; - try - { - string name = _target.Contracts.Loader.GetAppDomainFriendlyName(); - hr = StringHolderAssignCopy(pStrName, name); - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - int hrLocal = _legacy.GetAppDomainFullName(vmAppDomain, pStrName); - Debug.ValidateHResult(hr, hrLocal); - } -#endif - return hr; - } - - public int GetModuleSimpleName(ulong vmModule, nint pStrFilename) - => _legacy is not null ? _legacy.GetModuleSimpleName(vmModule, pStrFilename) : HResults.E_NOTIMPL; - - public int GetAssemblyPath(ulong vmAssembly, nint pStrFilename, Interop.BOOL* pResult) - { - *pResult = Interop.BOOL.FALSE; - int hr = HResults.S_OK; - try - { - Contracts.ILoader loader = _target.Contracts.Loader; - Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); - string path = loader.GetPath(handle); - if (string.IsNullOrEmpty(path)) - { - *pResult = Interop.BOOL.FALSE; - } - else - { - hr = StringHolderAssignCopy(pStrFilename, path); - *pResult = Interop.BOOL.TRUE; - } - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - Interop.BOOL resultLocal; - int hrLocal = _legacy.GetAssemblyPath(vmAssembly, pStrFilename, &resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); - } -#endif - return hr; - } - - public int ResolveTypeReference(DacDbiTypeRefData* pTypeRefInfo, DacDbiTypeRefData* pTargetRefInfo) - => _legacy is not null ? _legacy.ResolveTypeReference(pTypeRefInfo, pTargetRefInfo) : HResults.E_NOTIMPL; - - public int GetModulePath(ulong vmModule, nint pStrFilename, Interop.BOOL* pResult) - { - *pResult = Interop.BOOL.FALSE; - int hr = HResults.S_OK; - try - { - Contracts.ILoader loader = _target.Contracts.Loader; - Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); - string path = loader.GetPath(handle); - if (string.IsNullOrEmpty(path)) - { - *pResult = Interop.BOOL.FALSE; - } - else - { - hr = StringHolderAssignCopy(pStrFilename, path); - *pResult = Interop.BOOL.TRUE; - } - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - Interop.BOOL resultLocal; - int hrLocal = _legacy.GetModulePath(vmModule, pStrFilename, &resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); - } -#endif - return hr; - } - - public int GetMetadata(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer) - => _legacy is not null ? _legacy.GetMetadata(vmModule, pTargetBuffer) : HResults.E_NOTIMPL; - - public int GetSymbolsBuffer(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer, int* pSymbolFormat) - => _legacy is not null ? _legacy.GetSymbolsBuffer(vmModule, pTargetBuffer, pSymbolFormat) : HResults.E_NOTIMPL; - - public int GetModuleData(ulong vmModule, DacDbiModuleInfo* pData) - => _legacy is not null ? _legacy.GetModuleData(vmModule, pData) : HResults.E_NOTIMPL; - - public int GetDomainAssemblyData(ulong vmDomainAssembly, DacDbiDomainAssemblyInfo* pData) - => _legacy is not null ? _legacy.GetDomainAssemblyData(vmDomainAssembly, pData) : HResults.E_NOTIMPL; - - public int GetModuleForDomainAssembly(ulong vmDomainAssembly, ulong* pModule) - => _legacy is not null ? _legacy.GetModuleForDomainAssembly(vmDomainAssembly, pModule) : HResults.E_NOTIMPL; - - public int GetAddressType(ulong address, int* pRetVal) - => _legacy is not null ? _legacy.GetAddressType(address, pRetVal) : HResults.E_NOTIMPL; - - public int IsTransitionStub(ulong address, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.IsTransitionStub(address, pResult) : HResults.E_NOTIMPL; - - public int GetCompilerFlags(ulong vmDomainAssembly, Interop.BOOL* pfAllowJITOpts, Interop.BOOL* pfEnableEnC) - => _legacy is not null ? _legacy.GetCompilerFlags(vmDomainAssembly, pfAllowJITOpts, pfEnableEnC) : HResults.E_NOTIMPL; - - public int SetCompilerFlags(ulong vmDomainAssembly, Interop.BOOL fAllowJitOpts, Interop.BOOL fEnableEnC) - => _legacy is not null ? _legacy.SetCompilerFlags(vmDomainAssembly, fAllowJitOpts, fEnableEnC) : HResults.E_NOTIMPL; - - public int EnumerateAppDomains(nint fpCallback, nint pUserData) - { - int hr = HResults.S_OK; - try - { - TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); - ulong appDomain = _target.ReadPointer(appDomainPtr); - var callback = (delegate* unmanaged)fpCallback; - callback(appDomain, pUserData); - } - catch (System.Exception ex) - { - hr = ex.HResult; - } - return hr; - } - - public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, nint fpCallback, nint pUserData) - => _legacy is not null ? _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, fpCallback, pUserData) : HResults.E_NOTIMPL; - - public int EnumerateModulesInAssembly(ulong vmAssembly, nint fpCallback, nint pUserData) - => _legacy is not null ? _legacy.EnumerateModulesInAssembly(vmAssembly, fpCallback, pUserData) : HResults.E_NOTIMPL; - - public int RequestSyncAtEvent() - => _legacy is not null ? _legacy.RequestSyncAtEvent() : HResults.E_NOTIMPL; - - public int SetSendExceptionsOutsideOfJMC(Interop.BOOL sendExceptionsOutsideOfJMC) - => _legacy is not null ? _legacy.SetSendExceptionsOutsideOfJMC(sendExceptionsOutsideOfJMC) : HResults.E_NOTIMPL; - - public int MarkDebuggerAttachPending() - => _legacy is not null ? _legacy.MarkDebuggerAttachPending() : HResults.E_NOTIMPL; - - public int MarkDebuggerAttached(Interop.BOOL fAttached) - => _legacy is not null ? _legacy.MarkDebuggerAttached(fAttached) : HResults.E_NOTIMPL; - - public int Hijack(ulong vmThread, uint dwThreadId, nint pRecord, nint pOriginalContext, uint cbSizeContext, int reason, nint pUserData, ulong* pRemoteContextAddr) - => _legacy is not null ? _legacy.Hijack(vmThread, dwThreadId, pRecord, pOriginalContext, cbSizeContext, reason, pUserData, pRemoteContextAddr) : HResults.E_NOTIMPL; - - public int EnumerateThreads(nint fpCallback, nint pUserData) - { - int hr = HResults.S_OK; -#if DEBUG - List? cdacThreads = _legacy is not null ? new() : null; -#endif - try - { - Contracts.IThread threadContract = _target.Contracts.Thread; - Contracts.ThreadStoreData threadStore = threadContract.GetThreadStoreData(); - var callback = (delegate* unmanaged)fpCallback; - TargetPointer currentThread = threadStore.FirstThread; - while (currentThread != TargetPointer.Null) - { - Contracts.ThreadData threadData = threadContract.GetThreadData(currentThread); - // Match native: skip dead and unstarted threads - if ((threadData.State & (Contracts.ThreadState.Dead | Contracts.ThreadState.Unstarted)) == 0) - { - callback(currentThread.Value, pUserData); -#if DEBUG - cdacThreads?.Add(currentThread.Value); -#endif - } - currentThread = threadData.NextThread; - } - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - List dacThreads = new(); - GCHandle dacHandle = GCHandle.Alloc(dacThreads); - int hrLocal = _legacy.EnumerateThreads( - (nint)(delegate* unmanaged)&CollectThreadCallback, - GCHandle.ToIntPtr(dacHandle)); - dacHandle.Free(); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - { - Debug.Assert( - cdacThreads!.SequenceEqual(dacThreads), - $"Thread enumeration mismatch - cDAC: [{string.Join(",", cdacThreads!.Select(t => $"0x{t:x}"))}], DAC: [{string.Join(",", dacThreads.Select(t => $"0x{t:x}"))}]"); - } - } -#endif - return hr; - } - -#if DEBUG - [UnmanagedCallersOnly] - private static void CollectThreadCallback(ulong value, nint pUserData) - { - GCHandle handle = GCHandle.FromIntPtr(pUserData); - ((List)handle.Target!).Add(value); - } -#endif - - public int IsThreadMarkedDead(ulong vmThread, Interop.BOOL* pResult) - { - *pResult = Interop.BOOL.FALSE; - int hr = HResults.S_OK; - try - { - Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); - *pResult = (threadData.State & Contracts.ThreadState.Dead) != 0 ? 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.IsThreadMarkedDead(vmThread, &resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); - } -#endif - return hr; - } - - public int GetThreadHandle(ulong vmThread, nint pRetVal) - => _legacy is not null ? _legacy.GetThreadHandle(vmThread, pRetVal) : HResults.E_NOTIMPL; - - public int GetThreadObject(ulong vmThread, ulong* pRetVal) - => _legacy is not null ? _legacy.GetThreadObject(vmThread, pRetVal) : HResults.E_NOTIMPL; - - public int GetThreadAllocInfo(ulong vmThread, DacDbiThreadAllocInfo* pThreadAllocInfo) - => _legacy is not null ? _legacy.GetThreadAllocInfo(vmThread, pThreadAllocInfo) : HResults.E_NOTIMPL; - - public int SetDebugState(ulong vmThread, int debugState) - => _legacy is not null ? _legacy.SetDebugState(vmThread, debugState) : HResults.E_NOTIMPL; - - public int HasUnhandledException(ulong vmThread, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.HasUnhandledException(vmThread, pResult) : HResults.E_NOTIMPL; - - public int GetUserState(ulong vmThread, int* pRetVal) - => _legacy is not null ? _legacy.GetUserState(vmThread, pRetVal) : HResults.E_NOTIMPL; - - public int GetPartialUserState(ulong vmThread, int* pRetVal) - => _legacy is not null ? _legacy.GetPartialUserState(vmThread, pRetVal) : HResults.E_NOTIMPL; - - public int GetConnectionID(ulong vmThread, uint* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; -#if DEBUG - if (_legacy is not null) - { - uint retValLocal; - int hrLocal = _legacy.GetConnectionID(vmThread, &retValLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); - } -#endif - return hr; - } - - public int GetTaskID(ulong vmThread, ulong* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; -#if DEBUG - if (_legacy is not null) - { - ulong retValLocal; - int hrLocal = _legacy.GetTaskID(vmThread, &retValLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); - } -#endif - return hr; - } - - public int TryGetVolatileOSThreadID(ulong vmThread, uint* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); - uint osId = (uint)threadData.OSId.Value; - // Match native: SWITCHED_OUT_FIBER_OSID (0xbaadf00d) means thread is switched out - const uint SWITCHED_OUT_FIBER_OSID = 0xbaadf00d; - *pRetVal = osId == SWITCHED_OUT_FIBER_OSID ? 0 : osId; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - uint retValLocal; - int hrLocal = _legacy.TryGetVolatileOSThreadID(vmThread, &retValLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); - } -#endif - return hr; - } - - public int GetUniqueThreadID(ulong vmThread, uint* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - Contracts.ThreadData threadData = _target.Contracts.Thread.GetThreadData(new TargetPointer(vmThread)); - *pRetVal = (uint)threadData.OSId.Value; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - uint retValLocal; - int hrLocal = _legacy.GetUniqueThreadID(vmThread, &retValLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); - } -#endif - return hr; - } - - public int GetCurrentException(ulong vmThread, ulong* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - TargetPointer throwable = _target.Contracts.Thread.GetThrowableObject(new TargetPointer(vmThread)); - *pRetVal = throwable.Value; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - ulong retValLocal; - int hrLocal = _legacy.GetCurrentException(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 GetObjectForCCW(ulong ccwPtr, ulong* pRetVal) - => _legacy is not null ? _legacy.GetObjectForCCW(ccwPtr, pRetVal) : HResults.E_NOTIMPL; - - public int GetCurrentCustomDebuggerNotification(ulong vmThread, ulong* pRetVal) - => _legacy is not null ? _legacy.GetCurrentCustomDebuggerNotification(vmThread, pRetVal) : HResults.E_NOTIMPL; - - public int GetCurrentAppDomain(ulong* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - TargetPointer appDomainPtr = _target.ReadGlobalPointer(Constants.Globals.AppDomain); - *pRetVal = _target.ReadPointer(appDomainPtr); - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - ulong retValLocal; - int hrLocal = _legacy.GetCurrentAppDomain(&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 ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal) - => _legacy is not null ? _legacy.ResolveAssembly(vmScope, tkAssemblyRef, pRetVal) : HResults.E_NOTIMPL; - - public int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, nint pNativeVarData, nint pSequencePoints) - => _legacy is not null ? _legacy.GetNativeCodeSequencePointsAndVarInfo(vmMethodDesc, startAddress, fCodeAvailable, pNativeVarData, pSequencePoints) : HResults.E_NOTIMPL; - - public int GetManagedStoppedContext(ulong vmThread, ulong* pRetVal) - => _legacy is not null ? _legacy.GetManagedStoppedContext(vmThread, pRetVal) : HResults.E_NOTIMPL; - - public int CreateStackWalk(ulong vmThread, nint pInternalContextBuffer, nuint* ppSFIHandle) - => _legacy is not null ? _legacy.CreateStackWalk(vmThread, pInternalContextBuffer, ppSFIHandle) : HResults.E_NOTIMPL; - - public int DeleteStackWalk(nuint ppSFIHandle) - => _legacy is not null ? _legacy.DeleteStackWalk(ppSFIHandle) : HResults.E_NOTIMPL; - - public int GetStackWalkCurrentContext(nuint pSFIHandle, nint pContext) - => _legacy is not null ? _legacy.GetStackWalkCurrentContext(pSFIHandle, pContext) : HResults.E_NOTIMPL; - - public int SetStackWalkCurrentContext(ulong vmThread, nuint pSFIHandle, int flag, nint pContext) - => _legacy is not null ? _legacy.SetStackWalkCurrentContext(vmThread, pSFIHandle, flag, pContext) : HResults.E_NOTIMPL; - - public int UnwindStackWalkFrame(nuint pSFIHandle, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.UnwindStackWalkFrame(pSFIHandle, pResult) : HResults.E_NOTIMPL; - - public int CheckContext(ulong vmThread, nint pContext) - => _legacy is not null ? _legacy.CheckContext(vmThread, pContext) : HResults.E_NOTIMPL; - - public int GetStackWalkCurrentFrameInfo(nuint pSFIHandle, nint pFrameData, int* pRetVal) - => _legacy is not null ? _legacy.GetStackWalkCurrentFrameInfo(pSFIHandle, pFrameData, pRetVal) : HResults.E_NOTIMPL; - - public int GetCountOfInternalFrames(ulong vmThread, uint* pRetVal) - => _legacy is not null ? _legacy.GetCountOfInternalFrames(vmThread, pRetVal) : HResults.E_NOTIMPL; - - public int EnumerateInternalFrames(ulong vmThread, nint fpCallback, nint pUserData) - => _legacy is not null ? _legacy.EnumerateInternalFrames(vmThread, fpCallback, pUserData) : HResults.E_NOTIMPL; - - public int IsMatchingParentFrame(ulong fpToCheck, ulong fpParent, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.IsMatchingParentFrame(fpToCheck, fpParent, pResult) : HResults.E_NOTIMPL; - - public int GetStackParameterSize(ulong controlPC, uint* pRetVal) - => _legacy is not null ? _legacy.GetStackParameterSize(controlPC, pRetVal) : HResults.E_NOTIMPL; - - public int GetFramePointer(nuint pSFIHandle, ulong* pRetVal) - => _legacy is not null ? _legacy.GetFramePointer(pSFIHandle, pRetVal) : HResults.E_NOTIMPL; - - public int IsLeafFrame(ulong vmThread, nint pContext, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.IsLeafFrame(vmThread, pContext, pResult) : HResults.E_NOTIMPL; - - public int GetContext(ulong vmThread, nint pContextBuffer) - => _legacy is not null ? _legacy.GetContext(vmThread, pContextBuffer) : HResults.E_NOTIMPL; - - public int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Interop.BOOL fActive) - => _legacy is not null ? _legacy.ConvertContextToDebuggerRegDisplay(pInContext, pOutDRD, fActive) : HResults.E_NOTIMPL; - - public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal) - => _legacy is not null ? _legacy.IsDiagnosticsHiddenOrLCGMethod(vmMethodDesc, pRetVal) : HResults.E_NOTIMPL; - - public int GetVarArgSig(ulong VASigCookieAddr, ulong* pArgBase, DacDbiTargetBuffer* pRetVal) - => _legacy is not null ? _legacy.GetVarArgSig(VASigCookieAddr, pArgBase, pRetVal) : HResults.E_NOTIMPL; - - public int RequiresAlign8(ulong thExact, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.RequiresAlign8(thExact, pResult) : HResults.E_NOTIMPL; - - public int ResolveExactGenericArgsToken(uint dwExactGenericArgsTokenIndex, ulong rawToken, ulong* pRetVal) - => _legacy is not null ? _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, pRetVal) : HResults.E_NOTIMPL; - - public int GetILCodeAndSig(ulong vmDomainAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken) - => _legacy is not null ? _legacy.GetILCodeAndSig(vmDomainAssembly, functionToken, pTargetBuffer, pLocalSigToken) : HResults.E_NOTIMPL; - - public int GetNativeCodeInfo(ulong vmDomainAssembly, uint functionToken, nint pJitManagerList) - => _legacy is not null ? _legacy.GetNativeCodeInfo(vmDomainAssembly, functionToken, pJitManagerList) : HResults.E_NOTIMPL; - - public int GetNativeCodeInfoForAddr(ulong codeAddress, nint pCodeInfo, ulong* pVmModule, uint* pFunctionToken) - => _legacy is not null ? _legacy.GetNativeCodeInfoForAddr(codeAddress, pCodeInfo, pVmModule, pFunctionToken) : HResults.E_NOTIMPL; - - public int IsValueType(ulong vmTypeHandle, Interop.BOOL* pResult) - { - *pResult = Interop.BOOL.FALSE; - int hr = HResults.S_OK; - try - { - Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - Contracts.TypeHandle th = rts.GetTypeHandle(new TargetPointer(vmTypeHandle)); - CorElementType corType = rts.GetSignatureCorElementType(th); - *pResult = corType == CorElementType.ValueType ? 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.IsValueType(vmTypeHandle, &resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); - } -#endif - return hr; - } - - public int HasTypeParams(ulong vmTypeHandle, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.HasTypeParams(vmTypeHandle, pResult) : HResults.E_NOTIMPL; - - public int GetClassInfo(ulong vmAppDomain, ulong thExact, nint pData) - => _legacy is not null ? _legacy.GetClassInfo(vmAppDomain, thExact, pData) : HResults.E_NOTIMPL; - - public int GetInstantiationFieldInfo(ulong vmDomainAssembly, ulong vmTypeHandle, ulong vmExactMethodTable, nint pFieldList, nuint* pObjectSize) - => _legacy is not null ? _legacy.GetInstantiationFieldInfo(vmDomainAssembly, vmTypeHandle, vmExactMethodTable, pFieldList, pObjectSize) : HResults.E_NOTIMPL; - - public int TypeHandleToExpandedTypeInfo(int boxed, ulong vmAppDomain, ulong vmTypeHandle, nint pData) - => _legacy is not null ? _legacy.TypeHandleToExpandedTypeInfo(boxed, vmAppDomain, vmTypeHandle, pData) : HResults.E_NOTIMPL; - - public int GetObjectExpandedTypeInfo(int boxed, ulong vmAppDomain, ulong addr, nint pTypeInfo) - => _legacy is not null ? _legacy.GetObjectExpandedTypeInfo(boxed, vmAppDomain, addr, pTypeInfo) : HResults.E_NOTIMPL; - - public int GetObjectExpandedTypeInfoFromID(int boxed, ulong vmAppDomain, COR_TYPEID id, nint pTypeInfo) - => _legacy is not null ? _legacy.GetObjectExpandedTypeInfoFromID(boxed, vmAppDomain, id, pTypeInfo) : HResults.E_NOTIMPL; - - public int GetTypeHandle(ulong vmModule, uint metadataToken, ulong* pRetVal) - => _legacy is not null ? _legacy.GetTypeHandle(vmModule, metadataToken, pRetVal) : HResults.E_NOTIMPL; - - public int GetApproxTypeHandle(nint pTypeData, ulong* pRetVal) - => _legacy is not null ? _legacy.GetApproxTypeHandle(pTypeData, pRetVal) : HResults.E_NOTIMPL; - - public int GetExactTypeHandle(nint pTypeData, nint pArgInfo, ulong* pVmTypeHandle) - => _legacy is not null ? _legacy.GetExactTypeHandle(pTypeData, pArgInfo, pVmTypeHandle) : HResults.E_NOTIMPL; - - public int GetMethodDescParams(ulong vmAppDomain, ulong vmMethodDesc, ulong genericsToken, uint* pcGenericClassTypeParams, nint pGenericTypeParams) - => _legacy is not null ? _legacy.GetMethodDescParams(vmAppDomain, vmMethodDesc, genericsToken, pcGenericClassTypeParams, pGenericTypeParams) : HResults.E_NOTIMPL; - - public int GetThreadStaticAddress(ulong vmField, ulong vmRuntimeThread, ulong* pRetVal) - => _legacy is not null ? _legacy.GetThreadStaticAddress(vmField, vmRuntimeThread, pRetVal) : HResults.E_NOTIMPL; - - public int GetCollectibleTypeStaticAddress(ulong vmField, ulong vmAppDomain, ulong* pRetVal) - => _legacy is not null ? _legacy.GetCollectibleTypeStaticAddress(vmField, vmAppDomain, pRetVal) : HResults.E_NOTIMPL; - - public int GetEnCHangingFieldInfo(nint pEnCFieldInfo, nint pFieldData, Interop.BOOL* pfStatic) - => _legacy is not null ? _legacy.GetEnCHangingFieldInfo(pEnCFieldInfo, pFieldData, pfStatic) : HResults.E_NOTIMPL; - - public int GetTypeHandleParams(ulong vmAppDomain, ulong vmTypeHandle, nint pParams) - => _legacy is not null ? _legacy.GetTypeHandleParams(vmAppDomain, vmTypeHandle, pParams) : HResults.E_NOTIMPL; - - public int GetSimpleType(ulong vmAppDomain, int simpleType, uint* pMetadataToken, ulong* pVmModule, ulong* pVmDomainAssembly) - => _legacy is not null ? _legacy.GetSimpleType(vmAppDomain, simpleType, pMetadataToken, pVmModule, pVmDomainAssembly) : HResults.E_NOTIMPL; - - public int IsExceptionObject(ulong vmObject, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.IsExceptionObject(vmObject, pResult) : HResults.E_NOTIMPL; - - public int GetStackFramesFromException(ulong vmObject, nint pDacStackFrames) - => _legacy is not null ? _legacy.GetStackFramesFromException(vmObject, pDacStackFrames) : HResults.E_NOTIMPL; - - public int IsRcw(ulong vmObject, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.IsRcw(vmObject, pResult) : HResults.E_NOTIMPL; - - public int GetRcwCachedInterfaceTypes(ulong vmObject, ulong vmAppDomain, Interop.BOOL bIInspectableOnly, nint pDacInterfaces) - => _legacy is not null ? _legacy.GetRcwCachedInterfaceTypes(vmObject, vmAppDomain, bIInspectableOnly, pDacInterfaces) : HResults.E_NOTIMPL; - - public int GetRcwCachedInterfacePointers(ulong vmObject, Interop.BOOL bIInspectableOnly, nint pDacItfPtrs) - => _legacy is not null ? _legacy.GetRcwCachedInterfacePointers(vmObject, bIInspectableOnly, pDacItfPtrs) : HResults.E_NOTIMPL; - - public int GetCachedWinRTTypesForIIDs(ulong vmAppDomain, nint pIids, nint pTypes) - => _legacy is not null ? _legacy.GetCachedWinRTTypesForIIDs(vmAppDomain, pIids, pTypes) : HResults.E_NOTIMPL; - - public int GetCachedWinRTTypes(ulong vmAppDomain, nint piids, nint pTypes) - => _legacy is not null ? _legacy.GetCachedWinRTTypes(vmAppDomain, piids, pTypes) : HResults.E_NOTIMPL; - - public int GetTypedByRefInfo(ulong pTypedByRef, ulong vmAppDomain, nint pObjectData) - => _legacy is not null ? _legacy.GetTypedByRefInfo(pTypedByRef, vmAppDomain, pObjectData) : HResults.E_NOTIMPL; - - public int GetStringData(ulong objectAddress, nint pObjectData) - => _legacy is not null ? _legacy.GetStringData(objectAddress, pObjectData) : HResults.E_NOTIMPL; - - public int GetArrayData(ulong objectAddress, nint pObjectData) - => _legacy is not null ? _legacy.GetArrayData(objectAddress, pObjectData) : HResults.E_NOTIMPL; - - public int GetBasicObjectInfo(ulong objectAddress, int type, ulong vmAppDomain, nint pObjectData) - => _legacy is not null ? _legacy.GetBasicObjectInfo(objectAddress, type, vmAppDomain, pObjectData) : HResults.E_NOTIMPL; - - public int TestCrst(ulong vmCrst) - => _legacy is not null ? _legacy.TestCrst(vmCrst) : HResults.E_NOTIMPL; - - public int TestRWLock(ulong vmRWLock) - => _legacy is not null ? _legacy.TestRWLock(vmRWLock) : HResults.E_NOTIMPL; - - public int GetDebuggerControlBlockAddress(ulong* pRetVal) - => _legacy is not null ? _legacy.GetDebuggerControlBlockAddress(pRetVal) : HResults.E_NOTIMPL; - - public int GetObjectFromRefPtr(ulong ptr, ulong* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - *pRetVal = _target.ReadPointer(new TargetPointer(ptr)).Value; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - ulong retValLocal; - int hrLocal = _legacy.GetObjectFromRefPtr(ptr, &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 GetObject(ulong ptr, ulong* pRetVal) - { - // Native GetObject wraps the address directly in a VMPTR_Object without dereferencing. - *pRetVal = ptr; - int hr = HResults.S_OK; -#if DEBUG - if (_legacy is not null) - { - ulong retValLocal; - int hrLocal = _legacy.GetObject(ptr, &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 EnableNGENPolicy(int ePolicy) - => HResults.E_NOTIMPL; - - public int SetNGENCompilerFlags(uint dwFlags) - => _legacy is not null ? _legacy.SetNGENCompilerFlags(dwFlags) : HResults.E_NOTIMPL; - - public int GetNGENCompilerFlags(uint* pdwFlags) - => _legacy is not null ? _legacy.GetNGENCompilerFlags(pdwFlags) : HResults.E_NOTIMPL; - - public int GetVmObjectHandle(ulong handleAddress, ulong* pRetVal) - { - *pRetVal = handleAddress; - int hr = HResults.S_OK; -#if DEBUG - if (_legacy is not null) - { - ulong retValLocal; - int hrLocal = _legacy.GetVmObjectHandle(handleAddress, &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 IsVmObjectHandleValid(ulong vmHandle, Interop.BOOL* pResult) - { - *pResult = Interop.BOOL.FALSE; - int hr = HResults.S_OK; - try - { - TargetPointer obj = _target.ReadPointer(new TargetPointer(vmHandle)); - *pResult = obj != TargetPointer.Null ? 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.IsVmObjectHandleValid(vmHandle, &resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); - } -#endif - return hr; - } - - public int IsWinRTModule(ulong vmModule, Interop.BOOL* isWinRT) - { - *isWinRT = Interop.BOOL.FALSE; - int hr = HResults.S_OK; -#if DEBUG - if (_legacy is not null) - { - Interop.BOOL isWinRTLocal; - int hrLocal = _legacy.IsWinRTModule(vmModule, &isWinRTLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*isWinRT == isWinRTLocal, $"cDAC: {*isWinRT}, DAC: {isWinRTLocal}"); - } -#endif - return hr; - } - - public int GetAppDomainIdFromVmObjectHandle(ulong vmHandle, uint* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - // In modern .NET there is only one AppDomain (id=1). - // Return 0 for null handles to match native behavior. - *pRetVal = vmHandle != 0 ? 1u : 0u; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } -#if DEBUG - if (_legacy is not null) - { - uint retValLocal; - int hrLocal = _legacy.GetAppDomainIdFromVmObjectHandle(vmHandle, &retValLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}"); - } -#endif - return hr; - } - - public int GetHandleAddressFromVmHandle(ulong vmHandle, ulong* pRetVal) - { - *pRetVal = vmHandle; - int hr = HResults.S_OK; -#if DEBUG - if (_legacy is not null) - { - ulong retValLocal; - int hrLocal = _legacy.GetHandleAddressFromVmHandle(vmHandle, &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 GetObjectContents(ulong obj, DacDbiTargetBuffer* pRetVal) - => _legacy is not null ? _legacy.GetObjectContents(obj, pRetVal) : HResults.E_NOTIMPL; - - public int GetThreadOwningMonitorLock(ulong vmObject, DacDbiMonitorLockInfo* pRetVal) - => _legacy is not null ? _legacy.GetThreadOwningMonitorLock(vmObject, pRetVal) : HResults.E_NOTIMPL; - - public int EnumerateMonitorEventWaitList(ulong vmObject, nint fpCallback, nint pUserData) - => _legacy is not null ? _legacy.EnumerateMonitorEventWaitList(vmObject, fpCallback, pUserData) : HResults.E_NOTIMPL; - - public int GetAttachStateFlags(int* pRetVal) - { - *pRetVal = 0; - int hr = HResults.S_OK; - try - { - *pRetVal = _target.Contracts.Debugger.GetAttachStateFlags(); - } - catch (System.Exception ex) - { - hr = ex.HResult; - } - -#if DEBUG - if (_legacy is not null) - { - int resultLocal; - int hrLocal = _legacy.GetAttachStateFlags(&resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pRetVal == resultLocal); - } -#endif - - return hr; - } - - public int GetMetaDataFileInfoFromPEFile(ulong vmPEAssembly, uint* dwTimeStamp, uint* dwImageSize, nint pStrFilename, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.GetMetaDataFileInfoFromPEFile(vmPEAssembly, dwTimeStamp, dwImageSize, pStrFilename, pResult) : HResults.E_NOTIMPL; - - public int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.IsThreadSuspendedOrHijacked(vmThread, pResult) : HResults.E_NOTIMPL; - - public int AreGCStructuresValid(Interop.BOOL* pResult) - { - // Native DacDbiInterfaceImpl::AreGCStructuresValid always returns TRUE. - // DacDbi callers assume the runtime is suspended, so GC structures are always valid. - *pResult = Interop.BOOL.TRUE; - int hr = HResults.S_OK; -#if DEBUG - if (_legacy is not null) - { - Interop.BOOL resultLocal; - int hrLocal = _legacy.AreGCStructuresValid(&resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); - } -#endif - return hr; - } - - public int CreateHeapWalk(nuint* pHandle) - => _legacy is not null ? _legacy.CreateHeapWalk(pHandle) : HResults.E_NOTIMPL; - - public int DeleteHeapWalk(nuint handle) - => _legacy is not null ? _legacy.DeleteHeapWalk(handle) : HResults.E_NOTIMPL; - - public int WalkHeap(nuint handle, uint count, COR_HEAPOBJECT* objects, uint* fetched) - => _legacy is not null ? _legacy.WalkHeap(handle, count, objects, fetched) : HResults.E_NOTIMPL; - - public int GetHeapSegments(nint pSegments) - => _legacy is not null ? _legacy.GetHeapSegments(pSegments) : HResults.E_NOTIMPL; - - public int IsValidObject(ulong obj, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.IsValidObject(obj, pResult) : HResults.E_NOTIMPL; - - public int GetAppDomainForObject(ulong obj, ulong* pApp, ulong* pModule, ulong* pDomainAssembly, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.GetAppDomainForObject(obj, pApp, pModule, pDomainAssembly, pResult) : HResults.E_NOTIMPL; - - public int CreateRefWalk(nuint* pHandle, Interop.BOOL walkStacks, Interop.BOOL walkFQ, uint handleWalkMask) - => _legacy is not null ? _legacy.CreateRefWalk(pHandle, walkStacks, walkFQ, handleWalkMask) : HResults.E_NOTIMPL; - - public int DeleteRefWalk(nuint handle) - => _legacy is not null ? _legacy.DeleteRefWalk(handle) : HResults.E_NOTIMPL; - - public int WalkRefs(nuint handle, uint count, nint refs, uint* pFetched) - => _legacy is not null ? _legacy.WalkRefs(handle, count, refs, pFetched) : HResults.E_NOTIMPL; - - public int GetTypeID(ulong obj, COR_TYPEID* pType) - { - *pType = default; - int hr = HResults.S_OK; - try - { - TargetPointer mt = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(obj)); - pType->token1 = mt.Value; - pType->token2 = 0; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } - -#if DEBUG - if (_legacy is not null) - { - COR_TYPEID resultLocal; - int hrLocal = _legacy.GetTypeID(obj, &resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - { - Debug.Assert(pType->token1 == resultLocal.token1); - Debug.Assert(pType->token2 == resultLocal.token2); - } - } -#endif - - return hr; - } - - public int GetTypeIDForType(ulong vmTypeHandle, COR_TYPEID* pId) - { - *pId = default; - int hr = HResults.S_OK; - try - { - pId->token1 = vmTypeHandle; - pId->token2 = 0; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } - -#if DEBUG - if (_legacy is not null) - { - COR_TYPEID resultLocal; - int hrLocal = _legacy.GetTypeIDForType(vmTypeHandle, &resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - { - Debug.Assert(pId->token1 == resultLocal.token1); - Debug.Assert(pId->token2 == resultLocal.token2); - } - } -#endif - - return hr; - } - - public int GetObjectFields(nint id, uint celt, COR_FIELD* layout, uint* pceltFetched) - => _legacy is not null ? _legacy.GetObjectFields(id, celt, layout, pceltFetched) : HResults.E_NOTIMPL; - - public int GetTypeLayout(nint id, COR_TYPE_LAYOUT* pLayout) - => _legacy is not null ? _legacy.GetTypeLayout(id, pLayout) : HResults.E_NOTIMPL; - - public int GetArrayLayout(nint id, nint pLayout) - => _legacy is not null ? _legacy.GetArrayLayout(id, pLayout) : HResults.E_NOTIMPL; - - public int GetGCHeapInformation(COR_HEAPINFO* pHeapInfo) - { - *pHeapInfo = default; - int hr = HResults.S_OK; - try - { - Contracts.IGC gc = _target.Contracts.GC; - pHeapInfo->areGCStructuresValid = gc.GetGCStructuresValid() ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; - pHeapInfo->numHeaps = gc.GetGCHeapCount(); - pHeapInfo->pointerSize = (uint)_target.PointerSize; - - string[] identifiers = gc.GetGCIdentifiers(); - bool isServer = identifiers.Contains(GCIdentifiers.Server); - pHeapInfo->gcType = isServer ? 1 : 0; // CorDebugServerGC = 1, CorDebugWorkstationGC = 0 - pHeapInfo->concurrent = identifiers.Contains(GCIdentifiers.Background) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } - -#if DEBUG - if (_legacy is not null) - { - COR_HEAPINFO resultLocal; - int hrLocal = _legacy.GetGCHeapInformation(&resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - { - Debug.Assert(pHeapInfo->areGCStructuresValid == resultLocal.areGCStructuresValid); - Debug.Assert(pHeapInfo->numHeaps == resultLocal.numHeaps); - Debug.Assert(pHeapInfo->pointerSize == resultLocal.pointerSize); - Debug.Assert(pHeapInfo->gcType == resultLocal.gcType); - Debug.Assert(pHeapInfo->concurrent == resultLocal.concurrent); - } - } -#endif - - return hr; - } - - public int GetPEFileMDInternalRW(ulong vmPEAssembly, ulong* pAddrMDInternalRW) - => _legacy is not null ? _legacy.GetPEFileMDInternalRW(vmPEAssembly, pAddrMDInternalRW) : HResults.E_NOTIMPL; - - public int GetReJitInfo(ulong vmModule, uint methodTk, ulong* pReJitInfo) - => _legacy is not null ? _legacy.GetReJitInfo(vmModule, methodTk, pReJitInfo) : HResults.E_NOTIMPL; - - public int GetReJitInfoByAddress(ulong vmMethod, ulong codeStartAddress, ulong* pReJitInfo) - => _legacy is not null ? _legacy.GetReJitInfoByAddress(vmMethod, codeStartAddress, pReJitInfo) : HResults.E_NOTIMPL; - - public int GetSharedReJitInfo(ulong vmReJitInfo, ulong* pSharedReJitInfo) - => _legacy is not null ? _legacy.GetSharedReJitInfo(vmReJitInfo, pSharedReJitInfo) : HResults.E_NOTIMPL; - - public int GetSharedReJitInfoData(ulong sharedReJitInfo, DacDbiSharedReJitInfo* pData) - => _legacy is not null ? _legacy.GetSharedReJitInfoData(sharedReJitInfo, pData) : HResults.E_NOTIMPL; - - public int AreOptimizationsDisabled(ulong vmModule, uint methodTk, Interop.BOOL* pOptimizationsDisabled) - => _legacy is not null ? _legacy.AreOptimizationsDisabled(vmModule, methodTk, pOptimizationsDisabled) : HResults.E_NOTIMPL; - - public int GetDefinesBitField(uint* pDefines) - { - *pDefines = 0; - int hr = HResults.S_OK; - try - { - if (!_target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)) - throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!; - *pDefines = data.DefinesBitField; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } - -#if DEBUG - if (_legacy is not null) - { - uint resultLocal; - int hrLocal = _legacy.GetDefinesBitField(&resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pDefines == resultLocal); - } -#endif - - return hr; - } - - public int GetMDStructuresVersion(uint* pMDStructuresVersion) - { - *pMDStructuresVersion = 0; - int hr = HResults.S_OK; - try - { - if (!_target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)) - throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_NOTREADY)!; - *pMDStructuresVersion = data.MDStructuresVersion; - } - catch (System.Exception ex) - { - hr = ex.HResult; - } - -#if DEBUG - if (_legacy is not null) - { - uint resultLocal; - int hrLocal = _legacy.GetMDStructuresVersion(&resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pMDStructuresVersion == resultLocal); - } -#endif - - return hr; - } - - public int GetActiveRejitILCodeVersionNode(ulong vmModule, uint methodTk, ulong* pVmILCodeVersionNode) - => _legacy is not null ? _legacy.GetActiveRejitILCodeVersionNode(vmModule, methodTk, pVmILCodeVersionNode) : HResults.E_NOTIMPL; - - public int GetNativeCodeVersionNode(ulong vmMethod, ulong codeStartAddress, ulong* pVmNativeCodeVersionNode) - => _legacy is not null ? _legacy.GetNativeCodeVersionNode(vmMethod, codeStartAddress, pVmNativeCodeVersionNode) : HResults.E_NOTIMPL; - - public int GetILCodeVersionNode(ulong vmNativeCodeVersionNode, ulong* pVmILCodeVersionNode) - => _legacy is not null ? _legacy.GetILCodeVersionNode(vmNativeCodeVersionNode, pVmILCodeVersionNode) : HResults.E_NOTIMPL; - - public int GetILCodeVersionNodeData(ulong ilCodeVersionNode, DacDbiSharedReJitInfo* pData) - => _legacy is not null ? _legacy.GetILCodeVersionNodeData(ilCodeVersionNode, pData) : HResults.E_NOTIMPL; - - public int EnableGCNotificationEvents(Interop.BOOL fEnable) - => _legacy is not null ? _legacy.EnableGCNotificationEvents(fEnable) : HResults.E_NOTIMPL; - - public int IsDelegate(ulong vmObject, Interop.BOOL* pResult) - => _legacy is not null ? _legacy.IsDelegate(vmObject, pResult) : HResults.E_NOTIMPL; - - public int GetDelegateType(ulong delegateObject, int* delegateType) - => _legacy is not null ? _legacy.GetDelegateType(delegateObject, delegateType) : HResults.E_NOTIMPL; - - public int GetDelegateFunctionData(int delegateType, ulong delegateObject, ulong* ppFunctionDomainAssembly, uint* pMethodDef) - => _legacy is not null ? _legacy.GetDelegateFunctionData(delegateType, delegateObject, ppFunctionDomainAssembly, pMethodDef) : HResults.E_NOTIMPL; - - public int GetDelegateTargetObject(int delegateType, ulong delegateObject, ulong* ppTargetObj, ulong* ppTargetAppDomain) - => _legacy is not null ? _legacy.GetDelegateTargetObject(delegateType, delegateObject, ppTargetObj, ppTargetAppDomain) : HResults.E_NOTIMPL; - - public int GetLoaderHeapMemoryRanges(nint pRanges) - => _legacy is not null ? _legacy.GetLoaderHeapMemoryRanges(pRanges) : HResults.E_NOTIMPL; - - public int IsModuleMapped(ulong pModule, int* isModuleMapped) - => _legacy is not null ? _legacy.IsModuleMapped(pModule, isModuleMapped) : HResults.E_NOTIMPL; - - public int MetadataUpdatesApplied(Interop.BOOL* pResult) - { - *pResult = Interop.BOOL.FALSE; - int hr = HResults.S_OK; - try - { - *pResult = _target.Contracts.Debugger.MetadataUpdatesApplied() ? 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.MetadataUpdatesApplied(&resultLocal); - Debug.ValidateHResult(hr, hrLocal); - if (hr == HResults.S_OK) - Debug.Assert(*pResult == resultLocal); - } -#endif - - return hr; - } - - public int GetDomainAssemblyFromModule(ulong vmModule, ulong* pVmDomainAssembly) - => _legacy is not null ? _legacy.GetDomainAssemblyFromModule(vmModule, pVmDomainAssembly) : HResults.E_NOTIMPL; - - public int ParseContinuation(ulong continuationAddress, ulong* pDiagnosticIP, ulong* pNextContinuation, uint* pState) - => _legacy is not null ? _legacy.ParseContinuation(continuationAddress, pDiagnosticIP, pNextContinuation, pState) : HResults.E_NOTIMPL; - - public int GetAsyncLocals(ulong vmMethod, ulong codeAddr, uint state, nint pAsyncLocals) - => _legacy is not null ? _legacy.GetAsyncLocals(vmMethod, codeAddr, state, pAsyncLocals) : HResults.E_NOTIMPL; - - public int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex) - => _legacy is not null ? _legacy.GetGenericArgTokenIndex(vmMethod, pIndex) : HResults.E_NOTIMPL; -} diff --git a/src/native/managed/cdac/tests/DebuggerTests.cs b/src/native/managed/cdac/tests/DebuggerTests.cs deleted file mode 100644 index 3ad7c18f6e507f..00000000000000 --- a/src/native/managed/cdac/tests/DebuggerTests.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Microsoft.Diagnostics.DataContractReader.Contracts; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.Tests; - -public class DebuggerTests -{ - private static TargetTestHelpers.LayoutResult GetDebuggerLayout(TargetTestHelpers helpers) - { - return helpers.LayoutFields( - [ - new(nameof(Data.Debugger.LeftSideInitialized), DataType.int32), - new(nameof(Data.Debugger.Defines), DataType.uint32), - new(nameof(Data.Debugger.MDStructuresVersion), DataType.uint32), - ]); - } - - private static TestPlaceholderTarget BuildTarget( - MockTarget.Architecture arch, - int leftSideInitialized, - uint defines, - uint mdStructuresVersion, - int? attachStateFlags = null, - byte? metadataUpdatesApplied = null) - { - TargetTestHelpers helpers = new(arch); - var builder = new TestPlaceholderTarget.Builder(arch); - MockMemorySpace.Builder memBuilder = builder.MemoryBuilder; - MockMemorySpace.BumpAllocator allocator = memBuilder.CreateAllocator(0x1_0000, 0x2_0000); - - TargetTestHelpers.LayoutResult debuggerLayout = GetDebuggerLayout(helpers); - builder.AddTypes(new() { [DataType.Debugger] = new Target.TypeInfo() { Fields = debuggerLayout.Fields, Size = debuggerLayout.Stride } }); - - // Allocate and populate the Debugger struct - MockMemorySpace.HeapFragment debuggerFrag = allocator.Allocate(debuggerLayout.Stride, "Debugger"); - helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.LeftSideInitialized)].Offset, sizeof(int)), leftSideInitialized); - helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.Defines)].Offset, sizeof(uint)), defines); - helpers.Write(debuggerFrag.Data.AsSpan(debuggerLayout.Fields[nameof(Data.Debugger.MDStructuresVersion)].Offset, sizeof(uint)), mdStructuresVersion); - memBuilder.AddHeapFragment(debuggerFrag); - - // g_pDebugger is a pointer-to-Debugger. The global stores the address of g_pDebugger, - // so ReadGlobalPointer returns the location, and ReadPointer dereferences it. - MockMemorySpace.HeapFragment debuggerPtrFrag = allocator.Allocate((ulong)helpers.PointerSize, "g_pDebugger"); - helpers.WritePointer(debuggerPtrFrag.Data, debuggerFrag.Address); - memBuilder.AddHeapFragment(debuggerPtrFrag); - builder.AddGlobals((Constants.Globals.Debugger, debuggerPtrFrag.Address)); - - if (attachStateFlags.HasValue) - { - MockMemorySpace.HeapFragment attachFrag = allocator.Allocate(sizeof(uint), "CLRJitAttachState"); - helpers.Write(attachFrag.Data.AsSpan(0, sizeof(uint)), (uint)attachStateFlags.Value); - memBuilder.AddHeapFragment(attachFrag); - builder.AddGlobals((Constants.Globals.CLRJitAttachState, attachFrag.Address)); - } - - if (metadataUpdatesApplied.HasValue) - { - MockMemorySpace.HeapFragment metadataFrag = allocator.Allocate(1, "MetadataUpdatesApplied"); - helpers.Write(metadataFrag.Data.AsSpan(0, 1), metadataUpdatesApplied.Value); - memBuilder.AddHeapFragment(metadataFrag); - builder.AddGlobals((Constants.Globals.MetadataUpdatesApplied, metadataFrag.Address)); - } - - builder.AddContract(target => ((IContractFactory)new DebuggerFactory()).CreateContract(target, 1)); - - return builder.Build(); - } - - private static TestPlaceholderTarget BuildNullDebuggerTarget(MockTarget.Architecture arch) - { - TargetTestHelpers helpers = new(arch); - var builder = new TestPlaceholderTarget.Builder(arch); - MockMemorySpace.Builder memBuilder = builder.MemoryBuilder; - MockMemorySpace.BumpAllocator allocator = memBuilder.CreateAllocator(0x1_0000, 0x2_0000); - - // g_pDebugger is a pointer-to-Debugger that contains null. - MockMemorySpace.HeapFragment debuggerPtrFrag = allocator.Allocate((ulong)helpers.PointerSize, "g_pDebugger"); - helpers.WritePointer(debuggerPtrFrag.Data, 0); - memBuilder.AddHeapFragment(debuggerPtrFrag); - builder.AddGlobals((Constants.Globals.Debugger, debuggerPtrFrag.Address)); - builder.AddContract(target => ((IContractFactory)new DebuggerFactory()).CreateContract(target, 1)); - - return builder.Build(); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void TryGetDebuggerData_ReturnsTrue_WhenInitialized(MockTarget.Architecture arch) - { - Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0xDEADBEEF, mdStructuresVersion: 42); - IDebugger debugger = target.Contracts.Debugger; - - Assert.True(debugger.TryGetDebuggerData(out DebuggerData data)); - Assert.Equal(0xDEADBEEFu, data.DefinesBitField); - Assert.Equal(42u, data.MDStructuresVersion); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void TryGetDebuggerData_ReturnsFalse_WhenNotInitialized(MockTarget.Architecture arch) - { - Target target = BuildTarget(arch, leftSideInitialized: 0, defines: 0, mdStructuresVersion: 0); - IDebugger debugger = target.Contracts.Debugger; - - Assert.False(debugger.TryGetDebuggerData(out _)); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void TryGetDebuggerData_ReturnsFalse_WhenDebuggerNull(MockTarget.Architecture arch) - { - Target target = BuildNullDebuggerTarget(arch); - IDebugger debugger = target.Contracts.Debugger; - - Assert.False(debugger.TryGetDebuggerData(out _)); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void GetAttachStateFlags_ReturnsValue(MockTarget.Architecture arch) - { - Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, attachStateFlags: 0x42); - IDebugger debugger = target.Contracts.Debugger; - - Assert.Equal(0x42, debugger.GetAttachStateFlags()); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void GetAttachStateFlags_ReturnsZero_WhenValueIsZero(MockTarget.Architecture arch) - { - Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, attachStateFlags: 0); - IDebugger debugger = target.Contracts.Debugger; - - Assert.Equal(0, debugger.GetAttachStateFlags()); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void MetadataUpdatesApplied_ReturnsTrue_WhenSet(MockTarget.Architecture arch) - { - Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, metadataUpdatesApplied: 1); - IDebugger debugger = target.Contracts.Debugger; - - Assert.True(debugger.MetadataUpdatesApplied()); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void MetadataUpdatesApplied_ReturnsFalse_WhenNotSet(MockTarget.Architecture arch) - { - Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0, metadataUpdatesApplied: 0); - IDebugger debugger = target.Contracts.Debugger; - - Assert.False(debugger.MetadataUpdatesApplied()); - } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void MetadataUpdatesApplied_ReturnsFalse_WhenGlobalMissing(MockTarget.Architecture arch) - { - Target target = BuildTarget(arch, leftSideInitialized: 1, defines: 0, mdStructuresVersion: 0); - IDebugger debugger = target.Contracts.Debugger; - - Assert.False(debugger.MetadataUpdatesApplied()); - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs deleted file mode 100644 index feb10a49b2c69b..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiAppDomainDumpTests.cs +++ /dev/null @@ -1,217 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Microsoft.Diagnostics.DataContractReader.Contracts; -using Microsoft.Diagnostics.DataContractReader.Legacy; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// Dump-based integration tests for DacDbiImpl AppDomain, misc policy, and simple thread -/// property methods. Uses the BasicThreads debuggee (heap dump). -/// -public class DacDbiAppDomainDumpTests : DumpTestBase -{ - protected override string DebuggeeName => "BasicThreads"; - - private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetAppDomainId_ReturnsOneForValidAppDomain(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); - ulong appDomain = Target.ReadPointer(appDomainPtr); - - uint id; - int hr = dbi.GetAppDomainId(appDomain, &id); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(1u, id); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetAppDomainId_ReturnsZeroForNull(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - uint id; - int hr = dbi.GetAppDomainId(0, &id); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(0u, id); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetAppDomainFromId_ReturnsAppDomain(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - ulong appDomain; - int hr = dbi.GetAppDomainFromId(1, &appDomain); - Assert.Equal(System.HResults.S_OK, hr); - Assert.NotEqual(0UL, appDomain); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetAppDomainFromId_FailsForInvalidId(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - ulong appDomain; - int hr = dbi.GetAppDomainFromId(99, &appDomain); - Assert.True(hr < 0, "Expected failure HRESULT for invalid AppDomain ID"); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetCurrentAppDomain_ReturnsNonNull(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - ulong appDomain; - int hr = dbi.GetCurrentAppDomain(&appDomain); - Assert.Equal(System.HResults.S_OK, hr); - Assert.NotEqual(0UL, appDomain); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetCurrentAppDomain_MatchesGetAppDomainFromId(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - ulong currentAD; - int hr1 = dbi.GetCurrentAppDomain(¤tAD); - Assert.Equal(System.HResults.S_OK, hr1); - - ulong fromId; - int hr2 = dbi.GetAppDomainFromId(1, &fromId); - Assert.Equal(System.HResults.S_OK, hr2); - - Assert.Equal(currentAD, fromId); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void EnumerateAppDomains_CallsCallbackOnce(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - int count = 0; - delegate* unmanaged callback = &CountCallback; - int hr = dbi.EnumerateAppDomains((nint)callback, (nint)(&count)); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(1, count); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void IsAssemblyFullyTrusted_ReturnsTrue(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - ILoader loader = Target.Contracts.Loader; - TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); - ulong appDomain = Target.ReadPointer(appDomainPtr); - IEnumerable modules = loader.GetModuleHandles(new TargetPointer(appDomain), - AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); - - foreach (ModuleHandle module in modules) - { - TargetPointer moduleAddr = loader.GetModule(module); - Interop.BOOL result; - int hr = dbi.IsAssemblyFullyTrusted(moduleAddr, &result); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(Interop.BOOL.TRUE, result); - break; - } - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetConnectionID_ReturnsZero(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - IThread threadContract = Target.Contracts.Thread; - ThreadStoreData storeData = threadContract.GetThreadStoreData(); - - uint connId; - int hr = dbi.GetConnectionID(storeData.FirstThread, &connId); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(0u, connId); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetTaskID_ReturnsZero(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - IThread threadContract = Target.Contracts.Thread; - ThreadStoreData storeData = threadContract.GetThreadStoreData(); - - ulong taskId; - int hr = dbi.GetTaskID(storeData.FirstThread, &taskId); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(0UL, taskId); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void IsWinRTModule_ReturnsFalse(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - ILoader loader = Target.Contracts.Loader; - TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); - ulong appDomain = Target.ReadPointer(appDomainPtr); - IEnumerable modules = loader.GetModuleHandles(new TargetPointer(appDomain), - AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); - - foreach (ModuleHandle module in modules) - { - TargetPointer moduleAddr = loader.GetModule(module); - Interop.BOOL isWinRT; - int hr = dbi.IsWinRTModule(moduleAddr, &isWinRT); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(Interop.BOOL.FALSE, isWinRT); - break; - } - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void EnableNGENPolicy_ReturnsENotImpl(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - int hr = dbi.EnableNGENPolicy(0); - Assert.Equal(System.HResults.E_NOTIMPL, hr); - } - - [UnmanagedCallersOnly] - private static unsafe void CountCallback(ulong addr, nint userData) - { - (*(int*)userData)++; - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs deleted file mode 100644 index 718f1a9273a3a8..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiDebuggerDumpTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Diagnostics.DataContractReader.Contracts; -using Microsoft.Diagnostics.DataContractReader.Legacy; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// Dump-based integration tests for DacDbiImpl IDebugger contract methods. -/// Uses the BasicThreads debuggee (heap dump). -/// -public class DacDbiDebuggerDumpTests : DumpTestBase -{ - protected override string DebuggeeName => "BasicThreads"; - - private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void IsLeftSideInitialized_ReturnsNonZero(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - Interop.BOOL result; - int hr = dbi.IsLeftSideInitialized(&result); - Assert.Equal(System.HResults.S_OK, hr); - Assert.NotEqual(Interop.BOOL.FALSE, result); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetAttachStateFlags_Succeeds(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - int flags; - int hr = dbi.GetAttachStateFlags(&flags); - Assert.Equal(System.HResults.S_OK, hr); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetDefinesBitField_Succeeds(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - uint defines; - int hr = dbi.GetDefinesBitField(&defines); - Assert.Equal(System.HResults.S_OK, hr); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetMDStructuresVersion_Succeeds(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - uint version; - int hr = dbi.GetMDStructuresVersion(&version); - Assert.Equal(System.HResults.S_OK, hr); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void MetadataUpdatesApplied_Succeeds(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - Interop.BOOL result; - int hr = dbi.MetadataUpdatesApplied(&result); - Assert.Equal(System.HResults.S_OK, hr); - Assert.True(result == Interop.BOOL.TRUE || result == Interop.BOOL.FALSE); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void IsLeftSideInitialized_CrossValidateWithContract(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - Interop.BOOL dbiResult; - int hr = dbi.IsLeftSideInitialized(&dbiResult); - Assert.Equal(System.HResults.S_OK, hr); - - bool contractResult = Target.Contracts.Debugger.TryGetDebuggerData(out _); - Assert.Equal(contractResult, dbiResult != Interop.BOOL.FALSE); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetDefinesBitField_CrossValidateWithContract(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - uint dbiResult; - int hr = dbi.GetDefinesBitField(&dbiResult); - Assert.Equal(System.HResults.S_OK, hr); - - Assert.True(Target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)); - Assert.Equal(data.DefinesBitField, dbiResult); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetMDStructuresVersion_CrossValidateWithContract(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - uint dbiResult; - int hr = dbi.GetMDStructuresVersion(&dbiResult); - Assert.Equal(System.HResults.S_OK, hr); - - Assert.True(Target.Contracts.Debugger.TryGetDebuggerData(out Contracts.DebuggerData data)); - Assert.Equal(data.MDStructuresVersion, dbiResult); - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs deleted file mode 100644 index 55de104c4c978c..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiGCDumpTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Diagnostics.DataContractReader.Contracts; -using Microsoft.Diagnostics.DataContractReader.Legacy; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// Dump-based integration tests for DacDbiImpl GC methods. -/// Uses the BasicThreads debuggee (heap dump). -/// -public class DacDbiGCDumpTests : DumpTestBase -{ - protected override string DebuggeeName => "BasicThreads"; - - private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void AreGCStructuresValid_CrossValidateWithContract(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - Interop.BOOL result; - int hr = dbi.AreGCStructuresValid(&result); - Assert.Equal(System.HResults.S_OK, hr); - - bool contractResult = Target.Contracts.GC.GetGCStructuresValid(); - Assert.Equal(contractResult, result == Interop.BOOL.TRUE); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetGCHeapInformation_Succeeds(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - COR_HEAPINFO heapInfo; - int hr = dbi.GetGCHeapInformation(&heapInfo); - Assert.Equal(System.HResults.S_OK, hr); - - Assert.True(heapInfo.pointerSize == 4 || heapInfo.pointerSize == 8); - Assert.True(heapInfo.numHeaps >= 1); - Assert.True(heapInfo.areGCStructuresValid == Interop.BOOL.TRUE || heapInfo.areGCStructuresValid == Interop.BOOL.FALSE); - Assert.True(heapInfo.concurrent == Interop.BOOL.TRUE || heapInfo.concurrent == Interop.BOOL.FALSE); - Assert.True(heapInfo.gcType == 0 || heapInfo.gcType == 1); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetGCHeapInformation_MatchesPointerSize(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - COR_HEAPINFO heapInfo; - int hr = dbi.GetGCHeapInformation(&heapInfo); - Assert.Equal(System.HResults.S_OK, hr); - - Assert.Equal((uint)Target.PointerSize, heapInfo.pointerSize); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetGCHeapInformation_CrossValidateWithContract(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - COR_HEAPINFO heapInfo; - int hr = dbi.GetGCHeapInformation(&heapInfo); - Assert.Equal(System.HResults.S_OK, hr); - - IGC gc = Target.Contracts.GC; - bool contractValid = gc.GetGCStructuresValid(); - Assert.Equal(contractValid, heapInfo.areGCStructuresValid == Interop.BOOL.TRUE); - - uint heapCount = gc.GetGCHeapCount(); - Assert.Equal(heapCount, heapInfo.numHeaps); - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs deleted file mode 100644 index a24ac2cb9a6768..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiLoaderDumpTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Diagnostics.DataContractReader.Contracts; -using Microsoft.Diagnostics.DataContractReader.Legacy; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// Dump-based integration tests for DacDbiImpl loader, assembly, and module methods. -/// Uses the MultiModule debuggee (full dump), which loads assemblies from multiple ALCs. -/// -public class DacDbiLoaderDumpTests : DumpTestBase -{ - protected override string DebuggeeName => "MultiModule"; - protected override string DumpType => "full"; - - private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public void GetAppDomainFullName_ReturnsNonEmpty(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - TargetPointer appDomainPtr = Target.ReadGlobalPointer(Constants.Globals.AppDomain); - ulong appDomain = Target.ReadPointer(appDomainPtr); - - using var holder = new NativeStringHolder(); - int hr = dbi.GetAppDomainFullName(appDomain, holder.Ptr); - Assert.Equal(System.HResults.S_OK, hr); - Assert.False(string.IsNullOrEmpty(holder.Value), "AppDomain name should not be empty"); - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs deleted file mode 100644 index e47021a41484e2..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiObjectDumpTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Diagnostics.DataContractReader.Legacy; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// Dump-based integration tests for DacDbiImpl object handle methods. -/// Uses the BasicThreads debuggee (heap dump). -/// -public class DacDbiObjectDumpTests : DumpTestBase -{ - protected override string DebuggeeName => "BasicThreads"; - - private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetVmObjectHandle_IsIdentity(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - ulong testAddr = 0x12345678; - ulong result; - int hr = dbi.GetVmObjectHandle(testAddr, &result); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(testAddr, result); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetHandleAddressFromVmHandle_IsIdentity(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - ulong testAddr = 0xABCDEF00; - ulong result; - int hr = dbi.GetHandleAddressFromVmHandle(testAddr, &result); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(testAddr, result); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetAppDomainIdFromVmObjectHandle_ReturnsOneForNonZero(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - uint id; - int hr = dbi.GetAppDomainIdFromVmObjectHandle(0x12345678, &id); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(1u, id); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetAppDomainIdFromVmObjectHandle_ReturnsZeroForNull(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - uint id; - int hr = dbi.GetAppDomainIdFromVmObjectHandle(0, &id); - Assert.Equal(System.HResults.S_OK, hr); - Assert.Equal(0u, id); - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs deleted file mode 100644 index 11017bf43c1be6..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs +++ /dev/null @@ -1,168 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Microsoft.Diagnostics.DataContractReader.Contracts; -using Microsoft.Diagnostics.DataContractReader.Legacy; -using Microsoft.DotNet.XUnitExtensions; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// Dump-based integration tests for DacDbiImpl thread methods. -/// Uses the BasicThreads debuggee (heap dump), which spawns 5 named threads then crashes. -/// -public class DacDbiThreadDumpTests : DumpTestBase -{ - protected override string DebuggeeName => "BasicThreads"; - - private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void EnumerateThreads_CountMatchesContract(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - IThread threadContract = Target.Contracts.Thread; - ThreadStoreData storeData = threadContract.GetThreadStoreData(); - - int dbiCount = 0; - delegate* unmanaged callback = &CountThreadCallback; - int hr = dbi.EnumerateThreads((nint)callback, (nint)(&dbiCount)); - Assert.Equal(System.HResults.S_OK, hr); - - int expectedCount = 0; - TargetPointer current = storeData.FirstThread; - while (current != TargetPointer.Null) - { - ThreadData data = threadContract.GetThreadData(current); - bool isDead = (data.State & ThreadState.Dead) != 0; - bool isUnstarted = (data.State & ThreadState.Unstarted) != 0; - if (!isDead && !isUnstarted) - { - expectedCount++; - } - - current = data.NextThread; - } - - Assert.Equal(expectedCount, dbiCount); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void IsThreadMarkedDead_CrossValidateWithContract(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 isDead; - int hr = dbi.IsThreadMarkedDead(current, &isDead); - Assert.Equal(System.HResults.S_OK, hr); - - ThreadData data = threadContract.GetThreadData(current); - bool contractSaysDead = (data.State & ThreadState.Dead) != 0; - Assert.Equal(contractSaysDead, isDead == Interop.BOOL.TRUE); - - current = data.NextThread; - } - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void TryGetVolatileOSThreadID_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) - { - uint osId; - int hr = dbi.TryGetVolatileOSThreadID(current, &osId); - Assert.Equal(System.HResults.S_OK, hr); - - ThreadData data = threadContract.GetThreadData(current); - // DacDbi normalizes SWITCHED_OUT_FIBER_OSID (0xbaadf00d) to 0 - const uint SWITCHED_OUT_FIBER_OSID = 0xbaadf00d; - uint expectedOsId = (uint)data.OSId.Value; - if (expectedOsId == SWITCHED_OUT_FIBER_OSID) - expectedOsId = 0; - Assert.Equal(expectedOsId, osId); - - current = data.NextThread; - } - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetUniqueThreadID_MatchesContract(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - IThread threadContract = Target.Contracts.Thread; - ThreadStoreData storeData = threadContract.GetThreadStoreData(); - - TargetPointer current = storeData.FirstThread; - HashSet seenIds = new(); - - while (current != TargetPointer.Null) - { - uint uniqueId; - int hr = dbi.GetUniqueThreadID(current, &uniqueId); - Assert.Equal(System.HResults.S_OK, hr); - - ThreadData data = threadContract.GetThreadData(current); - Assert.Equal((uint)data.OSId.Value, uniqueId); - Assert.True(seenIds.Add(uniqueId), $"Duplicate thread ID: {uniqueId}"); - - current = data.NextThread; - } - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - public unsafe void GetCurrentException_CrossValidateWithContract(TestConfiguration config) - { - InitializeDumpTest(config); - DacDbiImpl dbi = CreateDacDbi(); - - IThread threadContract = Target.Contracts.Thread; - ThreadStoreData storeData = threadContract.GetThreadStoreData(); - - 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 - { - throw new SkipTestException("GetThrowableObject not yet implemented in Thread contract"); - } - - Assert.Equal(System.HResults.S_OK, hr); - } - - [UnmanagedCallersOnly] - private static unsafe void CountThreadCallback(ulong addr, nint userData) - { - (*(int*)userData)++; - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs deleted file mode 100644 index 8bca8e70b1bb24..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/NativeStringHolder.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// Creates a native-memory object that mimics the C++ IStringHolder vtable layout. -/// DacDbiImpl.StringHolderAssignCopy reads: objPtr -> vtable -> AssignCopy fn ptr. -/// This helper allocates that exact structure in unmanaged memory so we can test -/// string-returning DacDbiImpl methods directly (without COM marshaling). -/// -internal sealed class NativeStringHolder : IDisposable -{ - // Layout in native memory: - // _objectPtr -> [vtablePtr] (nint) - // _vtablePtr -> [fnPtr] (nint, the AssignCopy function pointer) - private readonly IntPtr _objectPtr; - private readonly IntPtr _vtablePtr; - private readonly GCHandle _delegateHandle; - private bool _disposed; - - // Delegate matching the native IStringHolder::AssignCopy(this, const WCHAR* psz) signature. - // Use ThisCall to match the C++ virtual method calling convention (thiscall on x86, no-op on x64/arm64). - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate int AssignCopyDelegate(IntPtr thisPtr, IntPtr psz); - - public string? Value { get; private set; } - - public NativeStringHolder() - { - AssignCopyDelegate assignCopy = AssignCopyImpl; - _delegateHandle = GCHandle.Alloc(assignCopy); - IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(assignCopy); - - // Allocate vtable (one slot: AssignCopy) - _vtablePtr = Marshal.AllocHGlobal(IntPtr.Size); - Marshal.WriteIntPtr(_vtablePtr, fnPtr); - - // Allocate object (one field: vtable pointer) - _objectPtr = Marshal.AllocHGlobal(IntPtr.Size); - Marshal.WriteIntPtr(_objectPtr, _vtablePtr); - } - - /// - /// The native pointer to pass as the nint IStringHolder parameter. - /// - public nint Ptr => _objectPtr; - - private int AssignCopyImpl(IntPtr thisPtr, IntPtr psz) - { - Value = Marshal.PtrToStringUni(psz); - return System.HResults.S_OK; - } - - public void Dispose() - { - if (!_disposed) - { - Marshal.FreeHGlobal(_objectPtr); - Marshal.FreeHGlobal(_vtablePtr); - _delegateHandle.Free(); - _disposed = true; - } - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/Program.cs deleted file mode 100644 index b6fe4395d5bef9..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/Program.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.CompilerServices; - -/// -/// Debuggee for cDAC dump tests — exercises stack reference enumeration. -/// Creates objects on the stack that should be reported as GC references, -/// then crashes with them still live. The test walks the stack and verifies -/// the expected references are found. -/// -internal static class Program -{ - /// - /// Marker string that tests can search for in the reported GC references - /// to verify that stack refs are being enumerated correctly. - /// - public const string MarkerValue = "cDAC-StackRefs-Marker-12345"; - - private static void Main() - { - MethodWithStackRefs(); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void MethodWithStackRefs() - { - // These locals will be GC-tracked in the JIT's GCInfo. - // The string has a known value we can find in the dump. - string marker = MarkerValue; - int[] array = [1, 2, 3, 4, 5]; - object boxed = 42; - - // Force the JIT to keep them alive at the FailFast call site. - GC.KeepAlive(marker); - GC.KeepAlive(array); - GC.KeepAlive(boxed); - - Environment.FailFast("cDAC dump test: StackRefs debuggee intentional crash"); - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/StackRefs.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/StackRefs.csproj deleted file mode 100644 index bb776824769fe6..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/StackRefs/StackRefs.csproj +++ /dev/null @@ -1,5 +0,0 @@ - - - Full - - diff --git a/src/native/managed/cdac/tests/DumpTests/DumpTestStackWalker.cs b/src/native/managed/cdac/tests/DumpTests/DumpTestStackWalker.cs deleted file mode 100644 index bff72c2b2f3b08..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/DumpTestStackWalker.cs +++ /dev/null @@ -1,302 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Diagnostics.DataContractReader.Contracts; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// A single resolved stack frame, carrying both the method name and the raw -/// MethodDesc pointer so that callers can perform ad-hoc assertions. -/// -internal readonly record struct ResolvedFrame(string? Name, TargetPointer MethodDescPtr); - -/// -/// Encapsulates a resolved stack walk for a thread, providing a builder-pattern -/// API to assert frame names, ordering, adjacency, and ad-hoc predicates. -/// -/// -/// -/// Inner-to-outer (callee -> caller, default): -/// -/// DumpTestStackWalker.Walk(Target, threadData) -/// .InnerToOuter() -/// .ExpectFrame("MethodC") -/// .ExpectFrame("MethodB") -/// .ExpectFrame("Main") -/// .Verify(); -/// -/// -/// -/// Outer-to-inner (caller -> callee): -/// -/// DumpTestStackWalker.Walk(Target, threadData) -/// .OuterToInner() -/// .ExpectFrame("Main") -/// .ExpectFrame("MethodA") -/// .ExpectFrame("MethodC") -/// .Verify(); -/// -/// -/// -/// Strict adjacency (no gaps allowed between the two frames): -/// -/// .ExpectFrame("CrashInVarargPInvoke") -/// .ExpectAdjacentFrame("Main") -/// -/// -/// -/// Ad-hoc predicate on matched frame: -/// -/// .ExpectFrame("IL_STUB_PInvoke", frame => -/// { -/// var md = rts.GetMethodDescHandle(frame.MethodDescPtr); -/// Assert.True(rts.IsILStub(md)); -/// }) -/// -/// -/// -internal sealed class DumpTestStackWalker -{ - private readonly ContractDescriptorTarget _target; - private readonly List _frames; - private readonly List _expectations = []; - private bool _outerToInner; - - private DumpTestStackWalker(ContractDescriptorTarget target, List frames) - { - _target = target; - _frames = frames; - } - - /// The cDAC target the stack was walked from. - public ContractDescriptorTarget Target => _target; - - /// - /// The fully resolved call stack in inner-to-outer order (callee -> caller). - /// - public IReadOnlyList Frames => _frames; - - /// - /// Creates a by walking the stack for - /// and resolving every frame's method name. - /// - public static DumpTestStackWalker Walk(ContractDescriptorTarget target, ThreadData threadData) - { - IStackWalk stackWalk = target.Contracts.StackWalk; - List frames = []; - - foreach (IStackDataFrameHandle frame in stackWalk.CreateStackWalk(threadData)) - { - TargetPointer methodDescPtr = stackWalk.GetMethodDescPtr(frame); - string? name = DumpTestHelpers.GetMethodName(target, methodDescPtr); - frames.Add(new ResolvedFrame(name, methodDescPtr)); - } - - return new DumpTestStackWalker(target, frames); - } - - /// - /// Sets the expectation direction to inner-to-outer (callee -> caller). - /// This is the default and matches the natural stack walk order. - /// - public DumpTestStackWalker InnerToOuter() - { - _outerToInner = false; - return this; - } - - /// - /// Sets the expectation direction to outer-to-inner (caller -> callee). - /// Expectations are matched starting from the outermost frame (e.g. Main) - /// toward the innermost (e.g. the crash site). - /// - public DumpTestStackWalker OuterToInner() - { - _outerToInner = true; - return this; - } - - /// - /// Prints the resolved call stack to the provided , - /// or to if no writer is given. - /// Useful for debugging test failures. - /// - public DumpTestStackWalker Print(Action? writer = null) - { - writer ??= Console.WriteLine; - writer($"Call stack ({_frames.Count} frames, {(_outerToInner ? "outer->inner" : "inner->outer")}):"); - - for (int i = 0; i < _frames.Count; i++) - { - int frameIndex = _outerToInner ? _frames.Count - 1 - i : i; - ResolvedFrame f = _frames[frameIndex]; - string name = f.Name ?? ""; - string md = f.MethodDescPtr != TargetPointer.Null - ? $"0x{(ulong)f.MethodDescPtr:X}" - : "null"; - writer($" [{i}] {name} (MethodDesc: {md})"); - } - - return this; - } - - /// - /// Expects a frame with after the previous expectation. - /// Gaps (unmatched frames) between this and the previous expectation are allowed. - /// - public DumpTestStackWalker ExpectFrame(string methodName, Action? assert = null) - { - _expectations.Add(new Expectation(methodName, adjacent: false, assert)); - return this; - } - - /// - /// Expects a frame with immediately after the - /// previously matched frame (no gaps allowed). - /// - public DumpTestStackWalker ExpectAdjacentFrame(string methodName, Action? assert = null) - { - Assert.True(_expectations.Count > 0, - "ExpectAdjacentFrame must follow a prior ExpectFrame or ExpectAdjacentFrame."); - _expectations.Add(new Expectation(methodName, adjacent: true, assert)); - return this; - } - - /// - /// Expects the next frame (at the current search position) to satisfy - /// , without matching by name. - /// Gaps before this frame are allowed. - /// - public DumpTestStackWalker ExpectFrameWhere(Func predicate, string description, Action? assert = null) - { - _expectations.Add(new Expectation(predicate, description, adjacent: false, assert)); - return this; - } - - /// - /// Expects the frame immediately after the previously matched frame to satisfy - /// (no gaps allowed). - /// - public DumpTestStackWalker ExpectAdjacentFrameWhere(Func predicate, string description, Action? assert = null) - { - Assert.True(_expectations.Count > 0, - "ExpectAdjacentFrameWhere must follow a prior expectation."); - _expectations.Add(new Expectation(predicate, description, adjacent: true, assert)); - return this; - } - - /// - /// Asserts that the call stack contains a frame with the given - /// , regardless of position or order. - /// - public DumpTestStackWalker AssertHasFrame(string methodName) - { - Assert.True(_frames.Any(f => string.Equals(f.Name, methodName, StringComparison.Ordinal)), - $"Expected frame '{methodName}' not found. Call stack: [{FormatCallStack(_frames)}]"); - return this; - } - - /// - /// Verifies all expectations added via , - /// , and . - /// - public void Verify() - { - // When outer-to-inner, reverse so expectations match caller -> callee order. - List ordered = _outerToInner - ? new List(Enumerable.Reverse(_frames)) - : _frames; - - int searchStart = 0; - int previousMatchIndex = -1; - - foreach (Expectation expectation in _expectations) - { - if (expectation.Adjacent) - { - int requiredIndex = previousMatchIndex + 1; - Assert.True(requiredIndex < ordered.Count, - $"Expected adjacent frame '{expectation.Description}' but stack ended at index {previousMatchIndex}. " + - $"Call stack: [{FormatCallStack(ordered)}]"); - - Assert.True(expectation.Matches(ordered[requiredIndex]), - $"Expected adjacent frame '{expectation.Description}' at index {requiredIndex}, " + - $"but found '{ordered[requiredIndex].Name ?? ""}'. " + - $"Call stack: [{FormatCallStack(ordered)}]"); - - expectation.RunAssert(ordered[requiredIndex]); - previousMatchIndex = requiredIndex; - searchStart = requiredIndex + 1; - } - else - { - int foundIndex = -1; - for (int i = searchStart; i < ordered.Count; i++) - { - if (expectation.Matches(ordered[i])) - { - foundIndex = i; - break; - } - } - - Assert.True(foundIndex >= 0, - $"Expected frame '{expectation.Description}' not found (searching from index {searchStart}). " + - $"Call stack: [{FormatCallStack(ordered)}]"); - - expectation.RunAssert(ordered[foundIndex]); - previousMatchIndex = foundIndex; - searchStart = foundIndex + 1; - } - } - } - - private static string FormatCallStack(List frames) - => string.Join(", ", frames.Select(f => f.Name ?? "")); - - private sealed class Expectation - { - private readonly string? _name; - private readonly Func? _predicate; - private readonly Action? _assert; - - public bool Adjacent { get; } - public string Description { get; } - - public Expectation(string name, bool adjacent, Action? assert) - { - _name = name; - _predicate = null; - _assert = assert; - Adjacent = adjacent; - Description = name; - } - - public Expectation(Func predicate, string description, bool adjacent, Action? assert) - { - _name = null; - _predicate = predicate; - _assert = assert; - Adjacent = adjacent; - Description = description; - } - - public bool Matches(ResolvedFrame frame) - { - if (_predicate is not null) - return _predicate(frame); - - return string.Equals(frame.Name, _name, StringComparison.Ordinal); - } - - public void RunAssert(ResolvedFrame frame) - { - _assert?.Invoke(frame); - } - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/SkipOnArchAttribute.cs b/src/native/managed/cdac/tests/DumpTests/SkipOnArchAttribute.cs deleted file mode 100644 index 86a28c2fd27707..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/SkipOnArchAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.DumpTests; - -/// -/// When applied to a test method, causes the test to be skipped when -/// the dump architecture matches the specified value. Checked by -/// before each test runs. -/// Multiple attributes can be stacked on a single method. -/// -[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public sealed class SkipOnArchAttribute : Attribute -{ - public string Arch { get; } - public string Reason { get; } - - public SkipOnArchAttribute(string arch, string reason) - { - Arch = arch; - Reason = reason; - } -} diff --git a/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs deleted file mode 100644 index 952b361636a6f8..00000000000000 --- a/src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs +++ /dev/null @@ -1,213 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.Diagnostics.DataContractReader.Contracts; -using Xunit; - -namespace Microsoft.Diagnostics.DataContractReader.DumpTests; - -/// -/// Dump-based integration tests for WalkStackReferences. -/// Uses the InitializeDumpTest overload to target different debuggees per test. -/// -public class StackReferenceDumpTests : DumpTestBase -{ - protected override string DebuggeeName => "StackWalk"; - protected override string DumpType => "full"; - - // --- StackWalk debuggee: basic stack walk --- - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] - public void WalkStackReferences_ReturnsWithoutThrowing(TestConfiguration config) - { - InitializeDumpTest(config, "StackWalk", "full"); - IStackWalk stackWalk = Target.Contracts.StackWalk; - - ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); - - IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); - Assert.NotNull(refs); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] - public void WalkStackReferences_RefsHaveValidSourceInfo(TestConfiguration config) - { - InitializeDumpTest(config, "StackWalk", "full"); - IStackWalk stackWalk = Target.Contracts.StackWalk; - - ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); - - IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); - foreach (StackReferenceData r in refs) - { - Assert.True(r.Source != TargetPointer.Null, "Stack reference should have a non-null Source (IP or Frame address)"); - Assert.True(r.StackPointer != TargetPointer.Null, "Stack reference should have a non-null StackPointer"); - } - } - - // --- GCRoots debuggee: objects kept alive on stack --- - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] - [SkipOnArch("x86", "GCInfo decoder does not support x86")] - public void GCRoots_WalkStackReferences_FindsRefs(TestConfiguration config) - { - InitializeDumpTest(config, "GCRoots", "full"); - IStackWalk stackWalk = Target.Contracts.StackWalk; - - ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "Main"); - - IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); - Assert.NotNull(refs); - Assert.True(refs.Count > 0, - "Expected GCRoots Main thread to have at least one stack reference (objects kept alive via GC.KeepAlive)"); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] - [SkipOnArch("x86", "GCInfo decoder does not support x86")] - public void GCRoots_RefsPointToValidObjects(TestConfiguration config) - { - InitializeDumpTest(config, "GCRoots", "full"); - IStackWalk stackWalk = Target.Contracts.StackWalk; - - ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "Main"); - - IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); - - int validObjectCount = 0; - foreach (StackReferenceData r in refs) - { - if (r.Object == TargetPointer.Null) - continue; - - try - { - TargetPointer methodTable = Target.ReadPointer(r.Object); - if (methodTable != TargetPointer.Null) - validObjectCount++; - } - catch - { - // Some refs may be interior pointers or otherwise unreadable - } - } - - Assert.True(validObjectCount > 0, - $"Expected at least one stack ref pointing to a valid object (total refs: {refs.Count})"); - } - - // --- StackRefs debuggee: known objects on stack with verifiable content --- - // These tests require Frame-based GC root scanning (ScanFrameRoots) which is not yet implemented. - - [Theory(Skip = "Requires Frame-based GC root scanning (ScanFrameRoots) — not yet implemented")] - [MemberData(nameof(TestConfigurations))] - public void StackRefs_FindsMarkerString(TestConfiguration config) - { - InitializeDumpTest(config, "StackRefs", "full"); - IStackWalk stackWalk = Target.Contracts.StackWalk; - IObject objectContract = Target.Contracts.Object; - - ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "MethodWithStackRefs"); - - IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); - Assert.True(refs.Count > 0, "Expected at least one stack reference from MethodWithStackRefs"); - - bool foundMarker = false; - string expectedMarker = "cDAC-StackRefs-Marker-12345"; - - foreach (StackReferenceData r in refs) - { - if (r.Object == TargetPointer.Null) - continue; - - try - { - string value = objectContract.GetStringValue(r.Object); - if (value == expectedMarker) - { - foundMarker = true; - break; - } - } - catch - { - // Not a string or not readable — skip - } - } - - Assert.True(foundMarker, - $"Expected to find marker string '{expectedMarker}' among {refs.Count} stack references"); - } - - [Theory(Skip = "Requires Frame-based GC root scanning (ScanFrameRoots) — not yet implemented")] - [MemberData(nameof(TestConfigurations))] - public void StackRefs_FindsArrayReference(TestConfiguration config) - { - InitializeDumpTest(config, "StackRefs", "full"); - IStackWalk stackWalk = Target.Contracts.StackWalk; - IObject objectContract = Target.Contracts.Object; - - ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "MethodWithStackRefs"); - - IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); - Assert.True(refs.Count > 0, "Expected at least one stack reference from MethodWithStackRefs"); - - // Look for the int[] { 1, 2, 3, 4, 5 } array using the Object contract. - bool foundArray = false; - - foreach (StackReferenceData r in refs) - { - if (r.Object == TargetPointer.Null) - continue; - - try - { - TargetPointer dataStart = objectContract.GetArrayData(r.Object, out uint count, out _, out _); - if (count != 5) - continue; - - int elem0 = Target.Read(dataStart + sizeof(int) * 0); - int elem1 = Target.Read(dataStart + sizeof(int) * 1); - int elem2 = Target.Read(dataStart + sizeof(int) * 2); - - if (elem0 == 1 && elem1 == 2 && elem2 == 3) - { - foundArray = true; - break; - } - } - catch - { - // Not an array or not readable — skip - } - } - - Assert.True(foundArray, - $"Expected to find int[]{{1,2,3,4,5}} among {refs.Count} stack references"); - } - - // --- PInvokeStub debuggee: Frame-based path --- - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] - [SkipOnOS(IncludeOnly = "windows", Reason = "PInvokeStub debuggee uses msvcrt.dll (Windows only)")] - public void PInvoke_WalkStackReferences_ReturnsWithoutThrowing(TestConfiguration config) - { - InitializeDumpTest(config, "PInvokeStub", "full"); - IStackWalk stackWalk = Target.Contracts.StackWalk; - - ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "Main"); - - IReadOnlyList refs = stackWalk.WalkStackReferences(crashingThread); - Assert.NotNull(refs); - } -} diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/BasicAlloc.csproj b/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/BasicAlloc.csproj deleted file mode 100644 index 6b512ec9245ec3..00000000000000 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/BasicAlloc.csproj +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/Program.cs b/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/Program.cs deleted file mode 100644 index f886c0ef72cefe..00000000000000 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/BasicAlloc/Program.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.CompilerServices; - -/// -/// Exercises basic object allocation patterns: objects, strings, arrays. -/// -internal static class Program -{ - [MethodImpl(MethodImplOptions.NoInlining)] - static object AllocAndHold() - { - object o = new object(); - string s = "hello world"; - int[] arr = new int[] { 1, 2, 3 }; - byte[] buf = new byte[256]; - GC.KeepAlive(o); - GC.KeepAlive(s); - GC.KeepAlive(arr); - GC.KeepAlive(buf); - return o; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void ManyLiveRefs() - { - object r0 = new object(); - object r1 = new object(); - object r2 = new object(); - object r3 = new object(); - object r4 = new object(); - object r5 = new object(); - object r6 = new object(); - object r7 = new object(); - string r8 = "live-string"; - int[] r9 = new int[10]; - - GC.KeepAlive(r0); GC.KeepAlive(r1); - GC.KeepAlive(r2); GC.KeepAlive(r3); - GC.KeepAlive(r4); GC.KeepAlive(r5); - GC.KeepAlive(r6); GC.KeepAlive(r7); - GC.KeepAlive(r8); GC.KeepAlive(r9); - } - - static int Main() - { - for (int i = 0; i < 2; i++) - { - AllocAndHold(); - ManyLiveRefs(); - } - return 100; - } -} diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Comprehensive.csproj b/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Comprehensive.csproj deleted file mode 100644 index 6b512ec9245ec3..00000000000000 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Comprehensive.csproj +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Program.cs b/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Program.cs deleted file mode 100644 index 6a2f26f146ef0f..00000000000000 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/Comprehensive/Program.cs +++ /dev/null @@ -1,253 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; - -/// -/// All-in-one comprehensive debuggee that exercises every scenario -/// in a single run: allocations, exceptions, generics, P/Invoke, threading. -/// -internal static class Program -{ - interface IKeepAlive { object GetRef(); } - class BoxHolder : IKeepAlive - { - object _value; - public BoxHolder() { _value = new object(); } - public BoxHolder(object v) { _value = v; } - [MethodImpl(MethodImplOptions.NoInlining)] - public object GetRef() => _value; - } - - struct LargeStruct { public object A, B, C, D; } - - [MethodImpl(MethodImplOptions.NoInlining)] - static object AllocAndHold() - { - object o = new object(); - string s = "hello world"; - int[] arr = new int[] { 1, 2, 3 }; - GC.KeepAlive(o); - GC.KeepAlive(s); - GC.KeepAlive(arr); - return o; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void NestedCall(int depth) - { - object o = new object(); - if (depth > 0) - NestedCall(depth - 1); - GC.KeepAlive(o); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void TryCatchScenario() - { - object before = new object(); - try - { - throw new InvalidOperationException("test"); - } - catch (InvalidOperationException ex) - { - object inCatch = new object(); - GC.KeepAlive(ex); - GC.KeepAlive(inCatch); - } - GC.KeepAlive(before); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void TryFinallyScenario() - { - object outerRef = new object(); - try - { - object innerRef = new object(); - GC.KeepAlive(innerRef); - } - finally - { - object finallyRef = new object(); - GC.KeepAlive(finallyRef); - } - GC.KeepAlive(outerRef); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void NestedExceptionScenario() - { - object a = new object(); - try - { - try - { - throw new ArgumentException("inner"); - } - catch (ArgumentException ex1) - { - GC.KeepAlive(ex1); - throw new InvalidOperationException("outer", ex1); - } - } - catch (InvalidOperationException ex2) - { - GC.KeepAlive(ex2); - } - GC.KeepAlive(a); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void FilterExceptionScenario() - { - object holder = new object(); - try - { - throw new ArgumentException("filter-test"); - } - catch (ArgumentException ex) when (FilterCheck(ex)) - { - GC.KeepAlive(ex); - } - GC.KeepAlive(holder); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static bool FilterCheck(Exception ex) - { - object filterLocal = new object(); - GC.KeepAlive(filterLocal); - return ex.Message.Contains("filter"); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static T GenericAlloc() where T : new() - { - T val = new T(); - object marker = new object(); - GC.KeepAlive(marker); - return val; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void InterfaceDispatchScenario() - { - IKeepAlive holder = new BoxHolder(new int[] { 42, 43 }); - object r = holder.GetRef(); - GC.KeepAlive(holder); - GC.KeepAlive(r); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void DelegateScenario() - { - object captured = new object(); - Func fn = () => { GC.KeepAlive(captured); return new object(); }; - object result = fn(); - GC.KeepAlive(result); - GC.KeepAlive(fn); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void StructWithRefsScenario() - { - LargeStruct ls; - ls.A = new object(); - ls.B = "struct-string"; - ls.C = new int[] { 10, 20 }; - ls.D = new BoxHolder(ls.A); - GC.KeepAlive(ls.A); - GC.KeepAlive(ls.B); - GC.KeepAlive(ls.C); - GC.KeepAlive(ls.D); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void PinnedScenario() - { - byte[] buffer = new byte[64]; - GCHandle pin = GCHandle.Alloc(buffer, GCHandleType.Pinned); - try - { - object other = new object(); - GC.KeepAlive(other); - GC.KeepAlive(buffer); - } - finally - { - pin.Free(); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void MultiThreadScenario() - { - ManualResetEventSlim ready = new ManualResetEventSlim(false); - ManualResetEventSlim go = new ManualResetEventSlim(false); - Thread t = new Thread(() => - { - object threadLocal = new object(); - ready.Set(); - go.Wait(); - NestedCall(5); - GC.KeepAlive(threadLocal); - }); - t.Start(); - ready.Wait(); - go.Set(); - NestedCall(3); - t.Join(); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void RethrowScenario() - { - object outerRef = new object(); - try - { - try - { - throw new ApplicationException("rethrow-test"); - } - catch (ApplicationException) - { - object catchRef = new object(); - GC.KeepAlive(catchRef); - throw; - } - } - catch (ApplicationException ex) - { - GC.KeepAlive(ex); - } - GC.KeepAlive(outerRef); - } - - static int Main() - { - for (int i = 0; i < 2; i++) - { - AllocAndHold(); - NestedCall(5); - TryCatchScenario(); - TryFinallyScenario(); - NestedExceptionScenario(); - FilterExceptionScenario(); - GenericAlloc(); - GenericAlloc>(); - InterfaceDispatchScenario(); - DelegateScenario(); - StructWithRefsScenario(); - PinnedScenario(); - MultiThreadScenario(); - RethrowScenario(); - } - return 100; - } -} diff --git a/src/native/managed/cdac/tests/StressTests/Debuggees/Directory.Build.props b/src/native/managed/cdac/tests/StressTests/Debuggees/Directory.Build.props deleted file mode 100644 index e0bc33ad142047..00000000000000 --- a/src/native/managed/cdac/tests/StressTests/Debuggees/Directory.Build.props +++ /dev/null @@ -1,15 +0,0 @@ - - - - - Exe - $(NetCoreAppToolCurrent) - true - enable - $(ArtifactsBinDir)StressTests\$(MSBuildProjectName)\$(Configuration)\ - true - - false - $(NoWarn);SA1400;IDE0059;SYSLIB1054;CA1852;CA1861 - - diff --git a/src/native/managed/cdac/tests/StressTests/README.md b/src/native/managed/cdac/tests/StressTests/README.md deleted file mode 100644 index c5bcde5675b3f0..00000000000000 --- a/src/native/managed/cdac/tests/StressTests/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# cDAC Stress Tests - -This folder contains stress tests that verify the cDAC's stack reference -enumeration against the runtime's GC root scanning. The tests run managed -debuggee applications under `corerun` with cDAC stress flags enabled, -triggering verification at allocation points, GC points, or instruction-level -GC stress points. - -## Quick Start - -```powershell -# Prerequisites: build CoreCLR Checked and generate core_root -# build.cmd clr+libs -rc Checked -lc Release -# src\tests\build.cmd Checked generatelayoutonly /p:LibrariesConfiguration=Release - -# Run all debuggees (allocation-point verification, no GCStress) -./RunStressTests.ps1 -SkipBuild - -# Run a single debuggee -./RunStressTests.ps1 -SkipBuild -Debuggee BasicAlloc - -# Run with instruction-level GCStress (slower, more thorough) -./RunStressTests.ps1 -SkipBuild -CdacStress 0x14 -GCStress 0x4 - -# Full comparison including walk parity and DAC cross-check -./RunStressTests.ps1 -SkipBuild -CdacStress 0x74 -GCStress 0x4 -``` - -## How It Works - -### DOTNET_CdacStress Flags - -The `DOTNET_CdacStress` environment variable is a bitmask that controls -**where** and **what** the runtime verifies: - -| Bit | Flag | Description | -|-----|------|-------------| -| 0x1 | ALLOC | Verify at managed allocation points | -| 0x2 | GC | Verify at GC collection points | -| 0x4 | INSTR | Verify at instruction-level GC stress points (requires `DOTNET_GCStress`) | -| 0x10 | REFS | Compare GC stack references (cDAC vs runtime) | -| 0x20 | WALK | Compare stack walk frame ordering (cDAC vs DAC) | -| 0x40 | USE_DAC | Also compare GC refs against the legacy DAC | -| 0x100 | UNIQUE | Only verify each instruction pointer once | - -Common combinations: -- `0x11` — ALLOC + REFS (fast, default) -- `0x14` — INSTR + REFS (thorough, requires `DOTNET_GCStress=0x4`) -- `0x31` — ALLOC + REFS + WALK (fast with walk parity check) -- `0x74` — INSTR + REFS + WALK + USE_DAC (full comparison) - -### Verification Flow - -At each stress point, the native hook (`cdacstress.cpp`) in the runtime: - -1. Suspends the current thread's context -2. Calls the cDAC's `GetStackReferences` to enumerate GC roots -3. Compares against the runtime's own GC root enumeration -4. Optionally compares against the legacy DAC's enumeration -5. Optionally compares stack walk frame ordering -6. Logs `[PASS]` or `[FAIL]` per verification point - -The script collects these results and reports aggregate pass/fail counts. - -## Debuggees - -Each debuggee is a standalone console application under `Debuggees/`: - -| Debuggee | Scenarios | -|----------|-----------| -| **BasicAlloc** | Object allocation, strings, arrays, many live refs | -| **Comprehensive** | All-in-one: allocations, deep stacks, exceptions, generics, P/Invoke, threading | - -All debuggees return exit code 100 on success. - -### Adding a New Debuggee - -1. Create a new folder under `Debuggees/` (e.g., `Debuggees/MyScenario/`) -2. Add a minimal `.csproj`: - ```xml - - ``` - The `Directory.Build.props` provides all common settings. -3. Add a `Program.cs` with a `Main()` that returns 100 -4. Use `[MethodImpl(MethodImplOptions.NoInlining)]` and `GC.KeepAlive()` - to prevent the JIT from optimizing away allocations and references - -The script auto-discovers all debuggees by scanning for `.csproj` files. - -## Script Parameters - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `-Configuration` | `Checked` | Runtime build configuration | -| `-CdacStress` | `0x11` | Hex bitmask for `DOTNET_CdacStress` | -| `-GCStress` | _(empty)_ | Hex value for `DOTNET_GCStress` (e.g., `0x4`) | -| `-Debuggee` | _(all)_ | Which debuggee(s) to run | -| `-SkipBuild` | off | Skip CoreCLR/cDAC build step | -| `-SkipBaseline` | off | Skip baseline (no-stress) verification | - -## Expected Results - -Most runs achieve >99.5% pass rate. A small number of failures (~0.2%) -are expected due to the ScanFrameRoots gap — the cDAC does not yet enumerate -GC roots from explicit frame stub data (e.g., `StubDispatchFrame`, -`PInvokeCalliFrame`). These are tracked in [known-issues.md](known-issues.md). - -Walk parity (`WALK` flag) should show 0 mismatches. diff --git a/src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 b/src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 deleted file mode 100644 index f51933048c5ac4..00000000000000 --- a/src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 +++ /dev/null @@ -1,309 +0,0 @@ -<# -.SYNOPSIS - Build and test the cDAC stress verification mode. - -.DESCRIPTION - This script: - 1. Builds CoreCLR native + cDAC tools (incremental) - 2. Generates core_root layout - 3. Builds debuggee test apps from the Debuggees/ folder - 4. Runs each debuggee under corerun with configurable cDAC stress flags - - Supports Windows, Linux, and macOS. - - The DOTNET_CdacStress environment variable controls WHERE and WHAT is verified: - WHERE (low nibble): - 0x1 = ALLOC — verify at allocation points - 0x2 = GC — verify at GC points - 0x4 = INSTR — verify at instruction-level GC stress points (requires DOTNET_GCStress) - WHAT (high nibble): - 0x10 = REFS — compare GC stack references (cDAC vs runtime) - 0x20 = WALK — compare stack walk frames (cDAC vs DAC) - 0x40 = USE_DAC — also compare GC refs against DAC - MODIFIER: - 0x100 = UNIQUE — only verify each IP once - -.PARAMETER Configuration - Runtime configuration: Checked (default) or Debug. - -.PARAMETER CdacStress - Hex value for DOTNET_CdacStress flags. Default: 0x11 (ALLOC|REFS). - Common values: - 0x11 = ALLOC|REFS (fast, allocation points only) - 0x14 = INSTR|REFS (thorough, requires GCStress) - 0x74 = INSTR|REFS|WALK|USE_DAC (full comparison, slow) - -.PARAMETER GCStress - Hex value for DOTNET_GCStress. Default: empty (disabled). - Set to 0x4 for instruction-level stress. - -.PARAMETER Debuggee - Which debuggee(s) to run. Default: All. - Auto-discovered from the Debuggees directory. - -.PARAMETER SkipBuild - Skip the CoreCLR/cDAC build step (use existing artifacts). - -.PARAMETER SkipBaseline - Skip baseline verification steps. - -.EXAMPLE - ./RunStressTests.ps1 -SkipBuild - ./RunStressTests.ps1 -Debuggee BasicAlloc -SkipBuild - ./RunStressTests.ps1 -CdacStress 0x74 -GCStress 0x4 # Full comparison with GCStress - ./RunStressTests.ps1 -CdacStress 0x114 -SkipBuild # Unique IPs only -#> -param( - [ValidateSet("Checked", "Debug")] - [string]$Configuration = "Checked", - - [string]$CdacStress = "0x11", - - [string]$GCStress = "", - - [string[]]$Debuggee = @(), - - [switch]$SkipBuild, - - [switch]$SkipBaseline -) - -$ErrorActionPreference = "Stop" -$scriptDir = $PSScriptRoot -$repoRoot = $scriptDir - -# Resolve repo root — walk up from script location to find build script -$buildScript = if ($IsWindows -or $env:OS -eq "Windows_NT") { "build.cmd" } else { "build.sh" } -while ($repoRoot -and !(Test-Path (Join-Path $repoRoot $buildScript))) { - $parent = Split-Path $repoRoot -Parent - if ($parent -eq $repoRoot) { $repoRoot = $null; break } - $repoRoot = $parent -} -if (-not $repoRoot) { - Write-Error "Could not find repo root ($buildScript). Place this script inside the runtime repo." - exit 1 -} - -# Detect platform -$isWin = ($IsWindows -or $env:OS -eq "Windows_NT") -$osName = if ($isWin) { "windows" } elseif ($IsMacOS) { "osx" } else { "linux" } -$arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() -$arch = switch ($arch) { - "x64" { "x64" } - "arm64" { "arm64" } - "arm" { "arm" } - "x86" { "x86" } - default { throw "Unsupported architecture: $arch" } -} - -$platformId = "$osName.$arch" -$coreRoot = Join-Path $repoRoot "artifacts" "tests" "coreclr" "$platformId.$Configuration" "Tests" "Core_Root" -$buildCmd = Join-Path $repoRoot $buildScript -$dotnetName = if ($isWin) { "dotnet.exe" } else { "dotnet" } -$corerunName = if ($isWin) { "corerun.exe" } else { "corerun" } -$dotnetExe = Join-Path $repoRoot ".dotnet" $dotnetName -$corerunExe = Join-Path $coreRoot $corerunName -$cdacDll = if ($isWin) { "mscordaccore_universal.dll" } elseif ($IsMacOS) { "libmscordaccore_universal.dylib" } else { "libmscordaccore_universal.so" } -$debuggeesDir = Join-Path $scriptDir "Debuggees" - -# Discover available debuggees -$allDebuggees = Get-ChildItem $debuggeesDir -Directory | Where-Object { Test-Path (Join-Path $_.FullName "*.csproj") } | ForEach-Object { $_.Name } - -# Resolve which debuggees to run -if ($Debuggee.Count -eq 0) { - $selectedDebuggees = $allDebuggees -} else { - $selectedDebuggees = $Debuggee - foreach ($d in $selectedDebuggees) { - if ($d -notin $allDebuggees) { - Write-Error "Unknown debuggee '$d'. Available: $($allDebuggees -join ', ')" - exit 1 - } - } -} - -Write-Host "=== cDAC Stress Test ===" -ForegroundColor Cyan -Write-Host " Repo root: $repoRoot" -Write-Host " Platform: $platformId" -Write-Host " Configuration: $Configuration" -Write-Host " CdacStress: $CdacStress" -Write-Host " GCStress: $(if ($GCStress) { $GCStress } else { '(disabled)' })" -Write-Host " Debuggees: $($selectedDebuggees -join ', ')" -Write-Host "" - -# --------------------------------------------------------------------------- -# Step 1: Build CoreCLR + cDAC -# --------------------------------------------------------------------------- -if (-not $SkipBuild) { - Write-Host ">>> Step 1: Building CoreCLR native + cDAC tools ($Configuration)..." -ForegroundColor Yellow - Push-Location $repoRoot - try { - $buildArgs = @("-subset", "clr.native+tools.cdac", "-c", $Configuration, "-rc", $Configuration, "-lc", "Release", "-bl") - & $buildCmd @buildArgs - if ($LASTEXITCODE -ne 0) { Write-Error "Build failed with exit code $LASTEXITCODE"; exit 1 } - } finally { - Pop-Location - } - - Write-Host ">>> Step 1b: Generating core_root layout..." -ForegroundColor Yellow - $testBuildScript = if ($isWin) { - Join-Path $repoRoot "src" "tests" "build.cmd" - } else { - Join-Path $repoRoot "src" "tests" "build.sh" - } - & $testBuildScript $Configuration generatelayoutonly -SkipRestorePackages /p:LibrariesConfiguration=Release - if ($LASTEXITCODE -ne 0) { Write-Error "Core_root generation failed"; exit 1 } -} else { - Write-Host ">>> Step 1: Skipping build (-SkipBuild)" -ForegroundColor DarkGray - if (!(Test-Path $corerunExe)) { - Write-Error "Core_root not found at $coreRoot. Run without -SkipBuild first." - exit 1 - } -} - -# Verify cDAC library exists -if (!(Test-Path (Join-Path $coreRoot $cdacDll))) { - Write-Error "$cdacDll not found in core_root. Ensure cDAC was built." - exit 1 -} - -# --------------------------------------------------------------------------- -# Step 2: Build debuggees -# --------------------------------------------------------------------------- -Write-Host ">>> Step 2: Building debuggees..." -ForegroundColor Yellow -foreach ($d in $selectedDebuggees) { - $csproj = Get-ChildItem (Join-Path $debuggeesDir $d) -Filter "*.csproj" | Select-Object -First 1 - & $dotnetExe build $csproj.FullName -c Release --nologo -v q - if ($LASTEXITCODE -ne 0) { Write-Error "Failed to build debuggee '$d'"; exit 1 } - Write-Host " Built $d" -ForegroundColor DarkGray -} - -# Helper: find the debuggee DLL in the build output -function Find-DebuggeeDll([string]$name) { - $binDir = Join-Path $repoRoot "artifacts" "bin" "StressTests" $name "Release" - if (!(Test-Path $binDir)) { - # Fall back to checking the project output directly - $projDir = Join-Path $debuggeesDir $name - $binDir = Join-Path $projDir "bin" "Release" - } - $dll = Get-ChildItem $binDir -Recurse -Filter "$name.dll" | Select-Object -First 1 - if (-not $dll) { - Write-Error "Could not find $name.dll in $binDir" - exit 1 - } - return $dll.FullName -} - -# Helper: clear stress environment variables -function Clear-StressEnv { - Remove-Item Env:\DOTNET_GCStress -ErrorAction SilentlyContinue - Remove-Item Env:\DOTNET_CdacStress -ErrorAction SilentlyContinue - Remove-Item Env:\DOTNET_CdacStressLogFile -ErrorAction SilentlyContinue - Remove-Item Env:\DOTNET_ContinueOnAssert -ErrorAction SilentlyContinue -} - -# Helper: run a debuggee with corerun and return exit code -function Invoke-Debuggee([string]$dllPath) { - $env:CORE_ROOT = $coreRoot - & $corerunExe $dllPath - return $LASTEXITCODE -} - -# --------------------------------------------------------------------------- -# Step 3: Run baseline (optional) -# --------------------------------------------------------------------------- -if (-not $SkipBaseline) { - Write-Host ">>> Step 3: Running baseline (no stress)..." -ForegroundColor Yellow - Clear-StressEnv - foreach ($d in $selectedDebuggees) { - $dll = Find-DebuggeeDll $d - $ec = Invoke-Debuggee $dll - if ($ec -ne 100) { - Write-Error "Baseline failed for '$d' (exit code $ec, expected 100)" - exit 1 - } - Write-Host " $d — baseline passed" -ForegroundColor DarkGray - } - Write-Host " All baselines passed." -ForegroundColor Green -} else { - Write-Host ">>> Skipping baseline (-SkipBaseline)" -ForegroundColor DarkGray -} - -# --------------------------------------------------------------------------- -# Step 4: Run with cDAC stress -# --------------------------------------------------------------------------- -Write-Host ">>> Step 4: Running with CdacStress=$CdacStress$(if ($GCStress) { " GCStress=$GCStress" })..." -ForegroundColor Yellow -$logDir = Join-Path $repoRoot "artifacts" "tests" "coreclr" "$platformId.$Configuration" "Tests" "cdacstresslogs" -New-Item -ItemType Directory -Force $logDir | Out-Null - -$totalPasses = 0 -$totalFails = 0 -$totalWalkOK = 0 -$totalWalkMM = 0 -$failedDebuggees = @() -$sw = [System.Diagnostics.Stopwatch]::StartNew() - -foreach ($d in $selectedDebuggees) { - $dll = Find-DebuggeeDll $d - $logFile = Join-Path $logDir "$d.log" - - Clear-StressEnv - $env:CORE_ROOT = $coreRoot - $env:DOTNET_CdacStress = $CdacStress - $env:DOTNET_CdacStressLogFile = $logFile - $env:DOTNET_ContinueOnAssert = "1" - if ($GCStress) { - $env:DOTNET_GCStress = $GCStress - } - - $dSw = [System.Diagnostics.Stopwatch]::StartNew() - & $corerunExe $dll - $ec = $LASTEXITCODE - $dSw.Stop() - - # Parse results - $passes = 0; $fails = 0; $walkOK = 0; $walkMM = 0 - if (Test-Path $logFile) { - $logContent = Get-Content $logFile - $passes = ($logContent | Select-String "^\[PASS\]").Count - $fails = ($logContent | Select-String "^\[FAIL\]").Count - $walkOK = ($logContent | Select-String "WALK_OK").Count - $walkMM = ($logContent | Select-String "WALK_MISMATCH").Count - } - - $totalPasses += $passes - $totalFails += $fails - $totalWalkOK += $walkOK - $totalWalkMM += $walkMM - - $status = if ($ec -eq 100) { "PASS" } else { "FAIL"; $failedDebuggees += $d } - $color = if ($ec -eq 100 -and $fails -eq 0) { "Green" } elseif ($ec -eq 100) { "Yellow" } else { "Red" } - $detail = "refs=$passes/$($passes+$fails)" - if ($walkOK -gt 0 -or $walkMM -gt 0) { $detail += " walk=$walkOK/$($walkOK+$walkMM)" } - Write-Host " $d — $status ($detail) [$($dSw.Elapsed.ToString('mm\:ss'))]" -ForegroundColor $color -} - -$sw.Stop() -Clear-StressEnv - -# --------------------------------------------------------------------------- -# Summary -# --------------------------------------------------------------------------- -Write-Host "" -Write-Host "=== Summary ===" -ForegroundColor Cyan -Write-Host " Elapsed: $($sw.Elapsed.ToString('mm\:ss'))" -Write-Host " Stress refs: $totalPasses PASS / $totalFails FAIL" -ForegroundColor $(if ($totalFails -eq 0) { "Green" } else { "Yellow" }) -if ($totalWalkOK -gt 0 -or $totalWalkMM -gt 0) { - Write-Host " Walk parity: $totalWalkOK OK / $totalWalkMM MISMATCH" -ForegroundColor $(if ($totalWalkMM -eq 0) { "Green" } else { "Yellow" }) -} -Write-Host " Logs: $logDir" - -if ($failedDebuggees.Count -gt 0) { - Write-Host "" - Write-Host "=== FAILED: $($failedDebuggees -join ', ') ===" -ForegroundColor Red - exit 1 -} else { - Write-Host "" - Write-Host "=== ALL PASSED ===" -ForegroundColor Green - exit 0 -} diff --git a/src/native/managed/cdac/tests/StressTests/known-issues.md b/src/native/managed/cdac/tests/StressTests/known-issues.md deleted file mode 100644 index 6445d255b67362..00000000000000 --- a/src/native/managed/cdac/tests/StressTests/known-issues.md +++ /dev/null @@ -1,57 +0,0 @@ -# cDAC Stack Reference Walking — Known Issues - -This document tracks known gaps between the cDAC's stack reference enumeration -and the legacy DAC's `GetStackReferences`. - -## Current Test Results - -Using `DOTNET_CdacStress` with cDAC-vs-DAC comparison: - -| Mode | Non-EH debuggees (6) | ExceptionHandling | -|------|-----------------------|-------------------| -| INSTR (0x4 + GCStress=0x4, step=10) | 0 failures | 0-2 failures | -| ALLOC+UNIQUE (0x101) | 0 failures | 4 failures | -| Walk comparison (0x20, IP+SP) | 0 mismatches | N/A | - -## Known Issue: cDAC Cannot Unwind Through Native Frames - -**Severity**: Low — only affects live-process stress testing during active -exception first-pass dispatch. Does not affect dump analysis where the thread -is suspended with a consistent Frame chain. - -**Pattern**: `cDAC < DAC` (cDAC reports 4 refs, DAC reports 10-13). -ExceptionHandling debuggee only, 4 deterministic occurrences per run. - -**Root cause**: The cDAC's `AMD64Unwinder.Unwind` (and equivalents for other -architectures) can only unwind **managed** frames — it checks -`ExecutionManager.GetCodeBlockHandle(IP)` first and returns false if the IP -is not in a managed code range. This means it cannot unwind through native -runtime frames (allocation helpers, EH dispatch code, etc.). - -When the allocation stress point fires during exception first-pass dispatch: - -1. The thread's `m_pFrame` is `FRAME_TOP` (no explicit Frames in the chain - because the InlinedCallFrame/SoftwareExceptionFrame have been popped or - not yet pushed at that point in the EH dispatch sequence) -2. The initial IP is in native code (allocation helper) -3. The cDAC attempts to unwind through native frames but - `GetCodeBlockHandle` returns null for native IPs → unwind fails -4. With no Frames and no ability to unwind, the walk stops early - -The legacy DAC's `DacStackReferenceWalker::WalkStack` succeeds because -`StackWalkFrames` calls `VirtualUnwindToFirstManagedCallFrame` which uses -OS-level unwind (`RtlVirtualUnwind` on Windows, `PAL_VirtualUnwind` on Unix) -that can unwind ANY native frame using PE `.pdata`/`.xdata` sections. - -**Possible fixes**: -1. **Ensure Frames are always available** — change the runtime to keep - an explicit Frame pushed during allocation points within EH dispatch. - The cDAC cannot do OS-level native unwind (it operates on dumps where - `RtlVirtualUnwind` is not available). The Frame chain is the only - mechanism the cDAC has for transitioning through native code to reach - managed frames. If `m_pFrame = FRAME_TOP` when the IP is native, the - cDAC cannot proceed. -2. **Accept as known limitation** — these failures only occur during - live-process stress testing at a narrow window during EH first-pass - dispatch. In dumps, the exception state is frozen and the Frame chain - is consistent. diff --git a/src/tests/restore.proj b/src/tests/restore.proj deleted file mode 100644 index 7b995a4ffcf9f0..00000000000000 --- a/src/tests/restore.proj +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/tests/restore.projects.props b/src/tests/restore.projects.props deleted file mode 100644 index 285dd6d6cb4d55..00000000000000 --- a/src/tests/restore.projects.props +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - From bf6385a82ad1216bbe67d7bd8989f4367d5b64cf Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 2 Apr 2026 13:33:01 -0400 Subject: [PATCH 4/7] Fix Acos method indentation and use float.Pi instead of magic literal - Fix extra leading space in AcosDouble, AcosSingle, and AcosSingleCoreDouble methods to match the indentation of AsinDouble/AsinSingle - Replace magic float literal 3.1415927f with float.Pi for clarity Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../System/Runtime/Intrinsics/VectorMath.cs | 356 +++++++++--------- 1 file changed, 178 insertions(+), 178 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs index d7c709a2e69c46..ab5731f89310d9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -3223,185 +3223,185 @@ private static TVectorDouble AsinSingleCoreDouble(TVectorDouble a } public static TVectorDouble AcosDouble(TVectorDouble x) - where TVectorDouble : unmanaged, ISimdVector - where TVectorUInt64 : unmanaged, ISimdVector - { - // This code is based on `acos` from amd/aocl-libm-ose - // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. - // - // Licensed under the BSD 3-Clause "New" or "Revised" License - // See THIRD-PARTY-NOTICES.TXT for the full license text - - // Implementation Notes - // -------------------- - // Based on the value of x, acos(x) is calculated as: - // For |x| <= 0.5: acos(x) = pi/2 - (x + x^3*R(x^2)) - // For |x| > 0.5: use acos(x) = pi - 2*asin(sqrt((1-|x|)/2)) or 2*asin(sqrt((1-x)/2)) - // where R(x^2) is a [5,4] rational minimax approximation (same as asin.c). - - // Rational polynomial coefficients (same as asin.c / AMD acos.c) - const double C1 = 0.227485835556935010735943483075; - const double C2 = -0.445017216867635649900123110649; - const double C3 = 0.275558175256937652532686256258; - const double C4 = -0.0549989809235685841612020091328; - const double C5 = 0.00109242697235074662306043804220; - const double C6 = 0.0000482901920344786991880522822991; - - const double D1 = 1.36491501334161032038194214209; - const double D2 = -3.28431505720958658909889444194; - const double D3 = 2.76568859157270989520376345954; - const double D4 = -0.943639137032492685763471240072; - const double D5 = 0.105869422087204370341222318533; - - const double PI = 3.1415926535897933e+00; // 0x400921fb54442d18 - const double PIBY2_HEAD = 1.5707963267948966e+00; // 0x3ff921fb54442d18 - const double PIBY2_TAIL = 6.1232339957367660e-17; // 0x3c91a62633145c07 - - TVectorDouble xneg = TVectorDouble.LessThan(x, TVectorDouble.Zero); - TVectorDouble ax = TVectorDouble.Abs(x); - - TVectorDouble transformMask = TVectorDouble.GreaterThanOrEqual(ax, TVectorDouble.Create(0.5)); - - // For |x| >= 0.5: r = 0.5*(1-ax), s = sqrt(r) - // For |x| < 0.5: r = ax*ax - TVectorDouble r = TVectorDouble.ConditionalSelect(transformMask, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); - TVectorDouble s = TVectorDouble.Sqrt(r); - - // Evaluate numerator: r*(C1 + r*(C2 + r*(C3 + r*(C4 + r*(C5 + r*C6))))) - TVectorDouble polyNum = TVectorDouble.Create(C6); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C5)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C4)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C3)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C2)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C1)); - - // Evaluate denominator: D1 + r*(D2 + r*(D3 + r*(D4 + r*D5))) - TVectorDouble polyDen = TVectorDouble.Create(D5); - polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D4)); - polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D3)); - polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D2)); - polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D1)); - - // u = r * polyNum / polyDen - TVectorDouble u = r * polyNum / polyDen; - - // For transform region (|x| >= 0.5): - // s1 = high part of s (clear low 32 bits for precision) - // c = (r - s1*s1) / (s + s1) - TVectorDouble s1 = Unsafe.BitCast(Unsafe.BitCast(s) & TVectorUInt64.Create(0xFFFFFFFF00000000)); - TVectorDouble c = (r - s1 * s1) / (s + s1); - - // For x < 0, |x| >= 0.5: acos(x) = pi - 2*(s + (s*u - piby2_tail)) - TVectorDouble transformNeg = TVectorDouble.Create(PI) - TVectorDouble.Create(2.0) * (s + (s * u - TVectorDouble.Create(PIBY2_TAIL))); - // For x > 0, |x| >= 0.5: acos(x) = 2*s1 + (2*c + 2*s*u) - TVectorDouble transformPos = TVectorDouble.Create(2.0) * s1 + (TVectorDouble.Create(2.0) * c + TVectorDouble.Create(2.0) * s * u); - TVectorDouble vTransform = TVectorDouble.ConditionalSelect(xneg, transformNeg, transformPos); - - // For |x| < 0.5: acos(x) = piby2_head - (x - (piby2_tail - x*u)) - TVectorDouble vNormal = TVectorDouble.Create(PIBY2_HEAD) - (x - (TVectorDouble.Create(PIBY2_TAIL) - x * u)); - - TVectorDouble result = TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); - - // Handle special cases: |x| > 1 returns NaN, x = ±1 returns 0 or π - TVectorDouble absXGreaterThanOne = TVectorDouble.GreaterThan(ax, TVectorDouble.One); - result = TVectorDouble.ConditionalSelect(absXGreaterThanOne, TVectorDouble.Create(double.NaN), result); - - TVectorDouble xEqualsOne = TVectorDouble.Equals(x, TVectorDouble.One); - result = TVectorDouble.ConditionalSelect(xEqualsOne, TVectorDouble.Zero, result); - - TVectorDouble xEqualsNegOne = TVectorDouble.Equals(x, TVectorDouble.Create(-1.0)); - result = TVectorDouble.ConditionalSelect(xEqualsNegOne, TVectorDouble.Create(PI), result); - - return result; - } + where TVectorDouble : unmanaged, ISimdVector + where TVectorUInt64 : unmanaged, ISimdVector + { + // This code is based on `acos` from amd/aocl-libm-ose + // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + // Implementation Notes + // -------------------- + // Based on the value of x, acos(x) is calculated as: + // For |x| <= 0.5: acos(x) = pi/2 - (x + x^3*R(x^2)) + // For |x| > 0.5: use acos(x) = pi - 2*asin(sqrt((1-|x|)/2)) or 2*asin(sqrt((1-x)/2)) + // where R(x^2) is a [5,4] rational minimax approximation (same as asin.c). + + // Rational polynomial coefficients (same as asin.c / AMD acos.c) + const double C1 = 0.227485835556935010735943483075; + const double C2 = -0.445017216867635649900123110649; + const double C3 = 0.275558175256937652532686256258; + const double C4 = -0.0549989809235685841612020091328; + const double C5 = 0.00109242697235074662306043804220; + const double C6 = 0.0000482901920344786991880522822991; + + const double D1 = 1.36491501334161032038194214209; + const double D2 = -3.28431505720958658909889444194; + const double D3 = 2.76568859157270989520376345954; + const double D4 = -0.943639137032492685763471240072; + const double D5 = 0.105869422087204370341222318533; + + const double PI = 3.1415926535897933e+00; // 0x400921fb54442d18 + const double PIBY2_HEAD = 1.5707963267948966e+00; // 0x3ff921fb54442d18 + const double PIBY2_TAIL = 6.1232339957367660e-17; // 0x3c91a62633145c07 + + TVectorDouble xneg = TVectorDouble.LessThan(x, TVectorDouble.Zero); + TVectorDouble ax = TVectorDouble.Abs(x); + + TVectorDouble transformMask = TVectorDouble.GreaterThanOrEqual(ax, TVectorDouble.Create(0.5)); + + // For |x| >= 0.5: r = 0.5*(1-ax), s = sqrt(r) + // For |x| < 0.5: r = ax*ax + TVectorDouble r = TVectorDouble.ConditionalSelect(transformMask, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); + TVectorDouble s = TVectorDouble.Sqrt(r); + + // Evaluate numerator: r*(C1 + r*(C2 + r*(C3 + r*(C4 + r*(C5 + r*C6))))) + TVectorDouble polyNum = TVectorDouble.Create(C6); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C5)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C4)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C3)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C2)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C1)); + + // Evaluate denominator: D1 + r*(D2 + r*(D3 + r*(D4 + r*D5))) + TVectorDouble polyDen = TVectorDouble.Create(D5); + polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D4)); + polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D3)); + polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D2)); + polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D1)); + + // u = r * polyNum / polyDen + TVectorDouble u = r * polyNum / polyDen; + + // For transform region (|x| >= 0.5): + // s1 = high part of s (clear low 32 bits for precision) + // c = (r - s1*s1) / (s + s1) + TVectorDouble s1 = Unsafe.BitCast(Unsafe.BitCast(s) & TVectorUInt64.Create(0xFFFFFFFF00000000)); + TVectorDouble c = (r - s1 * s1) / (s + s1); + + // For x < 0, |x| >= 0.5: acos(x) = pi - 2*(s + (s*u - piby2_tail)) + TVectorDouble transformNeg = TVectorDouble.Create(PI) - TVectorDouble.Create(2.0) * (s + (s * u - TVectorDouble.Create(PIBY2_TAIL))); + // For x > 0, |x| >= 0.5: acos(x) = 2*s1 + (2*c + 2*s*u) + TVectorDouble transformPos = TVectorDouble.Create(2.0) * s1 + (TVectorDouble.Create(2.0) * c + TVectorDouble.Create(2.0) * s * u); + TVectorDouble vTransform = TVectorDouble.ConditionalSelect(xneg, transformNeg, transformPos); + + // For |x| < 0.5: acos(x) = piby2_head - (x - (piby2_tail - x*u)) + TVectorDouble vNormal = TVectorDouble.Create(PIBY2_HEAD) - (x - (TVectorDouble.Create(PIBY2_TAIL) - x * u)); + + TVectorDouble result = TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); + + // Handle special cases: |x| > 1 returns NaN, x = ±1 returns 0 or π + TVectorDouble absXGreaterThanOne = TVectorDouble.GreaterThan(ax, TVectorDouble.One); + result = TVectorDouble.ConditionalSelect(absXGreaterThanOne, TVectorDouble.Create(double.NaN), result); + + TVectorDouble xEqualsOne = TVectorDouble.Equals(x, TVectorDouble.One); + result = TVectorDouble.ConditionalSelect(xEqualsOne, TVectorDouble.Zero, result); + + TVectorDouble xEqualsNegOne = TVectorDouble.Equals(x, TVectorDouble.Create(-1.0)); + result = TVectorDouble.ConditionalSelect(xEqualsNegOne, TVectorDouble.Create(PI), result); + + return result; + } public static TVectorSingle AcosSingle(TVectorSingle x) - where TVectorSingle : unmanaged, ISimdVector - where TVectorInt32 : unmanaged, ISimdVector - where TVectorDouble : unmanaged, ISimdVector - where TVectorInt64 : unmanaged, ISimdVector - { - // This code is based on `acosf` from amd/aocl-libm-ose - // Copyright (C) 2021-2023 Advanced Micro Devices, Inc. All rights reserved. - // - // Licensed under the BSD 3-Clause "New" or "Revised" License - // See THIRD-PARTY-NOTICES.TXT for the full license text - - TVectorSingle outOfRange = TVectorSingle.GreaterThan(TVectorSingle.Abs(x), TVectorSingle.One); - TVectorSingle xEqualsOne = TVectorSingle.Equals(x, TVectorSingle.One); - TVectorSingle xEqualsNegOne = TVectorSingle.Equals(x, TVectorSingle.Create(-1.0f)); - - TVectorSingle result; - - if (TVectorSingle.ElementCount == TVectorDouble.ElementCount) - { - TVectorDouble dx = Widen(x); - result = Narrow(AcosSingleCoreDouble(dx)); - } - else - { - TVectorDouble dxLo = WidenLower(x); - TVectorDouble dxHi = WidenUpper(x); - result = Narrow( - AcosSingleCoreDouble(dxLo), - AcosSingleCoreDouble(dxHi)); - } - - result = TVectorSingle.ConditionalSelect(outOfRange, TVectorSingle.Create(float.NaN), result); - result = TVectorSingle.ConditionalSelect(xEqualsOne, TVectorSingle.Zero, result); - result = TVectorSingle.ConditionalSelect(xEqualsNegOne, TVectorSingle.Create(3.1415927f), result); - - return result; - } - - private static TVectorDouble AcosSingleCoreDouble(TVectorDouble dx) - where TVectorDouble : unmanaged, ISimdVector - { - // Rational polynomial coefficients from AMD acosf.c (same as asinf.c) - const double C1 = 0.184161606965100694821398249421; - const double C2 = -0.0565298683201845211985026327361; - const double C3 = -0.0133819288943925804214011424456; - const double C4 = -0.00396137437848476485201154797087; - const double D1 = 1.10496961524520294485512696706; - const double D2 = -0.836411276854206731913362287293; - - // High-precision pi constants - const double PI = 3.1415926535897933e+00; // 0x400921fb54442d18 - const double PIBY2_HEAD = 1.5707963267948966e+00; // 0x3ff921fb54442d18 - const double PIBY2_TAIL = 6.1232339957367660e-17; // 0x3c91a62633145c07 - - TVectorDouble xneg = TVectorDouble.LessThan(dx, TVectorDouble.Zero); - TVectorDouble ax = TVectorDouble.Abs(dx); - - TVectorDouble transformMask = TVectorDouble.GreaterThanOrEqual(ax, TVectorDouble.Create(0.5)); - - // For |x| >= 0.5: r = 0.5*(1-ax), s = sqrt(r) - // For |x| < 0.5: r = ax*ax - TVectorDouble r = TVectorDouble.ConditionalSelect(transformMask, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); - TVectorDouble s = TVectorDouble.Sqrt(r); - - // Rational polynomial: u = r * (C1 + (C2 + (C3 + C4*r)*r)*r) / (D1 + D2*r) - TVectorDouble polyNum = TVectorDouble.Create(C4); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C3)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C2)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C1)); - - TVectorDouble polyDen = TVectorDouble.MultiplyAddEstimate(TVectorDouble.Create(D2), r, TVectorDouble.Create(D1)); - - TVectorDouble u = r * polyNum / polyDen; - - // For x < 0, |x| >= 0.5: acos(x) = pi - 2*(s + (s*u - piby2_tail)) - // Since we're in double for float output, piby2_tail correction provides extra precision - TVectorDouble transformNeg = TVectorDouble.Create(PI) - TVectorDouble.Create(2.0) * (s + (s * u - TVectorDouble.Create(PIBY2_TAIL))); - // For x > 0, |x| >= 0.5: acos(x) = 2*s + 2*s*u - // (s1/c correction omitted: computing in double for float output, precision is sufficient) - TVectorDouble transformPos = TVectorDouble.Create(2.0) * s + TVectorDouble.Create(2.0) * s * u; - TVectorDouble vTransform = TVectorDouble.ConditionalSelect(xneg, transformNeg, transformPos); - - // For |x| < 0.5: acos(x) = piby2_head - (x - (piby2_tail - x*u)) - TVectorDouble vNormal = TVectorDouble.Create(PIBY2_HEAD) - (dx - (TVectorDouble.Create(PIBY2_TAIL) - dx * u)); - - return TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); - } + where TVectorSingle : unmanaged, ISimdVector + where TVectorInt32 : unmanaged, ISimdVector + where TVectorDouble : unmanaged, ISimdVector + where TVectorInt64 : unmanaged, ISimdVector + { + // This code is based on `acosf` from amd/aocl-libm-ose + // Copyright (C) 2021-2023 Advanced Micro Devices, Inc. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + TVectorSingle outOfRange = TVectorSingle.GreaterThan(TVectorSingle.Abs(x), TVectorSingle.One); + TVectorSingle xEqualsOne = TVectorSingle.Equals(x, TVectorSingle.One); + TVectorSingle xEqualsNegOne = TVectorSingle.Equals(x, TVectorSingle.Create(-1.0f)); + + TVectorSingle result; + + if (TVectorSingle.ElementCount == TVectorDouble.ElementCount) + { + TVectorDouble dx = Widen(x); + result = Narrow(AcosSingleCoreDouble(dx)); + } + else + { + TVectorDouble dxLo = WidenLower(x); + TVectorDouble dxHi = WidenUpper(x); + result = Narrow( + AcosSingleCoreDouble(dxLo), + AcosSingleCoreDouble(dxHi)); + } + + result = TVectorSingle.ConditionalSelect(outOfRange, TVectorSingle.Create(float.NaN), result); + result = TVectorSingle.ConditionalSelect(xEqualsOne, TVectorSingle.Zero, result); + result = TVectorSingle.ConditionalSelect(xEqualsNegOne, TVectorSingle.Create(float.Pi), result); + + return result; + } + + private static TVectorDouble AcosSingleCoreDouble(TVectorDouble dx) + where TVectorDouble : unmanaged, ISimdVector + { + // Rational polynomial coefficients from AMD acosf.c (same as asinf.c) + const double C1 = 0.184161606965100694821398249421; + const double C2 = -0.0565298683201845211985026327361; + const double C3 = -0.0133819288943925804214011424456; + const double C4 = -0.00396137437848476485201154797087; + const double D1 = 1.10496961524520294485512696706; + const double D2 = -0.836411276854206731913362287293; + + // High-precision pi constants + const double PI = 3.1415926535897933e+00; // 0x400921fb54442d18 + const double PIBY2_HEAD = 1.5707963267948966e+00; // 0x3ff921fb54442d18 + const double PIBY2_TAIL = 6.1232339957367660e-17; // 0x3c91a62633145c07 + + TVectorDouble xneg = TVectorDouble.LessThan(dx, TVectorDouble.Zero); + TVectorDouble ax = TVectorDouble.Abs(dx); + + TVectorDouble transformMask = TVectorDouble.GreaterThanOrEqual(ax, TVectorDouble.Create(0.5)); + + // For |x| >= 0.5: r = 0.5*(1-ax), s = sqrt(r) + // For |x| < 0.5: r = ax*ax + TVectorDouble r = TVectorDouble.ConditionalSelect(transformMask, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); + TVectorDouble s = TVectorDouble.Sqrt(r); + + // Rational polynomial: u = r * (C1 + (C2 + (C3 + C4*r)*r)*r) / (D1 + D2*r) + TVectorDouble polyNum = TVectorDouble.Create(C4); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C3)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C2)); + polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C1)); + + TVectorDouble polyDen = TVectorDouble.MultiplyAddEstimate(TVectorDouble.Create(D2), r, TVectorDouble.Create(D1)); + + TVectorDouble u = r * polyNum / polyDen; + + // For x < 0, |x| >= 0.5: acos(x) = pi - 2*(s + (s*u - piby2_tail)) + // Since we're in double for float output, piby2_tail correction provides extra precision + TVectorDouble transformNeg = TVectorDouble.Create(PI) - TVectorDouble.Create(2.0) * (s + (s * u - TVectorDouble.Create(PIBY2_TAIL))); + // For x > 0, |x| >= 0.5: acos(x) = 2*s + 2*s*u + // (s1/c correction omitted: computing in double for float output, precision is sufficient) + TVectorDouble transformPos = TVectorDouble.Create(2.0) * s + TVectorDouble.Create(2.0) * s * u; + TVectorDouble vTransform = TVectorDouble.ConditionalSelect(xneg, transformNeg, transformPos); + + // For |x| < 0.5: acos(x) = piby2_head - (x - (piby2_tail - x*u)) + TVectorDouble vNormal = TVectorDouble.Create(PIBY2_HEAD) - (dx - (TVectorDouble.Create(PIBY2_TAIL) - dx * u)); + + return TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); + } } } From 58d2ed8c221342d8ffcafe773e19a50816f1bb1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:00:23 +0000 Subject: [PATCH 5/7] Port Acos from optimized aocl-libm-ose source with Sollya polynomial coefficients Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22f9ee13-f6b9-4c35-ad05-63ff26534e31 Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com> --- .../System/Runtime/Intrinsics/VectorMath.cs | 201 +++++++++--------- 1 file changed, 95 insertions(+), 106 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs index ab5731f89310d9..546a3c77b596aa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -3227,7 +3227,7 @@ public static TVectorDouble AcosDouble(TVectorDoub where TVectorUInt64 : unmanaged, ISimdVector { // This code is based on `acos` from amd/aocl-libm-ose - // Copyright (C) 2008-2022 Advanced Micro Devices, Inc. All rights reserved. + // Copyright (C) 2021-2022 Advanced Micro Devices, Inc. All rights reserved. // // Licensed under the BSD 3-Clause "New" or "Revised" License // See THIRD-PARTY-NOTICES.TXT for the full license text @@ -3235,83 +3235,75 @@ public static TVectorDouble AcosDouble(TVectorDoub // Implementation Notes // -------------------- // Based on the value of x, acos(x) is calculated as: - // For |x| <= 0.5: acos(x) = pi/2 - (x + x^3*R(x^2)) - // For |x| > 0.5: use acos(x) = pi - 2*asin(sqrt((1-|x|)/2)) or 2*asin(sqrt((1-x)/2)) - // where R(x^2) is a [5,4] rational minimax approximation (same as asin.c). - - // Rational polynomial coefficients (same as asin.c / AMD acos.c) - const double C1 = 0.227485835556935010735943483075; - const double C2 = -0.445017216867635649900123110649; - const double C3 = 0.275558175256937652532686256258; - const double C4 = -0.0549989809235685841612020091328; - const double C5 = 0.00109242697235074662306043804220; - const double C6 = 0.0000482901920344786991880522822991; - - const double D1 = 1.36491501334161032038194214209; - const double D2 = -3.28431505720958658909889444194; - const double D3 = 2.76568859157270989520376345954; - const double D4 = -0.943639137032492685763471240072; - const double D5 = 0.105869422087204370341222318533; - - const double PI = 3.1415926535897933e+00; // 0x400921fb54442d18 - const double PIBY2_HEAD = 1.5707963267948966e+00; // 0x3ff921fb54442d18 - const double PIBY2_TAIL = 6.1232339957367660e-17; // 0x3c91a62633145c07 + // + // 1. If x > 0.5: acos(x) = 2 * asin(sqrt((1 - x) / 2)) + // 2. If x < -0.5: acos(x) = pi - 2 * asin(sqrt((1 + x) / 2)) + // 3. If |x| <= 0.5: acos(x) = pi/2 - asin(x) + // + // asin(x) is approximated using the polynomial: + // x + C1*x^3 + C2*x^5 + ... + C12*x^25 + + // Polynomial coefficients obtained from Sollya + const double C1 = 0.166666666666647700; // 0x1.55555555552aap-3 + const double C2 = 0.075000000004179696; // 0x1.333333337cbaep-4 + const double C3 = 0.044642856781408560; // 0x1.6db6db3c0984p-5 + const double C4 = 0.030381960650355640; // 0x1.f1c72dd86cbafp-6 + const double C5 = 0.022371727970318958; // 0x1.6e89d3ff33aa4p-6 + const double C6 = 0.017360094637841349; // 0x1.1c6d83ae664b6p-6 + const double C7 = 0.013881842859634605; // 0x1.c6e1568b90518p-7 + const double C8 = 0.012189191110336799; // 0x1.8f6a58977fe49p-7 + const double C9 = 0.006449405266899452; // 0x1.a6ab10b3321bp-8 + const double C10 = 0.019725887785684789; // 0x1.43305ebb2428fp-6 + const double C11 = -0.016511752058748410; // -0x1.0e874ec5e3157p-6 + const double C12 = 0.032096272998247702; // 0x1.06eec35b3b142p-5 + + const double PIBY2 = 1.5707963267948966; // 0x1.921fb54442d18p+0 + const double PIBY4 = 0.78539816339744828; // 0x1.921fb54442d18p-1 TVectorDouble xneg = TVectorDouble.LessThan(x, TVectorDouble.Zero); TVectorDouble ax = TVectorDouble.Abs(x); - TVectorDouble transformMask = TVectorDouble.GreaterThanOrEqual(ax, TVectorDouble.Create(0.5)); - - // For |x| >= 0.5: r = 0.5*(1-ax), s = sqrt(r) - // For |x| < 0.5: r = ax*ax - TVectorDouble r = TVectorDouble.ConditionalSelect(transformMask, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); - TVectorDouble s = TVectorDouble.Sqrt(r); - - // Evaluate numerator: r*(C1 + r*(C2 + r*(C3 + r*(C4 + r*(C5 + r*C6))))) - TVectorDouble polyNum = TVectorDouble.Create(C6); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C5)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C4)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C3)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C2)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C1)); - - // Evaluate denominator: D1 + r*(D2 + r*(D3 + r*(D4 + r*D5))) - TVectorDouble polyDen = TVectorDouble.Create(D5); - polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D4)); - polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D3)); - polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D2)); - polyDen = TVectorDouble.MultiplyAddEstimate(polyDen, r, TVectorDouble.Create(D1)); - - // u = r * polyNum / polyDen - TVectorDouble u = r * polyNum / polyDen; - - // For transform region (|x| >= 0.5): - // s1 = high part of s (clear low 32 bits for precision) - // c = (r - s1*s1) / (s + s1) - TVectorDouble s1 = Unsafe.BitCast(Unsafe.BitCast(s) & TVectorUInt64.Create(0xFFFFFFFF00000000)); - TVectorDouble c = (r - s1 * s1) / (s + s1); - - // For x < 0, |x| >= 0.5: acos(x) = pi - 2*(s + (s*u - piby2_tail)) - TVectorDouble transformNeg = TVectorDouble.Create(PI) - TVectorDouble.Create(2.0) * (s + (s * u - TVectorDouble.Create(PIBY2_TAIL))); - // For x > 0, |x| >= 0.5: acos(x) = 2*s1 + (2*c + 2*s*u) - TVectorDouble transformPos = TVectorDouble.Create(2.0) * s1 + (TVectorDouble.Create(2.0) * c + TVectorDouble.Create(2.0) * s * u); - TVectorDouble vTransform = TVectorDouble.ConditionalSelect(xneg, transformNeg, transformPos); - - // For |x| < 0.5: acos(x) = piby2_head - (x - (piby2_tail - x*u)) - TVectorDouble vNormal = TVectorDouble.Create(PIBY2_HEAD) - (x - (TVectorDouble.Create(PIBY2_TAIL) - x * u)); - - TVectorDouble result = TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); - - // Handle special cases: |x| > 1 returns NaN, x = ±1 returns 0 or π + TVectorDouble gtHalf = TVectorDouble.GreaterThan(ax, TVectorDouble.Create(0.5)); + + // For |x| > 0.5: z = 0.5*(1-|x|), y = -2*sqrt(z) + // For |x| <= 0.5: z = |x|*|x|, y = |x| + TVectorDouble z = TVectorDouble.ConditionalSelect(gtHalf, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); + TVectorDouble y = TVectorDouble.ConditionalSelect(gtHalf, TVectorDouble.Create(-2.0) * TVectorDouble.Sqrt(z), ax); + + // Evaluate polynomial: P(z) = C1 + z*(C2 + z*(C3 + ... + z*C12)) + TVectorDouble poly = TVectorDouble.Create(C12); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C11)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C10)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C9)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C8)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C7)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C6)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C5)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C4)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C3)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C2)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C1)); + + // poly = y + y * z * P(z) + poly = y + y * z * poly; + + // Reconstruct acos using split constants for precision: + // |x| > 0.5: A = 0, B = pi/2 + // |x| <= 0.5: A = pi/4, B = pi/4 + // positive x: result = (A - poly) + A + // negative x: result = (B + poly) + B + TVectorDouble aConst = TVectorDouble.Create(PIBY4) & ~gtHalf; + TVectorDouble bConst = TVectorDouble.ConditionalSelect(gtHalf, TVectorDouble.Create(PIBY2), TVectorDouble.Create(PIBY4)); + + TVectorDouble posResult = (aConst - poly) + aConst; + TVectorDouble negResult = (bConst + poly) + bConst; + + TVectorDouble result = TVectorDouble.ConditionalSelect(xneg, negResult, posResult); + + // Handle special cases: |x| > 1 returns NaN TVectorDouble absXGreaterThanOne = TVectorDouble.GreaterThan(ax, TVectorDouble.One); result = TVectorDouble.ConditionalSelect(absXGreaterThanOne, TVectorDouble.Create(double.NaN), result); - TVectorDouble xEqualsOne = TVectorDouble.Equals(x, TVectorDouble.One); - result = TVectorDouble.ConditionalSelect(xEqualsOne, TVectorDouble.Zero, result); - - TVectorDouble xEqualsNegOne = TVectorDouble.Equals(x, TVectorDouble.Create(-1.0)); - result = TVectorDouble.ConditionalSelect(xEqualsNegOne, TVectorDouble.Create(PI), result); - return result; } @@ -3357,51 +3349,48 @@ public static TVectorSingle AcosSingle(TVectorDouble dx) where TVectorDouble : unmanaged, ISimdVector { - // Rational polynomial coefficients from AMD acosf.c (same as asinf.c) - const double C1 = 0.184161606965100694821398249421; - const double C2 = -0.0565298683201845211985026327361; - const double C3 = -0.0133819288943925804214011424456; - const double C4 = -0.00396137437848476485201154797087; - const double D1 = 1.10496961524520294485512696706; - const double D2 = -0.836411276854206731913362287293; - - // High-precision pi constants - const double PI = 3.1415926535897933e+00; // 0x400921fb54442d18 - const double PIBY2_HEAD = 1.5707963267948966e+00; // 0x3ff921fb54442d18 - const double PIBY2_TAIL = 6.1232339957367660e-17; // 0x3c91a62633145c07 + // Polynomial coefficients from Sollya (AMD aocl-libm-ose acosf.c) + const double C1 = 0.166667014360427856445; // 0x1.5555fcp-3 + const double C2 = 0.074944347143173218; // 0x1.32f8d8p-4 + const double C3 = 0.045550186187028885; // 0x1.7525aap-5 + const double C4 = 0.023858169093728065; // 0x1.86e46ap-6 + const double C5 = 0.042635641992092133; // 0x1.5d456cp-5 + + const double PIBY2 = 1.5707963267948966; // 0x1.921fb54442d18p+0 + const double PIBY4 = 0.78539816339744828; // 0x1.921fb54442d18p-1 TVectorDouble xneg = TVectorDouble.LessThan(dx, TVectorDouble.Zero); TVectorDouble ax = TVectorDouble.Abs(dx); - TVectorDouble transformMask = TVectorDouble.GreaterThanOrEqual(ax, TVectorDouble.Create(0.5)); - - // For |x| >= 0.5: r = 0.5*(1-ax), s = sqrt(r) - // For |x| < 0.5: r = ax*ax - TVectorDouble r = TVectorDouble.ConditionalSelect(transformMask, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); - TVectorDouble s = TVectorDouble.Sqrt(r); + TVectorDouble gtHalf = TVectorDouble.GreaterThan(ax, TVectorDouble.Create(0.5)); - // Rational polynomial: u = r * (C1 + (C2 + (C3 + C4*r)*r)*r) / (D1 + D2*r) - TVectorDouble polyNum = TVectorDouble.Create(C4); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C3)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C2)); - polyNum = TVectorDouble.MultiplyAddEstimate(polyNum, r, TVectorDouble.Create(C1)); + // For |x| > 0.5: z = 0.5*(1-|x|), y = -2*sqrt(z) + // For |x| <= 0.5: z = |x|*|x|, y = |x| + TVectorDouble z = TVectorDouble.ConditionalSelect(gtHalf, TVectorDouble.Create(0.5) * (TVectorDouble.One - ax), ax * ax); + TVectorDouble y = TVectorDouble.ConditionalSelect(gtHalf, TVectorDouble.Create(-2.0) * TVectorDouble.Sqrt(z), ax); - TVectorDouble polyDen = TVectorDouble.MultiplyAddEstimate(TVectorDouble.Create(D2), r, TVectorDouble.Create(D1)); + // Evaluate polynomial: P(z) = C1 + z*(C2 + z*(C3 + z*(C4 + z*C5))) + TVectorDouble poly = TVectorDouble.Create(C5); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C4)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C3)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C2)); + poly = TVectorDouble.MultiplyAddEstimate(poly, z, TVectorDouble.Create(C1)); - TVectorDouble u = r * polyNum / polyDen; + // poly = y + y * z * P(z) + poly = y + y * z * poly; - // For x < 0, |x| >= 0.5: acos(x) = pi - 2*(s + (s*u - piby2_tail)) - // Since we're in double for float output, piby2_tail correction provides extra precision - TVectorDouble transformNeg = TVectorDouble.Create(PI) - TVectorDouble.Create(2.0) * (s + (s * u - TVectorDouble.Create(PIBY2_TAIL))); - // For x > 0, |x| >= 0.5: acos(x) = 2*s + 2*s*u - // (s1/c correction omitted: computing in double for float output, precision is sufficient) - TVectorDouble transformPos = TVectorDouble.Create(2.0) * s + TVectorDouble.Create(2.0) * s * u; - TVectorDouble vTransform = TVectorDouble.ConditionalSelect(xneg, transformNeg, transformPos); + // Reconstruct acos using split constants for precision: + // |x| > 0.5: A = 0, B = pi/2 + // |x| <= 0.5: A = pi/4, B = pi/4 + // positive x: result = (A - poly) + A + // negative x: result = (B + poly) + B + TVectorDouble aConst = TVectorDouble.Create(PIBY4) & ~gtHalf; + TVectorDouble bConst = TVectorDouble.ConditionalSelect(gtHalf, TVectorDouble.Create(PIBY2), TVectorDouble.Create(PIBY4)); - // For |x| < 0.5: acos(x) = piby2_head - (x - (piby2_tail - x*u)) - TVectorDouble vNormal = TVectorDouble.Create(PIBY2_HEAD) - (dx - (TVectorDouble.Create(PIBY2_TAIL) - dx * u)); + TVectorDouble posResult = (aConst - poly) + aConst; + TVectorDouble negResult = (bConst + poly) + bConst; - return TVectorDouble.ConditionalSelect(transformMask, vTransform, vNormal); + return TVectorDouble.ConditionalSelect(xneg, negResult, posResult); } } } From 5c3e19f836b26bdd922f708ccbceadb348da1d6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:05:53 +0000 Subject: [PATCH 6/7] Fix acosf C1 coefficient and remove unused TVectorUInt64 type parameter Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/22f9ee13-f6b9-4c35-ad05-63ff26534e31 Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com> --- .../src/System/Runtime/Intrinsics/Vector128.cs | 2 +- .../src/System/Runtime/Intrinsics/Vector256.cs | 2 +- .../src/System/Runtime/Intrinsics/Vector512.cs | 2 +- .../src/System/Runtime/Intrinsics/Vector64.cs | 2 +- .../src/System/Runtime/Intrinsics/VectorMath.cs | 5 ++--- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs index 4b75443b4cecb4..4a6f03c719b45f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs @@ -860,7 +860,7 @@ public static Vector128 Acos(Vector128 vector) { if (IsHardwareAccelerated) { - return VectorMath.AcosDouble, Vector128>(vector); + return VectorMath.AcosDouble>(vector); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs index 45c26508a31e92..cb160d6eea0573 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs @@ -861,7 +861,7 @@ public static Vector256 Acos(Vector256 vector) { if (IsHardwareAccelerated) { - return VectorMath.AcosDouble, Vector256>(vector); + return VectorMath.AcosDouble>(vector); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs index c90455e40ce80d..a5fbd6819efe14 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs @@ -764,7 +764,7 @@ public static Vector512 Acos(Vector512 vector) { if (IsHardwareAccelerated) { - return VectorMath.AcosDouble, Vector512>(vector); + return VectorMath.AcosDouble>(vector); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs index 6cdb9704a15b0d..dda762d928e411 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector64.cs @@ -828,7 +828,7 @@ public static Vector64 Acos(Vector64 vector) { if (IsHardwareAccelerated) { - return VectorMath.AcosDouble, Vector64>(vector); + return VectorMath.AcosDouble>(vector); } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs index 546a3c77b596aa..364f6bd0467dde 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -3222,9 +3222,8 @@ private static TVectorDouble AsinSingleCoreDouble(TVectorDouble a return ax + ax * g * poly + (TVectorDouble.Create(PIBY2) & gtHalf); } - public static TVectorDouble AcosDouble(TVectorDouble x) + public static TVectorDouble AcosDouble(TVectorDouble x) where TVectorDouble : unmanaged, ISimdVector - where TVectorUInt64 : unmanaged, ISimdVector { // This code is based on `acos` from amd/aocl-libm-ose // Copyright (C) 2021-2022 Advanced Micro Devices, Inc. All rights reserved. @@ -3350,7 +3349,7 @@ private static TVectorDouble AcosSingleCoreDouble(TVectorDouble d where TVectorDouble : unmanaged, ISimdVector { // Polynomial coefficients from Sollya (AMD aocl-libm-ose acosf.c) - const double C1 = 0.166667014360427856445; // 0x1.5555fcp-3 + const double C1 = 0.1666679084300995; // 0x1.5555fcp-3 const double C2 = 0.074944347143173218; // 0x1.32f8d8p-4 const double C3 = 0.045550186187028885; // 0x1.7525aap-5 const double C4 = 0.023858169093728065; // 0x1.86e46ap-6 From 22bb6c08c8e0cbc94b0bd4a016a9e60f3f23251e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:51:17 +0000 Subject: [PATCH 7/7] Use IsNegative instead of LessThan for -0.0 handling in AcosDouble and AcosSingleCoreDouble Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c2512e5c-9595-4ce7-9916-76bffa2a2a5a Co-authored-by: tannergooding <10487869+tannergooding@users.noreply.github.com> --- .../src/System/Runtime/Intrinsics/VectorMath.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs index 364f6bd0467dde..63bed36497a0ce 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/VectorMath.cs @@ -3259,7 +3259,7 @@ public static TVectorDouble AcosDouble(TVectorDouble x) const double PIBY2 = 1.5707963267948966; // 0x1.921fb54442d18p+0 const double PIBY4 = 0.78539816339744828; // 0x1.921fb54442d18p-1 - TVectorDouble xneg = TVectorDouble.LessThan(x, TVectorDouble.Zero); + TVectorDouble xneg = TVectorDouble.IsNegative(x); TVectorDouble ax = TVectorDouble.Abs(x); TVectorDouble gtHalf = TVectorDouble.GreaterThan(ax, TVectorDouble.Create(0.5)); @@ -3358,7 +3358,7 @@ private static TVectorDouble AcosSingleCoreDouble(TVectorDouble d const double PIBY2 = 1.5707963267948966; // 0x1.921fb54442d18p+0 const double PIBY4 = 0.78539816339744828; // 0x1.921fb54442d18p-1 - TVectorDouble xneg = TVectorDouble.LessThan(dx, TVectorDouble.Zero); + TVectorDouble xneg = TVectorDouble.IsNegative(dx); TVectorDouble ax = TVectorDouble.Abs(dx); TVectorDouble gtHalf = TVectorDouble.GreaterThan(ax, TVectorDouble.Create(0.5));