diff options
author | Brian Smith <brian@dbsoft.org> | 2022-04-26 11:07:23 -0500 |
---|---|---|
committer | Brian Smith <brian@dbsoft.org> | 2022-04-26 11:07:23 -0500 |
commit | 9e2a89c71ddf67975da35eb100673f6b5546f292 (patch) | |
tree | ca7b972015f5ad2388d416fa0ec8f506cc9fb8d6 /js | |
parent | 68d1c14bdf79d1c536ae05a4ce33fd9601c277eb (diff) | |
download | uxp-9e2a89c71ddf67975da35eb100673f6b5546f292.tar.gz |
Issue #1829 - Revert "Issue #1751 -- Remove XP_DARWIN"
This reverts commit 3d671e4275c73a1484c72713304c6e04ec4ffc7c.
Diffstat (limited to 'js')
-rw-r--r-- | js/src/ds/MemoryProtectionExceptionHandler.cpp | 466 | ||||
-rw-r--r-- | js/src/jsmath.cpp | 2 | ||||
-rw-r--r-- | js/src/jsnativestack.cpp | 7 | ||||
-rw-r--r-- | js/src/jsstr.cpp | 2 | ||||
-rw-r--r-- | js/src/threading/posix/Thread.cpp | 4 | ||||
-rw-r--r-- | js/src/vm/Runtime.cpp | 6 | ||||
-rw-r--r-- | js/src/vm/Runtime.h | 7 | ||||
-rw-r--r-- | js/src/wasm/WasmSignalHandlers.cpp | 367 | ||||
-rw-r--r-- | js/src/wasm/WasmSignalHandlers.h | 29 |
9 files changed, 877 insertions, 13 deletions
diff --git a/js/src/ds/MemoryProtectionExceptionHandler.cpp b/js/src/ds/MemoryProtectionExceptionHandler.cpp index 881a0e63d5..e617556182 100644 --- a/js/src/ds/MemoryProtectionExceptionHandler.cpp +++ b/js/src/ds/MemoryProtectionExceptionHandler.cpp @@ -10,10 +10,13 @@ #if defined(XP_WIN) # include "jswin.h" -#elif defined(XP_UNIX) +#elif defined(XP_UNIX) && !defined(XP_DARWIN) # include <signal.h> # include <sys/types.h> # include <unistd.h> +#elif defined(XP_DARWIN) +# include <mach/mach.h> +# include <unistd.h> #endif #include "ds/SplayTree.h" @@ -201,7 +204,7 @@ MemoryProtectionExceptionHandler::uninstall() } } -#elif defined(XP_UNIX) +#elif defined(XP_UNIX) && !defined(XP_DARWIN) static struct sigaction sPrevSEGVHandler = {}; @@ -282,6 +285,465 @@ MemoryProtectionExceptionHandler::uninstall() } } +#elif defined(XP_DARWIN) + +/* + * The fact that we need to be able to forward to other exception handlers + * makes this code much more complicated. The forwarding logic and the + * structures required to make it work are heavily based on the code at + * www.ravenbrook.com/project/mps/prototype/2013-06-24/machtest/machtest/main.c + */ + +/* -------------------------------------------------------------------------- */ +/* Begin Mach definitions and helper functions */ +/* -------------------------------------------------------------------------- */ + +/* + * These are the message IDs associated with each exception type. + * We'll be using sIDRequest64, but we need the others for forwarding. + */ +static const mach_msg_id_t sIDRequest32 = 2401; +static const mach_msg_id_t sIDRequestState32 = 2402; +static const mach_msg_id_t sIDRequestStateIdentity32 = 2403; + +static const mach_msg_id_t sIDRequest64 = 2405; +static const mach_msg_id_t sIDRequestState64 = 2406; +static const mach_msg_id_t sIDRequestStateIdentity64 = 2407; + +/* + * Each message ID has an associated Mach message structure. + * We use the preprocessor to make defining them a little less arduous. + */ +# define REQUEST_HEADER_FIELDS\ + mach_msg_header_t header; + +# define REQUEST_IDENTITY_FIELDS\ + mach_msg_body_t msgh_body;\ + mach_msg_port_descriptor_t thread;\ + mach_msg_port_descriptor_t task; + +# define REQUEST_GENERAL_FIELDS(bits)\ + NDR_record_t NDR;\ + exception_type_t exception;\ + mach_msg_type_number_t code_count;\ + int##bits##_t code[2]; + +# define REQUEST_STATE_FIELDS\ + int flavor;\ + mach_msg_type_number_t old_state_count;\ + natural_t old_state[THREAD_STATE_MAX]; + +# define REQUEST_TRAILER_FIELDS\ + mach_msg_trailer_t trailer; + +# define EXCEPTION_REQUEST(bits)\ + struct ExceptionRequest##bits\ + {\ + REQUEST_HEADER_FIELDS\ + REQUEST_IDENTITY_FIELDS\ + REQUEST_GENERAL_FIELDS(bits)\ + REQUEST_TRAILER_FIELDS\ + };\ + +# define EXCEPTION_REQUEST_STATE(bits)\ + struct ExceptionRequestState##bits\ + {\ + REQUEST_HEADER_FIELDS\ + REQUEST_GENERAL_FIELDS(bits)\ + REQUEST_STATE_FIELDS\ + REQUEST_TRAILER_FIELDS\ + };\ + +# define EXCEPTION_REQUEST_STATE_IDENTITY(bits)\ + struct ExceptionRequestStateIdentity##bits\ + {\ + REQUEST_HEADER_FIELDS\ + REQUEST_IDENTITY_FIELDS\ + REQUEST_GENERAL_FIELDS(bits)\ + REQUEST_STATE_FIELDS\ + REQUEST_TRAILER_FIELDS\ + };\ + +/* This is needed because not all fields are naturally aligned on 64-bit. */ +# ifdef __MigPackStructs +# pragma pack(4) +# endif + +EXCEPTION_REQUEST(32) +EXCEPTION_REQUEST(64) +EXCEPTION_REQUEST_STATE(32) +EXCEPTION_REQUEST_STATE(64) +EXCEPTION_REQUEST_STATE_IDENTITY(32) +EXCEPTION_REQUEST_STATE_IDENTITY(64) + +/* We use this as a common base when forwarding to the previous handler. */ +union ExceptionRequestUnion { + mach_msg_header_t header; + ExceptionRequest32 r32; + ExceptionRequest64 r64; + ExceptionRequestState32 rs32; + ExceptionRequestState64 rs64; + ExceptionRequestStateIdentity32 rsi32; + ExceptionRequestStateIdentity64 rsi64; +}; + +/* This isn't really a full Mach message, but it's all we need to send. */ +struct ExceptionReply +{ + mach_msg_header_t header; + NDR_record_t NDR; + kern_return_t RetCode; +}; + +# ifdef __MigPackStructs +# pragma pack() +# endif + +# undef EXCEPTION_REQUEST_STATE_IDENTITY +# undef EXCEPTION_REQUEST_STATE +# undef EXCEPTION_REQUEST +# undef REQUEST_STATE_FIELDS +# undef REQUEST_GENERAL_FIELDS +# undef REQUEST_IDENTITY_FIELDS +# undef REQUEST_HEADER_FIELDS + +/* + * The exception handler we're forwarding to may not have the same behavior + * or thread state flavor as what we're using. These macros help populate + * the fields of the message we're about to send to the previous handler. + */ +# define COPY_REQUEST_COMMON(bits, id)\ + dst.header = src.header;\ + dst.header.msgh_id = id;\ + dst.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer));\ + dst.NDR = src.NDR;\ + dst.exception = src.exception;\ + dst.code_count = src.code_count;\ + dst.code[0] = int##bits##_t(src.code[0]);\ + dst.code[1] = int##bits##_t(src.code[1]); + +# define COPY_REQUEST_IDENTITY\ + dst.msgh_body = src.msgh_body;\ + dst.thread = src.thread;\ + dst.task = src.task; + +# define COPY_REQUEST_STATE(flavor, stateCount, state)\ + mach_msg_size_t stateSize = stateCount * sizeof(natural_t);\ + dst.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer) -\ + sizeof(dst.old_state) + stateSize);\ + dst.flavor = flavor;\ + dst.old_state_count = stateCount;\ + memcpy(dst.old_state, state, stateSize); + +# define COPY_EXCEPTION_REQUEST(bits)\ + static void\ + CopyExceptionRequest##bits(ExceptionRequest64& src,\ + ExceptionRequest##bits& dst)\ + {\ + COPY_REQUEST_COMMON(bits, sIDRequest##bits)\ + COPY_REQUEST_IDENTITY\ + } + +# define COPY_EXCEPTION_REQUEST_STATE(bits)\ + static void\ + CopyExceptionRequestState##bits(ExceptionRequest64& src,\ + ExceptionRequestState##bits& dst,\ + thread_state_flavor_t flavor,\ + mach_msg_type_number_t stateCount,\ + thread_state_t state)\ + {\ + COPY_REQUEST_COMMON(bits, sIDRequestState##bits)\ + COPY_REQUEST_STATE(flavor, stateCount, state)\ + } + +# define COPY_EXCEPTION_REQUEST_STATE_IDENTITY(bits)\ + static void\ + CopyExceptionRequestStateIdentity##bits(ExceptionRequest64& src,\ + ExceptionRequestStateIdentity##bits& dst,\ + thread_state_flavor_t flavor,\ + mach_msg_type_number_t stateCount,\ + thread_state_t state)\ + {\ + COPY_REQUEST_COMMON(bits, sIDRequestStateIdentity##bits)\ + COPY_REQUEST_IDENTITY\ + COPY_REQUEST_STATE(flavor, stateCount, state)\ + } + +COPY_EXCEPTION_REQUEST(32) +COPY_EXCEPTION_REQUEST_STATE(32) +COPY_EXCEPTION_REQUEST_STATE_IDENTITY(32) +COPY_EXCEPTION_REQUEST(64) +COPY_EXCEPTION_REQUEST_STATE(64) +COPY_EXCEPTION_REQUEST_STATE_IDENTITY(64) + +# undef COPY_EXCEPTION_REQUEST_STATE_IDENTITY +# undef COPY_EXCEPTION_REQUEST_STATE +# undef COPY_EXCEPTION_REQUEST +# undef COPY_REQUEST_STATE +# undef COPY_REQUEST_IDENTITY +# undef COPY_REQUEST_COMMON + +/* -------------------------------------------------------------------------- */ +/* End Mach definitions and helper functions */ +/* -------------------------------------------------------------------------- */ + +/* Every Mach exception handler is parameterized by these four properties. */ +struct MachExceptionParameters +{ + exception_mask_t mask; + mach_port_t port; + exception_behavior_t behavior; + thread_state_flavor_t flavor; +}; + +struct ExceptionHandlerState +{ + MachExceptionParameters current; + MachExceptionParameters previous; + + /* Each Mach exception handler runs in its own thread. */ + Thread handlerThread; +}; + +/* This choice of ID is arbitrary, but must not match our exception ID. */ +static const mach_msg_id_t sIDQuit = 42; + +static ExceptionHandlerState sMachExceptionState; + +/* + * The meat of our exception handler. This thread waits for an exception + * message, annotates the exception if needed, then forwards it to the + * previously installed handler (which will likely terminate the process). + */ +static void +MachExceptionHandler() +{ + kern_return_t ret; + MachExceptionParameters& current = sMachExceptionState.current; + MachExceptionParameters& previous = sMachExceptionState.previous; + + // We use the simplest kind of 64-bit exception message here. + ExceptionRequest64 request = {}; + request.header.msgh_local_port = current.port; + request.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(request)); + ret = mach_msg(&request.header, MACH_RCV_MSG, 0, request.header.msgh_size, + current.port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + + // Restore the previous handler. We're going to forward to it + // anyway, and if we crash while doing so we don't want to hang. + task_set_exception_ports(mach_task_self(), previous.mask, previous.port, + previous.behavior, previous.flavor); + + // If we failed even receiving the message, just give up. + if (ret != MACH_MSG_SUCCESS) + MOZ_CRASH("MachExceptionHandler: mach_msg failed to receive a message!"); + + // Terminate the thread if we're shutting down. + if (request.header.msgh_id == sIDQuit) + return; + + // The only other valid message ID is the one associated with the + // EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES behavior we chose. + if (request.header.msgh_id != sIDRequest64) + MOZ_CRASH("MachExceptionHandler: Unexpected Message ID!"); + + // Make sure we can understand the exception we received. + if (request.exception != EXC_BAD_ACCESS || request.code_count != 2) + MOZ_CRASH("MachExceptionHandler: Unexpected exception type!"); + + // Get the address that the offending code tried to access. + uintptr_t address = uintptr_t(request.code[1]); + + // If the faulting address is inside one of our protected regions, we + // want to annotate the crash to make it stand out from the crowd. + if (sProtectedRegions.isProtected(address)) { + ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n"); + MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)"); + } + + // Forward to the previous handler which may be a debugger, the unix + // signal handler, the crash reporter or something else entirely. + if (previous.port != MACH_PORT_NULL) { + mach_msg_type_number_t stateCount; + thread_state_data_t state; + if ((uint32_t(previous.behavior) & ~MACH_EXCEPTION_CODES) != EXCEPTION_DEFAULT) { + // If the previous handler requested thread state, get it here. + stateCount = THREAD_STATE_MAX; + ret = thread_get_state(request.thread.name, previous.flavor, state, &stateCount); + if (ret != KERN_SUCCESS) + MOZ_CRASH("MachExceptionHandler: Could not get the thread state to forward!"); + } + + // Depending on the behavior of the previous handler, the forwarded + // exception message will have a different set of fields. + // Of particular note is that exception handlers that lack + // MACH_EXCEPTION_CODES will get 32-bit fields even on 64-bit + // systems. It appears that OSX simply truncates these fields. + ExceptionRequestUnion forward; + switch (uint32_t(previous.behavior)) { + case EXCEPTION_DEFAULT: + CopyExceptionRequest32(request, forward.r32); + break; + case EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES: + CopyExceptionRequest64(request, forward.r64); + break; + case EXCEPTION_STATE: + CopyExceptionRequestState32(request, forward.rs32, + previous.flavor, stateCount, state); + break; + case EXCEPTION_STATE | MACH_EXCEPTION_CODES: + CopyExceptionRequestState64(request, forward.rs64, + previous.flavor, stateCount, state); + break; + case EXCEPTION_STATE_IDENTITY: + CopyExceptionRequestStateIdentity32(request, forward.rsi32, + previous.flavor, stateCount, state); + break; + case EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES: + CopyExceptionRequestStateIdentity64(request, forward.rsi64, + previous.flavor, stateCount, state); + break; + default: + MOZ_CRASH("MachExceptionHandler: Unknown previous handler behavior!"); + } + + // Forward the generated message to the old port. The local and remote + // port fields *and their rights* are swapped on arrival, so we need to + // swap them back first. + forward.header.msgh_bits = (request.header.msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) | + MACH_MSGH_BITS(MACH_MSGH_BITS_LOCAL(request.header.msgh_bits), + MACH_MSGH_BITS_REMOTE(request.header.msgh_bits)); + forward.header.msgh_local_port = forward.header.msgh_remote_port; + forward.header.msgh_remote_port = previous.port; + ret = mach_msg(&forward.header, MACH_SEND_MSG, forward.header.msgh_size, 0, + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (ret != MACH_MSG_SUCCESS) + MOZ_CRASH("MachExceptionHandler: Failed to forward to the previous handler!"); + } else { + // There was no previous task-level exception handler, so defer to the + // host level one instead. We set the return code to KERN_FAILURE to + // indicate that we did not handle the exception. + // The reply message ID is always the request ID + 100. + ExceptionReply reply = {}; + reply.header.msgh_bits = + MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.header.msgh_bits), 0); + reply.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(reply)); + reply.header.msgh_remote_port = request.header.msgh_remote_port; + reply.header.msgh_local_port = MACH_PORT_NULL; + reply.header.msgh_id = request.header.msgh_id + 100; + reply.NDR = request.NDR; + reply.RetCode = KERN_FAILURE; + ret = mach_msg(&reply.header, MACH_SEND_MSG, reply.header.msgh_size, 0, + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (ret != MACH_MSG_SUCCESS) + MOZ_CRASH("MachExceptionHandler: Failed to forward to the host level!"); + } +} + +static void +TerminateMachExceptionHandlerThread() +{ + // Send a simple quit message to the exception handler thread. + mach_msg_header_t msg; + msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); + msg.msgh_size = static_cast<mach_msg_size_t>(sizeof(msg)); + msg.msgh_remote_port = sMachExceptionState.current.port; + msg.msgh_local_port = MACH_PORT_NULL; + msg.msgh_reserved = 0; + msg.msgh_id = sIDQuit; + kern_return_t ret = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + + if (ret == MACH_MSG_SUCCESS) + sMachExceptionState.handlerThread.join(); + else + MOZ_CRASH("MachExceptionHandler: Handler thread failed to terminate!"); +} + +bool +MemoryProtectionExceptionHandler::install() +{ + MOZ_ASSERT(!sExceptionHandlerInstalled); + + // If the exception handler is disabled, report success anyway. + if (MemoryProtectionExceptionHandler::isDisabled()) + return true; + + kern_return_t ret; + mach_port_t task = mach_task_self(); + + // Allocate a new exception port with receive rights. + sMachExceptionState.current = {}; + MachExceptionParameters& current = sMachExceptionState.current; + ret = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, ¤t.port); + if (ret != KERN_SUCCESS) + return false; + + // Give the new port send rights as well. + ret = mach_port_insert_right(task, current.port, current.port, MACH_MSG_TYPE_MAKE_SEND); + if (ret != KERN_SUCCESS) { + mach_port_deallocate(task, current.port); + current = {}; + return false; + } + + // Start the thread that will receive the messages from our exception port. + if (!sMachExceptionState.handlerThread.init(MachExceptionHandler)) { + mach_port_deallocate(task, current.port); + current = {}; + return false; + } + + // Set the other properties of our new exception handler. + current.mask = EXC_MASK_BAD_ACCESS; + current.behavior = exception_behavior_t(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES); + current.flavor = THREAD_STATE_NONE; + + // Tell the task to use our exception handler, and save the previous one. + sMachExceptionState.previous = {}; + MachExceptionParameters& previous = sMachExceptionState.previous; + mach_msg_type_number_t previousCount = 1; + ret = task_swap_exception_ports(task, current.mask, current.port, current.behavior, + current.flavor, &previous.mask, &previousCount, + &previous.port, &previous.behavior, &previous.flavor); + if (ret != KERN_SUCCESS) { + TerminateMachExceptionHandlerThread(); + mach_port_deallocate(task, current.port); + previous = {}; + current = {}; + return false; + } + + // We should have info on the previous exception handler, even if it's null. + MOZ_ASSERT(previousCount == 1); + + sExceptionHandlerInstalled = true; + return sExceptionHandlerInstalled; +} + +void +MemoryProtectionExceptionHandler::uninstall() +{ + if (sExceptionHandlerInstalled) { + mach_port_t task = mach_task_self(); + + // Restore the previous exception handler. + MachExceptionParameters& previous = sMachExceptionState.previous; + task_set_exception_ports(task, previous.mask, previous.port, + previous.behavior, previous.flavor); + + TerminateMachExceptionHandlerThread(); + + // Release the Mach IPC port we used. + mach_port_deallocate(task, sMachExceptionState.current.port); + + sMachExceptionState.current = {}; + sMachExceptionState.previous = {}; + + sExceptionHandlerInstalled = false; + } +} + #else #error "This platform is not supported!" diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 64c2da8122..75ab8fcb29 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -49,7 +49,7 @@ # undef SystemFunction036 #endif -#if defined(__DragonFly__) || \ +#if defined(XP_DARWIN) || defined(__DragonFly__) || \ defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) # include <stdlib.h> # define HAVE_ARC4RANDOM diff --git a/js/src/jsnativestack.cpp b/js/src/jsnativestack.cpp index 34131f9667..42c84b2a42 100644 --- a/js/src/jsnativestack.cpp +++ b/js/src/jsnativestack.cpp @@ -8,7 +8,7 @@ #ifdef XP_WIN # include "jswin.h" -#elif defined(XP_UNIX) +#elif defined(XP_DARWIN) || defined(DARWIN) || defined(XP_UNIX) # include <pthread.h> # if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) @@ -127,6 +127,10 @@ void* js::GetNativeStackBaseImpl() { pthread_t thread = pthread_self(); +# if defined(XP_DARWIN) || defined(DARWIN) + return pthread_get_stackaddr_np(thread); + +# else pthread_attr_t sattr; pthread_attr_init(&sattr); # if defined(__OpenBSD__) @@ -166,6 +170,7 @@ js::GetNativeStackBaseImpl() # else return static_cast<char*>(stackBase) + stackSize; # endif +# endif } #endif /* !XP_WIN */ diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index b44fbdebca..6eb11b1cc1 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -1177,7 +1177,7 @@ FirstCharMatcher8bit(const char* text, uint32_t n, const char pat) static const char16_t* FirstCharMatcher16bit(const char16_t* text, uint32_t n, const char16_t pat) { -#if defined(XP_WIN) +#if defined(XP_DARWIN) || defined(XP_WIN) /* * Performance of memchr is horrible in OSX. Windows is better, * but it is still better to use UnrolledMatcher. diff --git a/js/src/threading/posix/Thread.cpp b/js/src/threading/posix/Thread.cpp index a5c2d2eacb..faca05c24d 100644 --- a/js/src/threading/posix/Thread.cpp +++ b/js/src/threading/posix/Thread.cpp @@ -151,7 +151,9 @@ js::ThisThread::SetName(const char* name) #endif int rv; -#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#ifdef XP_DARWIN + rv = pthread_setname_np(name); +#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); rv = 0; #elif defined(__NetBSD__) diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 23c50814fc..251c8258cf 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -10,12 +10,14 @@ #include "mozilla/ThreadLocal.h" #include "mozilla/Unused.h" -#if defined(XP_UNIX) +#if defined(XP_DARWIN) +#include <mach/mach.h> +#elif defined(XP_UNIX) #include <sys/resource.h> #elif defined(XP_WIN) #include <processthreadsapi.h> #include <windows.h> -#endif // defined(XP_UNIX) || defined(XP_WIN) +#endif // defined(XP_DARWIN) || defined(XP_UNIX) || defined(XP_WIN) #include <locale.h> #include <string.h> diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index eefc90cb4c..f1f2e07094 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -21,6 +21,9 @@ #include "jsclist.h" #include "jsscript.h" +#ifdef XP_DARWIN +# include "wasm/WasmSignalHandlers.h" +#endif #include "builtin/AtomicsObject.h" #include "builtin/Intl.h" #include "builtin/Promise.h" @@ -929,6 +932,10 @@ struct JSRuntime : public JS::shadow::Runtime, */ JSCList onNewGlobalObjectWatchers; +#if defined(XP_DARWIN) + js::wasm::MachExceptionHandler wasmMachExceptionHandler; +#endif + private: js::FreeOp* defaultFreeOp_; diff --git a/js/src/wasm/WasmSignalHandlers.cpp b/js/src/wasm/WasmSignalHandlers.cpp index dae590acba..7699b336cc 100644 --- a/js/src/wasm/WasmSignalHandlers.cpp +++ b/js/src/wasm/WasmSignalHandlers.cpp @@ -1,7 +1,6 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright 2014 Mozilla Foundation - * Copyright 2021 Moonchild Productions * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -229,6 +228,10 @@ class AutoSetHandlingSegFault # define EPC_sig(p) ((p)->uc_mcontext.mc_pc) # define RFP_sig(p) ((p)->uc_mcontext.mc_regs[30]) # endif +#elif defined(XP_DARWIN) +# define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip) +# define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip) +# define R15_sig(p) ((p)->uc_mcontext->__ss.__pc) #else # error "Don't know how to read/write to the thread state via the mcontext_t." #endif @@ -258,8 +261,34 @@ class AutoSetHandlingSegFault #endif // Define a context type for use in the emulator code. This is usually just -// the same as CONTEXT. +// the same as CONTEXT, but on Mac we use a different structure since we call +// into the emulator code from a Mach exception handler rather than a +// sigaction-style signal handler. +#if defined(XP_DARWIN) +# if defined(JS_CPU_X64) +struct macos_x64_context { + x86_thread_state64_t thread; + x86_float_state64_t float_; +}; +# define EMULATOR_CONTEXT macos_x64_context +# elif defined(JS_CPU_X86) +struct macos_x86_context { + x86_thread_state_t thread; + x86_float_state_t float_; +}; +# define EMULATOR_CONTEXT macos_x86_context +# elif defined(JS_CPU_ARM) +struct macos_arm_context { + arm_thread_state_t thread; + arm_neon_state_t float_; +}; +# define EMULATOR_CONTEXT macos_arm_context +# else +# error Unsupported architecture +# endif +#else # define EMULATOR_CONTEXT CONTEXT +#endif #if defined(JS_CPU_X64) # define PC_sig(p) RIP_sig(p) @@ -351,6 +380,7 @@ StoreValueFromGPImm(SharedMem<void*> addr, size_t size, int32_t imm) AtomicOperations::memcpySafeWhenRacy(addr, static_cast<void*>(&imm), size); } +# if !defined(XP_DARWIN) MOZ_COLD static void* AddressOfFPRegisterSlot(CONTEXT* context, FloatRegisters::Encoding encoding) { @@ -400,6 +430,57 @@ AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code) } MOZ_CRASH(); } +# else +MOZ_COLD static void* +AddressOfFPRegisterSlot(EMULATOR_CONTEXT* context, FloatRegisters::Encoding encoding) +{ + switch (encoding) { + case X86Encoding::xmm0: return &context->float_.__fpu_xmm0; + case X86Encoding::xmm1: return &context->float_.__fpu_xmm1; + case X86Encoding::xmm2: return &context->float_.__fpu_xmm2; + case X86Encoding::xmm3: return &context->float_.__fpu_xmm3; + case X86Encoding::xmm4: return &context->float_.__fpu_xmm4; + case X86Encoding::xmm5: return &context->float_.__fpu_xmm5; + case X86Encoding::xmm6: return &context->float_.__fpu_xmm6; + case X86Encoding::xmm7: return &context->float_.__fpu_xmm7; + case X86Encoding::xmm8: return &context->float_.__fpu_xmm8; + case X86Encoding::xmm9: return &context->float_.__fpu_xmm9; + case X86Encoding::xmm10: return &context->float_.__fpu_xmm10; + case X86Encoding::xmm11: return &context->float_.__fpu_xmm11; + case X86Encoding::xmm12: return &context->float_.__fpu_xmm12; + case X86Encoding::xmm13: return &context->float_.__fpu_xmm13; + case X86Encoding::xmm14: return &context->float_.__fpu_xmm14; + case X86Encoding::xmm15: return &context->float_.__fpu_xmm15; + default: break; + } + MOZ_CRASH(); +} + +MOZ_COLD static void* +AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code) +{ + switch (code) { + case X86Encoding::rax: return &context->thread.__rax; + case X86Encoding::rcx: return &context->thread.__rcx; + case X86Encoding::rdx: return &context->thread.__rdx; + case X86Encoding::rbx: return &context->thread.__rbx; + case X86Encoding::rsp: return &context->thread.__rsp; + case X86Encoding::rbp: return &context->thread.__rbp; + case X86Encoding::rsi: return &context->thread.__rsi; + case X86Encoding::rdi: return &context->thread.__rdi; + case X86Encoding::r8: return &context->thread.__r8; + case X86Encoding::r9: return &context->thread.__r9; + case X86Encoding::r10: return &context->thread.__r10; + case X86Encoding::r11: return &context->thread.__r11; + case X86Encoding::r12: return &context->thread.__r12; + case X86Encoding::r13: return &context->thread.__r13; + case X86Encoding::r14: return &context->thread.__r14; + case X86Encoding::r15: return &context->thread.__r15; + default: break; + } + MOZ_CRASH(); +} +# endif // !XP_DARWIN MOZ_COLD static void SetRegisterToCoercedUndefined(EMULATOR_CONTEXT* context, size_t size, @@ -705,7 +786,276 @@ WasmFaultHandler(LPEXCEPTION_POINTERS exception) return EXCEPTION_CONTINUE_SEARCH; } -#else // If not Windows, assume Unix-like +#elif defined(XP_DARWIN) +# include <mach/exc.h> + +static uint8_t** +ContextToPC(EMULATOR_CONTEXT* context) +{ +# if defined(JS_CPU_X64) + static_assert(sizeof(context->thread.__rip) == sizeof(void*), + "stored IP should be compile-time pointer-sized"); + return reinterpret_cast<uint8_t**>(&context->thread.__rip); +# elif defined(JS_CPU_X86) + static_assert(sizeof(context->thread.uts.ts32.__eip) == sizeof(void*), + "stored IP should be compile-time pointer-sized"); + return reinterpret_cast<uint8_t**>(&context->thread.uts.ts32.__eip); +# elif defined(JS_CPU_ARM) + static_assert(sizeof(context->thread.__pc) == sizeof(void*), + "stored IP should be compile-time pointer-sized"); + return reinterpret_cast<uint8_t**>(&context->thread.__pc); +# else +# error Unsupported architecture +# endif +} + +// This definition was generated by mig (the Mach Interface Generator) for the +// routine 'exception_raise' (exc.defs). +#pragma pack(4) +typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + int64_t code[2]; +} Request__mach_exception_raise_t; +#pragma pack() + +// The full Mach message also includes a trailer. +struct ExceptionRequest +{ + Request__mach_exception_raise_t body; + mach_msg_trailer_t trailer; +}; + +static bool +HandleMachException(JSRuntime* rt, const ExceptionRequest& request) +{ + // Don't allow recursive handling of signals, see AutoSetHandlingSegFault. + if (rt->handlingSegFault) + return false; + AutoSetHandlingSegFault handling(rt); + + // Get the port of the JSRuntime's thread from the message. + mach_port_t rtThread = request.body.thread.name; + + // Read out the JSRuntime thread's register state. + EMULATOR_CONTEXT context; +# if defined(JS_CPU_X64) + unsigned int thread_state_count = x86_THREAD_STATE64_COUNT; + unsigned int float_state_count = x86_FLOAT_STATE64_COUNT; + int thread_state = x86_THREAD_STATE64; + int float_state = x86_FLOAT_STATE64; +# elif defined(JS_CPU_X86) + unsigned int thread_state_count = x86_THREAD_STATE_COUNT; + unsigned int float_state_count = x86_FLOAT_STATE_COUNT; + int thread_state = x86_THREAD_STATE; + int float_state = x86_FLOAT_STATE; +# elif defined(JS_CPU_ARM) + unsigned int thread_state_count = ARM_THREAD_STATE_COUNT; + unsigned int float_state_count = ARM_NEON_STATE_COUNT; + int thread_state = ARM_THREAD_STATE; + int float_state = ARM_NEON_STATE; +# else +# error Unsupported architecture +# endif + kern_return_t kret; + kret = thread_get_state(rtThread, thread_state, + (thread_state_t)&context.thread, &thread_state_count); + if (kret != KERN_SUCCESS) + return false; + kret = thread_get_state(rtThread, float_state, + (thread_state_t)&context.float_, &float_state_count); + if (kret != KERN_SUCCESS) + return false; + + uint8_t** ppc = ContextToPC(&context); + uint8_t* pc = *ppc; + + if (request.body.exception != EXC_BAD_ACCESS || request.body.codeCnt != 2) + return false; + + WasmActivation* activation = rt->wasmActivationStack(); + if (!activation) + return false; + + const Instance* instance = activation->compartment()->wasm.lookupInstanceDeprecated(pc); + if (!instance || !instance->codeSegment().containsFunctionPC(pc)) + return false; + + uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(request.body.code[1]); + + // This check isn't necessary, but, since we can, check anyway to make + // sure we aren't covering up a real bug. + if (!IsHeapAccessAddress(*instance, faultingAddress)) + return false; + + HandleMemoryAccess(&context, pc, faultingAddress, *instance, ppc); + + // Update the thread state with the new pc and register values. + kret = thread_set_state(rtThread, float_state, (thread_state_t)&context.float_, float_state_count); + if (kret != KERN_SUCCESS) + return false; + kret = thread_set_state(rtThread, thread_state, (thread_state_t)&context.thread, thread_state_count); + if (kret != KERN_SUCCESS) + return false; + + return true; +} + +// Taken from mach_exc in /usr/include/mach/mach_exc.defs. +static const mach_msg_id_t sExceptionId = 2405; + +// The choice of id here is arbitrary, the only constraint is that sQuitId != sExceptionId. +static const mach_msg_id_t sQuitId = 42; + +static void +MachExceptionHandlerThread(JSRuntime* rt) +{ + mach_port_t port = rt->wasmMachExceptionHandler.port(); + kern_return_t kret; + + while(true) { + ExceptionRequest request; + kret = mach_msg(&request.body.Head, MACH_RCV_MSG, 0, sizeof(request), + port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + + // If we fail even receiving the message, we can't even send a reply! + // Rather than hanging the faulting thread (hanging the browser), crash. + if (kret != KERN_SUCCESS) { + fprintf(stderr, "MachExceptionHandlerThread: mach_msg failed with %d\n", (int)kret); + MOZ_CRASH(); + } + + // There are only two messages we should be receiving: an exception + // message that occurs when the runtime's thread faults and the quit + // message sent when the runtime is shutting down. + if (request.body.Head.msgh_id == sQuitId) + break; + if (request.body.Head.msgh_id != sExceptionId) { + fprintf(stderr, "Unexpected msg header id %d\n", (int)request.body.Head.msgh_bits); + MOZ_CRASH(); + } + + // Some thread just commited an EXC_BAD_ACCESS and has been suspended by + // the kernel. The kernel is waiting for us to reply with instructions. + // Our default is the "not handled" reply (by setting the RetCode field + // of the reply to KERN_FAILURE) which tells the kernel to continue + // searching at the process and system level. If this is an asm.js + // expected exception, we handle it and return KERN_SUCCESS. + bool handled = HandleMachException(rt, request); + kern_return_t replyCode = handled ? KERN_SUCCESS : KERN_FAILURE; + + // This magic incantation to send a reply back to the kernel was derived + // from the exc_server generated by 'mig -v /usr/include/mach/mach_exc.defs'. + __Reply__exception_raise_t reply; + reply.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.body.Head.msgh_bits), 0); + reply.Head.msgh_size = sizeof(reply); + reply.Head.msgh_remote_port = request.body.Head.msgh_remote_port; + reply.Head.msgh_local_port = MACH_PORT_NULL; + reply.Head.msgh_id = request.body.Head.msgh_id + 100; + reply.NDR = NDR_record; + reply.RetCode = replyCode; + mach_msg(&reply.Head, MACH_SEND_MSG, sizeof(reply), 0, MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + } +} + +MachExceptionHandler::MachExceptionHandler() + : installed_(false), + thread_(), + port_(MACH_PORT_NULL) +{} + +void +MachExceptionHandler::uninstall() +{ + if (installed_) { + thread_port_t thread = mach_thread_self(); + kern_return_t kret = thread_set_exception_ports(thread, + EXC_MASK_BAD_ACCESS, + MACH_PORT_NULL, + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, + THREAD_STATE_NONE); + mach_port_deallocate(mach_task_self(), thread); + if (kret != KERN_SUCCESS) + MOZ_CRASH(); + installed_ = false; + } + if (thread_.joinable()) { + // Break the handler thread out of the mach_msg loop. + mach_msg_header_t msg; + msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); + msg.msgh_size = sizeof(msg); + msg.msgh_remote_port = port_; + msg.msgh_local_port = MACH_PORT_NULL; + msg.msgh_reserved = 0; + msg.msgh_id = sQuitId; + kern_return_t kret = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (kret != KERN_SUCCESS) { + fprintf(stderr, "MachExceptionHandler: failed to send quit message: %d\n", (int)kret); + MOZ_CRASH(); + } + + // Wait for the handler thread to complete before deallocating the port. + thread_.join(); + } + if (port_ != MACH_PORT_NULL) { + DebugOnly<kern_return_t> kret = mach_port_destroy(mach_task_self(), port_); + MOZ_ASSERT(kret == KERN_SUCCESS); + port_ = MACH_PORT_NULL; + } +} + +bool +MachExceptionHandler::install(JSRuntime* rt) +{ + MOZ_ASSERT(!installed()); + kern_return_t kret; + mach_port_t thread; + + // Get a port which can send and receive data. + kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port_); + if (kret != KERN_SUCCESS) + goto error; + kret = mach_port_insert_right(mach_task_self(), port_, port_, MACH_MSG_TYPE_MAKE_SEND); + if (kret != KERN_SUCCESS) + goto error; + + // Create a thread to block on reading port_. + if (!thread_.init(MachExceptionHandlerThread, rt)) + goto error; + + // Direct exceptions on this thread to port_ (and thus our handler thread). + // Note: we are totally clobbering any existing *thread* exception ports and + // not even attempting to forward. Breakpad and gdb both use the *process* + // exception ports which are only called if the thread doesn't handle the + // exception, so we should be fine. + thread = mach_thread_self(); + kret = thread_set_exception_ports(thread, + EXC_MASK_BAD_ACCESS, + port_, + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, + THREAD_STATE_NONE); + mach_port_deallocate(mach_task_self(), thread); + if (kret != KERN_SUCCESS) + goto error; + + installed_ = true; + return true; + + error: + uninstall(); + return false; +} + +#else // If not Windows or Mac, assume Unix enum class Signal { SegFault, @@ -809,7 +1159,7 @@ WasmFaultHandler(int signum, siginfo_t* info, void* context) else previousSignal->sa_handler(signum); } -# endif // XP_WIN || assume Unix-like +# endif // XP_WIN || XP_DARWIN || assume unix static void RedirectIonBackedgesToInterruptCheck(JSRuntime* rt) @@ -918,6 +1268,9 @@ ProcessHasSignalHandlers() # if defined(XP_WIN) if (!AddVectoredExceptionHandler(/* FirstHandler = */ true, WasmFaultHandler)) return false; +# elif defined(XP_DARWIN) + // OSX handles seg faults via the Mach exception handler above, so don't + // install WasmFaultHandler. # else // SA_NODEFER allows us to reenter the signal handler if we crash while // handling the signal, and fall through to the Breakpad handler by testing @@ -953,6 +1306,12 @@ wasm::EnsureSignalHandlers(JSRuntime* rt) if (!ProcessHasSignalHandlers()) return true; +#if defined(XP_DARWIN) + // On OSX, each JSRuntime gets its own handler thread. + if (!rt->wasmMachExceptionHandler.installed() && !rt->wasmMachExceptionHandler.install(rt)) + return false; +#endif + return true; } diff --git a/js/src/wasm/WasmSignalHandlers.h b/js/src/wasm/WasmSignalHandlers.h index 7fe3dd86e6..87faa15025 100644 --- a/js/src/wasm/WasmSignalHandlers.h +++ b/js/src/wasm/WasmSignalHandlers.h @@ -1,7 +1,6 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright 2014 Mozilla Foundation - * Copyright 2021 Moonchild Productions * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +19,10 @@ #define wasm_signal_handlers_h #include "mozilla/Attributes.h" + +#if defined(XP_DARWIN) +# include <mach/mach.h> +#endif #include "threading/Thread.h" struct JSRuntime; @@ -43,6 +46,30 @@ EnsureSignalHandlers(JSRuntime* rt); bool HaveSignalHandlers(); +#if defined(XP_DARWIN) +// On OSX we are forced to use the lower-level Mach exception mechanism instead +// of Unix signals. Mach exceptions are not handled on the victim's stack but +// rather require an extra thread. For simplicity, we create one such thread +// per JSRuntime (upon the first use of asm.js in the JSRuntime). This thread +// and related resources are owned by AsmJSMachExceptionHandler which is owned +// by JSRuntime. +class MachExceptionHandler +{ + bool installed_; + js::Thread thread_; + mach_port_t port_; + + void uninstall(); + + public: + MachExceptionHandler(); + ~MachExceptionHandler() { uninstall(); } + mach_port_t port() const { return port_; } + bool installed() const { return installed_; } + bool install(JSRuntime* rt); +}; +#endif + // Test whether the given PC is within the innermost wasm activation. Return // false if it is not, or it cannot be determined. bool IsPCInWasmCode(void *pc); |