From 0fd4fb34ae2cfb657c4581a3a52a9f7dda9fdec1 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 21 Jun 2026 13:52:11 -0400 Subject: [PATCH 1/8] [cDAC] Enable cDAC GC stress tests on linux_arm (arm32) Adds linux_arm to the cdacStressPlatforms default list in runtime-diagnostics.yml so the CdacStressTests legs run against arm32 alongside x64/arm64. The Helix queue mapping for linux_arm already exists in prepare-cdac-stress-helix-steps.yml. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/pipelines/runtime-diagnostics.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml index f5fdf347715509..e5c4632258231c 100644 --- a/eng/pipelines/runtime-diagnostics.yml +++ b/eng/pipelines/runtime-diagnostics.yml @@ -73,6 +73,7 @@ parameters: - linux_x64 - windows_arm64 - linux_arm64 + - linux_arm resources: repositories: From fec4a86f0e69a4d2c2377f5052666ab21cf9ce0a Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 21 Jun 2026 15:36:27 -0400 Subject: [PATCH 2/8] [cDAC] Strip ARM32 Thumb bit from stack-ref Source IP On ARM32 the control PC carries the Thumb bit (LSB) to indicate execution mode. The native runtime applies PCODEToPINSTR (utilcode.h) before reporting the IP as StackRefData.Source (DAC's daccess.cpp uses `PCODEToPINSTR(GetControlPC(pRD))`; the in-process stress runtime oracle does the same in src/coreclr/vm/cdacstress.cpp). The cDAC was storing the raw PCODE, so every cDAC ref got keyed at IP|1 while the runtime reported at IP - producing universal mismatches in GC root verification. Fix: cache TargetArchitecture in GcScanContext at construction and mask the LSB in UpdateScanContext on ARM32 only. No-op on other architectures. Surfaced by the cDAC stress legs added by this PR on linux_arm (BasicCdacStressTests.GCStress_AllVerificationsPass: e.g. DynamicMethods reported 1676 fail / 18 pass / 0 known-issue, with the characteristic pair pattern Frame #N=IP and Frame #N+1=IP|1). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contracts/StackWalk/GC/GcScanContext.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 index abc141dd5cfdc5..e3c74e1b85b435 100644 --- 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 @@ -10,6 +10,7 @@ internal class GcScanContext { private readonly Target _target; + private readonly bool _isArm32; public bool ResolveInteriorPointers { get; } public List StackRefs { get; } = []; public TargetPointer StackPointer { get; private set; } @@ -24,13 +25,19 @@ internal class GcScanContext public GcScanContext(Target target, bool resolveInteriorPointers) { _target = target; + _isArm32 = target.Contracts.RuntimeInfo.GetTargetArchitecture() == RuntimeInfoArchitecture.Arm; ResolveInteriorPointers = resolveInteriorPointers; } public void UpdateScanContext(TargetPointer sp, TargetPointer ip, TargetPointer frame, StackRefData.SourceTypes? sourceTypeOverride = null) { StackPointer = sp; - InstructionPointer = ip; + // On ARM32 the control PC carries the Thumb bit (LSB) to indicate execution mode. + // The native runtime applies PCODEToPINSTR (utilcode.h) before reporting the IP as + // a StackRefData.Source so consumers compare data addresses, not PCODE values. Mirror + // that here: without this mask, every cDAC ref on arm32 would be keyed at IP|1 while + // the runtime reports at IP, producing universal mismatches in GC root verification. + InstructionPointer = _isArm32 ? new TargetPointer(ip.Value & ~1ul) : ip; Frame = frame; _sourceTypeOverride = sourceTypeOverride; } From df50d084d5ea3c4a474264a86ba24b8400fe53ac Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 21 Jun 2026 16:08:15 -0400 Subject: [PATCH 3/8] [cDAC] Type IPlatformContext.InstructionPointer as TargetCodePointer Replaces the ad-hoc IP.Value & ~1 mask in GcScanContext with a proper type-driven fix: IP is conceptually a code pointer (PCODE on ARM32 with the Thumb bit, ARM64 PtrAuth-bearing on supported targets), not a data address. Promote IPlatformContext / IPlatformAgnosticContext / ContextHolder.InstructionPointer and the Frame ReturnAddress fields to TargetCodePointer so callers can no longer accidentally treat them as data pointers. Conversion to a data address now lives in one place: GcScanContext.SetSource calls CodePointerUtils.AddressFromCodePointer when populating StackRefData.Source, matching native PCODEToPINSTR in src/coreclr/inc/utilcode.h. Other consumers (IsManaged, IsInterpreterCode, GetCodeBlockHandle) want TargetCodePointer directly and get it without manual masking; the few places that need the raw data address (AMD64 controlPC arithmetic, x64-only SOS GetJumpThunkTarget) convert explicitly via .AsTargetPointer. cDAC unit tests: Passed: 2571, Failed: 0, Skipped: 16. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contracts/IStackWalk.cs | 2 +- .../StackWalk/Context/AMD64/AMD64Unwinder.cs | 4 +-- .../StackWalk/Context/AMD64Context.cs | 2 +- .../StackWalk/Context/ARM/ARMUnwinder.cs | 2 +- .../StackWalk/Context/ARM64/ARM64Unwinder.cs | 2 +- .../StackWalk/Context/ARM64Context.cs | 2 +- .../Contracts/StackWalk/Context/ARMContext.cs | 2 +- .../StackWalk/Context/ContextHolder.cs | 2 +- .../Context/IPlatformAgnosticContext.cs | 2 +- .../StackWalk/Context/IPlatformContext.cs | 2 +- .../LoongArch64/LoongArch64Unwinder.cs | 2 +- .../StackWalk/Context/LoongArch64Context.cs | 2 +- .../Context/RISCV64/RISCV64Unwinder.cs | 2 +- .../StackWalk/Context/RISCV64Context.cs | 2 +- .../StackWalk/Context/X86/X86Unwinder.cs | 2 +- .../Contracts/StackWalk/Context/X86Context.cs | 2 +- .../StackWalk/FrameHandling/FrameHelpers.cs | 14 ++++----- .../StackWalk/FrameHandling/FrameIterator.cs | 2 +- .../Contracts/StackWalk/GC/GcScanContext.cs | 19 +++++------- .../StackWalk_1.ExceptionHandling.cs | 2 +- .../Contracts/StackWalk/StackWalk_1.cs | 29 +++++++++---------- .../Data/Frames/HijackFrame.cs | 2 +- .../Data/Frames/InlinedCallFrame.cs | 2 +- .../Data/Frames/SoftwareExceptionFrame.cs | 2 +- .../Data/Frames/TailCallFrame.cs | 2 +- .../Data/Frames/TransitionBlock.cs | 2 +- .../ClrDataFrame.cs | 3 +- .../SOSDacImpl.cs | 2 +- .../managed/cdac/scripts/StacksCommand.cs | 2 +- 29 files changed, 56 insertions(+), 61 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs index e1c0da91b5f8c5..f9047a304821c8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -97,7 +97,7 @@ public interface IStackWalk : IContract string GetFrameName(TargetPointer frameIdentifier) => throw new NotImplementedException(); TargetPointer GetMethodDescPtr(TargetPointer framePtr) => throw new NotImplementedException(); TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); - TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); + TargetCodePointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); IEnumerable GetFrames(TargetPointer threadPointer) => throw new NotImplementedException(); bool IsExceptionHandlingHelperInlinedCallFrame(TargetPointer frameAddress) => throw new NotImplementedException(); DebuggerEvalData GetDebuggerEvalData(TargetPointer funcEvalFrameAddress) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64/AMD64Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64/AMD64Unwinder.cs index c7bf7d3cd82425..bf76953355416e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64/AMD64Unwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64/AMD64Unwinder.cs @@ -45,10 +45,10 @@ public bool Unwind(ref AMD64Context context) Data.RuntimeFunction? primaryFunctionEntry; UnwindCode unwindOp; - if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh) + if (_eman.GetCodeBlockHandle(context.InstructionPointer) is not CodeBlockHandle cbh) return false; - TargetPointer controlPC = context.InstructionPointer; + TargetPointer controlPC = context.InstructionPointer.AsTargetPointer; TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); TargetPointer unwindInfoAddr = _eman.GetUnwindInfo(cbh); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs index 5ce1ed5ec6467c..9cc8a53983890a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs @@ -45,7 +45,7 @@ public TargetPointer StackPointer readonly get => new(Rsp); set => Rsp = value.Value; } - public TargetPointer InstructionPointer + public TargetCodePointer InstructionPointer { readonly get => new(Rip); set => Rip = value.Value; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs index 96c8c6236e39ba..ef742824198c10 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM/ARMUnwinder.cs @@ -19,7 +19,7 @@ internal class ARMUnwinder(Target target) public bool Unwind(ref ARMContext context) { - if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh) + if (_eman.GetCodeBlockHandle(context.InstructionPointer) is not CodeBlockHandle cbh) { return false; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64/ARM64Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64/ARM64Unwinder.cs index 625f972958ca23..e6e25c43435328 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64/ARM64Unwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64/ARM64Unwinder.cs @@ -51,7 +51,7 @@ internal class ARM64Unwinder(Target target) public bool Unwind(ref ARM64Context context) { - if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh) + if (_eman.GetCodeBlockHandle(context.InstructionPointer) is not CodeBlockHandle cbh) return false; TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs index fecff344c0aa10..db38995c2f7a54 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs @@ -51,7 +51,7 @@ public TargetPointer StackPointer readonly get => new(Sp); set => Sp = value.Value; } - public TargetPointer InstructionPointer + public TargetCodePointer InstructionPointer { readonly get => new(Pc); set => Pc = value.Value; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs index 1bec70d46057c6..d13c3f6ead4490 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs @@ -44,7 +44,7 @@ public TargetPointer StackPointer set => Sp = (uint)value.Value; } - public TargetPointer InstructionPointer + public TargetCodePointer InstructionPointer { readonly get => new(Pc); set => Pc = (uint)value.Value; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs index 41e777840d5253..6358c537268061 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs @@ -19,7 +19,7 @@ public sealed class ContextHolder : IPlatformAgnosticContext, IEquatable Context.StackPointerRegister; public TargetPointer StackPointer { get => Context.StackPointer; set => Context.StackPointer = value; } - public TargetPointer InstructionPointer { get => Context.InstructionPointer; set => Context.InstructionPointer = value; } + public TargetCodePointer InstructionPointer { get => Context.InstructionPointer; set => Context.InstructionPointer = value; } public TargetPointer FramePointer { get => Context.FramePointer; set => Context.FramePointer = value; } public uint RawContextFlags { get => Context.RawContextFlags; set => Context.RawContextFlags = value; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index f7d210a6de2df2..fc384bf929027b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -15,7 +15,7 @@ public interface IPlatformAgnosticContext public int StackPointerRegister { get; } public TargetPointer StackPointer { get; set; } - public TargetPointer InstructionPointer { get; set; } + public TargetCodePointer InstructionPointer { get; set; } public TargetPointer FramePointer { get; set; } public uint RawContextFlags { get; set; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs index a4debfc3b641d0..24da7a13513827 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs @@ -13,7 +13,7 @@ public interface IPlatformContext int StackPointerRegister { get; } TargetPointer StackPointer { get; set; } - TargetPointer InstructionPointer { get; set; } + TargetCodePointer InstructionPointer { get; set; } TargetPointer FramePointer { get; set; } uint RawContextFlags { get; set; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64/LoongArch64Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64/LoongArch64Unwinder.cs index f0f251dbb2daf6..022e1c97325ab0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64/LoongArch64Unwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64/LoongArch64Unwinder.cs @@ -36,7 +36,7 @@ internal class LoongArch64Unwinder(Target target) public bool Unwind(ref LoongArch64Context context) { - if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh) + if (_eman.GetCodeBlockHandle(context.InstructionPointer) is not CodeBlockHandle cbh) return false; TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs index e76d7ba37411ef..9f182801ebd609 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs @@ -49,7 +49,7 @@ public TargetPointer StackPointer readonly get => new(Sp); set => Sp = value.Value; } - public TargetPointer InstructionPointer + public TargetCodePointer InstructionPointer { readonly get => new(Pc); set => Pc = value.Value; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64/RISCV64Unwinder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64/RISCV64Unwinder.cs index c3b953f2c5c5ba..55a1d8acab2c7c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64/RISCV64Unwinder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64/RISCV64Unwinder.cs @@ -44,7 +44,7 @@ internal class RISCV64Unwinder(Target target) public bool Unwind(ref RISCV64Context context) { - if (_eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh) + if (_eman.GetCodeBlockHandle(context.InstructionPointer) is not CodeBlockHandle cbh) return false; TargetPointer imageBase = _eman.GetUnwindInfoBaseAddress(cbh); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs index b8f84d959983a9..b81fdf4447fb91 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs @@ -49,7 +49,7 @@ public TargetPointer StackPointer readonly get => new(Sp); set => Sp = value.Value; } - public TargetPointer InstructionPointer + public TargetCodePointer InstructionPointer { readonly get => new(Pc); set => Pc = value.Value; 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 f28dc4a9667fd3..69f1ef57f0aee9 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 @@ -52,7 +52,7 @@ public bool Unwind(ref X86Context context) { IExecutionManager eman = _target.Contracts.ExecutionManager; - if (eman.GetCodeBlockHandle(context.InstructionPointer.Value) is not CodeBlockHandle cbh) + if (eman.GetCodeBlockHandle(context.InstructionPointer) is not CodeBlockHandle cbh) { throw new InvalidOperationException("Unwind failed, unable to find code block for the instruction pointer."); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs index 735e889c798032..db1afdc2dd5bba 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs @@ -52,7 +52,7 @@ public TargetPointer StackPointer readonly get => new(Esp); set => Esp = (uint)value.Value; } - public TargetPointer InstructionPointer + public TargetCodePointer InstructionPointer { readonly get => new(Eip); set => Eip = (uint)value.Value; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs index c1a3175df3fb9c..17ad0a69484e39 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs @@ -211,10 +211,10 @@ public void UpdateContextFromFrame(Data.Frame frame, IPlatformAgnosticContext co /// /// Returns the return address for , matching native Frame::GetReturnAddress(). - /// Returns TargetPointer.Null if the Frame has no return address (e.g., non-active ICF, + /// Returns TargetCodePointer.Null if the Frame has no return address (e.g., non-active ICF, /// base Frame types, FuncEvalFrame during exception eval). /// - public TargetPointer GetReturnAddress(Data.Frame frame) + public TargetCodePointer GetReturnAddress(Data.Frame frame) { FrameType frameType = GetFrameType(frame.Identifier); switch (frameType) @@ -222,7 +222,7 @@ public TargetPointer GetReturnAddress(Data.Frame frame) // InlinedCallFrame: returns 0 if inactive, else m_pCallerReturnAddress case FrameType.InlinedCallFrame: Data.InlinedCallFrame icf = _target.ProcessedData.GetOrAdd(frame.Address); - return InlinedCallFrameHasActiveCall(icf) ? icf.CallerReturnAddress : TargetPointer.Null; + return InlinedCallFrameHasActiveCall(icf) ? icf.CallerReturnAddress : TargetCodePointer.Null; // TransitionFrame types: read return address from the transition block case FrameType.FramedMethodFrame: @@ -275,14 +275,14 @@ public TargetPointer GetReturnAddress(Data.Frame frame) Data.FuncEvalFrame funcEval = _target.ProcessedData.GetOrAdd(frame.Address); Data.DebuggerEval dbgEval = _target.ProcessedData.GetOrAdd(funcEval.DebuggerEvalPtr); if (dbgEval.EvalUsesHijack) - return TargetPointer.Null; + return TargetCodePointer.Null; Data.FramedMethodFrame funcEvalFmf = _target.ProcessedData.GetOrAdd(frame.Address); Data.TransitionBlock funcEvalTb = _target.ProcessedData.GetOrAdd(funcEvalFmf.TransitionBlockPtr); return funcEvalTb.ReturnAddress; // Base Frame and unknown types: return 0 (matches native Frame::GetReturnAddressPtr_Impl) default: - return TargetPointer.Null; + return TargetCodePointer.Null; } } @@ -467,7 +467,7 @@ public void SetContextToInterpMethodContextFrame( return; Data.InterpMethodContextFrame topContextFrame = _target.ProcessedData.GetOrAdd(topContextFramePtr); - context.InstructionPointer = new TargetPointer((ulong)topContextFrame.Ip); + context.InstructionPointer = new TargetCodePointer((ulong)topContextFrame.Ip); context.StackPointer = topContextFramePtr; context.FramePointer = topContextFrame.Stack; SetFirstArgRegister(context, interpreterFrame.Address); @@ -516,7 +516,7 @@ private bool VirtualUnwindInterpreterCallFrame(IPlatformAgnosticContext context) if (parentFrame.Ip == TargetPointer.Null) return false; - context.InstructionPointer = new TargetPointer((ulong)parentFrame.Ip); + context.InstructionPointer = new TargetCodePointer((ulong)parentFrame.Ip); context.StackPointer = currentFrame.ParentPtr; context.FramePointer = parentFrame.Stack; context.RawContextFlags = context.FullContextFlags; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs index c53fa6d389b229..cd53e98eb8e2fb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs @@ -54,7 +54,7 @@ public FrameType GetCurrentFrameType() /// /// Returns the return address of the current frame, matching native Frame::GetReturnAddress(). /// - public TargetPointer GetCurrentReturnAddress() + public TargetCodePointer GetCurrentReturnAddress() => frameHelpers.GetReturnAddress(CurrentFrame); /// 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 index e3c74e1b85b435..a7de5fba498b89 100644 --- 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 @@ -10,11 +10,10 @@ internal class GcScanContext { private readonly Target _target; - private readonly bool _isArm32; public bool ResolveInteriorPointers { get; } public List StackRefs { get; } = []; public TargetPointer StackPointer { get; private set; } - public TargetPointer InstructionPointer { get; private set; } + public TargetCodePointer InstructionPointer { get; private set; } public TargetPointer Frame { get; private set; } // When set, overrides the default IP/Frame source-type classification for reported roots. @@ -25,19 +24,13 @@ internal class GcScanContext public GcScanContext(Target target, bool resolveInteriorPointers) { _target = target; - _isArm32 = target.Contracts.RuntimeInfo.GetTargetArchitecture() == RuntimeInfoArchitecture.Arm; ResolveInteriorPointers = resolveInteriorPointers; } - public void UpdateScanContext(TargetPointer sp, TargetPointer ip, TargetPointer frame, StackRefData.SourceTypes? sourceTypeOverride = null) + public void UpdateScanContext(TargetPointer sp, TargetCodePointer ip, TargetPointer frame, StackRefData.SourceTypes? sourceTypeOverride = null) { StackPointer = sp; - // On ARM32 the control PC carries the Thumb bit (LSB) to indicate execution mode. - // The native runtime applies PCODEToPINSTR (utilcode.h) before reporting the IP as - // a StackRefData.Source so consumers compare data addresses, not PCODE values. Mirror - // that here: without this mask, every cDAC ref on arm32 would be keyed at IP|1 while - // the runtime reports at IP, producing universal mismatches in GC root verification. - InstructionPointer = _isArm32 ? new TargetPointer(ip.Value & ~1ul) : ip; + InstructionPointer = ip; Frame = frame; _sourceTypeOverride = sourceTypeOverride; } @@ -56,8 +49,12 @@ private void SetSource(StackRefData data) } else { + // StackRefData.Source is a data address (TargetPointer). Convert the code + // pointer through the platform's PCODE -> PINSTR transform so consumers + // comparing this against runtime-reported sources see matching bits on + // ARM32 (Thumb-bit-bearing PCs) and ARM64 (PtrAuth-bearing PCs). data.SourceType = StackRefData.SourceTypes.StackSourceIP; - data.Source = InstructionPointer; + data.Source = CodePointerUtils.AddressFromCodePointer(InstructionPointer, _target); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.ExceptionHandling.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.ExceptionHandling.cs index 8f5b4aa73620bd..0593e609d7f148 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.ExceptionHandling.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.ExceptionHandling.cs @@ -55,7 +55,7 @@ private TargetPointer FindParentStackFrameHelper( // Check for out-of-line finally funclets. Filter funclets can't be out-of-line. if (!isFilterFunclet) { - TargetPointer callerIp = callerContext.InstructionPointer; + TargetCodePointer 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 diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index d3a0d4d8a9018c..855a6a6d233e8c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -139,7 +139,7 @@ IEnumerable IStackWalk.CreateStackWalk(ThreadData threadD private void SetupContext(IPlatformAgnosticContext context, FrameIterator frameIterator, StackWalkState state, ref bool isFirst, out bool matchedIsInterrupted) { TargetPointer curSP = context.StackPointer; - TargetPointer curPc = context.InstructionPointer; + TargetCodePointer curPc = context.InstructionPointer; TargetPointer curFP = context.FramePointer; if (state == StackWalkState.Frameless) { @@ -398,7 +398,7 @@ private void ReportExceptionTrackerRoots(ThreadData threadData, GcScanContext sc // and the previous (nested) ExInfo; GCReportCallback reads the object through that slot. // ExInfo lives on the stack but is not a Frame, so it is treated specially here. exceptionContract.GetNestedExceptionInfo(pExInfo, out TargetPointer previous, out TargetPointer thrownObjectSlot); - scanContext.UpdateScanContext(pExInfo, TargetPointer.Null, pExInfo, StackRefData.SourceTypes.StackSourceOther); + scanContext.UpdateScanContext(pExInfo, TargetCodePointer.Null, pExInfo, StackRefData.SourceTypes.StackSourceOther); scanContext.GCReportCallback(thrownObjectSlot, GcScanFlags.None); pExInfo = previous; } @@ -420,7 +420,7 @@ private void ReportGCFrameRoots(ThreadData threadData, GcScanContext scanContext Data.GCFrame gcFrame = _target.ProcessedData.GetOrAdd(pGCFrame); // A GCFrame node lives on the stack but is a separate chain from the explicit Frame chain. - scanContext.UpdateScanContext(pGCFrame, TargetPointer.Null, pGCFrame, StackRefData.SourceTypes.StackSourceOther); + scanContext.UpdateScanContext(pGCFrame, TargetCodePointer.Null, pGCFrame, StackRefData.SourceTypes.StackSourceOther); GcScanFlags flags = (GcScanFlags)gcFrame.GCFlags; for (uint i = 0; i < gcFrame.NumObjRefs; i++) { @@ -847,8 +847,8 @@ private bool Next(StackWalkData handle) case StackWalkState.InitialNativeContext: case StackWalkState.NativeMarker: { - TargetPointer ip = handle.Context.InstructionPointer; - HijackKind hijackKind = _target.Contracts.Debugger.GetHijackKind(new TargetCodePointer(ip.Value)); + TargetCodePointer ip = handle.Context.InstructionPointer; + HijackKind hijackKind = _target.Contracts.Debugger.GetHijackKind(ip); if (hijackKind != HijackKind.None) { IPlatformAgnosticContext recoveredContext = RetrieveHijackedContext(handle.Context, hijackKind == HijackKind.UnhandledException); @@ -872,9 +872,9 @@ private bool Next(StackWalkData handle) // pInlinedFrame is set only for active InlinedCallFrames. { var frameType = handle.FrameIter.GetCurrentFrameType(); - TargetPointer returnAddress = handle.FrameIter.GetCurrentReturnAddress(); + TargetCodePointer returnAddress = handle.FrameIter.GetCurrentReturnAddress(); bool isActiveICF = frameType == FrameType.InlinedCallFrame - && returnAddress != TargetPointer.Null; + && returnAddress != TargetCodePointer.Null; // Record the frame type so UpdateState can detect exception frames // and set IsInterrupted when transitioning to the managed frame. @@ -1048,7 +1048,7 @@ TargetPointer IStackWalk.GetFrameAddress(IStackDataFrameHandle stackDataFrameHan return TargetPointer.Null; } - TargetPointer IStackWalk.GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) + TargetCodePointer IStackWalk.GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) { StackDataFrameHandle handle = AssertCorrectHandle(stackDataFrameHandle); return handle.Context.InstructionPointer; @@ -1084,8 +1084,8 @@ TargetPointer IStackWalk.GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHa IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; Data.InlinedCallFrame icf = _target.ProcessedData.GetOrAdd(framePtr); - TargetPointer returnAddress = icf.CallerReturnAddress; - if (returnAddress != TargetPointer.Null && _eman.GetCodeBlockHandle(returnAddress.Value) is CodeBlockHandle cbh) + TargetCodePointer returnAddress = icf.CallerReturnAddress; + if (returnAddress != TargetCodePointer.Null && _eman.GetCodeBlockHandle(returnAddress) is CodeBlockHandle cbh) { MethodDescHandle returnMethodDesc = rts.GetMethodDescHandle(_eman.GetMethodDesc(cbh)); reportInteropMD = rts.HasMDContextArg(returnMethodDesc); @@ -1211,10 +1211,9 @@ TargetPointer IStackWalk.GetRedirectedContextPointer(ThreadData threadData) return TargetPointer.Null; } - private bool IsManaged(TargetPointer ip, [NotNullWhen(true)] out CodeBlockHandle? codeBlockHandle) + private bool IsManaged(TargetCodePointer ip, [NotNullWhen(true)] out CodeBlockHandle? codeBlockHandle) { - TargetCodePointer codePointer = CodePointerUtils.CodePointerFromAddress(ip, _target); - if (_eman.GetCodeBlockHandle(codePointer) is CodeBlockHandle cbh && cbh.Address != TargetPointer.Null) + if (_eman.GetCodeBlockHandle(ip) is CodeBlockHandle cbh && cbh.Address != TargetPointer.Null) { codeBlockHandle = cbh; return true; @@ -1253,9 +1252,9 @@ private static StackDataFrameHandle AssertCorrectHandle(IStackDataFrameHandle st /// /// Checks if the given IP is in interpreter-managed code (CodeKind.Interpreter). /// - private bool IsInterpreterCode(TargetPointer ip) + private bool IsInterpreterCode(TargetCodePointer ip) { - return _eman.GetCodeKind(new TargetCodePointer(ip)) == CodeKind.Interpreter; + return _eman.GetCodeKind(ip) == CodeKind.Interpreter; } #endregion Interpreter diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackFrame.cs index 4f2097156803d0..8fbe09e198011a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackFrame.cs @@ -6,6 +6,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; [CdacType(nameof(DataType.HijackFrame))] internal partial class HijackFrame : IData { - [Field] public TargetPointer ReturnAddress { get; } + [Field] public TargetCodePointer ReturnAddress { get; } [Field] public TargetPointer HijackArgsPtr { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs index 40b4445313dd31..b4d382bc61db8e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs @@ -7,7 +7,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; internal partial class InlinedCallFrame : IData { [Field] public TargetPointer CallSiteSP { get; } - [Field] public TargetPointer CallerReturnAddress { get; } + [Field] public TargetCodePointer CallerReturnAddress { get; } [Field] public TargetPointer CalleeSavedFP { get; } [Field] public TargetPointer? SPAfterProlog { get; } [Field] public TargetPointer Datum { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs index 917ca02adccbbd..4e9370b76d05b1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/SoftwareExceptionFrame.cs @@ -9,5 +9,5 @@ internal partial class SoftwareExceptionFrame : IData [FieldAddress] public TargetPointer TargetContext { get; } - [Field] public TargetPointer ReturnAddress { get; } + [Field] public TargetCodePointer ReturnAddress { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TailCallFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TailCallFrame.cs index c67f2073bc017e..8bb32099efdd07 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TailCallFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TailCallFrame.cs @@ -9,5 +9,5 @@ internal partial class TailCallFrame : IData [FieldAddress] public TargetPointer CalleeSavedRegisters { get; } - [Field] public TargetPointer ReturnAddress { get; } + [Field] public TargetCodePointer ReturnAddress { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs index 4f85fa2b6bdd8e..3daacea455fdbb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs @@ -6,7 +6,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Data; [CdacType(nameof(DataType.TransitionBlock))] internal partial class TransitionBlock : IData { - [Field] public TargetPointer ReturnAddress { get; } + [Field] public TargetCodePointer ReturnAddress { get; } [FieldAddress] public TargetPointer CalleeSavedRegisters { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs index 8274846666da61..4e127457b5aae8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataFrame.cs @@ -457,8 +457,7 @@ private ClrDataValue CreateValueFromDebugInfo( IStackWalk stackWalk = _target.Contracts.StackWalk; IDebugInfo debugInfo = _target.Contracts.DebugInfo; - TargetPointer ip = stackWalk.GetInstructionPointer(_dataFrame); - TargetCodePointer codePointer = new TargetCodePointer(ip.Value); + TargetCodePointer codePointer = stackWalk.GetInstructionPointer(_dataFrame); byte[] context = stackWalk.GetRawContext(_dataFrame); IEnumerable varInfos = debugInfo.GetMethodVarInfo(codePointer, out uint codeOffset); NativeVarLocation[] locations = FindAndResolveVarLocation(varInfos, codeOffset, varInfoSlot, context, _target); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 5608f9a3ede3ca..07b45b921928cd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2206,7 +2206,7 @@ int ISOSDacInterface.GetJumpThunkTarget(void* ctx, ClrDataAddress* targetIP, Clr // Context is not stored in the target, but in our own process context.FillFromBuffer(new Span(ctx, (int)context.Size)); - TargetPointer pThunk = context.InstructionPointer; + TargetPointer pThunk = context.InstructionPointer.AsTargetPointer; if (IsJumpRel64(pThunk)) { diff --git a/src/native/managed/cdac/scripts/StacksCommand.cs b/src/native/managed/cdac/scripts/StacksCommand.cs index af66b98964158c..dd3a5aff1da81f 100644 --- a/src/native/managed/cdac/scripts/StacksCommand.cs +++ b/src/native/managed/cdac/scripts/StacksCommand.cs @@ -84,7 +84,7 @@ private static void Execute(string dumpPath) { try { - TargetPointer ip = stackWalk.GetInstructionPointer(frame); + TargetCodePointer ip = stackWalk.GetInstructionPointer(frame); TargetPointer mdPtr = stackWalk.GetMethodDescPtr(frame); string frameName; From ed40474911654c507b6c3b60cde1e9c4f1eeea6b Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 21 Jun 2026 16:24:00 -0400 Subject: [PATCH 4/8] [cDAC] Type Frame ReturnAddress fields as CodePointer These fields all hold PCODE on ARM32 (carry the Thumb bit), so the native data descriptor should advertise them as TYPE(CodePointer) rather than T_POINTER: - TransitionBlock::m_ReturnAddress -- aliased to the saved link register in callingconvention.h (line 143-147 ARM block: comment `alias saved link register as m_ReturnAddress`). LR on ARM32 in Thumb mode = PC | 1. - InlinedCallFrame::m_pCallerReturnAddress -- ARM asm `str lr, [r4, #InlinedCallFrame__m_pCallerReturnAddress]` (pinvokestubs.S:96, 182); used as PCODE in stubs.cpp:1348-1349 (`*pRD->pPC = m_pCallerReturnAddress; pRD->ControlPC = ...`). - HijackFrame::m_ReturnAddress -- ctor takes `LPVOID returnAddress` (threadsuspend.cpp:4799) sourced from `m_pvHJRetAddr` which is read from the on-stack saved LR slot (line 4546: `m_pvHJRetAddr = *esb->m_ppvRetAddrPtr`). - SoftwareExceptionFrame::m_ReturnAddress -- ARM block in excep.cpp:10474-10475 copies from `pTransitionBlock->m_ReturnAddress` directly into `m_Context.Pc` and `m_ReturnAddress`. - TailCallFrame::m_ReturnAddress -- x86-only (descriptor entry guarded by `TARGET_X86 && !UNIX_X86_ABI`); CodePointer is still semantically correct (no transform applies on x86). This unblocks the type-safe handling in the cDAC managed side where these fields are now declared `[Field] TargetCodePointer ReturnAddress`, and lets ReadCodePointerField's `"CodePointer"` type-name assertion pass against real (non-mock) descriptors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/datadescriptor/datadescriptor.inc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index d8ad288c1e1af2..8b0e5ffdb8a075 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1120,7 +1120,7 @@ CDAC_TYPE_END(Frame) CDAC_TYPE_BEGIN(InlinedCallFrame) CDAC_TYPE_SIZE(sizeof(InlinedCallFrame)) CDAC_TYPE_FIELD(InlinedCallFrame, T_POINTER, CallSiteSP, offsetof(InlinedCallFrame, m_pCallSiteSP)) -CDAC_TYPE_FIELD(InlinedCallFrame, T_POINTER, CallerReturnAddress, offsetof(InlinedCallFrame, m_pCallerReturnAddress)) +CDAC_TYPE_FIELD(InlinedCallFrame, TYPE(CodePointer), CallerReturnAddress, offsetof(InlinedCallFrame, m_pCallerReturnAddress)) CDAC_TYPE_FIELD(InlinedCallFrame, T_POINTER, CalleeSavedFP, offsetof(InlinedCallFrame, m_pCalleeSavedFP)) CDAC_TYPE_FIELD(InlinedCallFrame, T_POINTER, Datum, offsetof(InlinedCallFrame, m_Datum)) #ifdef TARGET_ARM @@ -1131,7 +1131,7 @@ CDAC_TYPE_END(InlinedCallFrame) CDAC_TYPE_BEGIN(SoftwareExceptionFrame) CDAC_TYPE_SIZE(sizeof(SoftwareExceptionFrame)) CDAC_TYPE_FIELD(SoftwareExceptionFrame, EXTERN_TYPE(Context), TargetContext, cdac_data::TargetContext) -CDAC_TYPE_FIELD(SoftwareExceptionFrame, T_POINTER, ReturnAddress, cdac_data::ReturnAddress) +CDAC_TYPE_FIELD(SoftwareExceptionFrame, TYPE(CodePointer), ReturnAddress, cdac_data::ReturnAddress) CDAC_TYPE_END(SoftwareExceptionFrame) CDAC_TYPE_BEGIN(FramedMethodFrame) @@ -1150,7 +1150,7 @@ CDAC_TYPE_END(InterpreterFrame) CDAC_TYPE_BEGIN(TransitionBlock) CDAC_TYPE_SIZE(sizeof(TransitionBlock)) -CDAC_TYPE_FIELD(TransitionBlock, T_POINTER, ReturnAddress, offsetof(TransitionBlock, m_ReturnAddress)) +CDAC_TYPE_FIELD(TransitionBlock, TYPE(CodePointer), ReturnAddress, offsetof(TransitionBlock, m_ReturnAddress)) CDAC_TYPE_FIELD(TransitionBlock, TYPE(CalleeSavedRegisters), CalleeSavedRegisters, offsetof(TransitionBlock, m_calleeSavedRegisters)) // Offset to where stack arguments begin (just past the end of the TransitionBlock) CDAC_TYPE_FIELD(TransitionBlock, T_UINT32, OffsetOfArgs, sizeof(TransitionBlock)) @@ -1208,7 +1208,7 @@ CDAC_TYPE_END(ResumableFrame) CDAC_TYPE_BEGIN(HijackFrame) CDAC_TYPE_SIZE(sizeof(HijackFrame)) -CDAC_TYPE_FIELD(HijackFrame, T_POINTER, ReturnAddress, cdac_data::ReturnAddress) +CDAC_TYPE_FIELD(HijackFrame, TYPE(CodePointer), ReturnAddress, cdac_data::ReturnAddress) CDAC_TYPE_FIELD(HijackFrame, T_POINTER, HijackArgsPtr, cdac_data::HijackArgsPtr) CDAC_TYPE_END(HijackFrame) @@ -1312,7 +1312,7 @@ CDAC_TYPE_END(FaultingExceptionFrame) CDAC_TYPE_BEGIN(TailCallFrame) CDAC_TYPE_SIZE(sizeof(TailCallFrame)) CDAC_TYPE_FIELD(TailCallFrame, TYPE(CalleeSavedRegisters), CalleeSavedRegisters, cdac_data::CalleeSavedRegisters) -CDAC_TYPE_FIELD(TailCallFrame, T_POINTER, ReturnAddress, cdac_data::ReturnAddress) +CDAC_TYPE_FIELD(TailCallFrame, TYPE(CodePointer), ReturnAddress, cdac_data::ReturnAddress) CDAC_TYPE_END(TailCallFrame) #endif // TARGET_X86 && !UNIX_X86_ABI From 0638ad9096d158ec9a1f5faaf066745ca091237f Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 21 Jun 2026 17:22:44 -0400 Subject: [PATCH 5/8] [cDAC] Fix DumpTests build for InstructionPointer / GetInstructionPointer type promotion Three call sites in DumpTests still expected TargetPointer for `IStackWalk.GetInstructionPointer` and `IPlatformAgnosticContext.InstructionPointer`. The promotion to TargetCodePointer (commit df50d084) didn't flag them locally because the DumpTests project doesn't build in the cdac unit test run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs | 2 +- .../managed/cdac/tests/DumpTests/StackWalkDumpTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs index 48126ad272f9f0..3b932d54ba7894 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs @@ -41,7 +41,7 @@ public unsafe void GetContext_Succeeds_ForCrashingThread(TestConfiguration confi IPlatformAgnosticContext ctx = IPlatformAgnosticContext.GetContextForPlatform(Target); ctx.FillFromBuffer(contextBuffer); - Assert.NotEqual(TargetPointer.Null, ctx.InstructionPointer); + Assert.NotEqual(TargetCodePointer.Null, ctx.InstructionPointer); } [ConditionalTheory] diff --git a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs index 63180dd9c47d3e..ea09ce4e67ebdf 100644 --- a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs @@ -369,8 +369,8 @@ public unsafe void VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize(Te if (!rts.IsILStub(mdHandle)) continue; - TargetPointer ip = stackWalk.GetInstructionPointer(frame); - Assert.NotEqual(TargetPointer.Null, ip); + TargetCodePointer ip = stackWalk.GetInstructionPointer(frame); + Assert.NotEqual(TargetCodePointer.Null, ip); DacpCodeHeaderData codeHeaderData; int hr = sosDac.GetCodeHeaderData(new ClrDataAddress(ip.Value), &codeHeaderData); @@ -408,6 +408,6 @@ public void GetContext_ReturnsNonEmptyContext(TestConfiguration config) var ctx = Contracts.StackWalkHelpers.IPlatformAgnosticContext.GetContextForPlatform(Target); ctx.FillFromBuffer(context); - Assert.NotEqual(TargetPointer.Null, ctx.InstructionPointer); + Assert.NotEqual(TargetCodePointer.Null, ctx.InstructionPointer); } } From 6df3d393110ec422cfefb6ccc9f76f99ee13e4c6 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 21 Jun 2026 18:37:35 -0400 Subject: [PATCH 6/8] Empty commit to re-trigger CI (refresh merge ref) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 39cebff34bf94f6bb2045e52f0132f4270025a44 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 21 Jun 2026 20:55:51 -0400 Subject: [PATCH 7/8] [cDAC] Address Copilot review feedback - InlinedCallFrameHasActiveCall: use TargetCodePointer.Null to match the now-TargetCodePointer CallerReturnAddress field. - GcScanContext.SetSource: narrow the comment about ARM64 PtrAuth - CodePointerUtils.AddressFromCodePointer currently throws for that case, so don't claim it's already handled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contracts/StackWalk/FrameHandling/FrameHelpers.cs | 2 +- .../Contracts/StackWalk/GC/GcScanContext.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs index 17ad0a69484e39..e04cdd7149993e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameHelpers.cs @@ -375,7 +375,7 @@ private IPlatformFrameHandler GetFrameHandler(IPlatformAgnosticContext context) private static bool InlinedCallFrameHasActiveCall(Data.InlinedCallFrame frame) { - return frame.CallerReturnAddress != TargetPointer.Null; + return frame.CallerReturnAddress != TargetCodePointer.Null; } private bool InlinedCallFrameHasFunction(Data.InlinedCallFrame frame) 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 index a7de5fba498b89..90dba965f3a8b7 100644 --- 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 @@ -52,7 +52,9 @@ private void SetSource(StackRefData data) // StackRefData.Source is a data address (TargetPointer). Convert the code // pointer through the platform's PCODE -> PINSTR transform so consumers // comparing this against runtime-reported sources see matching bits on - // ARM32 (Thumb-bit-bearing PCs) and ARM64 (PtrAuth-bearing PCs). + // ARM32 (where IP carries the Thumb bit). ARM64 pointer authentication is + // not yet handled by CodePointerUtils -- when it is, those targets will + // benefit from this same routing without further changes here. data.SourceType = StackRefData.SourceTypes.StackSourceIP; data.Source = CodePointerUtils.AddressFromCodePointer(InstructionPointer, _target); } From c47132ac9a999f48130292c4fdeeccb93adc0e7d Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Sun, 21 Jun 2026 21:35:36 -0400 Subject: [PATCH 8/8] Remove verbose comment in GcScanContext.SetSource Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contracts/StackWalk/GC/GcScanContext.cs | 6 ------ 1 file changed, 6 deletions(-) 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 index 90dba965f3a8b7..29e51cb5451dbd 100644 --- 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 @@ -49,12 +49,6 @@ private void SetSource(StackRefData data) } else { - // StackRefData.Source is a data address (TargetPointer). Convert the code - // pointer through the platform's PCODE -> PINSTR transform so consumers - // comparing this against runtime-reported sources see matching bits on - // ARM32 (where IP carries the Thumb bit). ARM64 pointer authentication is - // not yet handled by CodePointerUtils -- when it is, those targets will - // benefit from this same routing without further changes here. data.SourceType = StackRefData.SourceTypes.StackSourceIP; data.Source = CodePointerUtils.AddressFromCodePointer(InstructionPointer, _target); }