Bug 1333126: adding tests; r?gsvelto draft
authorCarl Corcoran <carlco@gmail.com>
Sun, 06 Aug 2017 08:46:50 +0200
changeset 648855 a61eca4a65740e88d3ef642f758ec93711e104bf
parent 648854 682da624d32593f330b17ab0de6676fbb27f5513
child 648856 0c0e96b8ed5f9e4f74321250c6cd2e99c23c28c4
child 648863 8fde6f7b5d061a036ef0bbaefb30291e8729a0cf
push id74910
push userbmo:ccorcoran@mozilla.com
push dateFri, 18 Aug 2017 12:14:35 +0000
reviewersgsvelto
bugs1333126
milestone57.0a1
Bug 1333126: adding tests; r?gsvelto MozReview-Commit-ID: BNv54jtWf7k
toolkit/crashreporter/test/CrashTestUtils.jsm
toolkit/crashreporter/test/moz.build
toolkit/crashreporter/test/nsTestCrasher.cpp
toolkit/crashreporter/test/unit/head_crashreporter.js
toolkit/crashreporter/test/unit/head_win64cfi.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.exe
toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.exe
toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.exe
toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe
toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js
toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js
toolkit/crashreporter/test/unit/xpcshell.ini
toolkit/crashreporter/test/win64UnwindInfoTests.asm
--- a/toolkit/crashreporter/test/CrashTestUtils.jsm
+++ b/toolkit/crashreporter/test/CrashTestUtils.jsm
@@ -3,26 +3,39 @@
 
 this.EXPORTED_SYMBOLS = ["CrashTestUtils"];
 
 this.CrashTestUtils = {
   // These will be defined using ctypes APIs below.
   crash: null,
   dumpHasStream: null,
   dumpHasInstructionPointerMemory: null,
+  dumpWin64CFITestSymbols: null,
 
   // Constants for crash()
   // Keep these in sync with nsTestCrasher.cpp!
   CRASH_INVALID_POINTER_DEREF: 0,
   CRASH_PURE_VIRTUAL_CALL:     1,
   CRASH_RUNTIMEABORT:          2,
   CRASH_OOM:                   3,
   CRASH_MOZ_CRASH:             4,
   CRASH_ABORT:                 5,
   CRASH_UNCAUGHT_EXCEPTION:    6,
+  CRASH_X64CFI_NO_MANS_LAND:   7,
+  CRASH_X64CFI_LAUNCHER:       8,
+  CRASH_X64CFI_UNKNOWN_OPCODE: 9,
+  CRASH_X64CFI_PUSH_NONVOL:    10,
+  CRASH_X64CFI_ALLOC_SMALL:    11,
+  CRASH_X64CFI_ALLOC_LARGE:    12,
+  CRASH_X64CFI_SAVE_NONVOL:    15,
+  CRASH_X64CFI_SAVE_NONVOL_FAR: 16,
+  CRASH_X64CFI_SAVE_XMM128:    17,
+  CRASH_X64CFI_SAVE_XMM128_FAR: 18,
+  CRASH_X64CFI_EPILOG:         19,
+  CRASH_X64CFI_EOF:            20,
 
   // Constants for dumpHasStream()
   // From google_breakpad/common/minidump_format.h
   MD_THREAD_LIST_STREAM:       3,
   MD_MEMORY_INFO_LIST_STREAM:  16
 };
 
 // Grab APIs from the testcrasher shared library
@@ -58,8 +71,14 @@ CrashTestUtils.dumpHasInstructionPointer
               ctypes.default_abi,
               ctypes.bool,
               ctypes.char.ptr);
 
 CrashTestUtils.dumpCheckMemory = lib.declare("DumpCheckMemory",
                                              ctypes.default_abi,
                                              ctypes.bool,
                                              ctypes.char.ptr);
+
+CrashTestUtils.getWin64CFITestFnAddrOffset =
+  lib.declare("GetWin64CFITestFnAddrOffset",
+    ctypes.default_abi,
+    ctypes.int32_t,
+    ctypes.int16_t);
--- a/toolkit/crashreporter/test/moz.build
+++ b/toolkit/crashreporter/test/moz.build
@@ -19,16 +19,21 @@ UNIFIED_SOURCES += [
     'dumputils.cpp',
     'nsTestCrasher.cpp',
 ]
 
 SOURCES += [
   'ExceptionThrower.cpp',
 ]
 
