Bug 844196 - Add SetJitExceptionHandler friend API and call it if unwinding reaches JIT code (r=jandem,dmajor)
--- a/js/src/jit/ExecutableAllocator.h
+++ b/js/src/jit/ExecutableAllocator.h
@@ -184,25 +184,30 @@ class ExecutableAllocator {
DestroyCallback destroyCallback;
public:
ExecutableAllocator()
: destroyCallback(NULL)
{
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());
}
~ExecutableAllocator()
{
for (size_t i = 0; i < m_smallPools.length(); i++)
--- a/js/src/jit/ExecutableAllocatorWin.cpp
+++ b/js/src/jit/ExecutableAllocatorWin.cpp
@@ -20,22 +20,22 @@
* 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"
-#include "jswin.h"
-
using namespace js::jit;
uint64_t ExecutableAllocator::rngSeed;
size_t ExecutableAllocator::determinePageSize()
{
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
@@ -80,28 +80,154 @@ 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 = &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);
}
ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
{
void *allocation = NULL;
// Randomization disabled to avoid a performance fault on x64 builds.
// See bug 728623.
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2661,16 +2661,44 @@ SetPropertyIgnoringNamedGetter(JSContext
JS_FRIEND_API(void)
ReportErrorWithId(JSContext *cx, const char *msg, JS::HandleId id);
// This function is for one specific use case, please don't use this for anything else!
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)
js_DefineOwnProperty(JSContext *cx, JSObject *objArg, jsid idArg,
JS::Handle<JSPropertyDescriptor> descriptor, bool *bp);
extern JS_FRIEND_API(bool)
js_ReportIsNotFunction(JSContext *cx, JS::HandleValue v);