Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d14cb42
Arm64: [PAC-RET] NativeAOT changes
SwapnilGaikwad Mar 11, 2026
9a83057
Merge main
SwapnilGaikwad Jun 5, 2026
3929397
Fix build failures on non-x86 machines
SwapnilGaikwad Jun 5, 2026
7205e0d
Merge main
SwapnilGaikwad Jun 5, 2026
8d25498
Address review comments
SwapnilGaikwad Jun 8, 2026
4dc9a08
Merge main
SwapnilGaikwad Jun 8, 2026
4d01608
Merge main
SwapnilGaikwad Jun 9, 2026
6c9909e
Avoid parsing Apple compact unwind as PAC DWARF info
SwapnilGaikwad Jun 10, 2026
8bd3656
Merge main
SwapnilGaikwad Jun 10, 2026
d38310d
Restore Linux ARM64 NativeAOT unwindability behavior
SwapnilGaikwad Jun 12, 2026
032eaec
Merge main
SwapnilGaikwad Jun 12, 2026
f6f137a
Revert cdac unwinder changes
SwapnilGaikwad Jun 12, 2026
5888b08
Merge main
SwapnilGaikwad Jun 12, 2026
a344ec2
Update return value explicitly when PAC info not found
SwapnilGaikwad Jun 15, 2026
10cc0c2
Enable hijacking current frame when interrupted in outside prolog/epi…
SwapnilGaikwad Jun 15, 2026
d0c45bd
Merge main
SwapnilGaikwad Jun 15, 2026
8294958
Merge branch 'main' into github-aot-pac
jkotas Jun 21, 2026
83d4c2a
Merge main
SwapnilGaikwad Jun 22, 2026
c3109ef
Track signing SP in unwind info to handle stack allocation before pac
SwapnilGaikwad Jun 22, 2026
b1c1625
Merge main
SwapnilGaikwad Jun 24, 2026
bdda93d
Merge main
SwapnilGaikwad Jun 25, 2026
f7a1db8
Avoid hijacking on the return instruction for NativeAOT
SwapnilGaikwad Jun 25, 2026
06190fe
Revert adding CFI_DEF_CFA before CFI_NEGATE_RA_STATE.
SwapnilGaikwad Jun 29, 2026
6e6132d
Avoid hijacking on RET only when PAC is enabled
SwapnilGaikwad Jun 29, 2026
1c941e0
Address review comments
SwapnilGaikwad Jun 29, 2026
27904f4
Merge main
SwapnilGaikwad Jun 30, 2026
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
1 change: 1 addition & 0 deletions src/coreclr/inc/cfi.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ enum CFI_OPCODE
CFI_ADJUST_CFA_OFFSET, // Offset is adjusted relative to the current one.
CFI_DEF_CFA_REGISTER, // New register is used to compute CFA
CFI_REL_OFFSET, // Register is saved at offset from the current CFA
CFI_DEF_CFA, // Take address from register and add offset to it
CFI_NEGATE_RA_STATE, // Sign the return address in lr with paciasp
};

Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/inc/clrnt.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ RtlpGetFunctionEndAddress (
else
{
// Get from the xdata record.
FunctionLength = *(PTR_ULONG64)(ImageBase + FunctionLength) & 0x3ffff;
FunctionLength = *(PTR_uint64_t)(ImageBase + FunctionLength) & 0x3ffff;
}

return FunctionEntry->BeginAddress + 4 * FunctionLength;
Expand Down Expand Up @@ -422,7 +422,7 @@ RtlpGetFunctionEndAddress (
if ((FunctionLength & 3) != 0) {
FunctionLength = (FunctionLength >> 2) & 0x7ff;
} else {
FunctionLength = *(PTR_ULONG64)(ImageBase + FunctionLength) & 0x3ffff;
FunctionLength = *(PTR_uint64_t)(ImageBase + FunctionLength) & 0x3ffff;
}

return FunctionEntry->BeginAddress + 4 * FunctionLength;
Expand Down Expand Up @@ -478,7 +478,7 @@ RtlpGetFunctionEndAddress (
if ((FunctionLength & 3) != 0) {
FunctionLength = (FunctionLength >> 2) & 0x7ff;
} else {
FunctionLength = *(PTR_ULONG64)(ImageBase + FunctionLength) & 0x3ffff;
FunctionLength = *(PTR_uint64_t)(ImageBase + FunctionLength) & 0x3ffff;
}

return FunctionEntry->BeginAddress + 2 * FunctionLength;
Expand Down
8 changes: 4 additions & 4 deletions src/coreclr/inc/daccess.h
Original file line number Diff line number Diff line change
Expand Up @@ -2372,23 +2372,23 @@ typedef DPTR(TADDR) PTR_TADDR;

#ifndef NATIVEAOT
typedef ArrayDPTR(BYTE) PTR_BYTE;
typedef DPTR(WORD) PTR_WORD;
Comment thread
SwapnilGaikwad marked this conversation as resolved.
Outdated
typedef DPTR(DWORD) PTR_DWORD;
typedef DPTR(ULONG64) PTR_ULONG64;
typedef DPTR(UINT64) PTR_UINT64;
typedef DPTR(PTR_BYTE) PTR_PTR_BYTE;
typedef DPTR(PTR_PTR_BYTE) PTR_PTR_PTR_BYTE;
typedef ArrayDPTR(signed char) PTR_SBYTE;
typedef ArrayDPTR(const BYTE) PTR_CBYTE;
typedef DPTR(INT8) PTR_INT8;
typedef DPTR(INT16) PTR_INT16;
typedef DPTR(UINT16) PTR_UINT16;
typedef DPTR(WORD) PTR_WORD;
typedef DPTR(USHORT) PTR_USHORT;
typedef DPTR(DWORD) PTR_DWORD;
typedef DPTR(LONG) PTR_LONG;
typedef DPTR(ULONG) PTR_ULONG;
typedef DPTR(INT32) PTR_INT32;
typedef DPTR(UINT32) PTR_UINT32;
typedef DPTR(ULONG64) PTR_ULONG64;
typedef DPTR(INT64) PTR_INT64;
typedef DPTR(UINT64) PTR_UINT64;
typedef DPTR(SIZE_T) PTR_SIZE_T;
typedef DPTR(int) PTR_int;
typedef DPTR(BOOL) PTR_BOOL;
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/nativeaot/Runtime/AsmOffsets.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ ASM_OFFSET( 30, 48, Thread, m_pTransitionFrame)
ASM_OFFSET( 34, 50, Thread, m_pDeferredTransitionFrame)
ASM_OFFSET( 40, 68, Thread, m_ppvHijackedReturnAddressLocation)
ASM_OFFSET( 44, 70, Thread, m_pvHijackedReturnAddress)
#if defined(TARGET_ARM64)
ASM_OFFSET( 48, 78, Thread, m_pSpForPacSign)
ASM_OFFSET( 4c, 80, Thread, m_pExInfoStackHead)
#else
ASM_OFFSET( 48, 78, Thread, m_pExInfoStackHead)
#endif
#ifdef TARGET_X86
ASM_OFFSET( 4c, FF, Thread, m_uHijackedReturnValueFlags)
#endif
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/nativeaot/Runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ if (WIN32)

list(APPEND FULL_RUNTIME_SOURCES windows/CoffNativeCodeManager.cpp)

if(CLR_CMAKE_TARGET_ARCH_ARM64)
list(APPEND FULL_RUNTIME_SOURCES
../../unwinder/arm64/unwinder.cpp
)
include_directories(../../inc)
include_directories(../../unwinder)
include_directories(../../unwinder/arm64)
endif()

set(ASM_SUFFIX asm)
else()

Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/nativeaot/Runtime/ICodeManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ class ICodeManager

virtual bool GetReturnAddressHijackInfo(MethodInfo * pMethodInfo,
REGDISPLAY * pRegisterSet, // in
PTR_PTR_VOID * ppvRetAddrLocation // out
PTR_PTR_VOID * ppvRetAddrLocation, // out
uintptr_t * pSpForArm64PacSign // out
) PURE_VIRTUAL

#ifdef TARGET_X86
Expand Down
35 changes: 25 additions & 10 deletions src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ EXTERN_C CODE_LOCATION RhpRethrow2;
#define FAILFAST_OR_DAC_FAIL_UNCONDITIONALLY(msg) { ASSERT_UNCONDITIONALLY(msg); RhFailFast(); }
#endif

#if defined(TARGET_ARM64)
extern "C" void* PacStripPtr(void* ptr);
#endif // TARGET_ARM64

static TADDR ReturnAddressToCanonicalPC(TADDR returnAddress)
{
#if defined(TARGET_ARM64)
returnAddress = (TADDR)PacStripPtr((void*)returnAddress);
#endif // TARGET_ARM64
return PCODEToPINSTR(dac_cast<PCODE>(returnAddress));
}

StackFrameIterator::StackFrameIterator(Thread * pThreadToWalk, PInvokeTransitionFrame* pInitialTransitionFrame)
{
STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ GC ]\n");
Expand Down Expand Up @@ -164,7 +176,7 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PInvokeTransitionF

#if !defined(FEATURE_PORTABLE_HELPERS) // @TODO: no portable version of regdisplay
memset(&m_RegDisplay, 0, sizeof(m_RegDisplay));
m_RegDisplay.SetIP((PCODE)PCODEToPINSTR((PCODE)pFrame->m_RIP));
m_RegDisplay.SetIP(ReturnAddressToCanonicalPC(dac_cast<TADDR>(pFrame->m_RIP)));
SetControlPC(dac_cast<PTR_VOID>(m_RegDisplay.GetIP()));

PTR_uintptr_t pPreservedRegsCursor = (PTR_uintptr_t)PTR_HOST_MEMBER_TADDR(PInvokeTransitionFrame, pFrame, m_PreservedRegs);
Expand Down Expand Up @@ -413,14 +425,15 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PTR_PAL_LIMITED_CO

// This codepath is used by the hijack stackwalk and we can get arbitrary ControlPCs from there. If this
// context has a non-managed control PC, then we're done.
if (!m_pInstance->IsManaged(dac_cast<PTR_VOID>(pCtx->GetIp())))
TADDR controlPC = ReturnAddressToCanonicalPC(pCtx->GetIp());
if (!m_pInstance->IsManaged(dac_cast<PTR_VOID>(controlPC)))
return;

//
// control state
//
m_RegDisplay.SP = pCtx->GetSp();
m_RegDisplay.IP = PCODEToPINSTR(pCtx->GetIp());
m_RegDisplay.IP = controlPC;
SetControlPC(dac_cast<PTR_VOID>(m_RegDisplay.GetIP()));

#ifdef TARGET_ARM
Expand Down Expand Up @@ -633,14 +646,15 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, NATIVE_CONTEXT* pC

// This codepath is used by the hijack stackwalk. The IP must be in managed code
// or in a conservatively reported assembly thunk.
ASSERT(IsValidReturnAddress((void*)pCtx->GetIp()));
TADDR controlPC = ReturnAddressToCanonicalPC(pCtx->GetIp());
ASSERT(IsValidReturnAddress(dac_cast<PTR_VOID>(controlPC)));

//
// control state
//
SetControlPC(dac_cast<PTR_VOID>(pCtx->GetIp()));
SetControlPC(dac_cast<PTR_VOID>(controlPC));
m_RegDisplay.SP = pCtx->GetSp();
m_RegDisplay.IP = pCtx->GetIp();
m_RegDisplay.IP = controlPC;

#ifdef TARGET_UNIX
#define PTR_TO_REG(ptr, reg) (&((ptr)->reg()))
Expand Down Expand Up @@ -1223,7 +1237,7 @@ void StackFrameIterator::UnwindFuncletInvokeThunk()

m_RegDisplay.pFP = SP++;

m_RegDisplay.SetIP(*SP++);
m_RegDisplay.SetIP(ReturnAddressToCanonicalPC(*SP++));

m_RegDisplay.pX19 = SP++;
m_RegDisplay.pX20 = SP++;
Expand Down Expand Up @@ -1636,7 +1650,7 @@ void StackFrameIterator::UnwindUniversalTransitionThunk()
stackFrame->UnwindVolatileArgRegisters(&m_RegDisplay);

PTR_uintptr_t addressOfPushedCallerIP = stackFrame->get_AddressOfPushedCallerIP();
m_RegDisplay.SetIP(PCODEToPINSTR(*addressOfPushedCallerIP));
m_RegDisplay.SetIP(ReturnAddressToCanonicalPC(*addressOfPushedCallerIP));
m_RegDisplay.SetSP((uintptr_t)dac_cast<TADDR>(stackFrame->get_CallerSP()));
SetControlPC(dac_cast<PTR_VOID>(m_RegDisplay.GetIP()));
#if defined(TARGET_AMD64) && defined(TARGET_WINDOWS)
Expand Down Expand Up @@ -1767,7 +1781,7 @@ void StackFrameIterator::UnwindThrowSiteThunk()
ASSERT_UNCONDITIONALLY("NYI for this arch");
#endif

m_RegDisplay.SetIP(PCODEToPINSTR(pContext->IP));
m_RegDisplay.SetIP(ReturnAddressToCanonicalPC(pContext->IP));
m_RegDisplay.SetSP(pContext->GetSp());
SetControlPC(dac_cast<PTR_VOID>(m_RegDisplay.GetIP()));

Expand Down Expand Up @@ -1862,7 +1876,8 @@ void StackFrameIterator::NextInternal()
// if the thread is safe to walk, it better not have a hijack in place.
ASSERT(!m_pThread->IsHijacked());

SetControlPC(dac_cast<PTR_VOID>(PCODEToPINSTR(m_RegDisplay.GetIP())));
m_RegDisplay.SetIP(ReturnAddressToCanonicalPC(m_RegDisplay.GetIP()));
SetControlPC(dac_cast<PTR_VOID>(m_RegDisplay.GetIP()));

PTR_VOID collapsingTargetFrame = NULL;

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/nativeaot/Runtime/arm64/AsmMacros.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ TrashRegister32Bit SETS "w":CC:("$TrashRegister32Bit":RIGHT:((:LEN:TrashRegister
str $trashReg1, [$trashReg2]
str xzr, [$threadReg, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
str xzr, [$threadReg, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
str xzr, [$threadReg, #OFFSETOF__Thread__m_pSpForPacSign]
0
MEND

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/nativeaot/Runtime/arm64/ExceptionHandling.S
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ LOCAL_LABEL(ClearThreadState):
// clear the Thread's hijack state
str xzr, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
str xzr, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
str xzr, [x2, #OFFSETOF__Thread__m_pSpForPacSign]

LOCAL_LABEL(NotHijacked):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ ClearThreadState
;; clear the Thread's hijack state
str xzr, [x2, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
str xzr, [x2, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
str xzr, [x2, #OFFSETOF__Thread__m_pSpForPacSign]

NotHijacked

Expand Down
15 changes: 11 additions & 4 deletions src/coreclr/nativeaot/Runtime/arm64/GcProbe.S
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <unixasmmacros.inc>
#include "AsmOffsets.inc"

.arch_extension pauth

#define PROBE_FRAME_SIZE 0x140 // 4 * 8 for fixed part of PInvokeTransitionFrame (fp, lr, m_pThread, m_Flags) +
// 10 * 8 for callee saved registers +
// 1 * 8 for caller SP +
Expand Down Expand Up @@ -103,7 +105,7 @@
// x9: thread pointer
// x0-x7, q0-q7 preserved, x10 trashed
//
.macro FixupHijackedCallstack
.macro FixupHijackedCallstack clearHijackState

// x9 <- GetThread()

Expand Down Expand Up @@ -150,22 +152,27 @@
#endif

//
// Fix the stack by restoring the original return address
// Fix the stack by restoring and authenticating the original return address.
//
ldr lr, [x9, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
ldr x16, [x9, #OFFSETOF__Thread__m_pSpForPacSign]
cbz x16, \clearHijackState
autia lr, x16

\clearHijackState:
//
// Clear hijack state
//
// Clear m_ppvHijackedReturnAddressLocation and m_pvHijackedReturnAddress
// Clear m_ppvHijackedReturnAddressLocation, m_pvHijackedReturnAddress, and m_pSpForPacSign
stp xzr, xzr, [x9, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
str xzr, [x9, #OFFSETOF__Thread__m_pSpForPacSign]
.endm

//
// GC Probe Hijack target
//
NESTED_ENTRY RhpGcProbeHijack, _TEXT, NoHandler
FixupHijackedCallstack
FixupHijackedCallstack LOCAL_LABEL(RhpGcProbeClearHijackState)

PREPARE_EXTERNAL_VAR_INDIRECT_W RhpTrapThreads, 10
tbnz x10, #TrapThreadsFlags_TrapThreads_Bit, LOCAL_LABEL(WaitForGC)
Expand Down
17 changes: 11 additions & 6 deletions src/coreclr/nativeaot/Runtime/arm64/GcProbe.asm
Original file line number Diff line number Diff line change
Expand Up @@ -112,25 +112,30 @@ PROBE_FRAME_SIZE field 0
;;
;; Register state on exit:
;; x9: thread pointer
;; x0-x7 preserved, x10 trashed
;; x0-x7 preserved, x10 and x16 trashed
;;
MACRO
FixupHijackedCallstack
FixupHijackedCallstack $ClearHijackStateLabel

;; x9 <- GetThread(), TRASHES x10
INLINE_GETTHREAD x9, x10

;;
;; Fix the stack by restoring the original return address
;; Fix the stack by restoring and authenticating the original return address.
;;
ldr lr, [x9, #OFFSETOF__Thread__m_pvHijackedReturnAddress]
ldr x16, [x9, #OFFSETOF__Thread__m_pSpForPacSign]
cbz x16, $ClearHijackStateLabel
DCD 0xDAC1161E ;; autib lr, x16 instruction in binary to avoid requiring PAC-enabled assemblers

$ClearHijackStateLabel
;;
;; Clear hijack state
;;
ASSERT OFFSETOF__Thread__m_pvHijackedReturnAddress == (OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation + 8)
;; Clear m_ppvHijackedReturnAddressLocation and m_pvHijackedReturnAddress
;; Clear m_ppvHijackedReturnAddressLocation, m_pvHijackedReturnAddress, and m_pSpForPacSign
stp xzr, xzr, [x9, #OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation]
str xzr, [x9, #OFFSETOF__Thread__m_pSpForPacSign]
MEND

MACRO
Expand All @@ -154,7 +159,7 @@ PROBE_FRAME_SIZE field 0
HijackTargetFakeProlog

LABELED_RETURN_ADDRESS RhpGcProbeHijack
FixupHijackedCallstack
FixupHijackedCallstack RhpGcProbeClearHijackState

ldr x10, =RhpTrapThreads
ldr w10, [x10]
Expand Down Expand Up @@ -201,7 +206,7 @@ WaitForGC
;;
;;
LEAF_ENTRY RhpGcStressHijack
FixupHijackedCallstack
FixupHijackedCallstack RhpGcStressClearHijackState
mov x12, #(DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_X0 + PTFF_SAVE_X1 + PTFF_SAVE_X2 + PTFF_SAVE_X3 + PTFF_SAVE_X4 + PTFF_SAVE_X5 + PTFF_SAVE_X6 + PTFF_SAVE_X7)
b RhpGcStressProbe
LEAF_END RhpGcStressHijack
Expand Down
31 changes: 31 additions & 0 deletions src/coreclr/nativeaot/Runtime/arm64/MiscStubs.S
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,34 @@

#include <unixasmmacros.inc>
#include "AsmOffsets.inc"

// void* PacStripPtr(void *);
// This function strips the pointer of PAC info that is passed as an argument.
// We prefer to strip a pointer where it's not going to be used to branch execution to.
.arch_extension pauth
LEAF_ENTRY PacStripPtr, _TEXT
xpaci x0
ret
LEAF_END PacStripPtr, _TEXT

// void* PacSignPtr(void *, void *);
// This function signs the input pointer using x1 as salt. It is a no-op on non-PAC enabled machines.
.arch_extension pauth
LEAF_ENTRY PacSignPtr, _TEXT
mov x17, x0
mov x16, x1
pacia1716
mov x0, x17
ret
LEAF_END PacSignPtr, _TEXT

// void* PacAuthPtr(void *, void *);
// This function authenticates the input signed-pointer using x1 as salt. It is a no-op on non-PAC enabled machines.
.arch_extension pauth
LEAF_ENTRY PacAuthPtr, _TEXT
mov x17, x0
mov x16, x1
autia1716
mov x0, x17
ret
LEAF_END PacAuthPtr, _TEXT
28 changes: 28 additions & 0 deletions src/coreclr/nativeaot/Runtime/arm64/MiscStubs.asm
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,32 @@

TEXTAREA

; void* PacStripPtr(void *);
; This function strips the pointer of PAC info that is passed as an argument.
; We prefer to strip a pointer where it's not going to be used to branch execution to.
LEAF_ENTRY PacStripPtr
DCD 0xDAC143E0 ; xpaci x0 instruction in binary to avoid requiring PAC-enabled assemblers
ret
LEAF_END PacStripPtr

; void* PacSignPtr(void *, void *);
; This function signs the input pointer using x1 as salt. It is a no-op on non-PAC enabled machines.
LEAF_ENTRY PacSignPtr
mov x17, x0
mov x16, x1
DCD 0xD503215F ; pacib1716 instruction in binary to avoid error while compiling with non-PAC enabled compilers
mov x0, x17
ret
LEAF_END PacSignPtr

; void* PacAuthPtr(void *, void *);
; This function authenticates the input signed-pointer using x1 as salt. It is a no-op on non-PAC enabled machines.
LEAF_ENTRY PacAuthPtr
mov x17, x0
mov x16, x1
DCD 0xD50321DF ; autib1716 instruction in binary to avoid error while compiling with non-PAC enabled compilers
mov x0, x17
ret
LEAF_END PacAuthPtr

end
Loading
Loading