Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/design/datacontracts/PlatformMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,28 @@ This contract exposes properties that describe the target platform
{
// Set if the target process is executing on arm32
HasArm32ThumbBit = 0x1,
// Set if arm64e pointer authentication is used in the target process
HasArm64PtrAuth = 0x2,
}
// Returns a pointer to a structure describing platform-specific precode stubs properties
TargetPointer GetPrecodeMachineDescriptor();

// Returns flags describing the behavior of code pointers
CodePointerFlags GetCodePointerFlags();

// Returns bits to clear from pointer-authenticated return addresses
ulong GetPointerAuthenticationMask();
```

## Version 1

`PointerAuthenticationMask` is optional; if absent, the mask is zero and return addresses are left unchanged.

<!-- BEGIN GENERATED: usage contract=PlatformMetadata version=c1 -->
### Data descriptors used

| Data Descriptor | Field | Type | Meaning |
| --- | --- | --- | --- |
| `PlatformMetadata` | `CodePointerFlags` | `uint8` | fields describing the behavior of target code pointers |
| `PlatformMetadata` | `PointerAuthenticationMask` | `uint64` | Bits to clear from pointer-authenticated return addresses |
| `PlatformMetadata` | `PrecodeMachineDescriptor` | `pointer` | precode stub-related platform specific properties |

### Global variables used
Expand Down
1 change: 1 addition & 0 deletions docs/design/datacontracts/data-descriptor-meanings.json
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@
"PInvokeCalliFrame.VASigCookiePtr": "Pointer to the varargs signature cookie for the unmanaged call",
"PInvokeMethodDesc.Size": "Base size for mcPInvoke classification",
"PlatformMetadata.CodePointerFlags": "fields describing the behavior of target code pointers",
"PlatformMetadata.PointerAuthenticationMask": "Bits to clear from pointer-authenticated return addresses",
"PlatformMetadata.PrecodeMachineDescriptor": "precode stub-related platform specific properties",
"PortableEntryPoint.ActualCode": "Native code for the entrypoint; on WebAssembly R2R code this is a function-table index (only defined if `FeaturePortableEntrypoints` is enabled)",
"PortableEntryPoint.Flags": "Portable entrypoint flags; `0x4` means the interpreter entrypoint is preferred over `ActualCode` (only defined if `FeaturePortableEntrypoints` is enabled)",
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/unwinder/arm64/unwinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,12 @@ Return Value:
--*/
{
UNREFERENCED_PARAMETER(Sp);
#if defined(TARGET_UNIX)
*Pointer &= 0x0000FFFFFFFFFFFF;
#else
// Windows uses 47-bit user addresses, so bit 47 can also contain PAC.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to change wording with certainty if we can confirm TCR_EL1.T0SZ always = 17 on Windows. It would make PAC algorithm use TCR_EL1.T0SZ = PACEffectiveTxSZ = 17 in InsertPAC(). Thus, bit indexed 47 will always be used for storing PAC.

*Pointer &= 0x00007FFFFFFFFFFF;
#endif
return;
}

Expand Down
11 changes: 9 additions & 2 deletions src/coreclr/vm/cdacplatformmetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ GVAL_IMPL(CDacPlatformMetadata, g_cdacPlatformMetadata);
#ifndef DACCESS_COMPILE
void CDacPlatformMetadata::Init()
{
(&g_cdacPlatformMetadata)->pointerAuthenticationMask = 0;
#if defined(TARGET_ARM)
(&g_cdacPlatformMetadata)->codePointerFlags = CDacCodePointerFlags::HasArm32ThumbBit;
#elif defined(TARGET_ARM64) && defined(TARGET_APPLE)
// TODO set HasArm64PtrAuth if arm64e
#elif defined(TARGET_ARM64)
// R2R code may use PAC even when JitPacEnabled is false.
// Bit 55 selects the VA range and is not part of PAC. See InsertPAC():
// https://developer.arm.com/documentation/ddi0602/latest/Shared-Pseudocode/aarch64-functions-pac
// XPACLRI accounts for VA size and pointer tagging; XOR gives the PAC mask.
const uintptr_t probe = ~(uintptr_t(1) << 55);
(&g_cdacPlatformMetadata)->pointerAuthenticationMask =
probe ^ reinterpret_cast<uintptr_t>(PacStripPtr(reinterpret_cast<void*>(probe)));
(&g_cdacPlatformMetadata)->codePointerFlags = CDacCodePointerFlags::None;
#else
(&g_cdacPlatformMetadata)->codePointerFlags = CDacCodePointerFlags::None;
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/cdacplatformmetadata.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ enum class CDacCodePointerFlags : uint8_t
{
None = 0,
HasArm32ThumbBit = 0x1,
HasArm64PtrAuth = 0x2,
};


struct CDacPlatformMetadata
{
PrecodeMachineDescriptor precode;
CDacCodePointerFlags codePointerFlags;
uint64_t pointerAuthenticationMask;
CDacPlatformMetadata() = default;
CDacPlatformMetadata(const CDacPlatformMetadata&) = delete;
CDacPlatformMetadata& operator=(const CDacPlatformMetadata&) = delete;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ CDAC_TYPE_INDETERMINATE(PlatformMetadata)
CDAC_TYPE_FIELD(PlatformMetadata, TYPE(PrecodeMachineDescriptor), PrecodeMachineDescriptor, offsetof(CDacPlatformMetadata, precode))
#endif // !FEATURE_PORTABLE_ENTRYPOINTS
CDAC_TYPE_FIELD(PlatformMetadata, T_UINT8, CodePointerFlags, offsetof(CDacPlatformMetadata, codePointerFlags))
CDAC_TYPE_FIELD(PlatformMetadata, T_UINT64, PointerAuthenticationMask, offsetof(CDacPlatformMetadata, pointerAuthenticationMask))
CDAC_TYPE_END(PlatformMetadata)

#ifndef FEATURE_PORTABLE_ENTRYPOINTS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts;
public enum CodePointerFlags : byte
{
HasArm32ThumbBit = 0x1,
HasArm64PtrAuth = 0x2,
}

public interface IPlatformMetadata : IContract
{
static string IContract.Name { get; } = nameof(PlatformMetadata);
TargetPointer GetPrecodeMachineDescriptor() => throw new NotImplementedException();
CodePointerFlags GetCodePointerFlags() => throw new NotImplementedException();
ulong GetPointerAuthenticationMask() => throw new NotImplementedException();
}

public readonly struct PlatformMetadata : IPlatformMetadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ internal static TargetCodePointer CodePointerFromAddress(TargetPointer address,
{
return new TargetCodePointer(address.Value | Arm32ThumbBit);
}
else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth))
{
throw new NotImplementedException($"{nameof(CodePointerFromAddress)}: ARM64 with pointer authentication");
}
Debug.Assert(flags == default);
return new TargetCodePointer(address.Value);
}
Expand All @@ -40,11 +36,18 @@ internal static TargetPointer AddressFromCodePointer(TargetCodePointer code, Tar
{
return new TargetPointer(code.Value & ~Arm32ThumbBit);
}
else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth))
{
throw new NotImplementedException($"{nameof(AddressFromCodePointer)}: ARM64 with pointer authentication");
}
Debug.Assert(flags == default);
return new TargetPointer(code.Value);
}

internal static TargetCodePointer StripPtrAuthFromReturnAddress(TargetCodePointer returnAddress, Target target)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this is separated out from stripping the thumb bit?

This breaks the idea that TargetCodePointers are the platform representation and TargetPointers are able to be used for comparison / arithmetic.

If there are cases we aren't converting correctly, I'd rather update those to use the proper TargetCodePointer/TargetPointer over changing the semantics.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This applies to return addresses during stackwalk only. It does not apply to arbitrary code addresses like Arm32 thumb-bit stripping.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddressFromCodePointer should handle both the PAC stripping and thumb bit stripping (where applicable), right now it does not.

@jkotas jkotas Oct 21, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature is about PAC for return addresses only.

If the PAC striping is done in AddressFromCodePointer, it is going to be done for code addresses, not just for return addresses. I guess it would work, but it would behave differently from how the runtime behaves. @max-charlamb Are you intentionally accepting this divergence to simplify cDAC?

{
if (returnAddress == TargetCodePointer.Null)
{
return TargetCodePointer.Null;
}

IPlatformMetadata metadata = target.Contracts.PlatformMetadata;
return new TargetCodePointer(returnAddress.Value & ~metadata.GetPointerAuthenticationMask());
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// 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;

internal readonly struct PlatformMetadata_1 : IPlatformMetadata
Expand All @@ -27,4 +25,7 @@ CodePointerFlags IPlatformMetadata.GetCodePointerFlags()
{
return (CodePointerFlags)_cdacMetadata.CodePointerFlags;
}

ulong IPlatformMetadata.GetPointerAuthenticationMask()
=> _cdacMetadata.PointerAuthenticationMask ?? 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,6 @@ internal TargetPointer CodePointerReadableInstrPointer(TargetCodePointer codePoi
{
return codePointer.AsTargetPointer & ~1ul;
}
if (codePointerFlags.HasFlag(CodePointerFlags.HasArm64PtrAuth))
{
throw new NotImplementedException("CodePointerReadableInstrPointer for ARM64 with pointer authentication");
}
Debug.Assert(codePointerFlags == 0);
return codePointer.AsTargetPointer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -763,12 +763,7 @@ private bool VirtualUnwindFull(
return false;
}

// TODO-PAC: Enable processing PAC-enabled return address
// context.Lr &= 0x0000FFFFFFFFFFFF;

//
// TODO: Implement support for UnwindFlags RTL_VIRTUAL_UNWIND2_VALIDATE_PAC.
//
context.Lr = CodePointerUtils.StripPtrAuthFromReturnAddress(new TargetCodePointer(context.Lr), _target).Value;
}

//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void HandleHijackFrame(HijackFrame frame)
{
HijackArgs args = _target.ProcessedData.GetOrAdd<Data.HijackArgs>(frame.HijackArgsPtr);

_holder.InstructionPointer = frame.ReturnAddress;
_holder.InstructionPointer = CodePointerUtils.StripPtrAuthFromReturnAddress(frame.ReturnAddress, _target);

// The stack pointer is the address immediately following HijackArgs
uint hijackArgsSize = Data.HijackArgs.GetSize(_target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public virtual void HandleInlinedCallFrame(InlinedCallFrame inlinedCallFrame)
return;
}

_context.InstructionPointer = inlinedCallFrame.CallerReturnAddress;
_context.InstructionPointer = CodePointerUtils.StripPtrAuthFromReturnAddress(inlinedCallFrame.CallerReturnAddress, _target);
_context.StackPointer = inlinedCallFrame.CallSiteSP;
_context.FramePointer = inlinedCallFrame.CalleeSavedFP;
}
Expand All @@ -46,7 +46,7 @@ public virtual void HandleTransitionFrame(FramedMethodFrame framedMethodFrame)
Data.TransitionBlock transitionBlock = _target.ProcessedData.GetOrAdd<Data.TransitionBlock>(framedMethodFrame.TransitionBlockPtr);
uint transitionBlockSize = Data.TransitionBlock.GetSize(_target);

_context.InstructionPointer = transitionBlock.ReturnAddress;
_context.InstructionPointer = CodePointerUtils.StripPtrAuthFromReturnAddress(transitionBlock.ReturnAddress, _target);
_context.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize;

Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd<Data.CalleeSavedRegisters>(transitionBlock.CalleeSavedRegisters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,16 @@ public TargetCodePointer GetTransitionBlockReturnAddress(Data.TransitionBlock tr
public TargetCodePointer GetReturnAddress(Data.Frame frame)
{
FrameType frameType = GetFrameType(frame.Identifier);
TargetCodePointer returnAddress;
switch (frameType)
{
// InlinedCallFrame: returns 0 if inactive, else m_pCallerReturnAddress
case FrameType.InlinedCallFrame:
Data.InlinedCallFrame icf = _target.ProcessedData.GetOrAdd<Data.InlinedCallFrame>(frame.Address);
return InlinedCallFrameHasActiveCall(icf) ? icf.CallerReturnAddress : TargetCodePointer.Null;
returnAddress = InlinedCallFrameHasActiveCall(icf)
? icf.CallerReturnAddress
: TargetCodePointer.Null;
break;

// TransitionFrame types: read return address from the transition block
case FrameType.FramedMethodFrame:
Expand All @@ -445,20 +449,22 @@ public TargetCodePointer GetReturnAddress(Data.Frame frame)
case FrameType.DynamicHelperFrame:
Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd<Data.FramedMethodFrame>(frame.Address);
Data.TransitionBlock tb = _target.ProcessedData.GetOrAdd<Data.TransitionBlock>(fmf.TransitionBlockPtr);
returnAddress = GetTransitionBlockReturnAddress(tb);
if (frameType == FrameType.StubDispatchFrame &&
_target.Contracts.RuntimeInfo.GetTargetArchitecture() == RuntimeInfoArchitecture.X86 &&
GetMethodDescPtr(frame.Address) == TargetPointer.Null)
{
Data.StubDispatchFrame dispatchFrame = _target.ProcessedData.GetOrAdd<Data.StubDispatchFrame>(frame.Address);
if (FindGCRefMap(dispatchFrame.Indirection) == TargetPointer.Null)
return new TargetCodePointer((uint)tb.ReturnAddress - X86FrameHandler.CallInstructionSize);
returnAddress = new TargetCodePointer((uint)tb.ReturnAddress - X86FrameHandler.CallInstructionSize);
}
return GetTransitionBlockReturnAddress(tb);
break;

// SoftwareExceptionFrame: stored m_ReturnAddress
case FrameType.SoftwareExceptionFrame:
Data.SoftwareExceptionFrame sef = _target.ProcessedData.GetOrAdd<Data.SoftwareExceptionFrame>(frame.Address);
return sef.ReturnAddress;
returnAddress = sef.ReturnAddress;
break;

// ResumableFrame / RedirectedThreadFrame: RIP from captured context
case FrameType.ResumableFrame:
Expand All @@ -482,25 +488,30 @@ public TargetCodePointer GetReturnAddress(Data.Frame frame)
// HijackFrame: stored m_ReturnAddress
case FrameType.HijackFrame:
Data.HijackFrame hf = _target.ProcessedData.GetOrAdd<Data.HijackFrame>(frame.Address);
return hf.ReturnAddress;
returnAddress = hf.ReturnAddress;
break;

// TailCallFrame: stored m_ReturnAddress
case FrameType.TailCallFrame:
Data.TailCallFrame tcf = _target.ProcessedData.GetOrAdd<Data.TailCallFrame>(frame.Address);
return tcf.ReturnAddress;
returnAddress = tcf.ReturnAddress;
break;

// FuncEvalFrame: returns 0 during exception eval
case FrameType.FuncEvalFrame:
Data.FuncEvalFrame funcEval = _target.ProcessedData.GetOrAdd<Data.FuncEvalFrame>(frame.Address);
Data.DebuggerEval dbgEval = _target.ProcessedData.GetOrAdd<Data.DebuggerEval>(funcEval.DebuggerEvalPtr);
if (!dbgEval.EvalUsesHijack)
return TargetCodePointer.Null;
return funcEval.ReturnAddress;
returnAddress = funcEval.ReturnAddress;
break;

// Base Frame and unknown types: return 0 (matches native Frame::GetReturnAddressPtr_Impl)
default:
return TargetCodePointer.Null;
}

return CodePointerUtils.StripPtrAuthFromReturnAddress(returnAddress, _target);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,7 @@ private bool Next(StackWalkData handle)
// (interpreter virtual unwind manages the IP), but we still need
// UpdateContextFromFrame to transition to Frameless in the
// interpreted method.
if (returnAddress != TargetPointer.Null
if (returnAddress != TargetCodePointer.Null
|| frameType == FrameType.InterpreterFrame)
{
handle.FrameIter.UpdateContextFromCurrentFrame(handle.Context);
Expand Down Expand Up @@ -1284,8 +1284,7 @@ TargetPointer IStackWalk.GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHa
{
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;

Data.InlinedCallFrame icf = _target.ProcessedData.GetOrAdd<Data.InlinedCallFrame>(framePtr);
TargetCodePointer returnAddress = icf.CallerReturnAddress;
TargetCodePointer returnAddress = _frameHelpers.GetReturnAddress(frameData);
if (returnAddress != TargetCodePointer.Null && _eman.GetCodeBlockHandle(returnAddress) is CodeBlockHandle cbh)
{
MethodDescHandle returnMethodDesc = rts.GetMethodDescHandle(_eman.GetMethodDesc(cbh));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ internal sealed partial class PlatformMetadata : IData<PlatformMetadata>
public partial TargetPointer PrecodeMachineDescriptor { get; }

[Field] public partial byte CodePointerFlags { get; }

[Field] public partial ulong? PointerAuthenticationMask { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static TargetCodePointer ToTargetCodePointer(this ClrDataAddress address,
}

/// <summary>
/// Converts a TargetCodePointer to an address TargetPointer, removing any platform-specific bits such as the ARM32 Thumb bit or ARM64 pointer authentication.
/// Converts a TargetCodePointer to an address TargetPointer, removing the ARM32 Thumb bit if present.
/// </summary>
public static TargetPointer ToAddress(this TargetCodePointer code, Target target)
{
Expand All @@ -94,10 +94,6 @@ public static TargetPointer ToAddress(this TargetCodePointer code, Target target
{
return new TargetPointer(code.Value & ~Arm32ThumbBit);
}
else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth))
{
throw new NotImplementedException($"{nameof(ToAddress)}: ARM64 with pointer authentication");
}
Debug.Assert(flags == default);
return new TargetPointer(code.Value);
}
Expand Down
53 changes: 53 additions & 0 deletions src/native/managed/cdac/tests/UnitTests/PlatformMetadataTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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 Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.TestInfrastructure;
using Xunit;

namespace Microsoft.Diagnostics.DataContractReader.Tests;

public class PlatformMetadataTests
{
private const ulong MetadataAddress = 0x1000;

[Fact]
public void MissingMaskLeavesReturnAddressUnchanged()
{
Target target = CreateTarget();
Assert.Equal(0UL, target.Contracts.PlatformMetadata.GetPointerAuthenticationMask());
Assert.Equal(0x1234UL, CodePointerUtils.StripPtrAuthFromReturnAddress(new(0x1234), target).Value);
}

[Theory]
[InlineData(0xff7f800000000000UL, 0xa53afffebc33aae4UL, 0x00007ffebc33aae4UL)]
[InlineData(0xff70000000000000UL, 0xa53afffebc33aae4UL, 0x000afffebc33aae4UL)]
public void ClearsOnlyMaskBits(ulong mask, ulong signedAddress, ulong address)
{
Target target = CreateTarget(mask);

Assert.Equal(address, CodePointerUtils.StripPtrAuthFromReturnAddress(new(signedAddress), target).Value);
}

private static Target CreateTarget(ulong? mask = null)
{
MockTarget.Architecture arch = new() { Is64Bit = true, IsLittleEndian = true };
TestPlaceholderTarget.Builder builder = new(arch);
TargetTestHelpers helpers = new(arch);
Dictionary<string, Target.FieldInfo> fields = [];
byte[] data = new byte[8];
if (mask.HasValue)
{
fields[nameof(Data.PlatformMetadata.PointerAuthenticationMask)] = new() { Offset = 0 };
helpers.Write(data, mask.Value);
}
builder.MemoryBuilder.AddHeapFragment(new() { Name = "PlatformMetadata", Address = MetadataAddress, Data = data });
return builder
.AddTypes(new Dictionary<DataType, Target.TypeInfo> { [DataType.PlatformMetadata] = new() { Fields = fields } })
.AddGlobals((Constants.Globals.PlatformMetadata, MetadataAddress))
.AddContract<IPlatformMetadata>("c1")
.Build();
}
}
Loading
Loading