Bug 1484835 - Extend the Windows JIT unwind handler to ARM64 r=luke
authorDavid Major <dmajor@mozilla.com>
Mon, 14 Jan 2019 14:06:24 +0000
changeset 510831 4d932b82695c7ac58901ef0a03f44fed6ce2d1f0
parent 510830 745be69de37d90a0debb060c50ed50717bdcbd13
child 510832 3fd5d8f120c72898ee74e2cd8194ef35880cd794
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1484835
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1484835 - Extend the Windows JIT unwind handler to ARM64 r=luke Because the .xdata format on ARM64 can only encode sizes up to 1M (much too small for our JIT code regions), we register a function table callback to provide RUNTIME_FUNCTIONs at runtime. Windows doesn't seem to care about the size fields on RUNTIME_FUNCTIONs that are created in this way, so the same RUNTIME_FUNCTION can work for any address in the region. We'll set up a generic one in RegisterExecutableMemory and the callback can just return a pointer to it. Differential Revision: https://phabricator.services.mozilla.com/D16261
js/src/jit/ProcessExecutableMemory.cpp
js/src/jsfriendapi.h
toolkit/crashreporter/nsExceptionHandler.cpp
--- a/js/src/jit/ProcessExecutableMemory.cpp
+++ b/js/src/jit/ProcessExecutableMemory.cpp
@@ -39,18 +39,17 @@
 #ifdef MOZ_VALGRIND
 #include <valgrind/valgrind.h>
 #endif
 
 using namespace js;
 using namespace js::jit;
 
 #ifdef XP_WIN
