Bug 1091912 - stop using mprotect to halt Ion/asm.js execution (r=bhackett)

--HG--
extra : rebase_source : 3642ce948e577bca54008d3c55206184b107d023
This commit is contained in:
Luke Wagner 2014-11-11 08:36:52 -06:00
parent e0f61bdabc
commit ea143386f2
23 changed files with 366 additions and 719 deletions

View File

@ -54,36 +54,6 @@ using mozilla::PodEqual;
using mozilla::Compression::LZ4;
using mozilla::Swap;
// At any time, the executable code of an asm.js module can be protected (as
// part of RequestInterruptForAsmJSCode). When we touch the executable outside
// of executing it (which the AsmJSFaultHandler will correctly handle), we need
// to guard against this by unprotecting the code (if it has been protected) and
// preventing it from being protected while we are touching it.
class AutoUnprotectCode
{
JSRuntime *rt_;
JSRuntime::AutoLockForInterrupt lock_;
const AsmJSModule &module_;
const bool protectedBefore_;
public:
AutoUnprotectCode(JSContext *cx, const AsmJSModule &module)
: rt_(cx->runtime()),
lock_(rt_),
module_(module),
protectedBefore_(module_.codeIsProtected(rt_))
{
if (protectedBefore_)
module_.unprotectCode(rt_);
}
~AutoUnprotectCode()
{
if (protectedBefore_)
module_.protectCode(rt_);
}
};
static uint8_t *
AllocateExecutableMemory(ExclusiveContext *cx, size_t bytes)
{
@ -113,8 +83,7 @@ AsmJSModule::AsmJSModule(ScriptSource *scriptSource, uint32_t srcStart, uint32_t
dynamicallyLinked_(false),
loadedFromCache_(false),
profilingEnabled_(false),
interrupted_(false),
codeIsProtected_(false)
interrupted_(false)
{
mozilla::PodZero(&pod);
pod.funcPtrTableAndExitBytes_ = SIZE_MAX;
@ -830,7 +799,6 @@ AsmJSModule::initHeap(Handle<ArrayBufferObjectMaybeShared *> heap, JSContext *cx
#endif
}
// This method assumes the caller has a live AutoUnprotectCode.
void
AsmJSModule::restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer)
{
@ -852,7 +820,6 @@ AsmJSModule::restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBu
heapDatum() = nullptr;
}
// This method assumes the caller has a live AutoUnprotectCode.
void
AsmJSModule::restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer,
uint8_t *prevCode,
@ -907,7 +874,6 @@ AsmJSModule::detachHeap(JSContext *cx)
MOZ_ASSERT_IF(active(), activation()->exitReason() == AsmJSExit::Reason_IonFFI ||
activation()->exitReason() == AsmJSExit::Reason_SlowFFI);
AutoUnprotectCode auc(cx, *this);
restoreHeapToInitialState(maybeHeap_);
MOZ_ASSERT(hasDetachedHeap());
@ -1568,8 +1534,6 @@ AsmJSModule::deserialize(ExclusiveContext *cx, const uint8_t *cursor)
bool
AsmJSModule::clone(JSContext *cx, ScopedJSDeletePtr<AsmJSModule> *moduleOut) const
{
AutoUnprotectCode auc(cx, *this);
*moduleOut = cx->new_<AsmJSModule>(scriptSource_, srcStart_, srcBodyStart_, pod.strict_,
pod.usesSignalHandlers_);
if (!*moduleOut)
@ -1628,7 +1592,6 @@ AsmJSModule::changeHeap(Handle<ArrayBufferObject*> newHeap, JSContext *cx)
if (interrupted_)
return false;
AutoUnprotectCode auc(cx, *this);
restoreHeapToInitialState(maybeHeap_);
initHeap(newHeap, cx);
return true;
@ -1669,9 +1632,6 @@ AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx)
AutoFlushICache afc("AsmJSModule::setProfilingEnabled");
setAutoFlushICacheRange();
// To enable profiling, we need to patch 3 kinds of things:
AutoUnprotectCode auc(cx, *this);
// Patch all internal (asm.js->asm.js) callsites to call the profiling
// prologues:
for (size_t i = 0; i < callSites_.length(); i++) {
@ -1818,59 +1778,6 @@ AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx)
profilingEnabled_ = enabled;
}
void
AsmJSModule::protectCode(JSRuntime *rt) const
{
MOZ_ASSERT(isDynamicallyLinked());
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
codeIsProtected_ = true;
if (!pod.functionBytes_)
return;
// Technically, we should be able to only take away the execute permissions,
// however this seems to break our emulators which don't always check
// execute permissions while executing code.
#if defined(XP_WIN)
DWORD oldProtect;
if (!VirtualProtect(codeBase(), functionBytes(), PAGE_NOACCESS, &oldProtect))
MOZ_CRASH();
#else // assume Unix
if (mprotect(codeBase(), functionBytes(), PROT_NONE))
MOZ_CRASH();
#endif
}
void
AsmJSModule::unprotectCode(JSRuntime *rt) const
{
MOZ_ASSERT(isDynamicallyLinked());
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
codeIsProtected_ = false;
if (!pod.functionBytes_)
return;
#if defined(XP_WIN)
DWORD oldProtect;
if (!VirtualProtect(codeBase(), functionBytes(), PAGE_EXECUTE_READWRITE, &oldProtect))
MOZ_CRASH();
#else // assume Unix
if (mprotect(codeBase(), functionBytes(), PROT_READ | PROT_WRITE | PROT_EXEC))
MOZ_CRASH();
#endif
}
bool
AsmJSModule::codeIsProtected(JSRuntime *rt) const
{
MOZ_ASSERT(isDynamicallyLinked());
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
return codeIsProtected_;
}
static bool
GetCPUID(uint32_t *cpuId)
{

View File

@ -826,10 +826,6 @@ class AsmJSModule
bool profilingEnabled_;
bool interrupted_;
// This field is accessed concurrently when requesting an interrupt.
// Access must be synchronized via the runtime's interrupt lock.
mutable bool codeIsProtected_;
void restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer);
void restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer, uint8_t *prevCode,
ExclusiveContext *cx);
@ -1529,12 +1525,6 @@ class AsmJSModule
MOZ_ASSERT(isDynamicallyLinked());
interrupted_ = interrupted;
}
// Additionally, these functions may only be called while holding the
// runtime's interrupt lock.
void protectCode(JSRuntime *rt) const;
void unprotectCode(JSRuntime *rt) const;
bool codeIsProtected(JSRuntime *rt) const;
};
// Store the just-parsed module in the cache using AsmJSCacheOps.

View File

