mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
13a564de70
commit
a47388af8c
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user