+if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
+    SOURCES += [
+        'win64UnwindInfoTests.asm',
+    ]
+
 if CONFIG['CLANG_CL']:
     SOURCES['ExceptionThrower.cpp'].flags += [
         '-Xclang',
         '-fcxx-exceptions',
     ]
 elif not CONFIG['_MSC_VER']:
     SOURCES['ExceptionThrower.cpp'].flags += [
         '-fexceptions',
--- a/toolkit/crashreporter/test/nsTestCrasher.cpp
+++ b/toolkit/crashreporter/test/nsTestCrasher.cpp
@@ -38,23 +38,90 @@ void fcn( A* p )
 
 void PureVirtualCall()
 {
   // generates a pure virtual function call
   B b;
   b.use(); // make sure b's actually used
 }
 
+extern "C" {
+#if XP_WIN && HAVE_64BIT_BUILD
+  // Implementation in win64unwindInfoTests.asm
+  uint64_t x64CrashCFITest_NO_MANS_LAND(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_Launcher(uint64_t returnpfn, void* testProc);
+  uint64_t x64CrashCFITest_UnknownOpcode(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_PUSH_NONVOL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_ALLOC_SMALL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_ALLOC_LARGE(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_NONVOL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_XMM128(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_XMM128_FAR(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_EPILOG(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_EOF(uint64_t returnpfn, void*);
+#endif // XP_WIN && HAVE_64BIT_BUILD
+}
+
 // Keep these in sync with CrashTestUtils.jsm!
 const int16_t CRASH_INVALID_POINTER_DEREF = 0;
 const int16_t CRASH_PURE_VIRTUAL_CALL     = 1;
 const int16_t CRASH_OOM                   = 3;
 const int16_t CRASH_MOZ_CRASH             = 4;
 const int16_t CRASH_ABORT                 = 5;
 const int16_t CRASH_UNCAUGHT_EXCEPTION    = 6;
+const int16_t CRASH_X64CFI_NO_MANS_LAND   = 7;
+const int16_t CRASH_X64CFI_LAUNCHER       = 8;
+const int16_t CRASH_X64CFI_UNKNOWN_OPCODE = 9;
+const int16_t CRASH_X64CFI_PUSH_NONVOL    = 10;
+const int16_t CRASH_X64CFI_ALLOC_SMALL    = 11;
+const int16_t CRASH_X64CFI_ALLOC_LARGE    = 12;
+const int16_t CRASH_X64CFI_SAVE_NONVOL    = 15;
+const int16_t CRASH_X64CFI_SAVE_NONVOL_FAR = 16;
+const int16_t CRASH_X64CFI_SAVE_XMM128    = 17;
+const int16_t CRASH_X64CFI_SAVE_XMM128_FAR = 18;
+const int16_t CRASH_X64CFI_EPILOG         = 19;
+const int16_t CRASH_X64CFI_EOF            = 20;
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+typedef decltype(&x64CrashCFITest_UnknownOpcode) win64CFITestFnPtr_t;
+
+static std::map<int16_t, win64CFITestFnPtr_t>
+GetWin64CFITestMap() {
+  std::map<int16_t, win64CFITestFnPtr_t> ret = {
+    { CRASH_X64CFI_NO_MANS_LAND, x64CrashCFITest_NO_MANS_LAND},
+    { CRASH_X64CFI_LAUNCHER, x64CrashCFITest_Launcher},
+    { CRASH_X64CFI_UNKNOWN_OPCODE, x64CrashCFITest_UnknownOpcode},
+    { CRASH_X64CFI_PUSH_NONVOL, x64CrashCFITest_PUSH_NONVOL},
+    { CRASH_X64CFI_ALLOC_SMALL, x64CrashCFITest_ALLOC_SMALL },
+    { CRASH_X64CFI_ALLOC_LARGE, x64CrashCFITest_ALLOC_LARGE },
+    { CRASH_X64CFI_SAVE_NONVOL, x64CrashCFITest_SAVE_NONVOL },
+    { CRASH_X64CFI_SAVE_NONVOL_FAR, x64CrashCFITest_SAVE_NONVOL_FAR },
+    { CRASH_X64CFI_SAVE_XMM128, x64CrashCFITest_SAVE_XMM128 },
+    { CRASH_X64CFI_SAVE_XMM128_FAR, x64CrashCFITest_SAVE_XMM128_FAR },
+    { CRASH_X64CFI_EPILOG, x64CrashCFITest_EPILOG },
+    { CRASH_X64CFI_EOF, x64CrashCFITest_EOF }
+  };
+  // ret values point to jump table entries, not the actual function bodies.
+  // Get the correct pointer by calling the function with returnpfn=1
+  for (auto it = ret.begin(); it != ret.end(); ++ it) {
+    it->second = (win64CFITestFnPtr_t)it->second(1, nullptr);
+  }
+  return ret;
+}
+
+void ReserveStack() {
+  // This ensures our tests have enough reserved stack space.
+  uint8_t* p = (uint8_t*)alloca(1024000);
+  // This ensures we don't optimized away this meaningless code at build time.
+  mozilla::Unused << (int)(uint64_t)p;
+}
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
 
 extern "C" NS_EXPORT
 void Crash(int16_t how)
 {
   switch (how) {
   case CRASH_INVALID_POINTER_DEREF: {
     volatile int* foo = (int*)0x42;
     *foo = 0;
@@ -79,16 +146,37 @@ void Crash(int16_t how)
   case CRASH_ABORT: {
     abort();
     break;
   }
   case CRASH_UNCAUGHT_EXCEPTION: {
     ThrowException();
     break;
   }
+#if XP_WIN && HAVE_64BIT_BUILD
+  case CRASH_X64CFI_UNKNOWN_OPCODE:
+  case CRASH_X64CFI_PUSH_NONVOL:
+  case CRASH_X64CFI_ALLOC_SMALL:
+  case CRASH_X64CFI_ALLOC_LARGE:
+  case CRASH_X64CFI_SAVE_NONVOL:
+  case CRASH_X64CFI_SAVE_NONVOL_FAR:
+  case CRASH_X64CFI_SAVE_XMM128:
+  case CRASH_X64CFI_SAVE_XMM128_FAR:
+  case CRASH_X64CFI_EPILOG: {
+    ReserveStack();
+    auto m = GetWin64CFITestMap();
+    if (m.find(how) == m.end()) {
+      break;
+    }
+    auto pfnTest = m[how];
+    auto pfnLauncher = m[CRASH_X64CFI_LAUNCHER];
+    pfnLauncher(0, pfnTest);
+    break;
+  }
+#endif // XP_WIN && HAVE_64BIT_BUILD
   default:
     break;
   }
 }
 
 char testData[32];
 
 extern "C" NS_EXPORT
@@ -114,8 +202,25 @@ static LONG WINAPI HandleException(EXCEP
 }
 
 extern "C" NS_EXPORT
 void TryOverrideExceptionHandler()
 {
   SetUnhandledExceptionFilter(HandleException);
 }
 #endif
+
+extern "C" NS_EXPORT uint32_t
+GetWin64CFITestFnAddrOffset(int16_t fnid) {
+#if XP_WIN && HAVE_64BIT_BUILD
+  // fnid uses the same constants as Crash().
+  // Returns the RVA of the requested function.
+  // Returns 0 on failure.
+  auto m = GetWin64CFITestMap();
+  if (m.find(fnid) == m.end()) {
+    return 0;
+  }
+  uint64_t moduleBase = (uint64_t)GetModuleHandleW(L"testcrasher.dll");
+  return ((uint64_t)m[fnid]) - moduleBase;
+#else
+  return 0;
+#endif // XP_WIN && HAVE_64BIT_BUILD
+}
--- a/toolkit/crashreporter/test/unit/head_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -1,13 +1,14 @@
-var {utils: Cu} = Components;
+var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://testing-common/AppData.jsm", this);
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 function getEventDir() {
   return OS.Path.join(do_get_tempdir().path, "crash-events");
 }
 
 /*
  * Run an xpcshell subprocess and crash it.
  *
@@ -19,25 +20,27 @@ function getEventDir() {
  *        This code will be evaluted between crasher_subprocess_head.js
  *        and crasher_subprocess_tail.js, so it will have access
  *        to everything defined in crasher_subprocess_head.js,
  *        which includes "crashReporter", a variable holding
  *        the crash reporter service.
  *
  * @param callback
  *        A JavaScript function to be called after the subprocess
- *        crashes. It will be passed (minidump, extra), where
- *         minidump is an nsIFile of the minidump file produced,
- *         and extra is an object containing the key,value pairs from
- *         the .extra file.
+ *        crashes. It will be passed (minidump, extra, extrafile), where
+ *         - minidump is an nsIFile of the minidump file produced,
+ *         - extra is an object containing the key,value pairs from
+ *           the .extra file.
+ *         - extrafile is an nsIFile of the extra file
  *
  * @param canReturnZero
  *       If true, the subprocess may return with a zero exit code.
  *       Certain types of crashes may not cause the process to
  *       exit with an error.
+ *
  */
 function do_crash(setup, callback, canReturnZero) {
   // get current process filename (xpcshell)
   let ds = Components.classes["@mozilla.org/file/directory_service;1"]
     .getService(Components.interfaces.nsIProperties);
   let bin = ds.get("XREExeF", Components.interfaces.nsIFile);
   if (!bin.exists()) {
     // weird, can't find xpcshell binary?
@@ -95,16 +98,41 @@ function getMinidump() {
     if (f.leafName.substr(-4) == ".dmp") {
       return f;
     }
   }
 
   return null;
 }
 
+function runMinidumpAnalyzer(dumpFile, additionalArgs) {
+  if (AppConstants.platform !== "win") {
+    return;
+  }
+
+  // find minidump-analyzer executable.
+  let ds = Cc["@mozilla.org/file/directory_service;1"]
+             .getService(Ci.nsIProperties);
+  let bin = ds.get("XREExeF", Ci.nsIFile);
+  ok(bin && bin.exists());
+  bin = bin.parent;
+  ok(bin && bin.exists());
+  bin.append("minidump-analyzer.exe");
+  ok(bin.exists());
+
+  let process = Cc["@mozilla.org/process/util;1"]
+                  .createInstance(Ci.nsIProcess);
+  process.init(bin);
+  let args = [dumpFile.path];
+  if (additionalArgs) {
+    args = args.concat(additionalArgs);
+  }
+  process.run(true /* blocking */, args, args.length);
+}
+
 function handleMinidump(callback) {
   // find minidump
   let minidump = getMinidump();
 
   if (minidump == null) {
     do_throw("No minidump found!");
   }
 
@@ -126,17 +154,17 @@ function handleMinidump(callback) {
       memoryfile.remove(false);
     }
   });
 
   do_check_true(extrafile.exists());
   let extra = parseKeyValuePairsFromFile(extrafile);
 
   if (callback) {
-    callback(minidump, extra);
+    callback(minidump, extra, extrafile);
   }
 
   if (minidump.exists()) {
     minidump.remove(false);
   }
   if (extrafile.exists()) {
     extrafile.remove(false);
   }
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/head_win64cfi.js
@@ -0,0 +1,193 @@
+/* import-globals-from head_crashreporter.js */
+
+let gTestCrasherSyms = null;
+let gModules = null;
+
+// Returns the offset (int) of an IP with a given base address.
+// This is effectively (ip - base), except a bit more complication due to
+// Javascript's shaky handling of 64-bit integers.
+// base & ip are passed as hex strings.
+function getModuleOffset(base, ip) {
+  let i = 0;
+  // Find where the two addresses diverge, which enables us to perform a 32-bit
+  // subtraction.
+  // e.g.    "0x1111111111112222"
+  //       - "0x1111111111111111"
+  // becomes 2222 - 1111
+  for (; i < base.length; ++i) {
+    if (base[i] != ip[i]) {
+      break;
+    }
+  }
+  if (i == base.length) {
+    return 0;
+  }
+  let lhs2 = "0x" + base.substring(i);
+  let rhs2 = "0x" + ip.substring(i);
+  return parseInt(rhs2) - parseInt(lhs2);
+}
+
+// Uses gTestCrasherSyms to convert an address to a symbol.
+function findNearestTestCrasherSymbol(addr) {
+  addr += 1; // Breakpad sometimes offsets addresses; correct for this.
+  let closestDistance = null;
+  let closestSym = null;
+  for (let sym in gTestCrasherSyms) {
+    if (addr >= gTestCrasherSyms[sym]) {
+      let thisDistance = addr - gTestCrasherSyms[sym];
+      if (closestDistance === null || thisDistance < closestDistance) {
+        closestDistance = thisDistance;
+        closestSym = sym;
+      }
+    }
+  }
+  if (closestSym === null) {
+    return null;
+  }
+  return { symbol: closestSym, offset: closestDistance }
+}
+
+// Populate known symbols for testcrasher.dll.
+// Use the same prop names as from CrashTestUtils to avoid the need for mapping.
+function initTestCrasherSymbols() {
+  gTestCrasherSyms = { };
+  for (let k in CrashTestUtils) {
+    // Not all keys here are valid symbol names. getWin64CFITestFnAddrOffset
+    // will return 0 in those cases, no need to filter here.
+    if (Number.isInteger(CrashTestUtils[k])) {
+      let t = CrashTestUtils.getWin64CFITestFnAddrOffset(CrashTestUtils[k]);
+      if (t > 0) {
+        gTestCrasherSyms[k] = t;
+      }
+    }
+  }
+}
+
+function stackFrameToString(frameIndex, frame) {
+  // Calculate the module offset.
+  let ip = frame.ip;
+  let symbol = "";
+  let moduleOffset = "unknown_offset";
+  let filename = "unknown_module";
+
+  if (typeof frame.module_index !== "undefined" && (frame.module_index >= 0)
+    && (frame.module_index < gModules.length)) {
+
+    let base = gModules[frame.module_index].base_addr;
+    moduleOffset = getModuleOffset(base, ip);
+    filename = gModules[frame.module_index].filename;
+
+    if (filename === "testcrasher.dll") {
+      let nearestSym = findNearestTestCrasherSymbol(moduleOffset);
+      if (nearestSym !== null) {
+        symbol = nearestSym.symbol + "+" + nearestSym.offset.toString(16);
+      }
+    }
+  }
+
+  let ret = "frames[" + frameIndex + "] ip=" + ip +
+    " " + symbol +
+    ", module:" + filename +
+    ", trust:" + frame.trust +
+    ", moduleOffset:" + moduleOffset.toString(16);
+  return ret;
+}
+
+function dumpStackFrames(frames, maxFrames) {
+  for (let i = 0; i < Math.min(maxFrames, frames.length); ++i) {
+    do_print(stackFrameToString(i, frames[i]));
+  }
+}
+
+// Test that the top of the given stack (from extra data) matches the given
+// expected frames.
+//
+// expected is { symbol: "", trust: "" }
+function assertStack(stack, expected) {
+  for (let i = 0; i < stack.length; ++i) {
+    if (i >= expected.length) {
+      ok("Top stack frames were expected");
+      return;
+    }
+    let frame = stack[i];
+    let expectedFrame = expected[i];
+    let dumpThisFrame = function() {
+      do_print("  Actual frame: " + stackFrameToString(i, frame));
+      do_print("Expected { symbol: " + expectedFrame.symbol + ", trust: " + expectedFrame.trust + "}");
+    };
+
+    if (expectedFrame.trust) {
+      if (frame.trust !== expectedFrame.trust) {
+        dumpThisFrame();
+        do_print("Expected frame trust did not match.");
+        ok(false);
+      }
+    }
+
+    if (expectedFrame.symbol) {
+      if (typeof frame.module_index === "undefined") {
+        // Without a module_index, it happened in an unknown module. Currently
+        // you can't specify an expected "unknown" module.
+        do_print("Unknown symbol in unknown module.");
+        ok(false);
+      }
+      if (frame.module_index < 0 || frame.module_index >= gModules.length) {
+        dumpThisFrame();
+        do_print("Unknown module.");
+        ok(false);
+        return;
+      }
+      let base = gModules[frame.module_index].base_addr;
+      let moduleOffset = getModuleOffset(base, frame.ip);
+      let filename = gModules[frame.module_index].filename;
+      if (filename == "testcrasher.dll") {
+        let nearestSym = findNearestTestCrasherSymbol(moduleOffset);
+        if (nearestSym === null) {
+          dumpThisFrame();
+          do_print("Unknown symbol.");
+          ok(false);
+          return;
+        }
+
+        if (nearestSym.symbol !== expectedFrame.symbol) {
+          dumpThisFrame();
+          do_print("Mismatching symbol.");
+          ok(false);
+        }
+      }
+    }
+  }
+}
+
+// Performs a crash, runs minidump-analyzer, and checks expected stack analysis.
+//
+// how: The crash to perform. Constants defined in both CrashTestUtils.jsm
+//   and nsTestCrasher.cpp (i.e. CRASH_X64CFI_PUSH_NONVOL)
+// expectedStack: An array of {"symbol", "trust"} where trust is "cfi",
+//   "context", "scan", et al. May be null if you don't need to check the stack.
+// minidumpAnalyzerArgs: An array of additional arguments to pass to
+//   minidump-analyzer.exe.
+function do_x64CFITest(how, expectedStack, minidumpAnalyzerArgs) {
+
+  // Setup is run in the subprocess so we cannot use any closures.
+  let setupFn = "crashType = CrashTestUtils." + how + ";";
+
+  let callbackFn = function(minidumpFile, extra, extraFile) {
+    runMinidumpAnalyzer(minidumpFile, minidumpAnalyzerArgs);
+
+    // Refresh updated extra data
+    extra = parseKeyValuePairsFromFile(extraFile);
+
+    initTestCrasherSymbols();
+    let stackTraces = JSON.parse(extra.StackTraces);
+    let crashingThreadIndex = stackTraces.crash_info.crashing_thread;
+    gModules = stackTraces.modules;
+    let crashingFrames = stackTraces.threads[crashingThreadIndex].frames;
+
+    dumpStackFrames(crashingFrames, 10);
+
+    assertStack(crashingFrames, expectedStack);
+  };
+
+  do_crash(setupFn, callbackFn, true, true);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_ALLOC_LARGE", [
+      { symbol: "CRASH_X64CFI_ALLOC_LARGE", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_EPILOG", [
+      { symbol: "CRASH_X64CFI_EPILOG", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
index 0000000000000000000000000000000000000000..5283cc5df7e0a7abb20dd540cc203c4266702fff
GIT binary patch
literal 1440
zc$|e)-%C?b9RJ>J*K9U7hQ@m8E>1tF7@^aLo)*o#()__DM0UwdH*~Sxjd?8<9~#R*
zFM*Z58SGD}NRgfdjVL#Z48*LLelgI89(xFG=iGCz+pbVw_T_uNzrW{uK6jmcvtR}Q
z3uZ+DSO!Wy_&=>+u0Q^=9$p+Sx|f+i(cL>7AK}uO)KDgp<OU<6n3A}G7?&9pxwy#r
zyF*+u6^(gpHY?I<Y}GBketc_ZPUo(9$K0%X{xtUpON;lz{6kf4&l5cu9~>re`V2j7
z0MYwq_)-X7*KNPx1k{=8!NFj^iP}D6>7!>KHiJXs8ZAecd3HYz?<Jh2QLrx#>QzT@
z;rufddK&8p4pJB|J^k<XR#A_+x{r}OQfy4Z($3KN9ZdNiI&rwIClifG5j>}f*5ou<
zNk07Zq-h+|?r=eq#yEajK(+j|gdErS>>h`^&TQX%Bh}{6Nj1j4G>JI-l7#wq56!HX
zii)CKS|$%5Z^;`<V-cTnzZL0w;K2eS*&Xr?M~6J(>X5(nuNDdgwf;3ew->fA3CK;#
z-P~nMKgxAB<#$Tslvd{_FAFHm=hnhFTg%Pv<<&t1GRPzzge`qFq+ij~pE0Gg*jLqS
z+z@<B4{l!J<puN<t9*8i^ypuO9>b0Z>DV;UoxyuE<x1ZX%ng_w7Rr@A8|F34$U?c&
zS8wPQ4EaMFcR#+ht}nJvo$Gp)ZwaY*_g@r}<ARtP74O8ws4&**6@sxuEHV<?H{vHB
z0DaC%YQNkYAXp;UQgMcoBC+e3^u!5PPz$l?xYlt-?&)H59ZgJ3L<fejzht49@{DPC
zWa<62T1}+Z`D$CUiaEBbr&pZ=+gxQ^D?s_X({b!}vfzxK2B-HFI2i<Wcu2--QRlEe
zuSqjD7Hr<5U}GAfCQbY3)W;tDMsza1_olgXG~tim2DsW5>}m@%H+iCoMA=XF_{2o-
UpT}|s^B)WSf9l3PY7O=N0y)qbQvd(}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js
@@ -0,0 +1,17 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles chained
+  // unwind code entries that form a circular reference
+  // (infinite loop).
+  let exe = do_get_file("test_crash_win64cfi_infinite_code_chain.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
index 0000000000000000000000000000000000000000..de48576f6536cc6895f23297752690da1a694a10
GIT binary patch
literal 1440
zc$|e)-Ahwp7=O+_)MncbLt|ZajJFRWM(FaQt3{hlnjcI-s?j#x(8a!*M^h7GtpYs+
zRvKLx_zwsbCDN6k5T&cg3o*O04+eVCWf#HedCxho`w;43Kc4q}zTfx#owZ$=0zCj2
z5V8!g2&%Z?|FVRz9C>4bR|oP9i<~dt&^-_v6w;Yge<qv|`ohU%N)&pdLS`r_#FB!$
zqf1DnB2kCgY$95XjoR05A79^^W@w+`r>B(lm+42S4bIOq4;8&RL-+nz-vEtcGjw_Y
zBKP$WeDA-^Y`@?r)aoq2bLiKpwy&sN>e_{7;5AffIZV$P_u}wgLRmEm_SF7;*%IuS
zf3jqs#yx@k62ePQ|NDB&q*Gtsr&1hZbVNjL<<$9ku6Pb60=9KzB4II%bL!NZf~HHw
zg+E7HjU(C_&S<(a&N~qxHQou4@IBt_u7F)9H}Ad^YjWh65@TH$CxUfBB*C5P$<;z$
zmgVz{^Z}#|>4RLK$5rmPEPnSrm?bp3Rl3TzN`v-R>05AlZEa1d-{Z~ghO7$#(m=~y
z+c6e?6zi-@ALaUcTAiO>DL~TR+)4<uwcPCPoH9s&4l<5|7z^7K=`Ae%1y?wQzOr7U
z6~TvDaMO}knkCP1>&>pv9>Hbkthgg8jGH?3&ft??MXB!~q7K0uRFwM6h!q4es3`SW
zDth_5++CjA?(JK{Ph0ModRFJ3%=45t?sI{}XdsyyO5ThmBY}}-XTTqgN5g~BJtIDO
zFR(dF<>G~IALSC|mclcVNK!lFq$5VTLe5ef<2B<MJ*S;xdongQ7U}Irf5D(~$}ytx
z(WstZtJO$roi4XEDV#Hw^>iw8pv_*iH3JmCJH}(qRt>hu39vbjgN-9li$iiI1DKo^
zFuklwb7mu$orl28)j?HS?Zf1XasM~Mr1HJj^=(6OcWfBolE>ff@ijF%BJp_9Pw(*=
W;s)l;q25CLF{uAf9oVC$qTXLgycI0~
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js
@@ -0,0 +1,17 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles chained
+  // IMAGE_RUNTIME_FUNCTION_ENTRY items that form a circular reference
+  // (infinite loop).
+  let exe = do_get_file("test_crash_win64cfi_infinite_entry_chain.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
index 0000000000000000000000000000000000000000..ab4ce326bdf34b9e417107d2d22adb3d01b006ee
GIT binary patch
literal 1440
zc$|e)-Ahwp7=O+_)Moo&XsoM_@%BN*2%TPZwP@y%<_DV~*&#RG(8YE(=CM?~Xe<Lg
z1Xg-yus@+9MY<9+qI4D+h#6h<#Xv8*>>^n2d*1W9?FjX-hv$91pZ9rxXB~aBU<3dY
z=3)_G87O(-|FnYHcH*TCUL46cEOY*RL(gz*giocDL+NmW9}G)UQsxJue0o&kV-oM{
z3i63$B<i+WEl8`eUAy@D@vYrC#+~zyxmor6Y3>o0CeMfYhpOC}Cwd?@I85T$4Bf2&
zk^4q)d<kA>wqI}(Y7I7UaM*94w$E64>Di0T;Lx~A%V9ESK8VA631w&$9Eihu#SvUM
z|7@9F#Dw55h49i-|6XqebsH=D=*cZd$7C$+9G&06mF~fa$8Fu|NLUWzISsTXugP-q
z;-5Q3<H+nOe5Z?=)W-?aBB~LlW#s4;GW$I4I<s@{ja-vOr_>nx(j?;TOET);tDade
z<ncN#FOvsQww29deIB2(zl-vB|APfYvfGs#j&^0l)vkQ&U)|W)Q0w;!+5M1xNkk2#
z+|506;YX>?mh!GxKc&_ADJUXJ3E8y}&en1>`#E(Gkqk142Qe4E>e8>W^k-b*9QIZ8
z8rKCMXTeP?g0g^~VpYhjkskf4(5>4MA%;x@-5I<$QZDx$#au^p6XkNB74sTqWTIT|
zv*~&Tg1+G9-H&fA>x*qu=R05Jnu96~e3!(;xF{t@r8_YxB966q#6UD24Ua?*jQGh1
zfX!J>ZC85yqzJ*5iZhguh@D~59V1vl&BVrV&2UEU>Ef7<CMG5#14GzfFi}jo$22^e
z>HW1@jilCvN?VhPIder%k2(joxk|Pc(6CCd%W%v&tHBvL15VFraB>K0@sOOw1Qw4C
zEYGV_oYf3g&oQuabx@U}eHitb4}T+!^zXf8>==#vVz&XVwFWv{{Y{PTNIYKhlRZ8$
V(EI1G+{OIIME{?<aE}^Yy}vLX7<&K!
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js
@@ -0,0 +1,16 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles an invalid pointer to the
+  // exception unwind information.
+  let exe = do_get_file("test_crash_win64cfi_invalid_exception_rva.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe
@@ -0,0 +1,1 @@
+this is not a valid PE file.
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js
@@ -0,0 +1,15 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles corrupt PE files.
+  let exe = do_get_file("test_crash_win64cfi_not_a_pe.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_PUSH_NONVOL", [
+      { symbol: "CRASH_X64CFI_PUSH_NONVOL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL", [
+      { symbol: "CRASH_X64CFI_SAVE_NONVOL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL_FAR", [
+      { symbol: "CRASH_X64CFI_SAVE_NONVOL_FAR", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_XMM128", [
+      { symbol: "CRASH_X64CFI_SAVE_XMM128", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_XMM128_FAR", [
+      { symbol: "CRASH_X64CFI_SAVE_XMM128_FAR", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js
@@ -0,0 +1,13 @@
+
+function run_test() {
+  // In the case of an unknown unwind code or missing CFI,
+  // make certain we can still walk the stack via stack scan. The crashing
+  // function places NO_MANS_LAND on the stack so it will get picked up via
+  // stack scan.
+  do_x64CFITest("CRASH_X64CFI_UNKNOWN_OPCODE", [
+      { symbol: "CRASH_X64CFI_UNKNOWN_OPCODE", trust: "context" },
+      // Trust may either be scan or frame_pointer; we don't really care as
+      // long as the address is expected.
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ]);
+}
--- a/toolkit/crashreporter/test/unit/xpcshell.ini
+++ b/toolkit/crashreporter/test/unit/xpcshell.ini
@@ -32,8 +32,70 @@ skip-if = os != 'win'
 
 [test_crashreporter_appmem.js]
 # we need to skip this due to bug 838613
 skip-if = (os != 'win' && os != 'linux') || (os=='linux' && bits==32)
 
 [test_crash_AsyncShutdown.js]
 [test_event_files.js]
 [test_crash_terminator.js]
+
+[test_crash_win64cfi_unknown_op.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_push_nonvol.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_alloc_small.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_alloc_large.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_nonvol.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_nonvol_far.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_xmm128.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_xmm128_far.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_epilog.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_infinite_entry_chain.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_infinite_entry_chain.exe
+
+[test_crash_win64cfi_infinite_code_chain.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_infinite_code_chain.exe
+
+[test_crash_win64cfi_invalid_exception_rva.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_invalid_exception_rva.exe
+
+[test_crash_win64cfi_not_a_pe.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_not_a_pe.exe
+
+
+
+
+
+
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/win64UnwindInfoTests.asm
@@ -0,0 +1,382 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+; Comments indicate stack memory layout during execution.
+; For example at the top of a function, where RIP just points to the return
+; address, the stack looks like
+; rip = [ra]
+; And after pushing rax to the stack,
+; rip = [rax][ra]
+; And then, after allocating 20h bytes on the stack,
+; rip = [..20..][rax][ra]
+; And then, after pushing a function pointer,
+; rip = [pfn][..20..][rax][ra]
+
+include ksamd64.inc
+
+.code
+
+; It helps to add padding between functions so they're not right up against
+; each other. Adds clarity to debugging, and gives a bit of leeway when
+; searching for symbols (e.g. a function whose last instruction is CALL
+; would push a return address that's in the next function.)
+PaddingBetweenFunctions macro
+  repeat 10h
+     int 3
+  endm
+endm
+
+DoCrash macro
+  mov rax, 7
+  mov byte ptr [rax], 9
+endm
+
+PaddingBetweenFunctions
+
+; There is no rip addressing mode in x64. The only way to get the value
+; of rip is to call a function, and pop it from the stack.
+WhoCalledMe proc
+  pop rax ; rax is now ra
+  push rax ; Restore ra so this function can return.
+  sub rax, 5 ; Correct for the size of the call instruction
+  ret
+WhoCalledMe endp
+
+PaddingBetweenFunctions
+
+; Any function that we expect to test against on the stack, we'll need its
+; real address. If we use function pointers in C, we'll get the address to jump
+; table entries. This bit of code at the beginning of each function will
+; return the real address we'd expect to see in stack traces.
+;
+; rcx (1st arg) = mode
+; rax (return)  = address of either NO_MANS_LAND or this function.
+;
+; When mode is 0, we place the address of NO_MANS_LAND in RAX, for the function
+; to use as it wants. This is just for convenience because almost all functions
+; here need this address at some point.
+;
+; When mode is 1, the address of this function is returned.
+TestHeader macro
+  call WhoCalledMe
+  test rcx, rcx
+  je continue_test
+  ret
+continue_test:
+  inc rcx
+  call x64CrashCFITest_NO_MANS_LAND
+  xor rcx, rcx
+endm
+
+; The point of this is to add a stack frame to test against.
+; void* x64CrashCFITest_Launcher(int getAddress, void* pTestFn)
+x64CrashCFITest_Launcher proc frame
+  TestHeader
+
+  .endprolog
+  call rdx
+  ret
+x64CrashCFITest_Launcher endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_NO_MANS_LAND(uint64_t mode);
+; Not meant to be called. Only when mode = 1 in order to return its address.
+; Place this function's address on the stack so the stack scanning algorithm
+; thinks this is a return address, and places it on the stack trace.
+x64CrashCFITest_NO_MANS_LAND proc frame
+  TestHeader
+  .endprolog
+  ret
+x64CrashCFITest_NO_MANS_LAND endp
+
+PaddingBetweenFunctions
+
+; Test that we:
+; - handle unknown opcodes gracefully
+; - fall back to other stack unwind strategies if CFI doesn't work
+;
+; In order to properly unwind this frame, we'd need to fully support
+; SET_FPREG with offsets, plus restoring registers via PUSH_NONVOL.
+; To do this, sprinkle the stack with bad return addresses
+; and stack pointers.
+x64CrashCFITest_UnknownOpcode proc frame
+  TestHeader
+
+  push rax
+  .allocstack 8
+
+  push rbp
+  .pushreg rbp
+
+  push rax
+  push rsp
+  push rax
+  push rsp
+  .allocstack 20h
+  ; rsp = [rsp][pfn][rsp][pfn][rbp][pfn][ra]
+
+  lea rbp, [rsp+10h]
+  .setframe rbp, 10h
+  ; rsp = [rsp][pfn] [rsp][pfn][rbp][pfn][ra]
+  ; rbp =           ^
+
+  .endprolog
+
+  ; Now modify RSP so measuring stack size from unwind ops will not help
+  ; finding the return address.
+  push rax
+  push rsp
+  ; rsp = [rsp][pfn][rsp][pfn] [rsp][pfn][rbp][pfn][ra]
+
+  DoCrash
+
+x64CrashCFITest_UnknownOpcode endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_PUSH_NONVOL(uint64_t mode);
+;
+; Test correct handling of PUSH_NONVOL unwind code.
+;
+x64CrashCFITest_PUSH_NONVOL proc frame
+  TestHeader
+
+  push r10
+  .pushreg r10
+  push r15
+  .pushreg r15
+  push rbx
+  .pushreg rbx
+  push rsi
+  .pushreg rsi
+  push rbp
+  .pushreg rbp
+  ; rsp = [rbp][rsi][rbx][r15][r10][ra]
+
+  push rax
+  .allocstack 8
+  ; rsp = [pfn][rbp][rsi][rbx][r15][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_PUSH_NONVOL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_ALLOC_SMALL(uint64_t mode);
+;
+; Small allocations are between 8bytes and 512kb-8bytes
+;
+x64CrashCFITest_ALLOC_SMALL proc frame
+  TestHeader
+
+  ; Trash rbp to force stack scan. This will force
+  ; correct behavior for test_crash_win64cfi_not_a_pe, et al.
+  xor rbp, rbp
+
+  push rax
+  push rax
+  push rax
+  push rax
+  .allocstack 20h
+  ; rsp = [pfn][pfn][pfn][pfn][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_ALLOC_SMALL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_ALLOC_LARGE(uint64_t mode);
+;
+; Allocations between 512kb and 4gb
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_ALLOC_LARGE proc frame
+  TestHeader
+
+  sub rsp, 0a000h
+  .allocstack 0a000h
+  ; rsp = [..640kb..][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..640kb-8..][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_ALLOC_LARGE endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_NONVOL(uint64_t mode);
+;
+; Test correct handling of SAVE_NONVOL unwind code.
+;
+x64CrashCFITest_SAVE_NONVOL proc frame
+  TestHeader
+
+  sub rsp, 30h
+  .allocstack 30h
+  ; rsp = [..30..][ra]
+
+  mov qword ptr [rsp+28h], r10
+  .savereg r10, 28h
+  mov qword ptr [rsp+20h], rbp
+  .savereg rbp, 20h
+  mov qword ptr [rsp+18h], rsi
+  .savereg rsi, 18h
+  mov qword ptr [rsp+10h], rbx
+  .savereg rbx, 10h
+  mov qword ptr [rsp+8], r15
+  .savereg r15, 8
+  ; rsp = [r15][rbx][rsi][rbp][r10][ra]
+
+  mov qword ptr [rsp], rax
+
+  ; rsp = [pfn][r15][rbx][rsi][rbp][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_NONVOL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t mode);
+;
+; Similar to the test above but adding 640kb to most offsets.
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_SAVE_NONVOL_FAR proc frame
+  TestHeader
+
+  sub rsp, 0a0030h
+  .allocstack 0a0030h
+  ; rsp = [..640k..][..30..][ra]
+
+  mov qword ptr [rsp+28h+0a0000h], r10
+  .savereg r10, 28h+0a0000h
+  mov qword ptr [rsp+20h+0a0000h], rbp
+  .savereg rbp, 20h+0a0000h
+  mov qword ptr [rsp+18h+0a0000h], rsi
+  .savereg rsi, 18h+0a0000h
+  mov qword ptr [rsp+10h+0a0000h], rbx
+  .savereg rbx, 10h+0a0000h
+  mov qword ptr [rsp+8+0a0000h], r15
+  .savereg r15, 8+0a0000h
+  ; rsp = [..640k..][..8..][r15][rbx][rsi][rbp][r10][ra]
+
+  mov qword ptr [rsp], rax
+
+  ; rsp = [pfn][..640k..][r15][rbx][rsi][rbp][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_NONVOL_FAR endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode);
+;
+; Test correct handling of SAVE_XMM128 unwind code.
+x64CrashCFITest_SAVE_XMM128 proc frame
+  TestHeader
+
+  sub rsp, 30h
+  .allocstack 30h
+  ; rsp = [..30..][ra]
+
+  movdqu [rsp+20h], xmm6
+  .savexmm128 xmm6, 20h
+  ; rsp = [..20..][xmm6][ra]
+
+  movdqu [rsp+10h], xmm15
+  .savexmm128 xmm15, 10h
+  ; rsp = [..10..][xmm15][xmm6][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..8..][xmm15][xmm6][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_XMM128 endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode);
+;
+; Similar to the test above but adding 640kb to most offsets.
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_SAVE_XMM128_FAR proc frame
+  TestHeader
+
+  sub rsp, 0a0030h
+  .allocstack 0a0030h
+  ; rsp = [..640kb..][..30..][ra]
+
+  movdqu [rsp+20h+0a0000h], xmm6
+  .savexmm128 xmm6, 20h+0a0000h
+  ; rsp = [..640kb..][..20..][xmm6][ra]
+
+  movdqu [rsp+10h+0a0000h], xmm6
+  .savexmm128 xmm15, 10h+0a0000h
+  ; rsp = [..640kb..][..10..][xmm15][xmm6][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..640kb..][..8..][xmm15][xmm6][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_XMM128_FAR endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_EPILOG(uint64_t mode);
+;
+; The epilog unwind op will also set the unwind version to 2.
+; Test that we don't choke on UWOP_EPILOG or version 2 unwind info.
+x64CrashCFITest_EPILOG proc frame
+  TestHeader
+
+  push rax
+  .allocstack 8
+  ; rsp = [pfn][ra]
+
+  .endprolog
+
+  DoCrash
+
+  .beginepilog
+
+  ret
+
+x64CrashCFITest_EPILOG endp
+
+PaddingBetweenFunctions
+
+; Having an EOF symbol at the end of this file contains symbolication to this
+; file. So addresses beyond this file don't get mistakenly symbolicated as a
+; meaningful function name.
+x64CrashCFITest_EOF proc frame
+  TestHeader
+  .endprolog
+  ret
+x64CrashCFITest_EOF endp
+
+end