@ -19,6 +19,7 @@
#include "asmjs/AsmJSSignalHandlers.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/PodOperations.h"
#include "asmjs/AsmJSModule.h"
#include "vm/Runtime.h"
@ -28,6 +29,51 @@ using namespace js::jit;
using JS::GenericNaN;
using mozilla::DebugOnly;
using mozilla::PodArrayZero;
#if defined(ANDROID)
# include <sys/system_properties.h>
# if defined(MOZ_LINKER)
extern "C" MFBT_API bool IsSignalHandlingBroken();
# endif
#endif
// For platforms where the signal/exception handler runs on the same
// thread/stack as the victim (Unix and Windows), we can use TLS to find any
// currently executing asm.js code.
static JSRuntime *
RuntimeForCurrentThread()
{
PerThreadData *threadData = TlsPerThreadData.get();
if (!threadData)
return nullptr;
return threadData->runtimeIfOnOwnerThread();
}
// Crashing inside the signal handler can cause the handler to be recursively
// invoked, eventually blowing the stack without actually showing a crash
// report dialog via Breakpad. To guard against this we watch for such
// recursion and fall through to the next handler immediately rather than
// trying to handle it.
class AutoSetHandlingSignal
{
JSRuntime *rt;
public:
explicit AutoSetHandlingSignal(JSRuntime *rt)
: rt(rt)
{
MOZ_ASSERT(!rt->handlingSignal);
rt->handlingSignal = true;
}
~AutoSetHandlingSignal()
{
MOZ_ASSERT(rt->handlingSignal);
rt->handlingSignal = false;
}
};
#if defined(XP_WIN)
# define XMM_sig(p,i) ((p)->Xmm##i)
@ -152,71 +198,12 @@ using mozilla::DebugOnly;
# define R15_sig(p) ((p)->uc_mcontext.mc_r15)
# endif
#elif defined(XP_MACOSX)
// Mach requires special treatment.
# define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip)
# define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip)
#else
# error "Don't know how to read/write to the thread state via the mcontext_t."
#endif
// For platforms where the signal/exception handler runs on the same
// thread/stack as the victim (Unix and Windows), we can use TLS to find any
// currently executing asm.js code.
#if !defined(XP_MACOSX)
static JSRuntime *
RuntimeForCurrentThread()
{
PerThreadData *threadData = TlsPerThreadData.get();
if (!threadData)
return nullptr;
return threadData->runtimeIfOnOwnerThread();
}
#endif // !defined(XP_MACOSX)
// Crashing inside the signal handler can cause the handler to be recursively
// invoked, eventually blowing the stack without actually showing a crash
// report dialog via Breakpad. To guard against this we watch for such
// recursion and fall through to the next handler immediately rather than
// trying to handle it.
class AutoSetHandlingSignal
{
JSRuntime *rt;
public:
explicit AutoSetHandlingSignal(JSRuntime *rt)
: rt(rt)
{
MOZ_ASSERT(!rt->handlingSignal);
rt->handlingSignal = true;
}
~AutoSetHandlingSignal()
{
MOZ_ASSERT(rt->handlingSignal);
rt->handlingSignal = false;
}
};
#if defined(JS_CODEGEN_X64)
template <class T>
static void
SetXMMRegToNaN(bool isFloat32, T *xmm_reg)
{
if (isFloat32) {
JS_STATIC_ASSERT(sizeof(T) == 4 * sizeof(float));
float *floats = reinterpret_cast<float*>(xmm_reg);
floats[0] = GenericNaN();
floats[1] = 0;
floats[2] = 0;
floats[3] = 0;
} else {
JS_STATIC_ASSERT(sizeof(T) == 2 * sizeof(double));
double *dbls = reinterpret_cast<double*>(xmm_reg);
dbls[0] = GenericNaN();
dbls[1] = 0;
}
}
#endif
#if defined(XP_WIN)
# include "jswin.h"
#else
@ -228,7 +215,7 @@ SetXMMRegToNaN(bool isFloat32, T *xmm_reg)
# include <sys/ucontext.h> // for ucontext_t, mcontext_t
#endif
#if defined(JS_CODEGEN_X64)
#if defined(JS_CPU_X64)
# if defined(__DragonFly__)
# include <machine/npx.h> // for union savefpu
# elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
@ -317,18 +304,6 @@ enum { REG_EIP = 14 };
# endif // !defined(__BIONIC_HAVE_UCONTEXT_T)
#endif // defined(ANDROID)
#if defined(ANDROID) && defined(MOZ_LINKER)
// Apparently, on some Android systems, the signal handler is always passed
// nullptr as the faulting address. This would cause the asm.js signal handler
// to think that a safe out-of-bounds access was a nullptr-deref. This
// brokenness is already detected by ElfLoader (enabled by MOZ_LINKER), so
// reuse that check to disable asm.js compilation on systems where the signal
// handler is broken.
extern "C" MFBT_API bool IsSignalHandlingBroken();
#else
static bool IsSignalHandlingBroken() { return false; }
#endif // defined(MOZ_LINKER)
#if !defined(XP_WIN)
# define CONTEXT ucontext_t
#endif
@ -343,41 +318,33 @@ static bool IsSignalHandlingBroken() { return false; }
# define PC_sig(p) EPC_sig(p)
#endif
static bool
HandleSimulatorInterrupt(JSRuntime *rt, AsmJSActivation *activation, void *faultingAddress)
{
// If the ARM simulator is enabled, the pc is in the simulator C++ code and
// not in the generated code, so we check the simulator's pc manually. Also
// note that we can't simply use simulator->set_pc() here because the
// simulator could be in the middle of an instruction. On ARM, the signal
// handlers are currently only used for Odin code, see bug 964258.
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
const AsmJSModule &module = activation->module();
if (module.containsFunctionPC((void *)rt->mainThread.simulator()->get_pc()) &&
module.containsFunctionPC(faultingAddress))
{
activation->setResumePC(nullptr);
int32_t nextpc = int32_t(module.interruptExit());
rt->mainThread.simulator()->set_resume_pc(nextpc);
return true;
}
#endif
return false;
}
#if !defined(XP_MACOSX)
static uint8_t **
ContextToPC(CONTEXT *context)
{
#ifdef JS_CODEGEN_NONE
MOZ_CRASH();
#else
return reinterpret_cast<uint8_t**>(&PC_sig(context));
#endif
}
# if defined(JS_CODEGEN_X64)
#if defined(JS_CPU_X64)
template <class T>
static void
SetXMMRegToNaN(bool isFloat32, T *xmm_reg)
{
if (isFloat32) {
JS_STATIC_ASSERT(sizeof(T) == 4 * sizeof(float));
float *floats = reinterpret_cast<float*>(xmm_reg);
floats[0] = GenericNaN();
floats[1] = 0;
floats[2] = 0;
floats[3] = 0;
} else {
JS_STATIC_ASSERT(sizeof(T) == 2 * sizeof(double));
double *dbls = reinterpret_cast<double*>(xmm_reg);
dbls[0] = GenericNaN();
dbls[1] = 0;
}
}
# if !defined(XP_MACOSX)
static void
SetRegisterToCoercedUndefined(CONTEXT *context, bool isFloat32, AnyRegister reg)
{
@ -423,13 +390,13 @@ SetRegisterToCoercedUndefined(CONTEXT *context, bool isFloat32, AnyRegister reg)
}
}
}
# endif // JS_CODEGEN_X64
#endif // !XP_MACOSX
# endif // !XP_MACOSX
#endif // JS_CPU_X64
#if defined(XP_WIN)
static bool
HandleException(PEXCEPTION_POINTERS exception)
HandleFault(PEXCEPTION_POINTERS exception)
{
EXCEPTION_RECORD *record = exception->ExceptionRecord;
CONTEXT *context = exception->ContextRecord;
@ -444,19 +411,13 @@ HandleException(PEXCEPTION_POINTERS exception)
if (record->NumberParameters < 2)
return false;
void *faultingAddress = (void*)record->ExceptionInformation[1];
JSRuntime *rt = RuntimeForCurrentThread();
// Don't allow recursive handling of signals, see AutoSetHandlingSignal.
JSRuntime *rt = RuntimeForCurrentThread();
if (!rt || rt->handlingSignal)
return false;
AutoSetHandlingSignal handling(rt);
if (rt->jitRuntime() && rt->jitRuntime()->handleAccessViolation(rt, faultingAddress))
return true;
AsmJSActivation *activation = PerThreadData::innermostAsmJSActivation();
AsmJSActivation *activation = rt->mainThread.asmJSActivationStack();
if (!activation)
return false;
@ -464,23 +425,10 @@ HandleException(PEXCEPTION_POINTERS exception)
if (!module.containsFunctionPC(pc))
return false;
// If we faulted trying to execute code in 'module', this must be an
// interrupt callback (see RequestInterruptForAsmJSCode). Redirect
// execution to a trampoline which will call js::HandleExecutionInterrupt.
// The trampoline will jump to activation->resumePC if execution isn't
// interrupted.
if (module.containsFunctionPC(faultingAddress)) {
activation->setResumePC(pc);
*ppc = module.interruptExit();
JSRuntime::AutoLockForInterrupt lock(rt);
module.unprotectCode(rt);
return true;
}
# if defined(JS_CODEGEN_X64)
# if defined(JS_CPU_X64)
// These checks aren't necessary, but, since we can, check anyway to make
// sure we aren't covering up a real bug.
void *faultingAddress = (void*)record->ExceptionInformation[1];
if (!module.maybeHeap() ||
faultingAddress < module.maybeHeap() ||
faultingAddress >= module.maybeHeap() + AsmJSMappedSize)
@ -512,9 +460,9 @@ HandleException(PEXCEPTION_POINTERS exception)
}
static LONG WINAPI
AsmJSExceptionHandler(LPEXCEPTION_POINTERS exception)
AsmJSFaultHandler(LPEXCEPTION_POINTERS exception)
{
if (HandleException(exception))
if (HandleFault(exception))
return EXCEPTION_CONTINUE_EXECUTION;
// No need to worry about calling other handlers, the OS does this for us.
@ -527,18 +475,16 @@ AsmJSExceptionHandler(LPEXCEPTION_POINTERS exception)
static uint8_t **
ContextToPC(x86_thread_state_t &state)
{
# if defined(JS_CODEGEN_X64)
# if defined(JS_CPU_X64)
JS_STATIC_ASSERT(sizeof(state.uts.ts64.__rip) == sizeof(void*));
return reinterpret_cast<uint8_t**>(&state.uts.ts64.__rip);
# elif defined(JS_CODEGEN_NONE)
MOZ_CRASH();
# else
JS_STATIC_ASSERT(sizeof(state.uts.ts32.__eip) == sizeof(void*));
return reinterpret_cast<uint8_t**>(&state.uts.ts32.__eip);
# endif
}
# if defined(JS_CODEGEN_X64)
# if defined(JS_CPU_X64)
static bool
SetRegisterToCoercedUndefined(mach_port_t rtThread, x86_thread_state64_t &state,
const AsmJSHeapAccess &heapAccess)
@ -650,45 +596,18 @@ HandleMachException(JSRuntime *rt, const ExceptionRequest &request)
if (request.body.exception != EXC_BAD_ACCESS || request.body.codeCnt != 2)
return false;
void *faultingAddress = (void*)request.body.code[1];
if (rt->jitRuntime() && rt->jitRuntime()->handleAccessViolation(rt, faultingAddress))
return true;
AsmJSActivation *activation = rt->mainThread.asmJSActivationStack();
if (!activation)
return false;
const AsmJSModule &module = activation->module();
if (HandleSimulatorInterrupt(rt, activation, faultingAddress)) {
JSRuntime::AutoLockForInterrupt lock(rt);
module.unprotectCode(rt);
return true;
}
if (!module.containsFunctionPC(pc))
return false;
// If we faulted trying to execute code in 'module', this must be an
// interrupt callback (see RequestInterruptForAsmJSCode). Redirect
// execution to a trampoline which will call js::HandleExecutionInterrupt.
// The trampoline will jump to activation->resumePC if execution isn't
// interrupted.
if (module.containsFunctionPC(faultingAddress)) {
activation->setResumePC(pc);
*ppc = module.interruptExit();
JSRuntime::AutoLockForInterrupt lock(rt);
module.unprotectCode(rt);
// Update the thread state with the new pc.
kret = thread_set_state(rtThread, x86_THREAD_STATE, (thread_state_t)&state, x86_THREAD_STATE_COUNT);
return kret == KERN_SUCCESS;
}
# if defined(JS_CODEGEN_X64)
# if defined(JS_CPU_X64)
// These checks aren't necessary, but, since we can, check anyway to make
// sure we aren't covering up a real bug.
void *faultingAddress = (void*)request.body.code[1];
if (!module.maybeHeap() ||
faultingAddress < module.maybeHeap() ||
faultingAddress >= module.maybeHeap() + AsmJSMappedSize)
@ -879,55 +798,30 @@ AsmJSMachExceptionHandler::install(JSRuntime *rt)
// Be very cautious and default to not handling; we don't want to accidentally
// silence real crashes from real bugs.
static bool
HandleSignal(int signum, siginfo_t *info, void *ctx)
HandleFault(int signum, siginfo_t *info, void *ctx)
{
CONTEXT *context = (CONTEXT *)ctx;
uint8_t **ppc = ContextToPC(context);
uint8_t *pc = *ppc;
void *faultingAddress = info->si_addr;
JSRuntime *rt = RuntimeForCurrentThread();
// Don't allow recursive handling of signals, see AutoSetHandlingSignal.
JSRuntime *rt = RuntimeForCurrentThread();
if (!rt || rt->handlingSignal)
return false;
AutoSetHandlingSignal handling(rt);
if (rt->jitRuntime() && rt->jitRuntime()->handleAccessViolation(rt, faultingAddress))
return true;
AsmJSActivation *activation = PerThreadData::innermostAsmJSActivation();
AsmJSActivation *activation = rt->mainThread.asmJSActivationStack();
if (!activation)
return false;
const AsmJSModule &module = activation->module();
if (HandleSimulatorInterrupt(rt, activation, faultingAddress)) {
JSRuntime::AutoLockForInterrupt lock(rt);
module.unprotectCode(rt);
return true;
}
if (!module.containsFunctionPC(pc))
return false;
// If we faulted trying to execute code in 'module', this must be an
// interrupt callback (see RequestInterruptForAsmJSCode). Redirect
// execution to a trampoline which will call js::HandleExecutionInterrupt.
// The trampoline will jump to activation->resumePC if execution isn't
// interrupted.
if (module.containsFunctionPC(faultingAddress)) {
activation->setResumePC(pc);
*ppc = module.interruptExit();
JSRuntime::AutoLockForInterrupt lock(rt);
module.unprotectCode(rt);
return true;
}
# if defined(JS_CODEGEN_X64)
# if defined(JS_CPU_X64)
// These checks aren't necessary, but, since we can, check anyway to make
// sure we aren't covering up a real bug.
void *faultingAddress = info->si_addr;
if (!module.maybeHeap() ||
faultingAddress < module.maybeHeap() ||
faultingAddress >= module.maybeHeap() + AsmJSMappedSize)
@ -954,12 +848,12 @@ HandleSignal(int signum, siginfo_t *info, void *ctx)
# endif
}
static struct sigaction sPrevHandler;
static struct sigaction sPrevSEGVHandler;
static void
AsmJSFaultHandler(int signum, siginfo_t *info, void *context)
{
if (HandleSignal(signum, info, context))
if (HandleFault(signum, info, context))
return;
// This signal is not for any asm.js code we expect, so we need to forward
@ -974,90 +868,201 @@ AsmJSFaultHandler(int signum, siginfo_t *info, void *context)
// signal to it's original disposition and returning.
//
// Note: the order of these tests matter.
if (sPrevHandler.sa_flags & SA_SIGINFO)
sPrevHandler.sa_sigaction(signum, info, context);
else if (sPrevHandler.sa_handler == SIG_DFL || sPrevHandler.sa_handler == SIG_IGN)
sigaction(signum, &sPrevHandler, nullptr);
if (sPrevSEGVHandler.sa_flags & SA_SIGINFO)
sPrevSEGVHandler.sa_sigaction(signum, info, context);
else if (sPrevSEGVHandler.sa_handler == SIG_DFL || sPrevSEGVHandler.sa_handler == SIG_IGN)
sigaction(signum, &sPrevSEGVHandler, nullptr);
else
sPrevHandler.sa_handler(signum);
sPrevSEGVHandler.sa_handler(signum);
}
#endif
#if !defined(XP_MACOSX)
static bool sInstalledHandlers = false;
static void
RedirectIonBackedgesToInterruptCheck(JSRuntime *rt)
{
if (jit::JitRuntime *jitRuntime = rt->jitRuntime()) {
// If the backedge list is being mutated, the pc must be in C++ code and
// thus not in a JIT iloop. We assume that the interrupt flag will be
// checked at least once before entering JIT code (if not, no big deal;
// the browser will just request another interrupt in a second).
if (!jitRuntime->mutatingBackedgeList())
jitRuntime->patchIonBackedges(rt, jit::JitRuntime::BackedgeInterruptCheck);
}
}
static void
RedirectJitCodeToInterruptCheck(JSRuntime *rt, CONTEXT *context)
{
RedirectIonBackedgesToInterruptCheck(rt);
if (AsmJSActivation *activation = rt->mainThread.asmJSActivationStack()) {
const AsmJSModule &module = activation->module();
#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
if (module.containsFunctionPC((void*)rt->mainThread.simulator()->get_pc()))
rt->mainThread.simulator()->set_resume_pc(int32_t(module.interruptExit()));
#endif
uint8_t **ppc = ContextToPC(context);
uint8_t *pc = *ppc;
if (module.containsFunctionPC(pc)) {
activation->setResumePC(pc);
*ppc = module.interruptExit();
}
}
}
#if !defined(XP_WIN)
// For the interrupt signal, pick a signal number that:
// - is not otherwise used by mozilla or standard libraries
// - defaults to nostop and noprint on gdb/lldb so that noone is bothered
// SIGVTALRM a relative of SIGALRM, so intended for user code, but, unlike
// SIGALRM, not used anywhere else in Mozilla.
static const int sInterruptSignal = SIGVTALRM;
static void
JitInterruptHandler(int signum, siginfo_t *info, void *context)
{
if (JSRuntime *rt = RuntimeForCurrentThread())
RedirectJitCodeToInterruptCheck(rt, (CONTEXT*)context);
}
#endif
bool
js::EnsureAsmJSSignalHandlersInstalled(JSRuntime *rt)
js::EnsureSignalHandlersInstalled(JSRuntime *rt)
{
#ifdef JS_CODEGEN_NONE
// Don't install signal handlers in builds with the JIT disabled.
return false;
#if defined(XP_MACOSX)
// On OSX, each JSRuntime gets its own handler thread.
if (!rt->asmJSMachExceptionHandler.installed() && !rt->asmJSMachExceptionHandler.install(rt))
return false;
#endif
// All the rest of the handlers are process-wide and thus must only be
// installed once. We assume that there are no races creating the first
// JSRuntime of the process.
static bool sTried = false;
static bool sResult = false;
if (sTried)
return sResult;
sTried = true;
#if defined(ANDROID)
// Before Android 4.4 (SDK version 19), there is a bug
// https://android-review.googlesource.com/#/c/52333
// in Bionic's pthread_join which causes pthread_join to return early when
// pthread_kill is used (on any thread). Nobody expects the pthread_cond_wait
// EINTRquisition.
char version_string[PROP_VALUE_MAX];
PodArrayZero(version_string);
if (__system_property_get("ro.build.version.sdk", version_string) > 0) {
if (atol(version_string) < 19)
return false;
}
# if defined(MOZ_LINKER)
// Signal handling is broken on some android systems.
if (IsSignalHandlingBroken())
return false;
#if defined(XP_MACOSX)
// On OSX, each JSRuntime gets its own handler.
return rt->asmJSMachExceptionHandler.installed() || rt->asmJSMachExceptionHandler.install(rt);
#else
// Assume Windows or Unix. For these platforms, there is a single,
// process-wide signal handler installed. Take care to only install it once.
if (sInstalledHandlers)
return true;
# if defined(XP_WIN)
if (!AddVectoredExceptionHandler(/* FirstHandler = */true, AsmJSExceptionHandler))
return false;
# else
// Assume Unix. 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 handlingSignal.
struct sigaction sigAction;
sigAction.sa_flags = SA_SIGINFO | SA_NODEFER;
sigAction.sa_sigaction = &AsmJSFaultHandler;
sigemptyset(&sigAction.sa_mask);
if (sigaction(SIGSEGV, &sigAction, &sPrevHandler))
return false;
# endif
sInstalledHandlers = true;
#endif
#if defined(XP_WIN)
// Windows uses SuspendThread to stop the main thread from another thread,
// so the only handler we need is for asm.js out-of-bound faults.
if (!AddVectoredExceptionHandler(/* FirstHandler = */true, AsmJSFaultHandler))
MOZ_CRASH("unable to install vectored exception handler");
#else
// The interrupt handler allows the main thread to be paused from another
// thread (see InterruptRunningJitCode).
struct sigaction interruptHandler;
interruptHandler.sa_flags = SA_SIGINFO;
interruptHandler.sa_sigaction = &JitInterruptHandler;
sigemptyset(&interruptHandler.sa_mask);
struct sigaction prev;
if (sigaction(sInterruptSignal, &interruptHandler, &prev))
MOZ_CRASH("unable to install interrupt handler");
// There shouldn't be any other handlers installed for sInterruptSignal. If
// there are, we could always forward, but we need to understand what we're
// doing to avoid problematic interference.
if ((prev.sa_flags & SA_SIGINFO && prev.sa_sigaction) ||
(prev.sa_handler != SIG_DFL && prev.sa_handler != SIG_IGN))
{
MOZ_CRASH("contention for interrupt signal");
}
// Lastly, install a SIGSEGV handler to handle safely-out-of-bounds asm.js
// heap access. OSX handles seg faults via the Mach exception handler above,
// so don't install AsmJSFaultHandler.
# if !defined(XP_MACOSX)
// 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
// handlingSignal.
struct sigaction faultHandler;
faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER;
faultHandler.sa_sigaction = &AsmJSFaultHandler;
sigemptyset(&faultHandler.sa_mask);
if (sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler))
MOZ_CRASH("unable to install segv handler");
# endif // defined(XP_MACOSX)
#endif // defined(XP_WIN)
sResult = true;
return true;
}
// To interrupt execution of a JSRuntime, any thread may call
// JS_RequestInterruptCallback (JSRuntime::requestInterruptCallback from inside
// the engine). In the simplest case, this sets some state that is polled at
// regular intervals (function prologues, loop headers). For tight loops, this
// poses non-trivial overhead. For asm.js, we can do better: when another
// thread requests an interrupt, we simply mprotect all of the innermost asm.js
// module activation's code. This will trigger a SIGSEGV, taking us into
// AsmJSFaultHandler. From there, we can manually redirect execution to call
// js::HandleExecutionInterrupt. The memory is un-protected from the signal
// handler after control flow is redirected.
// JSRuntime::requestInterrupt sets interrupt_ (which is checked frequently by
// C++ code at every Baseline JIT loop backedge) and jitStackLimit_ (which is
// checked at every Baseline and Ion JIT function prologue). The remaining
// sources of potential iloops (Ion loop backedges and all asm.js code) are
// handled by this function:
// 1. Ion loop backedges are patched to instead point to a stub that handles the
// interrupt;
// 2. if the main thread's pc is inside asm.js code, the pc is updated to point
// to a stub that handles the interrupt.
void
js::RequestInterruptForAsmJSCode(JSRuntime *rt, int interruptModeRaw)
js::InterruptRunningJitCode(JSRuntime *rt)
{
switch (JSRuntime::InterruptMode(interruptModeRaw)) {
case JSRuntime::RequestInterruptMainThread:
case JSRuntime::RequestInterruptAnyThread:
break;
case JSRuntime::RequestInterruptAnyThreadDontStopIon:
case JSRuntime::RequestInterruptAnyThreadForkJoin:
// It is ok to wait for asm.js execution to complete; we aren't trying
// to break an iloop or anything. Avoid the overhead of protecting all
// the code and taking a fault.
// If signal handlers weren't installed, then Ion and asm.js emit normal
// interrupt checks and don't need asynchronous interruption.
if (!rt->canUseSignalHandlers())
return;
// If we are on runtime's main thread, then: pc is not in asm.js code (so
// nothing to do for asm.js) and we can patch Ion backedges without any
// special synchronization.
if (rt == RuntimeForCurrentThread()) {
RedirectIonBackedgesToInterruptCheck(rt);
return;
}
AsmJSActivation *activation = rt->mainThread.asmJSActivationStack();
if (!activation)
return;
// We are not on the runtime's main thread, so to do 1 and 2 above, we need
// to halt the runtime's main thread first.
#if defined(XP_WIN)
// On Windows, we can simply suspend the main thread and work directly on
// its context from this thread.
HANDLE thread = (HANDLE)rt->ownerThreadNative();
if (SuspendThread(thread) == -1)
MOZ_CRASH("Failed to suspend main thread");
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
activation->module().protectCode(rt);
CONTEXT context;
context.ContextFlags = CONTEXT_CONTROL;
if (!GetThreadContext(thread, &context))
MOZ_CRASH("Failed to get suspended thread context");
RedirectJitCodeToInterruptCheck(rt, &context);
if (!SetThreadContext(thread, &context))
MOZ_CRASH("Failed to set suspended thread context");
if (ResumeThread(thread) == -1)
MOZ_CRASH("Failed to resume main thread");
#else
// On Unix, we instead deliver an async signal to the main thread which
// halts the thread and callers our JitInterruptHandler (which has already
// been installed by EnsureSignalHandlersInstalled).
pthread_t thread = (pthread_t)rt->ownerThreadNative();
pthread_kill(thread, sInterruptSignal);
#endif
}
// This is not supported by clang-cl yet.

View File

@ -28,15 +28,16 @@ struct JSRuntime;
namespace js {
// Returns whether signal handlers for asm.js and for JitRuntime access
// violations have been installed.
// Set up any signal/exception handlers needed to execute code in the given
// runtime. Return whether runtime can:
// - rely on fault handler support for avoiding asm.js heap bounds checks
// - rely on InterruptRunningJitCode to halt running Ion/asm.js from any thread
bool
EnsureAsmJSSignalHandlersInstalled(JSRuntime *rt);
EnsureSignalHandlersInstalled(JSRuntime *rt);
// Force any currently-executing asm.js code to call
// js::HandleExecutionInterrupt.
// Force any currently-executing asm.js code to call HandleExecutionInterrupt.
extern void
RequestInterruptForAsmJSCode(JSRuntime *rt, int interruptMode);
InterruptRunningJitCode(JSRuntime *rt);
// 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

View File

@ -7624,20 +7624,6 @@ CodeGenerator::link(JSContext *cx, types::CompilerConstraintList *constraints)
return false;
discardIonCode.ionScript = ionScript;
// Lock the runtime against interrupt callbacks during the link.
// We don't want an interrupt request to protect the code for the script
// before it has been filled in, as we could segv before the runtime's
// patchable backedges have been fully updated.
JSRuntime::AutoLockForInterrupt lock(cx->runtime());
// Make sure we don't segv while filling in the code, to avoid deadlocking
// inside the signal handler.
cx->runtime()->jitRuntime()->ensureIonCodeAccessible(cx->runtime());
// Implicit interrupts are used only for sequential code. In parallel mode
// use the normal executable allocator so that we cannot segv during
// execution off the main thread.
//
// Also, note that creating the code here during an incremental GC will
// trace the code and mark all GC things it refers to. This captures any
// read barriers which were skipped while compiling the script off thread.

View File

@ -62,29 +62,3 @@ ExecutableAllocator::addSizeOfCode(JS::CodeSizes *sizes) const
}
}
void
ExecutableAllocator::toggleAllCodeAsAccessible(bool accessible)
{
if (!m_pools.initialized())
return;
for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront()) {
ExecutablePool* pool = r.front();
pool->toggleAllCodeAsAccessible(accessible);
}
}
bool
ExecutableAllocator::codeContains(char* address)
{
if (!m_pools.initialized())
return false;
for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront()) {
ExecutablePool* pool = r.front();
if (pool->codeContains(address))
return true;
}
return false;
}

