diff --git a/docs/design/datacontracts/GCInfo.md b/docs/design/datacontracts/GCInfo.md index 963d7eb21514bc..70964f409a93d1 100644 --- a/docs/design/datacontracts/GCInfo.md +++ b/docs/design/datacontracts/GCInfo.md @@ -1,8 +1,8 @@ # Contract GCInfo -This contract is for fetching information related to GCInfo associated with native code. Currently, this contract does not support x86 architecture. +This contract is for fetching information related to GCInfo associated with native code. -The GCInfo contract has platform specific implementations as GCInfo differs per architecture. With the exception of x86, all platforms have a common encoding scheme with different encoding lengths and normalization functions for data. x86 uses an entirely different scheme which is not currently supported by this contract. +The GCInfo contract has platform specific implementations as GCInfo differs per architecture. With the exception of x86, all platforms have a common encoding scheme with different encoding lengths and normalization functions for data. x86 uses an entirely different scheme which is partially supported by this contract. ## APIs of contract @@ -25,10 +25,27 @@ uint GetCodeLength(IGCInfoHandle handle); // Returns the stack base register number decoded from GCInfo uint GetStackBaseRegister(IGCInfoHandle handle); +// Returns the size in bytes of the outgoing-argument scratch area in the current +// frame, decoded from `_fixedStackParameterScratchArea` (`SizeOfStackOutgoingAndScratchArea`, +// encoded via `SIZE_OF_STACK_AREA_ENCBASE`). Platform-specific and can be non-zero on +// AMD64/ARM64/ARM/RISCV64/LoongArch64. Not supported on x86 (the x86 GC info scheme has +// no separate scratch-area concept; the x86 GC walker uses per-offset transitions). +uint GetSizeOfStackParameterArea(IGCInfoHandle handle); + +// Returns the size in bytes of stack-passed arguments that the callee pops on return, +// mirroring native `EECodeManager::GetStackParameterSize` (eetwain.cpp). Non-zero only +// on x86 (where managed code uses `__stdcall` calling convention with callee-popped args): +// returns 0 for varargs (caller-popped) and otherwise the argument size from the GC info +// header. Returns 0 on every other architecture. +uint GetCalleePoppedArgumentsSize(IGCInfoHandle handle); + // Returns the list of interruptible code offset ranges from the GCInfo +// (not implemented for x86 — x86 encodes per-offset transitions rather than explicit ranges). IReadOnlyList GetInterruptibleRanges(IGCInfoHandle handle); // Returns all live GC slots at the given instruction offset +// (not implemented for x86 — see X86GCInfo for the underlying transition data; the cDAC +// adapter is future work). IReadOnlyList EnumerateLiveSlots(IGCInfoHandle handle, uint instructionOffset, GcSlotEnumerationOptions options); ``` diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 5ca5484bc71a19..5064eda133f6f5 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -663,6 +663,8 @@ The x86 platform has some major differences to other platforms. In general this #### GCInfo Parsing The GCInfo structure is encoded using a variety of formats to optimize for speed of decoding and size on disk. For information on decoding and parsing refer to [GC Information Encoding for x86](../coreclr/jit/jit-gc-info-x86.md). +The x86 GCInfo decoder lives under the [GCInfo contract](GCInfo.md) at `src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/` (`X86GCInfo` and the supporting `InfoHdr`, `GCArgTable`, `GCTransition`, `CallPattern`, `GCInfoTargetExtensions` types). It is shared between the GCInfo contract (which exposes the offset-independent queries `GetCodeLength` / `GetStackBaseRegister` / `GetCalleePoppedArgumentsSize` for SOS callers) and the x86 stack walker (which constructs `X86GCInfo` directly with a `relativeOffset` to access offset-bound state — `IsInProlog`, `IsInEpilog`, `PrologOffset`, `EpilogOffset`, `PushedArgSize` — that is not exposed through `IGCInfoDecoder`). + #### Unwinding Algorithm The x86 architecture uses a custom unwinding algorithm defined in `gc_unwind_x86.inl`. The cDAC uses a copy of this algorithm ported to managed code in `X86Unwinder.cs`. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs index 990af49d265f64..74e085dc62508c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGCInfo.cs @@ -52,6 +52,7 @@ public interface IGCInfo : IContract uint GetCodeLength(IGCInfoHandle handle) => throw new NotImplementedException(); uint GetStackBaseRegister(IGCInfoHandle handle) => throw new NotImplementedException(); uint GetSizeOfStackParameterArea(IGCInfoHandle handle) => throw new NotImplementedException(); + uint GetCalleePoppedArgumentsSize(IGCInfoHandle handle) => throw new NotImplementedException(); IReadOnlyList GetInterruptibleRanges(IGCInfoHandle handle) => throw new NotImplementedException(); IReadOnlyList EnumerateLiveSlots(IGCInfoHandle handle, uint instructionOffset, GcSlotEnumerationOptions options) => throw new NotImplementedException(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index 7c8465b2baf26e..1e9aff1540ba9a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -419,9 +419,9 @@ uint IExecutionManager.GetStackParameterSize(CodeBlockHandle codeInfoHandle) if (gcInfoAddress == TargetPointer.Null) throw new InvalidOperationException($"GC info not available for {codeInfoHandle.Address}"); - uint relOffset = (uint)eman.GetRelativeOffset(codeInfoHandle).Value; - StackWalkHelpers.X86.GCInfo gcInfo = new(_target, gcInfoAddress, gcInfoVersion, relOffset); - return gcInfo.Header.VarArgs ? 0u : gcInfo.Header.ArgCount * (uint)_target.PointerSize; + IGCInfo gcInfoContract = _target.Contracts.GCInfo; + IGCInfoHandle handle = gcInfoContract.DecodePlatformSpecificGCInfo(gcInfoAddress, gcInfoVersion); + return gcInfoContract.GetCalleePoppedArgumentsSize(handle); } TargetPointer IExecutionManager.FindReadyToRunModule(TargetPointer address) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.cs new file mode 100644 index 00000000000000..fa2d810c765fdd --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.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.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +/// +/// IGCInfo implementation for x86. x86 uses the legacy bit-packed InfoHdr GC info format, +/// which is fundamentally different from the GcInfoDecoder format used by every other +/// architecture, so it cannot share . +/// The decoder lives at and is shared with the +/// x86 stack walker. +/// +internal sealed class GCInfoX86_1 : IGCInfo +{ + private readonly Target _target; + + internal GCInfoX86_1(Target target) + { + _target = target; + } + + IGCInfoHandle IGCInfo.DecodePlatformSpecificGCInfo(TargetPointer gcInfoAddress, uint gcVersion) + => new GCInfoHelpers.X86.X86GCInfo(_target, gcInfoAddress, gcVersion); + + IGCInfoHandle IGCInfo.DecodeInterpreterGCInfo(TargetPointer gcInfoAddress, uint gcVersion) + => new GcInfoDecoder(_target, gcInfoAddress, gcVersion); + + uint IGCInfo.GetCodeLength(IGCInfoHandle gcInfoHandle) + => AssertCorrectHandle(gcInfoHandle).GetCodeLength(); + + uint IGCInfo.GetStackBaseRegister(IGCInfoHandle gcInfoHandle) + => AssertCorrectHandle(gcInfoHandle).GetStackBaseRegister(); + + uint IGCInfo.GetSizeOfStackParameterArea(IGCInfoHandle gcInfoHandle) + => AssertCorrectHandle(gcInfoHandle).GetSizeOfStackParameterArea(); + + uint IGCInfo.GetCalleePoppedArgumentsSize(IGCInfoHandle gcInfoHandle) + => AssertCorrectHandle(gcInfoHandle).GetCalleePoppedArgumentsSize(); + + IReadOnlyList IGCInfo.GetInterruptibleRanges(IGCInfoHandle gcInfoHandle) + => AssertCorrectHandle(gcInfoHandle).GetInterruptibleRanges(); + + IReadOnlyList IGCInfo.EnumerateLiveSlots(IGCInfoHandle gcInfoHandle, uint instructionOffset, GcSlotEnumerationOptions options) + => AssertCorrectHandle(gcInfoHandle).EnumerateLiveSlots(instructionOffset, options); + + private static IGCInfoDecoder AssertCorrectHandle(IGCInfoHandle gcInfoHandle) + { + if (gcInfoHandle is not IGCInfoDecoder handle) + throw new System.ArgumentException("Invalid GC info handle", nameof(gcInfoHandle)); + + return handle; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs index ad261440db826b..d13e0f6051e98d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfo_1.cs @@ -40,6 +40,12 @@ uint IGCInfo.GetSizeOfStackParameterArea(IGCInfoHandle gcInfoHandle) return handle.GetSizeOfStackParameterArea(); } + uint IGCInfo.GetCalleePoppedArgumentsSize(IGCInfoHandle gcInfoHandle) + { + IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle); + return handle.GetCalleePoppedArgumentsSize(); + } + IReadOnlyList IGCInfo.GetInterruptibleRanges(IGCInfoHandle gcInfoHandle) { IGCInfoDecoder handle = AssertCorrectHandle(gcInfoHandle); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs index e6b146e4396284..279992fb4cd0dc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.cs @@ -12,6 +12,11 @@ internal interface IGCInfoDecoder : IGCInfoHandle uint GetCodeLength(); uint GetStackBaseRegister(); uint GetSizeOfStackParameterArea(); + + // Default 0; mirrors native EECodeManager::GetStackParameterSize, which only + // returns non-zero on x86 (where managed code uses __stdcall, callee-popped args). + uint GetCalleePoppedArgumentsSize() => 0; + IReadOnlyList GetInterruptibleRanges(); IReadOnlyList EnumerateLiveSlots(uint instructionOffset, GcSlotEnumerationOptions options); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/CallPattern.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.cs similarity index 97% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/CallPattern.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.cs index da2024c826262a..e660eea2452c3c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/CallPattern.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers.X86; internal static class CallPattern { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCArgTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.cs similarity index 99% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCArgTable.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.cs index 3d2ec4afdaf071..d0284332d230d6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCArgTable.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers.X86; public class GCArgTable { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfo.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs similarity index 80% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfo.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs index 513e3401acab8e..9a8c44f7234f38 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfo.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs @@ -7,7 +7,7 @@ using System.Diagnostics; using System.Linq; -namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers.X86; [Flags] public enum RegMask @@ -27,7 +27,7 @@ public enum RegMask RM_CALLEE_TRASHED = RM_ALL & ~RM_CALLEE_SAVED, } -public record GCInfo +public record X86GCInfo : IGCInfoDecoder { private const uint MINIMUM_SUPPORTED_GCINFO_VERSION = 4; private const uint MAXIMUM_SUPPORTED_GCINFO_VERSION = 5; @@ -69,7 +69,7 @@ public record GCInfo public uint PushedArgSize => _pushedArgSize.Value; private readonly Lazy _pushedArgSize; - public GCInfo(Target target, TargetPointer gcInfoAddress, uint gcInfoVersion, uint relativeOffset) + public X86GCInfo(Target target, TargetPointer gcInfoAddress, uint gcInfoVersion, uint relativeOffset = 0) { if (gcInfoVersion < MINIMUM_SUPPORTED_GCINFO_VERSION) { @@ -242,4 +242,35 @@ private uint CalculatePushedArgSize() return (uint)(depth * _target.PointerSize); } + + uint IGCInfoDecoder.GetCodeLength() => MethodSize; + + uint IGCInfoDecoder.GetStackBaseRegister() + { + // x86 ModRM register encoding: ESP = 4, EBP = 5. EBP is the stack base for + // EBP-frames and double-aligned frames; otherwise stack base is ESP. + const uint REG_ESP = 4; + const uint REG_EBP = 5; + return (Header.EbpFrame || Header.DoubleAlign) ? REG_EBP : REG_ESP; + } + + uint IGCInfoDecoder.GetSizeOfStackParameterArea() + => throw new NotSupportedException( + "x86 GC info does not encode a separate outgoing-argument scratch area; the cDAC " + + "GC scanner does not consume scratch-area sizing on x86 (the legacy x86 GC walker " + + "reasons over per-offset transitions instead)."); + + uint IGCInfoDecoder.GetCalleePoppedArgumentsSize() + { + // Mirrors native ::GetStackParameterSize(hdrInfo) in gc_unwind_x86.inl: varargs are + // caller-popped (return 0); other methods report the argument size from the GC info + // header. Used by EECodeManager::GetStackParameterSize on x86. + return Header.VarArgs ? 0u : Header.ArgCount * (uint)_target.PointerSize; + } + + IReadOnlyList IGCInfoDecoder.GetInterruptibleRanges() + => throw new NotSupportedException("x86 GC info does not encode explicit interruptible ranges; per-offset transitions are used instead. Decoding for the cDAC IGCInfoDecoder consumers is not yet implemented."); + + IReadOnlyList IGCInfoDecoder.EnumerateLiveSlots(uint instructionOffset, GcSlotEnumerationOptions options) + => throw new NotSupportedException("x86 GC info live-slot enumeration through IGCInfoDecoder is not yet implemented; the underlying InfoHdr/Transitions data is decoded but the IGCInfoDecoder.EnumerateLiveSlots adapter is future work."); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfoTargetExtensions.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.cs similarity index 96% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfoTargetExtensions.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.cs index 8634e0cde7afbe..b92cfe3664baeb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCInfoTargetExtensions.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers.X86; public static class GCInfoTargetExtensions { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCTransition.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.cs similarity index 98% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCTransition.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.cs index 71e9003e997e6a..d693702051410b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/GCTransition.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Text; -namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers.X86; public enum Action { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/InfoHdr.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.cs similarity index 99% rename from src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/InfoHdr.cs rename to src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.cs index 42a204084f2d8c..1e2745ba16798f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/GCInfoDecoding/InfoHdr.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.cs @@ -6,7 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; -namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; +namespace Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers.X86; /// /// based on src\inc\gcinfotypes.h InfoHdrSmall diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs index e7a4c4505ca95d..f28dc4a9667fd3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; +using Microsoft.Diagnostics.DataContractReader.Contracts.GCInfoHelpers.X86; using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86Context; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.X86; @@ -62,7 +63,7 @@ public bool Unwind(ref X86Context context) TargetPointer funcletStart = eman.GetFuncletStartAddress(cbh); bool isFunclet = eman.IsFunclet(cbh); - GCInfo gcInfo = new(_target, gcInfoAddress, gcInfoVersion, relOffset); + X86GCInfo gcInfo = new(_target, gcInfoAddress, gcInfoVersion, relOffset); if (gcInfo.IsInEpilog) { @@ -96,7 +97,7 @@ public bool Unwind(ref X86Context context) #endregion #region Unwind Logic - private void UnwindEpilog(ref X86Context context, GCInfo gcInfo, TargetPointer epilogBase) + private void UnwindEpilog(ref X86Context context, X86GCInfo gcInfo, TargetPointer epilogBase) { Debug.Assert(gcInfo.IsInEpilog); Debug.Assert(gcInfo.EpilogOffset > 0); @@ -114,7 +115,7 @@ private void UnwindEpilog(ref X86Context context, GCInfo gcInfo, TargetPointer e context.Esp += ESPIncrementOnReturn(gcInfo); } - private void UnwindEbpDoubleAlignFrameEpilog(ref X86Context context, GCInfo gcInfo, TargetPointer epilogBase) + private void UnwindEbpDoubleAlignFrameEpilog(ref X86Context context, X86GCInfo gcInfo, TargetPointer epilogBase) { /* See how many instructions we have executed in the epilog to determine which callee-saved registers @@ -241,7 +242,7 @@ have already been popped */ context.Esp = esp; } - private void UnwindEspFrameEpilog(ref X86Context context, GCInfo gcInfo, TargetPointer epilogBase) + private void UnwindEspFrameEpilog(ref X86Context context, X86GCInfo gcInfo, TargetPointer epilogBase) { Debug.Assert(gcInfo.IsInEpilog); Debug.Assert(!gcInfo.Header.EbpFrame && !gcInfo.Header.DoubleAlign); @@ -307,7 +308,7 @@ regsMask as well to exclude registers which have already been popped. */ context.Esp = esp; } - private void UnwindEspFrame(ref X86Context context, GCInfo gcInfo, TargetPointer methodStart) + private void UnwindEspFrame(ref X86Context context, X86GCInfo gcInfo, TargetPointer methodStart) { Debug.Assert(!gcInfo.Header.EbpFrame && !gcInfo.Header.DoubleAlign); Debug.Assert(!gcInfo.IsInEpilog); @@ -347,7 +348,7 @@ private void UnwindEspFrame(ref X86Context context, GCInfo gcInfo, TargetPointer context.Esp = esp + ESPIncrementOnReturn(gcInfo); } - private void UnwindEspFrameProlog(ref X86Context context, GCInfo gcInfo, TargetPointer methodStart) + private void UnwindEspFrameProlog(ref X86Context context, X86GCInfo gcInfo, TargetPointer methodStart) { Debug.Assert(gcInfo.IsInProlog); Debug.Assert(!gcInfo.Header.EbpFrame && !gcInfo.Header.DoubleAlign); @@ -426,7 +427,7 @@ private void UnwindEspFrameProlog(ref X86Context context, GCInfo gcInfo, TargetP private bool UnwindEbpDoubleAlignFrame( ref X86Context context, - GCInfo gcInfo, + X86GCInfo gcInfo, TargetPointer methodStart, TargetPointer funcletStart, bool isFunclet) @@ -510,7 +511,7 @@ private bool UnwindEbpDoubleAlignFrame( return true; } - private void UnwindEbpDoubleAlignFrameProlog(ref X86Context context, GCInfo gcInfo, TargetPointer methodStart) + private void UnwindEbpDoubleAlignFrameProlog(ref X86Context context, X86GCInfo gcInfo, TargetPointer methodStart) { Debug.Assert(gcInfo.IsInProlog); Debug.Assert(gcInfo.Header.EbpFrame || gcInfo.Header.DoubleAlign); @@ -610,7 +611,7 @@ private static bool InstructionAlreadyExecuted(uint walkOffset, uint actualHaltO return walkOffset < actualHaltOffset; } - private uint ESPIncrementOnReturn(GCInfo gcInfo) + private uint ESPIncrementOnReturn(X86GCInfo gcInfo) { uint stackParameterSize = gcInfo.Header.VarArgs ? 0 // varargs are caller-popped diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index fe3fabf7f8d8f9..1343a6028056bb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -60,6 +60,7 @@ public static void Register(ContractRegistry registry) return arch switch { RuntimeInfoArchitecture.X64 => new GCInfo_1(t), + RuntimeInfoArchitecture.X86 => new GCInfoX86_1(t), RuntimeInfoArchitecture.Arm64 => new GCInfo_1(t), RuntimeInfoArchitecture.Arm => new GCInfo_1(t), RuntimeInfoArchitecture.LoongArch64 => new GCInfo_1(t), diff --git a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs index e0832f67547037..63180dd9c47d3e 100644 --- a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs @@ -333,6 +333,63 @@ public unsafe void VarargPInvoke_GetCodeHeaderDataWithInvalidPrecodeAddress(Test Assert.Fail("Expected to find a frame with a valid entry point"); } + /// + /// Exercises for the IL stub MethodDesc + /// that ships a vararg P/Invoke, passing the live instruction pointer of an executing + /// IL stub frame. This is the path SOS !clru uses after !IP2MD: the IP is + /// inside the JIT-emitted code body, so GetCodeHeaderData takes the full path + /// through IGCInfo.GetCodeLength rather than the precode/stub fallback. Regression + /// coverage for the x86 cDAC GetCodeHeaderData failure where the IGCInfo contract had no + /// x86 implementation and threw NotImplementedException, breaking !clru on .NET 11. + /// + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + [SkipOnOS(IncludeOnly = "windows", Reason = "VarargPInvoke debuggee uses msvcrt.dll (Windows only)")] + public unsafe void VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize(TestConfiguration config) + { + InitializeDumpTest(config, "VarargPInvoke", "full"); + IStackWalk stackWalk = Target.Contracts.StackWalk; + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + ISOSDacInterface sosDac = new SOSDacImpl(Target, legacyObj: null); + + ThreadData crashingThread = DumpTestHelpers.FindThreadWithMethod(Target, "Main"); + IEnumerable frames = DumpTestStackWalker.LegacyVisibleFrames(stackWalk, crashingThread); + + foreach (IStackDataFrameHandle frame in frames) + { + if (frame.State != StackWalkState.Frameless) + continue; + + TargetPointer methodDescPtr = stackWalk.GetMethodDescPtr(frame); + if (methodDescPtr == TargetPointer.Null) + continue; + + MethodDescHandle mdHandle = rts.GetMethodDescHandle(methodDescPtr); + if (!rts.IsILStub(mdHandle)) + continue; + + TargetPointer ip = stackWalk.GetInstructionPointer(frame); + Assert.NotEqual(TargetPointer.Null, ip); + + DacpCodeHeaderData codeHeaderData; + int hr = sosDac.GetCodeHeaderData(new ClrDataAddress(ip.Value), &codeHeaderData); + AssertHResult(HResults.S_OK, hr); + + Assert.Equal(JitTypes.TYPE_JIT, codeHeaderData.JITType); + Assert.Equal(methodDescPtr.ToClrDataAddress(Target), codeHeaderData.MethodDescPtr); + Assert.True(codeHeaderData.MethodSize > 0, + $"Expected non-zero MethodSize for IL stub (was {codeHeaderData.MethodSize}). " + + "On x86 this asserts that the GCInfo contract returns a valid X86GCInfo decoder " + + "rather than the default IGCInfo whose GetCodeLength throws NotImplementedException."); + Assert.NotEqual(default, codeHeaderData.MethodStart); + + return; + } + + Assert.Fail("Expected to find an IL stub Frameless frame on the crashing thread stack"); + } + // ========== GetContext API tests ========== [ConditionalTheory]