Skip to content

Use signals for activation injection on macOS #46657

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
1 change: 0 additions & 1 deletion src/coreclr/pal/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ if(CLR_CMAKE_TARGET_OSX)
add_definitions(-DXSTATE_SUPPORTED)
endif()
set(PLATFORM_SOURCES
arch/${PAL_ARCH_SOURCES_DIR}/activationhandlerwrapper.S
arch/${PAL_ARCH_SOURCES_DIR}/context.S
arch/${PAL_ARCH_SOURCES_DIR}/dispatchexceptionwrapper.S
exception/machexception.cpp
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/pal/src/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ check_type_size("struct pt_regs" PT_REGS)
set(CMAKE_EXTRA_INCLUDE_FILES)
set(CMAKE_EXTRA_INCLUDE_FILES signal.h)
set(CMAKE_EXTRA_INCLUDE_FILES)
set(CMAKE_EXTRA_INCLUDE_FILES ucontext.h)
set(CMAKE_EXTRA_INCLUDE_FILES sys/ucontext.h)
check_type_size(ucontext_t UCONTEXT_T)
set(CMAKE_EXTRA_INCLUDE_FILES)
set(CMAKE_EXTRA_INCLUDE_FILES pthread.h)
Expand Down
188 changes: 0 additions & 188 deletions src/coreclr/pal/src/exception/machexception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1416,192 +1416,4 @@ SEHInitializeMachExceptions(DWORD flags)
return TRUE;
}

extern "C"
void
ActivationHandler(CONTEXT* context)
{
if (g_activationFunction != NULL)
{
g_activationFunction(context);
}

#ifdef TARGET_ARM64
// RtlRestoreContext assembly corrupts X16 & X17, so it cannot be
// used for Activation restore
MachSetThreadContext(context);
Copy link
Contributor

@sdmaclea sdmaclea Jan 7, 2021

Choose a reason for hiding this comment

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

How is context restored for activation using signals? How are we preventing X16/X17 corruption?

Maybe we are just returning from the signal handler and the kernel is taking care of it

Copy link
Contributor

Choose a reason for hiding this comment

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

I am actually wondering if we should change the JIT to not use X17 unless it is marked unsafe for GC.

Copy link
Member Author

Choose a reason for hiding this comment

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

The context is restored by the kernel. We return from the signal handler and let the kernel do the job. We just copy CONTEXT to the ucontext_t passed to the signal handler before returning.

#else
RtlRestoreContext(context, NULL);
#endif
DebugBreak();
}

extern "C" void ActivationHandlerWrapper();
extern "C" int ActivationHandlerReturnOffset;
extern "C" unsigned int XmmYmmStateSupport();

#if defined(HOST_AMD64)
bool IsHardwareException(x86_exception_state64_t exceptionState)
{
static const int MaxHardwareExceptionVector = 31;
return exceptionState.__trapno <= MaxHardwareExceptionVector;
}
#elif defined(HOST_ARM64)
bool IsHardwareException(arm_exception_state64_t exceptionState)
{
// Infer exception state from the ESR_EL* register value.
// Bits 31-26 represent the ESR.EC field
const int ESR_EC_SHIFT = 26;
const int ESR_EC_MASK = 0x3f;
const int esr_ec = (exceptionState.__esr >> ESR_EC_SHIFT) & ESR_EC_MASK;

const int ESR_EC_SVC = 0x15; // Supervisor Call exception from aarch64.

// Assume only supervisor calls from aarch64 are not hardware exceptions
return (esr_ec != ESR_EC_SVC);
}
#else
#error Unexpected architecture
#endif

