--- 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