Bug 844196 - Add SetJitExceptionHandler friend API and call it if unwinding reaches JIT code (r=jandem,dmajor)

--HG--
extra : rebase_source : 554cd690441562977aa4756a6fc2a0c0884a13b9
This commit is contained in:
Luke Wagner 2014-10-08 14:24:15 -05:00
parent 13a564de70
commit a47388af8c
3 changed files with 170 additions and 11 deletions

View File

@ -189,15 +189,20 @@ public:
{
if (!pageSize) {
pageSize = determinePageSize();
/*
* On Windows, VirtualAlloc effectively allocates in 64K chunks.
* (Technically, it allocates in page chunks, but the starting
* address is always a multiple of 64K, so each allocation uses up
* 64K of address space.) So a size less than that would be
* pointless. But it turns out that 64KB is a reasonable size for
* all platforms. (This assumes 4KB pages.)
*/
// On Windows, VirtualAlloc effectively allocates in 64K chunks.
// (Technically, it allocates in page chunks, but the starting
// address is always a multiple of 64K, so each allocation uses up
// 64K of address space.) So a size less than that would be
// pointless. But it turns out that 64KB is a reasonable size for
// all platforms. (This assumes 4KB pages.) On 64-bit windows,
// AllocateExecutableMemory prepends an extra page for structured
// exception handling data (see comments in function) onto whatever
// is passed in, so subtract one page here.
#if defined(JS_CPU_X64) && defined(XP_WIN)
largeAllocSize = pageSize * 15;
#else
largeAllocSize = pageSize * 16;
#endif
}
MOZ_ASSERT(m_smallPools.empty());

View File

@ -25,12 +25,12 @@
#include "mozilla/WindowsVersion.h"
#include "jsfriendapi.h"
#include "jsmath.h"
#include "jswin.h"
#include "jit/ExecutableAllocator.h"
#include "jswin.h"
using namespace js::jit;
uint64_t ExecutableAllocator::rngSeed;
@ -85,18 +85,144 @@ RandomizeIsBroken()
return !!result;
}
#ifdef JS_CPU_X64
static js::JitExceptionHandler sJitExceptionHandler;
JS_FRIEND_API(void)
js::SetJitExceptionHandler(JitExceptionHandler handler)
{
MOZ_ASSERT(!sJitExceptionHandler);
sJitExceptionHandler = handler;
}
// From documentation for UNWIND_INFO on
// http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
struct UnwindInfo
{
uint8_t version : 3;
uint8_t flags : 5;
uint8_t sizeOfPrologue;
uint8_t countOfUnwindCodes;
uint8_t frameRegister : 4;
uint8_t frameOffset : 4;
ULONG exceptionHandler;
};
static const unsigned ThunkLength = 12;
struct ExceptionHandlerRecord
{
RUNTIME_FUNCTION runtimeFunction;
UnwindInfo unwindInfo;
uint8_t thunk[ThunkLength];
};
// This function must match the function pointer type PEXCEPTION_HANDLER
// mentioned in:
// http://msdn.microsoft.com/en-us/library/ssa62fwe.aspx.
// This type is rather elusive in documentation; Wine is the best I've found:
// http://source.winehq.org/source/include/winnt.h
static DWORD
ExceptionHandler(PEXCEPTION_RECORD exceptionRecord, _EXCEPTION_REGISTRATION_RECORD *,
PCONTEXT context, _EXCEPTION_REGISTRATION_RECORD **)
{
return sJitExceptionHandler(exceptionRecord, context);
}
// For an explanation of the problem being solved here, see
// SetJitExceptionFilter in jsfriendapi.h.
static bool
RegisterExecutableMemory(void *p, size_t bytes, size_t pageSize)
{
ExceptionHandlerRecord *r = reinterpret_cast<ExceptionHandlerRecord*>(p);
// All these fields are specified to be offsets from the base of the
// executable code (which is 'p'), even if they have 'Address' in their
// names. In particular, exceptionHandler is a ULONG offset which is a
// 32-bit integer. Since 'p' can be farther than INT32_MAX away from
// sJitExceptionHandler, we must generate a little thunk inside the
// record. The record is put on its own page so that we can take away write
// access to protect against accidental clobbering.
r->runtimeFunction.BeginAddress = pageSize;
r->runtimeFunction.EndAddress = (DWORD)bytes;
r->runtimeFunction.UnwindData = offsetof(ExceptionHandlerRecord, unwindInfo);
r->unwindInfo.version = 1;
r->unwindInfo.flags = UNW_FLAG_EHANDLER;
r->unwindInfo.sizeOfPrologue = 0;
r->unwindInfo.countOfUnwindCodes = 0;
r->unwindInfo.frameRegister = 0;
r->unwindInfo.frameOffset = 0;
r->unwindInfo.exceptionHandler = offsetof(ExceptionHandlerRecord, thunk);
// mov imm64, rax
r->thunk[0] = 0x48;
r->thunk[1] = 0xb8;
void *handler = &ExceptionHandler;
memcpy(&r->thunk[2], &handler, 8);
// jmp rax
r->thunk[10] = 0xff;
r->thunk[11] = 0xe0;
DWORD oldProtect;
if (!VirtualProtect(p, pageSize, PAGE_EXECUTE_READ, &oldProtect))
return false;
return RtlAddFunctionTable(&r->runtimeFunction, 1, reinterpret_cast<DWORD64>(p));
}
static void
UnregisterExecutableMemory(void *p, size_t bytes, size_t pageSize)
{
ExceptionHandlerRecord *r = reinterpret_cast<ExceptionHandlerRecord*>(p);
RtlDeleteFunctionTable(&r->runtimeFunction);
}
#endif
void *
js::jit::AllocateExecutableMemory(void *addr, size_t bytes, unsigned permissions, const char *tag,
size_t pageSize)
{
MOZ_ASSERT(bytes % pageSize == 0);
return VirtualAlloc(addr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
MOZ_ASSERT(permissions == PAGE_EXECUTE_READWRITE);
#ifdef JS_CPU_X64
if (sJitExceptionHandler)
bytes += pageSize;
#endif
void *p = VirtualAlloc(addr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
if (!p)
return nullptr;
#ifdef JS_CPU_X64
if (sJitExceptionHandler) {
if (!RegisterExecutableMemory(p, bytes, pageSize)) {
VirtualFree(p, 0, MEM_RELEASE);
return nullptr;
}
p = (uint8_t*)p + pageSize;
}
#endif
return p;
}
void
js::jit::DeallocateExecutableMemory(void *addr, size_t bytes, size_t pageSize)
{
MOZ_ASSERT(bytes % pageSize == 0);
#ifdef JS_CPU_X64
if (sJitExceptionHandler) {
addr = (uint8_t*)addr - pageSize;
UnregisterExecutableMemory(addr, bytes, pageSize);
}
#endif
VirtualFree(addr, 0, MEM_RELEASE);
}

View File

@ -2666,6 +2666,34 @@ extern JS_FRIEND_API(bool)
ExecuteInGlobalAndReturnScope(JSContext *cx, JS::HandleObject obj, JS::HandleScript script,
JS::MutableHandleObject scope);
#if defined(XP_WIN) && defined(_WIN64)
// Parameters use void* types to avoid #including windows.h. The return value of
// this function is returned from the exception handler.
typedef long
(*JitExceptionHandler)(void *exceptionRecord, // PEXECTION_RECORD
void *context); // PCONTEXT
// Windows uses "structured exception handling" to handle faults. When a fault
// occurs, the stack is searched for a handler (similar to C++ exception
// handling). If the search does not find a handler, the "unhandled exception
// filter" is called. Breakpad uses the unhandled exception filter to do crash
// reporting. Unfortunately, on Win64, JIT code on the stack completely throws
// off this unwinding process and prevents the unhandled exception filter from
// being called. The reason is that Win64 requires unwind information be
// registered for all code regions and JIT code has none. While it is possible
// to register full unwind information for JIT code, this is a lot of work (one
// has to be able to recover the frame pointer at any PC) so instead we register
// a handler for all JIT code that simply calls breakpad's unhandled exception
// filter (which will perform crash reporting and then terminate the process).
// This would be wrong if there was an outer __try block that expected to handle
// the fault, but this is not generally allowed.
//
// Gecko must call SetJitExceptionFilter before any JIT code is compiled and
// only once per process.
extern JS_FRIEND_API(void)
SetJitExceptionHandler(JitExceptionHandler handler);
#endif
} /* namespace js */
extern JS_FRIEND_API(bool)