-// TODO: implement the necessary support for AArch64.
-#if defined(HAVE_64BIT_BUILD) && defined(_M_X64)
+#if defined(HAVE_64BIT_BUILD)
 #define NEED_JIT_UNWIND_HANDLING
 #endif
 
 static void* ComputeRandomAllocationAddress() {
   /*
    * Inspiration is V8's OS::Allocate in platform-win32.cc.
    *
    * VirtualAlloc takes 64K chunks out of the virtual address space, so we
@@ -78,29 +77,51 @@ static void* ComputeRandomAllocationAddr
 #ifdef NEED_JIT_UNWIND_HANDLING
 static js::JitExceptionHandler sJitExceptionHandler;
 
 JS_FRIEND_API void js::SetJitExceptionHandler(JitExceptionHandler handler) {
   MOZ_ASSERT(!sJitExceptionHandler);
   sJitExceptionHandler = handler;
 }
 
+#if defined(_M_ARM64)
+// See the ".xdata records" section of
+// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling
+// These records can have various fields present or absent depending on the
+// bits set in the header. We'll be using a mostly-zero dummy structure with
+// no slots for epilogs or unwind codes. But when epilogCount and codeWords are
+// both zero, the interpretation is that there is an additional word containing
+// extended epilog count and code words (which in our case will also be zero).
+struct UnwindInfo {
+  uint32_t functionLength : 18;
+  uint32_t version : 2;
+  uint32_t hasExceptionHandler : 1;
+  uint32_t packedEpilog : 1;
+  uint32_t epilogCount : 5;
+  uint32_t codeWords : 5;
+  uint16_t extEpilogCount;
+  uint8_t extCodeWords;
+  uint8_t reserved;
+  uint32_t exceptionHandler;
+};
+static const unsigned ThunkLength = 20;
+#else
 // 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;
+#endif
 
 struct ExceptionHandlerRecord {
   RUNTIME_FUNCTION runtimeFunction;
   UnwindInfo unwindInfo;
   uint8_t thunk[ThunkLength];
 };
 
 // This function must match the function pointer type PEXCEPTION_HANDLER
@@ -109,76 +130,105 @@ struct ExceptionHandlerRecord {
 // 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);
 }
 
+PRUNTIME_FUNCTION RuntimeFunctionCallback(DWORD64 ControlPc, PVOID 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) {
   if (!VirtualAlloc(p, pageSize, MEM_COMMIT, PAGE_READWRITE)) {
     MOZ_CRASH();
   }
 
   ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
+  void* handler = JS_FUNC_TO_DATA_PTR(void*, ExceptionHandler);
+
+  // Because the .xdata format on ARM64 can only encode sizes up to 1M (much
+  // too small for our JIT code regions), we register a function table callback
+  // to provide RUNTIME_FUNCTIONs at runtime. Windows doesn't seem to care about
+  // the size fields on RUNTIME_FUNCTIONs that are created in this way, so the
+  // same RUNTIME_FUNCTION can work for any address in the region. We'll set up
+  // a generic one now and the callback can just return a pointer to it.
 
   // 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.
 
+#if defined(_M_ARM64)
+  r->runtimeFunction.BeginAddress = pageSize;
+  r->runtimeFunction.UnwindData = offsetof(ExceptionHandlerRecord, unwindInfo);
+  static_assert(offsetof(ExceptionHandlerRecord, unwindInfo) % 4 == 0,
+                "The ARM64 .pdata format requires that exception information "
+                "RVAs be 4-byte aligned.");
+
+  memset(&r->unwindInfo, 0, sizeof(r->unwindInfo));
+  r->unwindInfo.hasExceptionHandler = true;
+  r->unwindInfo.exceptionHandler = offsetof(ExceptionHandlerRecord, thunk);
+
+  uint32_t* thunk = (uint32_t*)r->thunk;
+  uint16_t* addr = (uint16_t*)&handler;
+
+  // xip0/r16 should be safe to clobber: Windows just used it to call our thunk.
+  const uint8_t reg = 16;
+
+  // Say `handler` is 0x4444333322221111, then:
+  thunk[0] = 0xd2800000 | addr[0] << 5 | reg;  // mov  xip0, 1111
+  thunk[1] = 0xf2a00000 | addr[1] << 5 | reg;  // movk xip0, 2222 lsl #0x10
+  thunk[2] = 0xf2c00000 | addr[2] << 5 | reg;  // movk xip0, 3333 lsl #0x20
+  thunk[3] = 0xf2e00000 | addr[3] << 5 | reg;  // movk xip0, 4444 lsl #0x30
+  thunk[4] = 0xd61f0000 | reg << 5;            // br xip0
+#else
   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;
+#endif
 
   DWORD oldProtect;
   if (!VirtualProtect(p, pageSize, PAGE_EXECUTE_READ, &oldProtect)) {
     MOZ_CRASH();
   }
 
   // XXX NB: The profiler believes this function is only called from the main
   // thread. If that ever becomes untrue, the profiler must be updated
   // immediately.
   AutoSuppressStackWalking suppress;
-  return RtlAddFunctionTable(&r->runtimeFunction, 1,
-                             reinterpret_cast<DWORD64>(p));
+  return RtlInstallFunctionTableCallback((DWORD64)p | 0x3, (DWORD64)p, bytes,
+                                         RuntimeFunctionCallback, NULL, NULL);
 }
 
 static void UnregisterExecutableMemory(void* p, size_t bytes, size_t pageSize) {
-  ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
-
-  // XXX NB: The profiler believes this function is only called from the main
-  // thread. If that ever becomes untrue, the profiler must be updated
-  // immediately.
-  AutoSuppressStackWalking suppress;
-  RtlDeleteFunctionTable(&r->runtimeFunction);
+  // There's no such thing as RtlUninstallFunctionTableCallback, so there's
+  // nothing to do here.
 }
 #endif
 
 static void* ReserveProcessExecutableMemory(size_t bytes) {
 #ifdef NEED_JIT_UNWIND_HANDLING
   size_t pageSize = gc::SystemPageSize();
   if (sJitExceptionHandler) {
     bytes += pageSize;
@@ -469,16 +519,18 @@ class ProcessExecutableMemory {
     base_ = static_cast<uint8_t*>(p);
 
     mozilla::Array<uint64_t, 2> seed;
     GenerateXorShift128PlusSeed(seed);
     rng_.emplace(seed[0], seed[1]);
     return true;
   }
 
+  uint8_t* base() const { return base_; }
+
   bool initialized() const { return base_ != nullptr; }
 
   size_t bytesAllocated() const {
     MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
     return pagesAllocated_ * ExecutableCodePageSize;
   }
 
   void release() {
@@ -680,8 +732,23 @@ bool js::jit::ReprotectRegion(void* star
   if (mprotect(pageStart, size, flags)) {
     return false;
   }
 #endif
 
   execMemory.assertValidAddress(pageStart, size);
   return true;
 }
+
+#if defined(XP_WIN) && defined(NEED_JIT_UNWIND_HANDLING)
+static PRUNTIME_FUNCTION RuntimeFunctionCallback(DWORD64 ControlPc,
+                                                 PVOID Context) {
+  MOZ_ASSERT(sJitExceptionHandler);
+
+  // RegisterExecutableMemory already set up the runtime function in the
+  // exception-data page preceding the allocation.
+  uint8_t* p = execMemory.base();
+  if (!p) {
+    return nullptr;
+  }
+  return (PRUNTIME_FUNCTION)(p - gc::SystemPageSize());
+}
+#endif
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2620,17 +2620,17 @@ extern JS_FRIEND_API JSObject* GetJSMEnv
 
 // Determine if obj is a JSMEnvironment
 //
 // NOTE: This may return true for an NonSyntacticVariablesObject generated by
 // other embedding such as a Gecko FrameScript. Caller can check compartment.
 extern JS_FRIEND_API bool IsJSMEnvironment(JSObject* obj);
 
 // Matches the condition in js/src/jit/ProcessExecutableMemory.cpp
-#if defined(XP_WIN) && defined(HAVE_64BIT_BUILD) && defined(_M_X64)
+#if defined(XP_WIN) && defined(HAVE_64BIT_BUILD)
 // 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
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -352,17 +352,17 @@ static LPTOP_LEVEL_EXCEPTION_FILTER WINA
         stub_SetUnhandledExceptionFilter(lpTopLevelExceptionFilter);
     return previousUnhandledExceptionFilter;
   }
 
   // intercept attempts to change the filter
   return nullptr;
 }
 
-#if defined(HAVE_64BIT_BUILD) && defined(_M_X64)
+#if defined(HAVE_64BIT_BUILD)
 static LPTOP_LEVEL_EXCEPTION_FILTER sUnhandledExceptionFilter = nullptr;
 
 static long JitExceptionHandler(void* exceptionRecord, void* context) {
   EXCEPTION_POINTERS pointers = {(PEXCEPTION_RECORD)exceptionRecord,
                                  (PCONTEXT)context};
   return sUnhandledExceptionFilter(&pointers);
 }
 
@@ -1555,17 +1555,17 @@ nsresult SetExceptionHandler(nsIFile* aX
   if (!gExceptionHandler) return NS_ERROR_OUT_OF_MEMORY;
 
 #ifdef XP_WIN
   gExceptionHandler->set_handle_debug_exceptions(true);
 
   // Initially set sIncludeContextHeap to true for debugging startup crashes
   // even if the controlling pref value is false.
   SetIncludeContextHeap(true);
-#if defined(HAVE_64BIT_BUILD) && defined(_M_X64)
+#if defined(HAVE_64BIT_BUILD)
   // Tell JS about the new filter before we disable SetUnhandledExceptionFilter
   SetJitExceptionHandler();
 #endif
 
   // protect the crash reporter from being unloaded
   gBlockUnhandledExceptionFilter = true;
   gKernel32Intercept.Init("kernel32.dll");
   DebugOnly<bool> ok = stub_SetUnhandledExceptionFilter.Set(
@@ -3250,17 +3250,17 @@ bool SetRemoteExceptionHandler(const nsA
   gExceptionHandler = new google_breakpad::ExceptionHandler(
       L"", ChildFPEFilter,
       nullptr,  // no minidump callback
       reinterpret_cast<void*>(aCrashTimeAnnotationFile),
       google_breakpad::ExceptionHandler::HANDLER_ALL, GetMinidumpType(),
       NS_ConvertASCIItoUTF16(crashPipe).get(), nullptr);
   gExceptionHandler->set_handle_debug_exceptions(true);
 
-#if defined(HAVE_64BIT_BUILD) && defined(_M_X64)
+#if defined(HAVE_64BIT_BUILD)
   SetJitExceptionHandler();
 #endif
 
   mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize);
 
   oldTerminateHandler = std::set_terminate(&TerminateHandler);
 
   // we either do remote or nothing, no fallback to regular crash reporting