View File

@ -170,12 +170,6 @@ private:
MOZ_ASSERT(m_end >= m_freePtr);
return m_end - m_freePtr;
}
void toggleAllCodeAsAccessible(bool accessible);
bool codeContains(char* address) {
return address >= m_allocation.pages && address < m_freePtr;
}
};
class ExecutableAllocator {
@ -260,8 +254,6 @@ public:
}
void addSizeOfCode(JS::CodeSizes *sizes) const;
void toggleAllCodeAsAccessible(bool accessible);
bool codeContains(char* address);
void setDestroyCallback(DestroyCallback destroyCallback) {
this->destroyCallback = destroyCallback;

View File

@ -91,18 +91,3 @@ void ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSe
}
#endif
void
ExecutablePool::toggleAllCodeAsAccessible(bool accessible)
{
char* begin = m_allocation.pages;
size_t size = m_freePtr - begin;
if (size) {
// N.B. Some systems, like 32bit Mac OS 10.6, implicitly add PROT_EXEC
// when mprotect'ing memory with any flag other than PROT_NONE. Be
// sure to use PROT_NONE when making inaccessible.
int flags = accessible ? PROT_READ | PROT_WRITE | PROT_EXEC : PROT_NONE;
if (mprotect(begin, size, flags))
MOZ_CRASH();
}
}

