Bug 1333126: adding tests; r=gsvelto

MozReview-Commit-ID: BNv54jtWf7k

--HG--
extra : rebase_source : e89ea6927ef3cae3d95f46ee5f69f62d04a12f00
This commit is contained in:
Carl Corcoran 2017-08-06 08:46:50 +02:00
Родитель 03ac54b366
Коммит bbf7c0b952
24 изменённых файлов: 935 добавлений и 6 удалений

Просмотреть файл

@ -8,6 +8,7 @@ this.CrashTestUtils = {
crash: null,
dumpHasStream: null,
dumpHasInstructionPointerMemory: null,
dumpWin64CFITestSymbols: null,
// Constants for crash()
// Keep these in sync with nsTestCrasher.cpp!
@ -18,6 +19,18 @@ this.CrashTestUtils = {
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
@ -63,3 +76,9 @@ 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);

Просмотреть файл

@ -24,6 +24,11 @@ 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',

Просмотреть файл

@ -43,6 +43,24 @@ void PureVirtualCall()
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;
@ -50,6 +68,55 @@ 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)
@ -84,6 +151,27 @@ void Crash(int16_t how)
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;
}
@ -119,3 +207,20 @@ 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
}

Просмотреть файл

@ -1,8 +1,9 @@
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");
@ -24,15 +25,17 @@ function getEventDir() {
*
* @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)
@ -100,6 +103,31 @@ function getMinidump() {
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();
@ -131,7 +159,7 @@ function handleMinidump(callback) {
let extra = parseKeyValuePairsFromFile(extrafile);
if (callback) {
callback(minidump, extra);
callback(minidump, extra, extrafile);
}
if (minidump.exists()) {

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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" }
]);
}

Просмотреть файл

@ -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" }
]);
}

Просмотреть файл

@ -0,0 +1,7 @@
function run_test() {
do_x64CFITest("CRASH_X64CFI_EPILOG", [
{ symbol: "CRASH_X64CFI_EPILOG", trust: "context" },
{ symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
]);
}

Двоичный файл не отображается.

Просмотреть файл

@ -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]);
}

Двоичный файл не отображается.

Просмотреть файл

@ -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]);
}

Двоичный файл не отображается.

Просмотреть файл

@ -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]);
}

Просмотреть файл

@ -0,0 +1 @@
this is not a valid PE file.

Просмотреть файл

@ -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]);
}

Просмотреть файл

@ -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" }
]);
}

Просмотреть файл

@ -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" }
]);
}

Просмотреть файл

@ -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" }
]);
}

Просмотреть файл

@ -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" }
]);
}

Просмотреть файл

@ -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" }
]);
}

Просмотреть файл

@ -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 }
]);
}

Просмотреть файл

@ -37,3 +37,65 @@ 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

Просмотреть файл

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