☠☠ backed out by 111655db0b16 ☠ ☠ | |
author | Luke Wagner <luke@mozilla.com> |
Fri, 09 Nov 2018 09:44:33 -0600 | |
changeset 445375 | 0abbc0f316d3d1b4c47338ff34e8a2e02e6479f0 |
parent 445374 | dfdd63aae0c53fe1ce5b91bccb0262040cbf90b4 |
child 445376 | 6efc034e158167911c023da1231d90a287c4cb1b |
push id | 109731 |
push user | lwagner@mozilla.com |
push date | Fri, 09 Nov 2018 15:46:27 +0000 |
treeherder | mozilla-inbound@0abbc0f316d3 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bbouvier |
bugs | 1505271 |
milestone | 65.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -881,17 +881,17 @@ CreateBuffer(JSContext* cx, uint32_t ini return true; } bool js::CreateWasmBuffer(JSContext* cx, const wasm::Limits& memory, MutableHandleArrayBufferObjectMaybeShared buffer) { MOZ_ASSERT(memory.initial % wasm::PageSize == 0); - MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers()); + MOZ_RELEASE_ASSERT(cx->wasmHaveSignalHandlers); MOZ_RELEASE_ASSERT((memory.initial / wasm::PageSize) <= wasm::MaxMemoryInitialPages); // Prevent applications specifying a large max (like UINT32_MAX) from // unintentially OOMing the browser on 32-bit: they just want "a lot of // memory". Maintain the invariant that initialSize <= maxSize. Maybe<uint32_t> maxSize = memory.maximum; if (sizeof(void*) == 4 && maxSize) { @@ -927,17 +927,17 @@ js::CreateWasmBuffer(JSContext* cx, cons // Note this function can return false with or without an exception pending. The // asm.js caller checks cx->isExceptionPending before propagating failure. // Returning false without throwing means that asm.js linking will fail which // will recompile as non-asm.js. /* static */ bool ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer) { MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0); - MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers()); + MOZ_RELEASE_ASSERT(cx->wasmHaveSignalHandlers); if (buffer->forInlineTypedObject()) { return false; } if (!buffer->isWasm() && buffer->isPreparedForAsmJS()) { return true; }
--- a/js/src/vm/JSContext.cpp +++ b/js/src/vm/JSContext.cpp @@ -119,19 +119,16 @@ JSContext::init(ContextKind kind) #ifdef JS_SIMULATOR simulator_ = jit::Simulator::Create(); if (!simulator_) { return false; } #endif - if (!wasm::EnsureSignalHandlers(this)) { - return false; - } } else { atomsZoneFreeLists_ = js_new<gc::FreeLists>(); if (!atomsZoneFreeLists_) { return false; } } // Set the ContextKind last, so that ProtectedData checks will allow us to @@ -1338,16 +1335,18 @@ JSContext::JSContext(JSRuntime* runtime, #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) runningOOMTest(false), #endif enableAccessValidation(false), inUnsafeRegion(0), generationalDisabled(0), compactingDisabledCount(0), suppressProfilerSampling(false), + wasmTriedToInstallSignalHandlers(false), + wasmHaveSignalHandlers(false), tempLifoAlloc_((size_t)TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), debuggerMutations(0), ionPcScriptCache(nullptr), throwing(false), overRecursed_(false), propagatingForcedReturn_(false), liveVolatileJitFrameIter_(nullptr), reportGranularity(JS_DEFAULT_JITREPORT_GRANULARITY),
--- a/js/src/vm/JSContext.h +++ b/js/src/vm/JSContext.h @@ -618,19 +618,20 @@ struct JSContext : public JS::RootingCon } void disableProfilerSampling() { suppressProfilerSampling = true; } void enableProfilerSampling() { suppressProfilerSampling = false; } -#if defined(XP_DARWIN) - js::wasm::MachExceptionHandler wasmMachExceptionHandler; -#endif + // Used by wasm::EnsureThreadSignalHandlers(cx) to install thread signal + // handlers once per JSContext/thread. + bool wasmTriedToInstallSignalHandlers; + bool wasmHaveSignalHandlers; /* Temporary arena pool used while compiling and decompiling. */ static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024; private: js::ThreadData<js::LifoAlloc> tempLifoAlloc_; public: js::LifoAlloc& tempLifoAlloc() { return tempLifoAlloc_.ref(); } const js::LifoAlloc& tempLifoAlloc() const { return tempLifoAlloc_.ref(); }
--- a/js/src/vm/MutexIDs.h +++ b/js/src/vm/MutexIDs.h @@ -50,16 +50,17 @@ _(RuntimeScriptData, 500) \ _(WasmFuncTypeIdSet, 500) \ _(WasmCodeProfilingLabels, 500) \ _(WasmCompileTaskState, 500) \ _(WasmCodeBytesEnd, 500) \ _(WasmStreamEnd, 500) \ _(WasmStreamStatus, 500) \ _(WasmRuntimeInstances, 500) \ + _(WasmSignalInstallState, 500) \ \ _(IcuTimeZoneStateMutex, 600) \ _(ThreadId, 600) \ _(WasmCodeSegmentMap, 600) \ _(WasmDeferredValidation, 600) \ _(TraceLoggerGraphState, 600) \ _(VTuneLock, 600)
--- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -43,16 +43,17 @@ #include "util/Windows.h" #include "vm/DateTime.h" #include "vm/Debugger.h" #include "vm/JSAtom.h" #include "vm/JSObject.h" #include "vm/JSScript.h" #include "vm/TraceLogging.h" #include "vm/TraceLoggingGraph.h" +#include "wasm/WasmSignalHandlers.h" #include "gc/GC-inl.h" #include "vm/JSContext-inl.h" using namespace js; using mozilla::Atomic; using mozilla::DebugOnly; @@ -176,16 +177,19 @@ JSRuntime::JSRuntime(JSRuntime* parentRu : js::StackFormat::SpiderMonkey), wasmInstances(mutexid::WasmRuntimeInstances), moduleResolveHook(), moduleMetadataHook(), moduleDynamicImportHook() { JS_COUNT_CTOR(JSRuntime); liveRuntimesCount++; + + // See function comment for why we call this now, not in JS_Init(). + wasm::EnsureEagerProcessSignalHandlers(); } JSRuntime::~JSRuntime() { JS_COUNT_DTOR(JSRuntime); MOZ_ASSERT(!initialized_); DebugOnly<size_t> oldCount = liveRuntimesCount--;
--- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -24,18 +24,16 @@ #include "js/RootingAPI.h" #include "js/TypeDecls.h" #include "js/UniquePtr.h" #include "vm/ArgumentsObject.h" #include "vm/JSFunction.h" #include "vm/JSScript.h" #include "vm/SavedFrame.h" #include "wasm/WasmFrameIter.h" -#include "wasm/WasmSignalHandlers.h" -#include "wasm/WasmTypes.h" namespace JS { namespace dbg { #ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wattributes" #endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING
--- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -11086,20 +11086,16 @@ BaseCompiler::finish() } } // wasm } // js bool js::wasm::BaselineCanCompile() { - // On all platforms we require signals for Wasm. - // If we made it this far we must have signals. - MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers()); - #if defined(JS_CODEGEN_ARM) // Simplifying assumption: require SDIV and UDIV. // // I have no good data on ARM populations allowing me to say that // X% of devices in the market implement SDIV and UDIV. However, // they are definitely implemented on the Cortex-A7 and Cortex-A15 // and on all ARMv8 systems. if (!HasIDIV()) {
--- a/js/src/wasm/WasmCompile.cpp +++ b/js/src/wasm/WasmCompile.cpp @@ -527,18 +527,16 @@ DecodeCodeSection(const ModuleEnvironmen return mg.finishFuncDefs(); } SharedModule wasm::CompileBuffer(const CompileArgs& args, const ShareableBytes& bytecode, UniqueChars* error, UniqueCharsVector* warnings, UniqueLinkData* maybeLinkData) { - MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers()); - Decoder d(bytecode.bytes, 0, error, warnings); CompilerEnvironment compilerEnv(args); ModuleEnvironment env(args.gcTypesConfigured, &compilerEnv, args.sharedMemoryEnabled ? Shareable::True : Shareable::False); if (!DecodeModuleEnvironment(d, &env)) { return nullptr; @@ -571,18 +569,16 @@ wasm::CompileBuffer(const CompileArgs& a return mg.finishModule(bytecode, nullptr, maybeLinkData); } void wasm::CompileTier2(const CompileArgs& args, const Bytes& bytecode, const Module& module, Atomic<bool>* cancelled) { - MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers()); - UniqueChars error; Decoder d(bytecode, 0, &error); HasGcTypes gcTypesConfigured = HasGcTypes::False; // No optimized backend support yet OptimizedBackend optimizedBackend = args.forceCranelift ? OptimizedBackend::Cranelift : OptimizedBackend::Ion; @@ -709,18 +705,16 @@ wasm::CompileStreaming(const CompileArgs const Bytes& envBytes, const Bytes& codeBytes, const ExclusiveBytesPtr& codeBytesEnd, const ExclusiveStreamEndData& exclusiveStreamEnd, const Atomic<bool>& cancelled, UniqueChars* error, UniqueCharsVector* warnings) { - MOZ_ASSERT(wasm::HaveSignalHandlers()); - CompilerEnvironment compilerEnv(args); Maybe<ModuleEnvironment> env; { Decoder d(envBytes, 0, error, warnings); env.emplace(args.gcTypesConfigured, &compilerEnv,
--- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -66,17 +66,17 @@ wasm::HasCompilerSupport(JSContext* cx) if (!cx->jitSupportsFloatingPoint()) { return false; } if (!cx->jitSupportsUnalignedAccesses()) { return false; } - if (!wasm::HaveSignalHandlers()) { + if (!wasm::EnsureFullSignalHandlers(cx)) { return false; } #if !MOZ_LITTLE_ENDIAN return false; #endif #ifdef ENABLE_WASM_THREAD_OPS
--- a/js/src/wasm/WasmModule.cpp +++ b/js/src/wasm/WasmModule.cpp @@ -1248,16 +1248,18 @@ Module::instantiate(JSContext* cx, Handle<FunctionVector> funcImports, HandleWasmTableObject tableImport, HandleWasmMemoryObject memoryImport, HandleValVector globalImportValues, WasmGlobalObjectVector& globalObjs, HandleObject instanceProto, MutableHandleWasmInstanceObject instance) const { + MOZ_RELEASE_ASSERT(cx->wasmHaveSignalHandlers); + if (!instantiateFunctions(cx, funcImports)) { return false; } RootedWasmMemoryObject memory(cx, memoryImport); if (!instantiateMemory(cx, &memory)) { return false; }
--- a/js/src/wasm/WasmSignalHandlers.cpp +++ b/js/src/wasm/WasmSignalHandlers.cpp @@ -17,37 +17,40 @@ */ #include "wasm/WasmSignalHandlers.h" #include "mozilla/DebugOnly.h" #include "mozilla/ScopeExit.h" #include "mozilla/ThreadLocal.h" +#include "threading/Thread.h" #include "vm/Runtime.h" #include "wasm/WasmInstance.h" +#if defined(XP_WIN) +# include "util/Windows.h" +#elif defined(XP_DARWIN) +# include <mach/exc.h> +# include <mach/mach.h> +#else +# include <signal.h> +#endif + using namespace js; using namespace js::wasm; using mozilla::DebugOnly; // ============================================================================= // This following pile of macros and includes defines the ToRegisterState() and // the ContextTo{PC,FP,SP,LR}() functions from the (highly) platform-specific // CONTEXT struct which is provided to the signal handler. // ============================================================================= -#if defined(XP_WIN) -# include "util/Windows.h" -#else -# include <signal.h> -# include <sys/mman.h> -#endif - #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) # include <sys/ucontext.h> // for ucontext_t, mcontext_t #endif #if defined(__x86_64__) # if defined(__DragonFly__) # include <machine/npx.h> // for union savefpu # elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ @@ -473,19 +476,18 @@ HandleTrap(CONTEXT* context, JSContext* // will call finishWasmTrap(). jit::JitActivation* activation = cx->activation()->asJit(); activation->startWasmTrap(trap, bytecode.offset(), ToRegisterState(context)); SetContextPC(context, segment.trapCode()); return true; } // ============================================================================= -// The following platform specific signal/exception handlers are installed by -// wasm::EnsureSignalHandlers() and funnel all potential wasm traps into -// HandleTrap() above. +// The following platform-specific handlers funnel all signals/exceptions into +// the shared HandleTrap() above. // ============================================================================= #if defined(XP_WIN) static LONG WINAPI WasmTrapHandler(LPEXCEPTION_POINTERS exception) { if (sAlreadyHandlingTrap.get()) { @@ -503,17 +505,19 @@ WasmTrapHandler(LPEXCEPTION_POINTERS exc if (!HandleTrap(exception->ContextRecord, TlsContext.get())) { return EXCEPTION_CONTINUE_SEARCH; } return EXCEPTION_CONTINUE_EXECUTION; } #elif defined(XP_DARWIN) -# include <mach/exc.h> +// On OSX we are forced to use the lower-level Mach exception mechanism instead +// of Unix signals because breakpad uses Mach exceptions and would otherwise +// report a crash before wasm gets a chance to handle the exception. // 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; @@ -530,17 +534,17 @@ typedef struct { // The full Mach message also includes a trailer. struct ExceptionRequest { Request__mach_exception_raise_t body; mach_msg_trailer_t trailer; }; static bool -HandleMachException(JSContext* cx, const ExceptionRequest& request) +HandleMachException(const ExceptionRequest& request) { // Get the port of the JSContext's thread from the message. mach_port_t cxThread = request.body.thread.name; // Read out the JSRuntime thread's register state. CONTEXT context; # if defined(__x86_64__) unsigned int thread_state_count = x86_THREAD_STATE64_COUNT; @@ -576,17 +580,17 @@ HandleMachException(JSContext* cx, const request.body.exception != EXC_BAD_INSTRUCTION) { return false; } { AutoNoteSingleThreadedRegion anstr; AutoHandlingTrap aht; - if (!HandleTrap(&context, cx)) { + if (!HandleTrap(&context)) { return false; } } // Update the thread state with the new pc and register values. kret = thread_set_state(cxThread, float_state, (thread_state_t)&context.float_, float_state_count); if (kret != KERN_SUCCESS) { return false; @@ -594,58 +598,48 @@ HandleMachException(JSContext* cx, const kret = thread_set_state(cxThread, 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 mach_port_t sMachDebugPort = MACH_PORT_NULL; static void -MachExceptionHandlerThread(JSContext* cx) +MachExceptionHandlerThread() { - mach_port_t port = cx->wasmMachExceptionHandler.port(); - kern_return_t kret; + // Taken from mach_exc in /usr/include/mach/mach_exc.defs. + static const unsigned EXCEPTION_MSG_ID = 2405; - while(true) { + while (true) { ExceptionRequest request; - kret = mach_msg(&request.body.Head, MACH_RCV_MSG, 0, sizeof(request), - port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + kern_return_t kret = mach_msg(&request.body.Head, MACH_RCV_MSG, 0, sizeof(request), + sMachDebugPort, 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) { + if (request.body.Head.msgh_id != EXCEPTION_MSG_ID) { 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(cx, request); + bool handled = HandleMachException(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; @@ -653,111 +647,16 @@ MachExceptionHandlerThread(JSContext* cx 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 | EXC_MASK_BAD_INSTRUCTION, - 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(JSContext* cx) -{ - MOZ_ASSERT(!installed()); - kern_return_t kret; - mach_port_t thread; - - auto onFailure = mozilla::MakeScopeExit([&] { - uninstall(); - }); - - // 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) { - return false; - } - kret = mach_port_insert_right(mach_task_self(), port_, port_, MACH_MSG_TYPE_MAKE_SEND); - if (kret != KERN_SUCCESS) { - return false; - } - - // Create a thread to block on reading port_. - if (!thread_.init(MachExceptionHandlerThread, cx)) { - return false; - } - - // 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 | EXC_MASK_BAD_INSTRUCTION, - port_, - EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, - THREAD_STATE_NONE); - mach_port_deallocate(mach_task_self(), thread); - if (kret != KERN_SUCCESS) { - return false; - } - - installed_ = true; - onFailure.release(); - return true; -} - #else // If not Windows or Mac, assume Unix #ifdef __mips__ static const uint32_t kWasmTrapSignal = SIGFPE; #else static const uint32_t kWasmTrapSignal = SIGILL; #endif @@ -805,74 +704,83 @@ WasmTrapHandler(int signum, siginfo_t* i } } # endif // XP_WIN || XP_DARWIN || assume unix #if defined(ANDROID) && defined(MOZ_LINKER) extern "C" MFBT_API bool IsSignalHandlingBroken(); #endif -static bool sTriedInstallSignalHandlers = false; -static bool sHaveSignalHandlers = false; - -static bool -ProcessHasSignalHandlers() +struct InstallState { - // We assume that there are no races creating the first JSRuntime of the process. - if (sTriedInstallSignalHandlers) { - return sHaveSignalHandlers; + bool tried; + bool success; + InstallState() : tried(false), success(false) {} +}; + +static ExclusiveData<InstallState> sEagerInstallState(mutexid::WasmSignalInstallState); + +void +wasm::EnsureEagerProcessSignalHandlers() +{ + auto eagerInstallState = sEagerInstallState.lock(); + if (eagerInstallState->tried) { + return; } - sTriedInstallSignalHandlers = true; + + eagerInstallState->tried = true; + MOZ_RELEASE_ASSERT(eagerInstallState->success == false); #if defined (JS_CODEGEN_NONE) // If there is no JIT, then there should be no Wasm signal handlers. - return false; + return; #endif // Signal handlers are currently disabled when recording or replaying. if (mozilla::recordreplay::IsRecordingOrReplaying()) { - return false; + return; } #if defined(ANDROID) && defined(MOZ_LINKER) // Signal handling is broken on some android systems. if (IsSignalHandlingBroken()) { - return false; + return; } #endif // Initalize ThreadLocal flag used by WasmTrapHandler sAlreadyHandlingTrap.infallibleInit(); // Install a SIGSEGV handler to handle safely-out-of-bounds asm.js heap // access and/or unaligned accesses. #if defined(XP_WIN) # if defined(_M_ARM64) // The AArch64 Windows build is not ready for this yet. - return false; + return; # endif # if defined(MOZ_ASAN) // Under ASan we need to let the ASan runtime's ShadowExceptionHandler stay // in the first handler position. This requires some coordination with // MemoryProtectionExceptionHandler::isDisabled(). const bool firstHandler = false; # else // Otherwise, WasmTrapHandler needs to go first, so that we can recover // from wasm faults and continue execution without triggering handlers // such as MemoryProtectionExceptionHandler that assume we are crashing. const bool firstHandler = true; # endif if (!AddVectoredExceptionHandler(firstHandler, WasmTrapHandler)) { - return false; + // Windows has all sorts of random security knobs for disabling things + // so make this a dynamic failure that disables wasm, not a MOZ_CRASH(). + return; } #elif defined(XP_DARWIN) - // OSX handles seg faults via the Mach exception handler above, so don't - // install WasmTrapHandler. + // All the Mach setup in EnsureLazyProcessSignalHandlers. #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 // handlingSegFault. // Allow handling OOB with signals on all architectures struct sigaction faultHandler; faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; @@ -899,46 +807,107 @@ ProcessHasSignalHandlers() wasmTrapHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; wasmTrapHandler.sa_sigaction = WasmTrapHandler; sigemptyset(&wasmTrapHandler.sa_mask); if (sigaction(kWasmTrapSignal, &wasmTrapHandler, &sPrevWasmTrapHandler)) { MOZ_CRASH("unable to install wasm trap handler"); } #endif - sHaveSignalHandlers = true; + eagerInstallState->success = true; +} + +static ExclusiveData<InstallState> sLazyInstallState(mutexid::WasmSignalInstallState); + +static bool +EnsureLazyProcessSignalHandlers() +{ + auto lazyInstallState = sLazyInstallState.lock(); + if (lazyInstallState->tried) { + return lazyInstallState->success; + } + + lazyInstallState->tried = true; + MOZ_RELEASE_ASSERT(lazyInstallState->success == false); + +#ifdef XP_DARWIN + // Create the port that all JSContext threads will redirect their traps to. + kern_return_t kret; + kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sMachDebugPort); + if (kret != KERN_SUCCESS) { + return false; + } + kret = mach_port_insert_right(mach_task_self(), sMachDebugPort, sMachDebugPort, + MACH_MSG_TYPE_MAKE_SEND); + if (kret != KERN_SUCCESS) { + return false; + } + + // Create the thread that will wait on and service sMachDebugPort. + // It's not useful to destroy this thread on process shutdown so + // immediately detach on successful start. + Thread handlerThread; + if (!handlerThread.init(MachExceptionHandlerThread)) { + return false; + } + handlerThread.detach(); +#endif + + lazyInstallState->success = true; return true; } bool -wasm::EnsureSignalHandlers(JSContext* cx) +wasm::EnsureFullSignalHandlers(JSContext* cx) { - // Nothing to do if the platform doesn't support it. - if (!ProcessHasSignalHandlers()) { - return true; + if (cx->wasmTriedToInstallSignalHandlers) { + return cx->wasmHaveSignalHandlers; + } + + cx->wasmTriedToInstallSignalHandlers = true; + MOZ_RELEASE_ASSERT(!cx->wasmHaveSignalHandlers); + + { + auto eagerInstallState = sEagerInstallState.lock(); + MOZ_RELEASE_ASSERT(eagerInstallState->tried); + if (!eagerInstallState->success) { + return false; + } } -#if defined(XP_DARWIN) - // On OSX, each JSContext which runs wasm gets its own handler thread. - if (!cx->wasmMachExceptionHandler.installed() && !cx->wasmMachExceptionHandler.install(cx)) { + if (!EnsureLazyProcessSignalHandlers()) { + return false; + } + +#ifdef XP_DARWIN + // In addition to the process-wide signal handler setup, OSX needs each + // thread configured to send its exceptions to sMachDebugPort. While there + // are also task-level (i.e. process-level) exception ports, those are + // "claimed" by breakpad and chaining Mach exceptions is dark magic that we + // avoid by instead intercepting exceptions at the thread level before they + // propagate to the process-level. This works because there are no other + // uses of thread-level exception ports. + MOZ_RELEASE_ASSERT(sMachDebugPort != MACH_PORT_NULL); + thread_port_t thisThread = mach_thread_self(); + kern_return_t kret = thread_set_exception_ports(thisThread, + EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION, + sMachDebugPort, + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, + THREAD_STATE_NONE); + mach_port_deallocate(mach_task_self(), thisThread); + if (kret != KERN_SUCCESS) { return false; } #endif + cx->wasmHaveSignalHandlers = true; return true; } bool -wasm::HaveSignalHandlers() -{ - MOZ_ASSERT(sTriedInstallSignalHandlers); - return sHaveSignalHandlers; -} - -bool wasm::MemoryAccessTraps(const RegisterState& regs, uint8_t* addr, uint32_t numBytes, uint8_t** newPC) { const wasm::CodeSegment* codeSegment = wasm::LookupCodeSegment(regs.pc); if (!codeSegment || !codeSegment->isModule()) { return false; } const wasm::ModuleSegment& segment = *codeSegment->asModule();
--- a/js/src/wasm/WasmSignalHandlers.h +++ b/js/src/wasm/WasmSignalHandlers.h @@ -16,71 +16,53 @@ * limitations under the License. */ #ifndef wasm_signal_handlers_h #define wasm_signal_handlers_h #include "mozilla/Attributes.h" -#if defined(XP_DARWIN) -# include <mach/mach.h> -#endif - #include "js/ProfilingFrameIterator.h" -#include "threading/Thread.h" #include "wasm/WasmProcess.h" namespace js { namespace wasm { typedef JS::ProfilingFrameIterator::RegisterState RegisterState; -// Ensure the given JSRuntime is set up to use signals. Failure to enable signal -// handlers indicates some catastrophic failure and creation of the runtime must -// fail. -MOZ_MUST_USE bool -EnsureSignalHandlers(JSContext* cx); +// This function performs the low-overhead signal handler initialization that we +// want to do eagerly to ensure a more-deterministic global process state. This +// is especially relevant for signal handlers since handler ordering depends on +// installation order: the wasm signal handler must run *before* the other crash +// handlers (ds/MemoryProtectionExceptionHandler.h and breakpad) and since POSIX +// signal handlers work LIFO, this function needs to be called at the end of the +// startup process, after the other two handlers have been installed. Currently, +// this is achieved by having JSRuntime() call this function. There can be +// multiple JSRuntimes per process so this function can thus be called multiple +// times, having no effect after the first call. +void +EnsureEagerProcessSignalHandlers(); -// Return whether signals can be used in this process for asm.js/wasm -// out-of-bounds. +// Assuming EnsureEagerProcessSignalHandlers() has already been called, +// this function performs the full installation of signal handlers which must +// be performed per-thread/JSContext. This operation may incur some overhead and +// so should be done only when needed to use wasm. Currently, this is done in +// wasm::HasCompilerSupport() which is called when deciding whether to expose the +// 'WebAssembly' object on the global object. bool -HaveSignalHandlers(); +EnsureFullSignalHandlers(JSContext* cx); // Return whether, with the given simulator register state, a memory access to // 'addr' of size 'numBytes' needs to trap and, if so, where the simulator // should redirect pc to. bool MemoryAccessTraps(const RegisterState& regs, uint8_t* addr, uint32_t numBytes, uint8_t** newPC); // Return whether, with the given simulator register state, an illegal // instruction fault is expected and, if so, the value of the next PC. bool HandleIllegalInstruction(const RegisterState& regs, uint8_t** newPC); -#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 JSContext (upon the first use of wasm in the JSContext). This thread -// and related resources are owned by AsmJSMachExceptionHandler which is owned -// by JSContext. -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(JSContext* cx); -}; -#endif - } // namespace wasm } // namespace js #endif // wasm_signal_handlers_h