/*++
Function :
InjectActivationInternal

Sets up the specified thread to call the ActivationHandler.

Parameters:
pThread - PAL thread instance

Return value :
PAL_ERROR
--*/
PAL_ERROR
InjectActivationInternal(CPalThread* pThread)
{
PAL_ERROR palError;

mach_port_t threadPort = pThread->GetMachPortSelf();

kern_return_t MachRet = SuspendMachThread(threadPort);
palError = (MachRet == KERN_SUCCESS) ? NO_ERROR : ERROR_GEN_FAILURE;

if (palError == NO_ERROR)
{
#if defined(HOST_AMD64)
x86_exception_state64_t ExceptionState;
const thread_state_flavor_t exceptionFlavor = x86_EXCEPTION_STATE64;
const mach_msg_type_number_t exceptionCount = x86_EXCEPTION_STATE64_COUNT;

x86_thread_state64_t ThreadState;
const thread_state_flavor_t threadFlavor = x86_THREAD_STATE64;
const mach_msg_type_number_t threadCount = x86_THREAD_STATE64_COUNT;
#elif defined(HOST_ARM64)
arm_exception_state64_t ExceptionState;
const thread_state_flavor_t exceptionFlavor = ARM_EXCEPTION_STATE64;
const mach_msg_type_number_t exceptionCount = ARM_EXCEPTION_STATE64_COUNT;

arm_thread_state64_t ThreadState;
const thread_state_flavor_t threadFlavor = ARM_THREAD_STATE64;
const mach_msg_type_number_t threadCount = ARM_THREAD_STATE64_COUNT;
#else
#error Unexpected architecture
#endif
mach_msg_type_number_t count = exceptionCount;

MachRet = thread_get_state(threadPort,
exceptionFlavor,
(thread_state_t)&ExceptionState,
&count);
_ASSERT_MSG(MachRet == KERN_SUCCESS, "thread_get_state for *_EXCEPTION_STATE64\n");

// Inject the activation only if the thread doesn't have a pending hardware exception
if (!IsHardwareException(ExceptionState))
{
count = threadCount;
MachRet = thread_get_state(threadPort,
threadFlavor,
(thread_state_t)&ThreadState,
&count);
_ASSERT_MSG(MachRet == KERN_SUCCESS, "thread_get_state for *_THREAD_STATE64\n");

#if defined(HOST_AMD64)
if ((g_safeActivationCheckFunction != NULL) && g_safeActivationCheckFunction(ThreadState.__rip, /* checkingCurrentThread */ FALSE))
{
// TODO: it would be nice to preserve the red zone in case a jitter would want to use it
// Do we really care about unwinding through the wrapper?
size_t* sp = (size_t*)ThreadState.__rsp;
*(--sp) = ThreadState.__rip;
*(--sp) = ThreadState.__rbp;
size_t rbpAddress = (size_t)sp;
#elif defined(HOST_ARM64)
if ((g_safeActivationCheckFunction != NULL) && g_safeActivationCheckFunction((size_t)arm_thread_state64_get_pc_fptr(ThreadState), /* checkingCurrentThread */ FALSE))
{
// TODO: it would be nice to preserve the red zone in case a jitter would want to use it
// Do we really care about unwinding through the wrapper?
size_t* sp = (size_t*)arm_thread_state64_get_sp(ThreadState);
*(--sp) = (size_t)arm_thread_state64_get_pc_fptr(ThreadState);
*(--sp) = arm_thread_state64_get_fp(ThreadState);
size_t fpAddress = (size_t)sp;
#else
#error Unexpected architecture
#endif
size_t contextAddress = (((size_t)sp) - sizeof(CONTEXT)) & ~15;

// Fill in the context in the helper frame with the full context of the suspended thread.
// The ActivationHandler will use the context to resume the execution of the thread
// after the activation function returns.
CONTEXT *pContext = (CONTEXT *)contextAddress;
#if defined(HOST_AMD64)
pContext->ContextFlags = CONTEXT_FULL | CONTEXT_SEGMENTS;
#else
pContext->ContextFlags = CONTEXT_FULL;
#endif
#ifdef XSTATE_SUPPORTED
if (XmmYmmStateSupport() == 1)
{
pContext->ContextFlags |= CONTEXT_XSTATE;
}
#endif
MachRet = CONTEXT_GetThreadContextFromPort(threadPort, pContext);
_ASSERT_MSG(MachRet == KERN_SUCCESS, "CONTEXT_GetThreadContextFromPort\n");

#if defined(HOST_AMD64)
size_t returnAddressAddress = contextAddress - sizeof(size_t);
*(size_t*)(returnAddressAddress) = ActivationHandlerReturnOffset + (size_t)ActivationHandlerWrapper;

// Make the instruction register point to ActivationHandler
ThreadState.__rip = (size_t)ActivationHandler;
ThreadState.__rsp = returnAddressAddress;
ThreadState.__rbp = rbpAddress;
ThreadState.__rdi = contextAddress;
#elif defined(HOST_ARM64)
// Make the call to ActivationHandler
arm_thread_state64_set_lr_fptr(ThreadState, ActivationHandlerReturnOffset + (size_t)ActivationHandlerWrapper);
arm_thread_state64_set_pc_fptr(ThreadState, ActivationHandler);
arm_thread_state64_set_sp(ThreadState, contextAddress);
arm_thread_state64_set_fp(ThreadState, fpAddress);
ThreadState.__x[0] = contextAddress;
#else
#error Unexpected architecture
#endif

MachRet = thread_set_state(threadPort,
threadFlavor,
(thread_state_t)&ThreadState,
threadCount);
_ASSERT_MSG(MachRet == KERN_SUCCESS, "thread_set_state\n");
}
}

MachRet = thread_resume(threadPort);
palError = (MachRet == ERROR_SUCCESS) ? NO_ERROR : ERROR_GEN_FAILURE;
}
else
{
printf("Suspension failed with error 0x%x\n", palError);
}

return palError;
}

#endif // HAVE_MACH_EXCEPTIONS
49 changes: 33 additions & 16 deletions src/coreclr/pal/src/exception/signal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,19 @@ SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do
#include <unistd.h>
#include <sys/mman.h>


#endif // !HAVE_MACH_EXCEPTIONS
#include "pal/context.h"

#ifdef SIGRTMIN
#define INJECT_ACTIVATION_SIGNAL SIGRTMIN
#else
#define INJECT_ACTIVATION_SIGNAL SIGUSR1
#endif

#if !defined(INJECT_ACTIVATION_SIGNAL) && defined(FEATURE_HIJACK)
#error FEATURE_HIJACK requires INJECT_ACTIVATION_SIGNAL to be defined
#endif
#endif // !HAVE_MACH_EXCEPTIONS

using namespace CorUnix;

Expand All @@ -66,6 +69,9 @@ typedef void (*SIGFUNC)(int, siginfo_t *, void *);
/* internal function declarations *********************************************/

static void sigterm_handler(int code, siginfo_t *siginfo, void *context);
#ifdef INJECT_ACTIVATION_SIGNAL
static void inject_activation_handler(int code, siginfo_t *siginfo, void *context);
#endif
#if !HAVE_MACH_EXCEPTIONS
static void sigill_handler(int code, siginfo_t *siginfo, void *context);
static void sigfpe_handler(int code, siginfo_t *siginfo, void *context);
Expand All @@ -77,9 +83,6 @@ static void sigquit_handler(int code, siginfo_t *siginfo, void *context);

static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext, int numParams, ...);