View File

@ -251,22 +251,6 @@ void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize);
}
void
ExecutablePool::toggleAllCodeAsAccessible(bool accessible)
{
char* begin = m_allocation.pages;
size_t size = m_freePtr - begin;
if (size) {
// N.B. DEP is not on automatically in Windows XP, so be sure to use
// PAGE_NOACCESS instead of PAGE_READWRITE when making inaccessible.
DWORD oldProtect;
int flags = accessible ? PAGE_EXECUTE_READWRITE : PAGE_NOACCESS;
if (!VirtualProtect(begin, size, flags, &oldProtect))
MOZ_CRASH();
}
}
#if ENABLE_ASSEMBLER_WX_EXCLUSIVE
#error "ASSEMBLER_WX_EXCLUSIVE not yet suported on this platform."
#endif

View File

@ -167,7 +167,7 @@ JitRuntime::JitRuntime()
baselineDebugModeOSRHandler_(nullptr),
functionWrappers_(nullptr),
osrTempData_(nullptr),
ionCodeProtected_(false),
mutatingBackedgeList_(false),
ionReturnOverride_(MagicValue(JS_ARG_POISON)),
jitcodeGlobalTable_(nullptr)
{
@ -191,7 +191,6 @@ bool
JitRuntime::initialize(JSContext *cx)
{
MOZ_ASSERT(cx->runtime()->currentThreadHasExclusiveAccess());
MOZ_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock());
AutoCompartment ac(cx, cx->atomsCompartment());
@ -366,85 +365,17 @@ JitRuntime::freeOsrTempData()
ExecutableAllocator *
JitRuntime::createIonAlloc(JSContext *cx)
{
MOZ_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock());
ionAlloc_ = js_new<ExecutableAllocator>();
if (!ionAlloc_)
js_ReportOutOfMemory(cx);
return ionAlloc_;
}
void
JitRuntime::ensureIonCodeProtected(JSRuntime *rt)
{
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
if (!rt->signalHandlersInstalled() || ionCodeProtected_ || !ionAlloc_)
return;
// Protect all Ion code in the runtime to trigger an access violation the
// next time any of it runs on the main thread.
ionAlloc_->toggleAllCodeAsAccessible(false);
ionCodeProtected_ = true;
}
bool
JitRuntime::handleAccessViolation(JSRuntime *rt, void *faultingAddress)
{
if (!rt->signalHandlersInstalled() || !ionAlloc_ || !ionAlloc_->codeContains((char *) faultingAddress))
return false;
// All places where the interrupt lock is taken must either ensure that Ion
// code memory won't be accessed within, or call ensureIonCodeAccessible to
// render the memory safe for accessing. Otherwise taking the lock below
// will deadlock the process.
MOZ_ASSERT(!rt->currentThreadOwnsInterruptLock());
// Taking this lock is necessary to prevent the interrupting thread from marking
// the memory as inaccessible while we are patching backedges. This will cause us
// to SEGV while still inside the signal handler, and the process will terminate.
JSRuntime::AutoLockForInterrupt lock(rt);
// Ion code in the runtime faulted after it was made inaccessible. Reset
// the code privileges and patch all loop backedges to perform an interrupt
// check instead.
ensureIonCodeAccessible(rt);
return true;
}
void
JitRuntime::ensureIonCodeAccessible(JSRuntime *rt)
{
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
// This can only be called on the main thread and while handling signals,
// which happens on a separate thread in OS X.
#ifndef XP_MACOSX
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
#endif
if (ionCodeProtected_) {
ionAlloc_->toggleAllCodeAsAccessible(true);
ionCodeProtected_ = false;
}
if (rt->hasPendingInterrupt()) {
// The interrupt handler needs to be invoked by this thread, but we may
// be inside a signal handler and have no idea what is above us on the
// stack (probably we are executing Ion code at an arbitrary point, but
// we could be elsewhere, say repatching a jump for an IonCache).
// Patch all backedges in the runtime so they will invoke the interrupt
// handler the next time they execute.
patchIonBackedges(rt, BackedgeInterruptCheck);
}
}
void
JitRuntime::patchIonBackedges(JSRuntime *rt, BackedgeTarget target)
{
#ifndef XP_MACOSX
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
#endif
MOZ_ASSERT_IF(target == BackedgeLoopHeader, mutatingBackedgeList_);
MOZ_ASSERT_IF(target == BackedgeInterruptCheck, !mutatingBackedgeList_);
// Patch all loop backedges in Ion code so that they either jump to the
// normal loop header or to an interrupt handler each time they run.
@ -460,47 +391,6 @@ JitRuntime::patchIonBackedges(JSRuntime *rt, BackedgeTarget target)
}
}
void
jit::RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode)
{
JitRuntime *jitRuntime = rt->jitRuntime();
if (!jitRuntime)
return;
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
// The mechanism for interrupting normal ion code varies depending on how
// the interrupt is being requested.
switch (mode) {
case JSRuntime::RequestInterruptMainThread:
// When requesting an interrupt from the main thread, Ion loop
// backedges can be patched directly. Make sure we don't segv while
// patching the backedges, to avoid deadlocking inside the signal
// handler.
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
jitRuntime->ensureIonCodeAccessible(rt);
break;
case JSRuntime::RequestInterruptAnyThread:
// When requesting an interrupt from off the main thread, protect
// Ion code memory so that the main thread will fault and enter a
// signal handler when trying to execute the code. The signal
// handler will unprotect the code and patch loop backedges so
// that the interrupt handler is invoked afterwards.
jitRuntime->ensureIonCodeProtected(rt);
break;
case JSRuntime::RequestInterruptAnyThreadDontStopIon:
case JSRuntime::RequestInterruptAnyThreadForkJoin:
// The caller does not require Ion code to be interrupted.
// Nothing more needs to be done.
break;
default:
MOZ_CRASH("Bad interrupt mode");
}
}
JitCompartment::JitCompartment()
: stubCodes_(nullptr),
baselineCallReturnAddr_(nullptr),
@ -870,10 +760,6 @@ JitCode::trace(JSTracer *trc)
void
JitCode::finalize(FreeOp *fop)
{
// Make sure this can't race with an interrupting thread, which may try
// to read the contents of the pool we are releasing references in.
MOZ_ASSERT(fop->runtime()->currentThreadOwnsInterruptLock());
// If this jitcode has a bytecode map, de-register it.
if (hasBytecodeMap_) {
MOZ_ASSERT(fop->runtime()->jitRuntime()->hasJitcodeGlobalTable());
@ -883,7 +769,7 @@ JitCode::finalize(FreeOp *fop)
// Buffer can be freed at any time hereafter. Catch use-after-free bugs.
// Don't do this if the Ion code is protected, as the signal handler will
// deadlock trying to reacquire the interrupt lock.
if (fop->runtime()->jitRuntime() && !fop->runtime()->jitRuntime()->ionCodeProtected())
if (fop->runtime()->jitRuntime())
memset(code_, JS_SWEPT_CODE_PATTERN, bufferSize_);
code_ = nullptr;
@ -1144,6 +1030,9 @@ IonScript::copyPatchableBackedges(JSContext *cx, JitCode *code,
PatchableBackedgeInfo *backedges,
MacroAssembler &masm)
{
JitRuntime *jrt = cx->runtime()->jitRuntime();
JitRuntime::AutoMutateBackedges amb(jrt);
for (size_t i = 0; i < backedgeEntries_; i++) {
PatchableBackedgeInfo &info = backedges[i];
PatchableBackedge *patchableBackedge = &backedgeList()[i];
@ -1167,7 +1056,7 @@ IonScript::copyPatchableBackedges(JSContext *cx, JitCode *code,
else
PatchBackedge(backedge, loopHeader, JitRuntime::BackedgeLoopHeader);
cx->runtime()->jitRuntime()->addPatchableBackedge(patchableBackedge);
jrt->addPatchableBackedge(patchableBackedge);
}
}
@ -1358,11 +1247,10 @@ IonScript::unlinkFromRuntime(FreeOp *fop)
// The writes to the executable buffer below may clobber backedge jumps, so
// make sure that those backedges are unlinked from the runtime and not
// reclobbered with garbage if an interrupt is requested.
JSRuntime *rt = fop->runtime();
for (size_t i = 0; i < backedgeEntries_; i++) {
PatchableBackedge *backedge = &backedgeList()[i];
rt->jitRuntime()->removePatchableBackedge(backedge);
}
JitRuntime *jrt = fop->runtime()->jitRuntime();
JitRuntime::AutoMutateBackedges amb(jrt);
for (size_t i = 0; i < backedgeEntries_; i++)
jrt->removePatchableBackedge(&backedgeList()[i]);
// Clear the list of backedges, so that this method is idempotent. It is
// called during destruction, and may be additionally called when the

View File

@ -201,8 +201,6 @@ size_t SizeOfIonData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf);
void DestroyIonScripts(FreeOp *fop, JSScript *script);
void TraceIonScripts(JSTracer* trc, JSScript *script);
void RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode);
bool RematerializeAllFrames(JSContext *cx, JSCompartment *comp);
bool UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp,
AutoDebugModeInvalidation &invalidate);

