Skip to content

Commit

Permalink
support single reg return hijack
Browse files Browse the repository at this point in the history
  • Loading branch information
VSadov committed Jul 6, 2022
1 parent 819706d commit d808039
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 319 deletions.
11 changes: 4 additions & 7 deletions src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,9 +517,8 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PTR_PAL_LIMITED_CO
#endif // TARGET_ARM
}

// Prepare to start a stack walk from the context listed in the supplied CONTEXT.
// The supplied context can describe a location in either managed or unmanaged code. In the
// latter case the iterator is left in an invalid state when this function returns.
// Prepare to start a stack walk from the context listed in the supplied NATIVE_CONTEXT.
// The supplied context can describe a location in managed code.
void StackFrameIterator::InternalInit(Thread * pThreadToWalk, NATIVE_CONTEXT* pCtx, uint32_t dwFlags)
{
ASSERT((dwFlags & MethodStateCalculated) == 0);
Expand All @@ -533,10 +532,8 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, NATIVE_CONTEXT* pC
// properly walk it in parallel.
ResetNextExInfoForSP(pCtx->GetSp());

// 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())))
return;
// This codepath is used by the hijack stackwalk. It must be in managed code.
ASSERT(m_pInstance->IsManaged(dac_cast<PTR_VOID>(pCtx->GetIp())));

//
// control state
Expand Down
213 changes: 213 additions & 0 deletions src/coreclr/nativeaot/Runtime/amd64/GcProbe.S
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,221 @@
// The .NET Foundation licenses this file to you under the MIT license.

.intel_syntax noprefix
#include <AsmOffsets.inc> // generated by the build from AsmOffsets.cpp
#include <unixasmmacros.inc>

//
// See PUSH_COOP_PINVOKE_FRAME, this macro is very similar, but also saves RAX and accepts the register
// bitmask in RCX
//
// On entry:
// - BITMASK: bitmask describing pushes, may be volatile register or constant value
// - RAX: managed function return value, may be an object or byref
// - preserved regs: need to stay preserved, may contain objects or byrefs
//
// INVARIANTS
// - The macro assumes it is called from a prolog, prior to a frame pointer being setup.
// - All preserved registers remain unchanged from their values in managed code.
//
.macro PUSH_PROBE_FRAME threadReg, trashReg, BITMASK
push_register rdx // save RDX, it might contain an objectref
push_register rax // save RAX, it might contain an objectref
lea \trashReg, [rsp + 0x18]
push_register \trashReg // save caller`s RSP
push_nonvol_reg r15 // save preserved registers
push_nonvol_reg r14 // ..
push_nonvol_reg r13 // ..
push_nonvol_reg r12 // ..
push_nonvol_reg rbx // ..
push_register \BITMASK // save the register bitmask passed in by caller
push_register \threadReg // Thread * (unused by stackwalker)
push_nonvol_reg rbp // save caller`s RBP
mov \trashReg, [rsp + 11*8] // Find the return address
push_register \trashReg // save m_RIP
lea \trashReg, [rsp + 0] // trashReg == address of frame

// allocate space for xmm0 and alignment
alloc_stack 0x18

// save xmm0 in case it`s being used as a return value
movdqa [rsp + 0], xmm0

// link the frame into the Thread
mov [\threadReg + OFFSETOF__Thread__m_pDeferredTransitionFrame], \trashReg
.endm

//
// Remove the frame from a previous call to PUSH_PROBE_FRAME from the top of the stack and restore preserved
// registers and return value to their values from before the probe was called (while also updating any
// object refs or byrefs).
.macro POP_PROBE_FRAME
movdqa xmm0, [rsp + 0]
add rsp, 0x18 + 8 // skip xmm0 and discard RIP
pop rbp
pop rax // discard Thread*
pop rax // discard BITMASK
pop rbx
pop r12
pop r13
pop r14
pop r15
pop rax // discard caller RSP
pop rax
pop rdx
.endm

//
// Macro to clear the hijack state. This is safe to do because the suspension code will not Unhijack this
// thread if it finds it at an IP that isn`t managed code.
//
// Register state on entry:
// R11: thread pointer
//
// Register state on exit:
// RCX: trashed
//
.macro ClearHijackState
xor ecx, ecx
mov [r11 + OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation], rcx
mov [r11 + OFFSETOF__Thread__m_pvHijackedReturnAddress], rcx
.endm


//
// The prolog for all GC suspension hijacks (normal and stress). Fixes up the hijacked return address, and
// clears the hijack state.
//
// Register state on entry:
// All registers correct for return to the original return address.
//
// Register state on exit:
// R11: thread pointer
// RAX, RDX preserved, other volatile regs trashed
//
.macro FixupHijackedCallstack
// preserve RAX, RDX as they may contain retuvalues
push rax
push rdx

// rax = GetThread(), makes nested calls
INLINE_GETTHREAD
mov r11, rax

pop rdx
pop rax

//
// Fix the stack by pushing the original return address
//
mov rcx, [r11 + OFFSETOF__Thread__m_pvHijackedReturnAddress]
push rcx

ClearHijackState
.endm

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RhpWaitForGCNoAbort -- rare path for WaitForGCCompletion
//
//
// INPUT: RDI: transition frame
//
// TRASHES: RCX, RDI, R8, R9, R10, R11
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
NESTED_ENTRY RhpWaitForGCNoAbort, _TEXT, NoHandler
END_PROLOGUE

mov rdx, [rdi + OFFSETOF__PInvokeTransitionFrame__m_pThread]

test dword ptr [rdx + OFFSETOF__Thread__m_ThreadStateFlags], TSF_DoNotTriggerGc
jnz Done

// passing transition frame pointer in rdi
call C_FUNC(RhpWaitForGC2)

Done:
ret

NESTED_END RhpWaitForGCNoAbort, _TEXT

//
// Set the Thread state and wait for a GC to complete.
//
// Register state on entry:
// RBX: thread pointer
//
// Register state on exit:
// RBX: thread pointer
// All other registers trashed
//

.macro WaitForGCCompletion
test dword ptr [rbx + OFFSETOF__Thread__m_ThreadStateFlags], TSF_SuppressGcStress + TSF_DoNotTriggerGc
jnz LOCAL_LABEL(NoWait)

mov rdi, [rbx + OFFSETOF__Thread__m_pDeferredTransitionFrame]
call C_FUNC(RhpWaitForGCNoAbort)
LOCAL_LABEL(NoWait):

.endm


// TODO: what to do with this?
// EXTERN RhpPInvokeExceptionGuard : PROC

//
//
//
// GC Probe Hijack targets
//
//
NESTED_ENTRY RhpGcProbeHijackScalar, _TEXT, NoHandler
END_PROLOGUE
FixupHijackedCallstack
mov ecx, DEFAULT_FRAME_SAVE_FLAGS
jmp C_FUNC(RhpGcProbe)
NESTED_END RhpGcProbeHijackScalar, _TEXT

NESTED_ENTRY RhpGcProbeHijackObject, _TEXT, NoHandler
END_PROLOGUE
FixupHijackedCallstack
mov ecx, DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_RAX + PTFF_RAX_IS_GCREF
jmp C_FUNC(RhpGcProbe)
NESTED_END RhpGcProbeHijackObject, _TEXT

NESTED_ENTRY RhpGcProbeHijackByref, _TEXT, NoHandler
END_PROLOGUE
FixupHijackedCallstack
mov ecx, DEFAULT_FRAME_SAVE_FLAGS + PTFF_SAVE_RAX + PTFF_RAX_IS_BYREF
jmp C_FUNC(RhpGcProbe)
NESTED_END RhpGcProbeHijackByref, _TEXT

NESTED_ENTRY RhpGcProbe, _TEXT, NoHandler
test dword ptr [C_VAR(RhpTrapThreads)], TrapThreadsFlags_TrapThreads
jnz LOCAL_LABEL(RhpGcProbe_Trap)
ret
LOCAL_LABEL(RhpGcProbe_Trap):
PUSH_PROBE_FRAME r11, rax, rcx
END_PROLOGUE

mov rbx, r11
WaitForGCCompletion

mov rax, [rbx + OFFSETOF__Thread__m_pDeferredTransitionFrame]
test dword ptr [rax + OFFSETOF__PInvokeTransitionFrame__m_Flags], PTFF_THREAD_ABORT
jnz LOCAL_LABEL(Abort)
POP_PROBE_FRAME
ret
LOCAL_LABEL(Abort):
POP_PROBE_FRAME
mov rcx, STATUS_REDHAWK_THREAD_ABORT
pop rdx // return address as exception RIP
jmp C_FUNC(RhpThrowHwEx) // Throw the ThreadAbortException as a special kind of hardware exception

NESTED_END RhpGcProbe, _TEXT


LEAF_ENTRY RhpGcPoll, _TEXT
cmp dword ptr [C_VAR(RhpTrapThreads)], TrapThreadsFlags_None
jne LOCAL_LABEL(RhpGcPoll_RarePath)
Expand Down
Loading

0 comments on commit d808039

Please sign in to comment.