#ifdef INJECT_ACTIVATION_SIGNAL
static void inject_activation_handler(int code, siginfo_t *siginfo, void *context);
#endif
#endif // !HAVE_MACH_EXCEPTIONS

static void handle_signal(int signal_id, SIGFUNC sigfunc, struct sigaction *previousAction, int additionalFlags = 0, bool skipIgnored = false);
Expand All @@ -94,8 +97,13 @@ bool g_enable_alternate_stack_check = false;
#endif // !HAVE_MACH_EXCEPTIONS

static bool g_registered_sigterm_handler = false;
static bool g_registered_activation_handler = false;

struct sigaction g_previous_sigterm;
#ifdef INJECT_ACTIVATION_SIGNAL
struct sigaction g_previous_activation;
#endif

#if !HAVE_MACH_EXCEPTIONS
struct sigaction g_previous_sigill;
struct sigaction g_previous_sigtrap;
Expand All @@ -105,10 +113,6 @@ struct sigaction g_previous_sigsegv;
struct sigaction g_previous_sigint;
struct sigaction g_previous_sigquit;

#ifdef INJECT_ACTIVATION_SIGNAL
struct sigaction g_previous_activation;
#endif

// Offset of the local variable containing pointer to windows style context in the common_signal_handler function.
// This offset is relative to the frame pointer.
int g_common_signal_handler_context_locvar_offset = 0;
Expand Down Expand Up @@ -174,9 +178,6 @@ BOOL SEHInitializeSignals(CorUnix::CPalThread *pthrCurrent, DWORD flags)
handle_signal(SIGINT, sigint_handler, &g_previous_sigint, 0 /* additionalFlags */, true /* skipIgnored */);
handle_signal(SIGQUIT, sigquit_handler, &g_previous_sigquit, 0 /* additionalFlags */, true /* skipIgnored */);