View File

@ -82,10 +82,6 @@ class Linker
}
JitCode *newCodeForIonScript(JSContext *cx) {
// The caller must lock the runtime against interrupt requests, as the
// thread requesting an interrupt may use the executable allocator below.
MOZ_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock());
ExecutableAllocator *alloc = cx->runtime()->jitRuntime()->getIonAlloc(cx);
if (!alloc)
return nullptr;

View File

@ -217,12 +217,11 @@ class JitRuntime
// (after returning from JIT code).
uint8_t *osrTempData_;
// Whether all Ion code in the runtime is protected, and will fault if it
// is accessed.
bool ionCodeProtected_;
// If signal handlers are installed, this contains all loop backedges for
// IonScripts in the runtime.
// List of all backedges in all Ion code. The backedge edge list is accessed
// asynchronously when the main thread is paused and mutatingBackedgeList_
// is false. Thus, the list must only be mutated while mutatingBackedgeList_
// is true.
volatile bool mutatingBackedgeList_;
InlineList<PatchableBackedge> backedgeList_;
// In certain cases, we want to optimize certain opcodes to typed instructions,
@ -274,29 +273,39 @@ class JitRuntime
ExecutableAllocator *execAlloc() const {
return execAlloc_;
}
ExecutableAllocator *getIonAlloc(JSContext *cx) {
MOZ_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock());
return ionAlloc_ ? ionAlloc_ : createIonAlloc(cx);
}
ExecutableAllocator *ionAlloc(JSRuntime *rt) {
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
return ionAlloc_;
}
bool hasIonAlloc() const {
return !!ionAlloc_;
}
bool ionCodeProtected() {
return ionCodeProtected_;
}
class AutoMutateBackedges
{
JitRuntime *jrt_;
public:
AutoMutateBackedges(JitRuntime *jrt) : jrt_(jrt) {
MOZ_ASSERT(!jrt->mutatingBackedgeList_);
jrt->mutatingBackedgeList_ = true;
}
~AutoMutateBackedges() {
MOZ_ASSERT(jrt_->mutatingBackedgeList_);
jrt_->mutatingBackedgeList_ = false;
}
};
bool mutatingBackedgeList() const {
return mutatingBackedgeList_;
}
void addPatchableBackedge(PatchableBackedge *backedge) {
MOZ_ASSERT(mutatingBackedgeList_);
backedgeList_.pushFront(backedge);
}
void removePatchableBackedge(PatchableBackedge *backedge) {
MOZ_ASSERT(mutatingBackedgeList_);
backedgeList_.remove(backedge);
}
@ -305,12 +314,8 @@ class JitRuntime
BackedgeInterruptCheck
};
void ensureIonCodeProtected(JSRuntime *rt);
void ensureIonCodeAccessible(JSRuntime *rt);
void patchIonBackedges(JSRuntime *rt, BackedgeTarget target);
bool handleAccessViolation(JSRuntime *rt, void *faultingAddress);
JitCode *getVMWrapper(const VMFunction &f) const;
JitCode *debugTrapHandler(JSContext *cx);
JitCode *getBaselineDebugModeOSRHandler(JSContext *cx);

