blob: fe068b908140cb7461c7db45367d28ed1485dd25 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
*
* Copyright (C) 2008 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "mozilla/WindowsVersion.h"
#include "jsfriendapi.h"
#include "jsmath.h"
#include "jswin.h"
#include "jit/ExecutableAllocator.h"
using namespace js::jit;
size_t
ExecutableAllocator::determinePageSize()
{
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
return system_info.dwPageSize;
}
void*
ExecutableAllocator::computeRandomAllocationAddress()
{
/*
* Inspiration is V8's OS::Allocate in platform-win32.cc.
*
* VirtualAlloc takes 64K chunks out of the virtual address space, so we
* keep 16b alignment.
*
* x86: V8 comments say that keeping addresses in the [64MiB, 1GiB) range
* tries to avoid system default DLL mapping space. In the end, we get 13
* bits of randomness in our selection.
* x64: [2GiB, 4TiB), with 25 bits of randomness.
*/
#ifdef JS_CPU_X64
static const uintptr_t base = 0x0000000080000000;
static const uintptr_t mask = 0x000003ffffff0000;
#elif defined(JS_CPU_X86)
static const uintptr_t base = 0x04000000;
static const uintptr_t mask = 0x3fff0000;
#else
# error "Unsupported architecture"
#endif
if (randomNumberGenerator.isNothing()) {
mozilla::Array<uint64_t, 2> seed;
js::GenerateXorShift128PlusSeed(seed);
randomNumberGenerator.emplace(seed[0], seed[1]);
}
uint64_t rand = randomNumberGenerator.ref().next();
return (void*) (base | (rand & mask));
}
static bool
RandomizeIsBrokenImpl()
{
// We disable everything before Vista, for now.
return !mozilla::IsVistaOrLater();
}
static bool
RandomizeIsBroken()
{
// Use the compiler's intrinsic guards for |static type value = expr| to avoid some potential
// races if runtimes are created from multiple threads.
static int result = RandomizeIsBrokenImpl();
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 = JS_FUNC_TO_DATA_PTR(void*, 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);
#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);
}
ExecutablePool::Allocation
ExecutableAllocator::systemAlloc(size_t n)
{
void* allocation = nullptr;
if (!RandomizeIsBroken()) {
void* randomAddress = computeRandomAllocationAddress();
allocation = AllocateExecutableMemory(randomAddress, n, initialProtectionFlags(Executable),
"js-jit-code", pageSize);
}
if (!allocation) {
allocation = AllocateExecutableMemory(nullptr, n, initialProtectionFlags(Executable),
"js-jit-code", pageSize);
}
ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
return alloc;
}
void
ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
{
DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize);
}
void
ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
{
MOZ_ASSERT(nonWritableJitCode);
MOZ_ASSERT(pageSize);
// Calculate the start of the page containing this region,
// and account for this extra memory within size.
intptr_t startPtr = reinterpret_cast<intptr_t>(start);
intptr_t pageStartPtr = startPtr & ~(pageSize - 1);
void* pageStart = reinterpret_cast<void*>(pageStartPtr);
size += (startPtr - pageStartPtr);
// Round size up
size += (pageSize - 1);
size &= ~(pageSize - 1);
DWORD oldProtect;
int flags = (setting == Writable) ? PAGE_READWRITE : PAGE_EXECUTE_READ;
if (!VirtualProtect(pageStart, size, flags, &oldProtect))
MOZ_CRASH();
}
/* static */ unsigned
ExecutableAllocator::initialProtectionFlags(ProtectionSetting protection)
{
if (!nonWritableJitCode)
return PAGE_EXECUTE_READWRITE;
return (protection == Writable) ? PAGE_READWRITE : PAGE_EXECUTE_READ;
}