summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/test/unit/head_win64cfi.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/test/unit/head_win64cfi.js')
-rw-r--r--toolkit/crashreporter/test/unit/head_win64cfi.js215
1 files changed, 215 insertions, 0 deletions
diff --git a/toolkit/crashreporter/test/unit/head_win64cfi.js b/toolkit/crashreporter/test/unit/head_win64cfi.js
new file mode 100644
index 0000000000..4df99213db
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/head_win64cfi.js
@@ -0,0 +1,215 @@
+/* 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) {
+ info(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 () {
+ info(" Actual frame: " + stackFrameToString(i, frame));
+ info(
+ "Expected { symbol: " +
+ expectedFrame.symbol +
+ ", trust: " +
+ expectedFrame.trust +
+ "}"
+ );
+ };
+
+ if (expectedFrame.trust) {
+ if (expectedFrame.trust.startsWith("!")) {
+ // A "!" prefix on the frame trust matching is a logical "not".
+ if (frame.trust === expectedFrame.trust.substring(1)) {
+ dumpThisFrame();
+ info("Expected frame trust matched when it should not have.");
+ ok(false);
+ }
+ } else if (frame.trust !== expectedFrame.trust) {
+ dumpThisFrame();
+ info("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.
+ info("Unknown symbol in unknown module.");
+ ok(false);
+ }
+ if (frame.module_index < 0 || frame.module_index >= gModules.length) {
+ dumpThisFrame();
+ info("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();
+ info("Unknown symbol.");
+ ok(false);
+ return;
+ }
+
+ if (nearestSym.symbol !== expectedFrame.symbol) {
+ dumpThisFrame();
+ info("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.
+async 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 = async function (minidumpFile, extra, extraFile) {
+ runMinidumpAnalyzer(minidumpFile, minidumpAnalyzerArgs);
+
+ // Refresh updated extra data
+ extra = await IOUtils.readJSON(extraFile.path);
+
+ initTestCrasherSymbols();
+ let stackTraces = 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);
+}