View File

@ -522,15 +522,11 @@ InterruptCheck(JSContext *cx)
{
gc::MaybeVerifyBarriers(cx);
// Fix loop backedges so that they do not invoke the interrupt again.
// No lock is held here and it's possible we could segv in the middle here
// and end up with a state where some fraction of the backedges point to
// the interrupt handler and some don't. This is ok since the interrupt
// is definitely about to be handled; if there are still backedges
// afterwards which point to the interrupt handler, the next time they are
// taken the backedges will just be reset again.
cx->runtime()->jitRuntime()->patchIonBackedges(cx->runtime(),
JitRuntime::BackedgeLoopHeader);
{
JitRuntime *jrt = cx->runtime()->jitRuntime();
JitRuntime::AutoMutateBackedges amb(jrt);
jrt->patchIonBackedges(cx->runtime(), JitRuntime::BackedgeLoopHeader);
}
return CheckForInterrupt(cx);
}

View File

@ -5107,7 +5107,7 @@ JS_GetInterruptCallback(JSRuntime *rt)
JS_PUBLIC_API(void)
JS_RequestInterruptCallback(JSRuntime *rt)
{
rt->requestInterrupt(JSRuntime::RequestInterruptAnyThread);
rt->requestInterrupt(JSRuntime::RequestInterruptUrgent);
}
JS_PUBLIC_API(bool)

