mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1091912 - stop using mprotect to halt Ion/asm.js execution (r=bhackett)
--HG-- extra : rebase_source : 3642ce948e577bca54008d3c55206184b107d023
This commit is contained in:
parent
e0f61bdabc
commit
ea143386f2
@ -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)
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user