#ifdef INJECT_ACTIVATION_SIGNAL
handle_signal(INJECT_ACTIVATION_SIGNAL, inject_activation_handler, &g_previous_activation);
#endif
if (!pthrCurrent->EnsureSignalAlternateStack())
{
return FALSE;
Expand Down Expand Up @@ -224,6 +225,11 @@ BOOL SEHInitializeSignals(CorUnix::CPalThread *pthrCurrent, DWORD flags)
handle_signal(SIGTERM, sigterm_handler, &g_previous_sigterm);
}

#ifdef INJECT_ACTIVATION_SIGNAL
handle_signal(INJECT_ACTIVATION_SIGNAL, inject_activation_handler, &g_previous_activation);
g_registered_activation_handler = true;
#endif

return TRUE;
}

Expand Down Expand Up @@ -257,11 +263,15 @@ void SEHCleanupSignals()
restore_signal(SIGSEGV, &g_previous_sigsegv);
restore_signal(SIGINT, &g_previous_sigint);
restore_signal(SIGQUIT, &g_previous_sigquit);
}
#endif // !HAVE_MACH_EXCEPTIONS

#ifdef INJECT_ACTIVATION_SIGNAL
if (g_registered_activation_handler)
{
restore_signal(INJECT_ACTIVATION_SIGNAL, &g_previous_activation);
#endif
}
#endif // !HAVE_MACH_EXCEPTIONS
#endif

if (g_registered_sigterm_handler)
{
Expand Down Expand Up @@ -686,7 +696,6 @@ static void sigterm_handler(int code, siginfo_t *siginfo, void *context)
}
}

#if !HAVE_MACH_EXCEPTIONS
#ifdef INJECT_ACTIVATION_SIGNAL
/*++
Function :
Expand All @@ -703,7 +712,13 @@ Parameters :
static void inject_activation_handler(int code, siginfo_t *siginfo, void *context)
{
// Only accept activations from the current process
if (g_activationFunction != NULL && siginfo->si_pid == getpid())
if (g_activationFunction != NULL && (siginfo->si_pid == getpid()
#ifdef HOST_OSX
// On OSX si_pid is sometimes 0. It was confirmed by Apple to be expected, as the si_pid is tracked at the process level. So when multiple
// signals are in flight in the same process at the same time, it may be overwritten / zeroed.
|| siginfo->si_pid == 0
#endif
))
{
_ASSERTE(g_safeActivationCheckFunction != NULL);

Expand All @@ -713,7 +728,7 @@ static void inject_activation_handler(int code, siginfo_t *siginfo, void *contex
CONTEXTFromNativeContext(
ucontext,
&winContext,
CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT);
CONTEXT_CONTROL | CONTEXT_INTEGER);

if (g_safeActivationCheckFunction(CONTEXTGetPC(&winContext), /* checkingCurrentThread */ TRUE))
{
Expand Down Expand Up @@ -779,6 +794,8 @@ PAL_ERROR InjectActivationInternal(CorUnix::CPalThread* pThread)
#endif
}

#if !HAVE_MACH_EXCEPTIONS

/*++
Function :
signal_ignore_handler
Expand Down
Loading