View File

@ -293,7 +293,6 @@ struct ThreadSafeContext : ContextFriendFields,
void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
void *stackLimitAddressForJitCode(StackKind kind);
size_t gcSystemPageSize() { return gc::SystemPageSize(); }
bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); }
bool canUseSignalHandlers() const { return runtime_->canUseSignalHandlers(); }
bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; }
bool jitSupportsSimd() const { return runtime_->jitSupportsSimd; }

View File

@ -33,6 +33,7 @@
using namespace js;
using namespace js::gc;
using namespace js::jit;
using mozilla::DebugOnly;
@ -125,18 +126,18 @@ JSRuntime::createJitRuntime(JSContext *cx)
// accessed by other threads with an exclusive context.
AutoLockForExclusiveAccess atomsLock(cx);
// The runtime will only be created on its owning thread, but reads of a
// runtime's jitRuntime() can occur when another thread is requesting an
// interrupt.
AutoLockForInterrupt lock(this);
MOZ_ASSERT(!jitRuntime_);
jitRuntime_ = cx->new_<jit::JitRuntime>();
if (!jitRuntime_)
jit::JitRuntime *jrt = cx->new_<jit::JitRuntime>();
if (!jrt)
return nullptr;
// Protect jitRuntime_ from being observed (by InterruptRunningJitCode)
// while it is being initialized. Unfortunately, initialization depends on
// jitRuntime_ being non-null, so we can't just wait to assign jitRuntime_.
JitRuntime::AutoMutateBackedges amb(jrt);
jitRuntime_ = jrt;
if (!jitRuntime_->initialize(cx)) {
js_delete(jitRuntime_);
jitRuntime_ = nullptr;

View File

@ -645,12 +645,7 @@ FinalizeArenas(FreeOp *fop,
case FINALIZE_SYMBOL:
return FinalizeTypedArenas<JS::Symbol>(fop, src, dest, thingKind, budget, keepArenas);
case FINALIZE_JITCODE:
{
// JitCode finalization may release references on an executable
// allocator that is accessed when requesting interrupts.
JSRuntime::AutoLockForInterrupt lock(fop->runtime());
return FinalizeTypedArenas<jit::JitCode>(fop, src, dest, thingKind, budget, keepArenas);
}
default:
MOZ_CRASH("Invalid alloc kind");
}
@ -1063,7 +1058,7 @@ class js::gc::AutoMaybeStartBackgroundAllocation
}
~AutoMaybeStartBackgroundAllocation() {
if (runtime && !runtime->currentThreadOwnsInterruptLock())
if (runtime)
runtime->gc.startBackgroundAllocTaskIfIdle();
}
};
@ -3000,7 +2995,7 @@ GCRuntime::requestMajorGC(JS::gcreason::Reason reason)
majorGCRequested = true;
majorGCTriggerReason = reason;
rt->requestInterrupt(JSRuntime::RequestInterruptMainThread);
rt->requestInterrupt(JSRuntime::RequestInterruptUrgent);
}
void
@ -3012,7 +3007,7 @@ GCRuntime::requestMinorGC(JS::gcreason::Reason reason)
minorGCRequested = true;
minorGCTriggerReason = reason;
rt->requestInterrupt(JSRuntime::RequestInterruptMainThread);
rt->requestInterrupt(JSRuntime::RequestInterruptUrgent);
}
bool
@ -3031,10 +3026,6 @@ GCRuntime::triggerGC(JS::gcreason::Reason reason)
if (!CurrentThreadCanAccessRuntime(rt))
return false;
/* Don't trigger GCs when allocating under the interrupt callback lock. */
if (rt->currentThreadOwnsInterruptLock())
return false;
/* GC is already running. */
if (rt->isHeapCollecting())
return false;
@ -3094,10 +3085,6 @@ GCRuntime::triggerZoneGC(Zone *zone, JS::gcreason::Reason reason)
if (zone->usedByExclusiveThread)
return false;
/* Don't trigger GCs when allocating under the interrupt callback lock. */
if (rt->currentThreadOwnsInterruptLock())
return false;
/* GC is already running. */
if (rt->isHeapCollecting())
return false;
@ -5344,10 +5331,8 @@ GCRuntime::endSweepPhase(bool lastGC)
if (jit::ExecutableAllocator *execAlloc = rt->maybeExecAlloc())
execAlloc->purge();
if (rt->jitRuntime() && rt->jitRuntime()->hasIonAlloc()) {
JSRuntime::AutoLockForInterrupt lock(rt);
if (rt->jitRuntime() && rt->jitRuntime()->hasIonAlloc())
rt->jitRuntime()->ionAlloc(rt)->purge();
}
/*
* This removes compartments from rt->compartment, so we do it last to make

View File

@ -1667,9 +1667,7 @@ ForkJoinShared::setAbortFlagAndRequestInterrupt(bool fatal)
abort_ = true;
fatal_ = fatal_ || fatal;
// Note: The ForkJoin trigger here avoids the expensive memory protection needed to
// interrupt Ion code compiled for sequential execution.
cx_->runtime()->requestInterrupt(JSRuntime::RequestInterruptAnyThreadForkJoin);
cx_->runtime()->requestInterrupt(JSRuntime::RequestInterruptCanWait);
}
void

View File

@ -1065,7 +1065,7 @@ HelperThread::handleIonWorkload()
// at the next interrupt callback. Don't interrupt Ion code for this, as
// this incorporation can be delayed indefinitely without affecting
// performance as long as the main thread is actually executing Ion code.
rt->requestInterrupt(JSRuntime::RequestInterruptAnyThreadDontStopIon);
rt->requestInterrupt(JSRuntime::RequestInterruptCanWait);
// Notify the main thread in case it is waiting for the compilation to finish.
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);

View File

@ -27,6 +27,7 @@
#include "jsobj.h"
#include "jsscript.h"
#include "jswatchpoint.h"
#include "jswin.h"
#include "jswrapper.h"
#include "asmjs/AsmJSSignalHandlers.h"
@ -140,8 +141,6 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
interruptPar_(false),
handlingSignal(false),
interruptCallback(nullptr),
interruptLock(nullptr),
interruptLockOwner(nullptr),
exclusiveAccessLock(nullptr),
exclusiveAccessOwner(nullptr),
mainThreadHasExclusiveAccess(false),
@ -152,6 +151,7 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
defaultVersion_(JSVERSION_DEFAULT),
futexAPI_(nullptr),
ownerThread_(nullptr),
ownerThreadNative_(0),
tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
execAlloc_(nullptr),
jitRuntime_(nullptr),
@ -256,9 +256,19 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
{
ownerThread_ = PR_GetCurrentThread();
interruptLock = PR_NewLock();
if (!interruptLock)
return false;
// Get a platform-native handle for the owner thread, used by
// js::InterruptRunningJitCode to halt the runtime's main thread.
#ifdef XP_WIN
size_t openFlags = THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME;
HANDLE self = OpenThread(openFlags, false, GetCurrentThreadId());
if (!self)
MOZ_CRASH("Unable to open thread handle");
static_assert(sizeof(HANDLE) <= sizeof(ownerThreadNative_), "need bigger field");
ownerThreadNative_ = (size_t)self;
#else
static_assert(sizeof(pthread_t) <= sizeof(ownerThreadNative_), "need bigger field");
ownerThreadNative_ = (size_t)pthread_self();
#endif
exclusiveAccessLock = PR_NewLock();
if (!exclusiveAccessLock)
@ -325,7 +335,7 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint();
jitSupportsSimd = js::jit::JitSupportsSimd();
signalHandlersInstalled_ = EnsureAsmJSSignalHandlersInstalled(this);
signalHandlersInstalled_ = EnsureSignalHandlersInstalled(this);
canUseSignalHandlers_ = signalHandlersInstalled_ && !SignalBasedTriggersDisabled();
if (!spsProfiler.init())
@ -391,10 +401,6 @@ JSRuntime::~JSRuntime()
MOZ_ASSERT(!numExclusiveThreads);
mainThreadHasExclusiveAccess = true;
MOZ_ASSERT(!interruptLockOwner);
if (interruptLock)
PR_DestroyLock(interruptLock);
/*
* Even though all objects in the compartment are dead, we may have keep
* some filenames around because of gcKeepAtoms.
@ -444,6 +450,11 @@ JSRuntime::~JSRuntime()
MOZ_ASSERT(oldCount > 0);
js::TlsPerThreadData.set(nullptr);
#ifdef XP_WIN
if (ownerThreadNative_)
CloseHandle((HANDLE)ownerThreadNative_);
#endif
}
void
@ -500,13 +511,9 @@ JSRuntime::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Runtim
if (execAlloc_)
execAlloc_->addSizeOfCode(&rtSizes->code);
{
AutoLockForInterrupt lock(this);
if (jitRuntime()) {
if (jit::ExecutableAllocator *ionAlloc = jitRuntime()->ionAlloc(this))
ionAlloc->addSizeOfCode(&rtSizes->code);
}
}
if (jitRuntime() && jitRuntime()->ionAlloc(this))
jitRuntime()->ionAlloc(this)->addSizeOfCode(&rtSizes->code);
rtSizes->gc.marker += gc.marker.sizeOfExcludingThis(mallocSizeOf);
#ifdef JSGC_GENERATIONAL
@ -611,11 +618,8 @@ JSRuntime::requestInterrupt(InterruptMode mode)
interruptPar_ = true;
mainThread.jitStackLimit_ = UINTPTR_MAX;
if (canUseSignalHandlers()) {
AutoLockForInterrupt lock(this);
RequestInterruptForAsmJSCode(this, mode);
jit::RequestInterruptForIonCode(this, mode);
}
if (mode == JSRuntime::RequestInterruptUrgent)
InterruptRunningJitCode(this);
}
bool
@ -836,8 +840,6 @@ JSRuntime::assertCanLock(RuntimeLock which)
MOZ_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread());
case HelperThreadStateLock:
MOZ_ASSERT(!HelperThreadState().isLocked());
case InterruptLock:
MOZ_ASSERT(!currentThreadOwnsInterruptLock());
case GCLock:
gc.assertCanLock();
break;

View File

@ -452,7 +452,6 @@ AtomStateOffsetToName(const JSAtomState &atomState, size_t offset)
enum RuntimeLock {
ExclusiveAccessLock,
HelperThreadStateLock,
InterruptLock,
GCLock
};
@ -541,14 +540,6 @@ class PerThreadData : public PerThreadDataFriendFields
TraceLogger *traceLogger;
#endif
/*
* asm.js maintains a stack of AsmJSModule activations (see AsmJS.h). This
* stack is used by JSRuntime::requestInterrupt to stop long-running asm.js
* without requiring dynamic polling operations in the generated
* code. Since requestInterrupt may run on a separate thread than the
* JSRuntime's owner thread all reads/writes must be synchronized (by
* rt->interruptLock).
*/
private:
friend class js::Activation;
friend class js::ActivationIterator;
@ -566,11 +557,11 @@ class PerThreadData : public PerThreadDataFriendFields
/*
* Points to the most recent profiling activation running on the
* thread. Protected by rt->interruptLock.
* thread.
*/
js::Activation * volatile profilingActivation_;
/* See AsmJSActivation comment. Protected by rt->interruptLock. */
/* See AsmJSActivation comment. */
js::AsmJSActivation * volatile asmJSActivationStack_;
/* Pointer to the current AutoFlushICache. */
@ -714,10 +705,8 @@ struct JSRuntime : public JS::shadow::Runtime,
public:
enum InterruptMode {
RequestInterruptMainThread,
RequestInterruptAnyThread,
RequestInterruptAnyThreadDontStopIon,
RequestInterruptAnyThreadForkJoin
RequestInterruptUrgent,
RequestInterruptCanWait
};
// Any thread can call requestInterrupt() to request that the main JS thread
@ -770,37 +759,6 @@ struct JSRuntime : public JS::shadow::Runtime,
void assertCanLock(js::RuntimeLock which) {}
#endif
private:
/*
* Lock taken when triggering an interrupt from another thread.
* Protects all data that is touched in this process.
*/
PRLock *interruptLock;
PRThread *interruptLockOwner;
public:
class AutoLockForInterrupt {
JSRuntime *rt;
public:
explicit AutoLockForInterrupt(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
rt->assertCanLock(js::InterruptLock);
PR_Lock(rt->interruptLock);
rt->interruptLockOwner = PR_GetCurrentThread();
}
~AutoLockForInterrupt() {
MOZ_ASSERT(rt->currentThreadOwnsInterruptLock());
rt->interruptLockOwner = nullptr;
PR_Unlock(rt->interruptLock);
}
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
bool currentThreadOwnsInterruptLock() {
return interruptLockOwner == PR_GetCurrentThread();
}
private:
/*
* Lock taken when using per-runtime or per-zone data that could otherwise
@ -852,9 +810,14 @@ struct JSRuntime : public JS::shadow::Runtime,
private:
/* See comment for JS_AbortIfWrongThread in jsapi.h. */
void *ownerThread_;
size_t ownerThreadNative_;
friend bool js::CurrentThreadCanAccessRuntime(JSRuntime *rt);
public:
size_t ownerThreadNative() const {
return ownerThreadNative_;
}
/* Temporary arena pool used while compiling and decompiling. */
static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024;
js::LifoAlloc tempLifoAlloc;
@ -1089,16 +1052,15 @@ struct JSRuntime : public JS::shadow::Runtime,
#endif
private:
// Whether asm.js signal handlers have been installed and can be used for
// performing interrupt checks in loops.
// Whether EnsureSignalHandlersInstalled succeeded in installing all the
// relevant handlers for this platform.
bool signalHandlersInstalled_;
// Whether we should use them or they have been disabled for making
// debugging easier. If signal handlers aren't installed, it is set to false.
bool canUseSignalHandlers_;
public:
bool signalHandlersInstalled() const {
return signalHandlersInstalled_;
}
bool canUseSignalHandlers() const {
return canUseSignalHandlers_;
}

View File

@ -1558,11 +1558,7 @@ AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module)
module.activation() = this;
prevAsmJS_ = cx->mainThread().asmJSActivationStack_;
{
JSRuntime::AutoLockForInterrupt lock(cx->runtime());
cx->mainThread().asmJSActivationStack_ = this;
}
cx->mainThread().asmJSActivationStack_ = this;
// Now that the AsmJSActivation is fully initialized, make it visible to
// asynchronous profiling.
@ -1585,7 +1581,6 @@ AsmJSActivation::~AsmJSActivation()
JSContext *cx = cx_->asJSContext();
MOZ_ASSERT(cx->mainThread().asmJSActivationStack_ == this);
JSRuntime::AutoLockForInterrupt lock(cx->runtime());
cx->mainThread().asmJSActivationStack_ = prevAsmJS_;
}
@ -1609,7 +1604,6 @@ void
Activation::registerProfiling()
{
MOZ_ASSERT(isProfiling());
JSRuntime::AutoLockForInterrupt lock(cx_->asJSContext()->runtime());
cx_->perThreadData->profilingActivation_ = this;
}
@ -1617,7 +1611,6 @@ void
Activation::unregisterProfiling()
{
MOZ_ASSERT(isProfiling());
JSRuntime::AutoLockForInterrupt lock(cx_->asJSContext()->runtime());
MOZ_ASSERT(cx_->perThreadData->profilingActivation_ == this);
cx_->perThreadData->profilingActivation_ = prevProfiling_;
}