From 6f4615d2361267b46933f05b3b2680beeb929e0d Mon Sep 17 00:00:00 2001 From: Carl Corcoran Date: Sun, 6 Aug 2017 08:46:50 +0200 Subject: [PATCH] Bug 1333126 - Use win64 PE unwind metadata to improve client-side stack walking; r=gsvelto This includes tests that cover both regular CFI stack walking as well as pathological corner cases. MozReview-Commit-ID: GDARnPSemyu --HG-- extra : source : 1a140f07e06de7e7d96a37d693db832311b301ae --- .../minidump-analyzer/MinidumpAnalyzerUtils.h | 149 +++++++ .../MozStackFrameSymbolizer.cpp | 120 ++++++ .../MozStackFrameSymbolizer.h | 48 +++ .../Win64ModuleUnwindMetadata.cpp | 265 ++++++++++++ .../Win64ModuleUnwindMetadata.h | 57 +++ .../minidump-analyzer/minidump-analyzer.cpp | 116 +----- .../crashreporter/minidump-analyzer/moz.build | 12 + toolkit/crashreporter/test/CrashTestUtils.jsm | 19 + toolkit/crashreporter/test/moz.build | 5 + toolkit/crashreporter/test/nsTestCrasher.cpp | 105 +++++ .../test/unit/head_crashreporter.js | 41 +- .../crashreporter/test/unit/head_win64cfi.js | 193 +++++++++ .../unit/test_crash_win64cfi_alloc_large.js | 7 + .../unit/test_crash_win64cfi_alloc_small.js | 7 + .../test/unit/test_crash_win64cfi_epilog.js | 7 + ...est_crash_win64cfi_infinite_code_chain.exe | Bin 0 -> 1440 bytes ...test_crash_win64cfi_infinite_code_chain.js | 17 + ...st_crash_win64cfi_infinite_entry_chain.exe | Bin 0 -> 1440 bytes ...est_crash_win64cfi_infinite_entry_chain.js | 17 + ...t_crash_win64cfi_invalid_exception_rva.exe | Bin 0 -> 1440 bytes ...st_crash_win64cfi_invalid_exception_rva.js | 16 + .../unit/test_crash_win64cfi_not_a_pe.exe | 1 + .../test/unit/test_crash_win64cfi_not_a_pe.js | 15 + .../unit/test_crash_win64cfi_push_nonvol.js | 7 + .../unit/test_crash_win64cfi_save_nonvol.js | 7 + .../test_crash_win64cfi_save_nonvol_far.js | 7 + .../unit/test_crash_win64cfi_save_xmm128.js | 7 + .../test_crash_win64cfi_save_xmm128_far.js | 7 + .../unit/test_crash_win64cfi_unknown_op.js | 13 + toolkit/crashreporter/test/unit/xpcshell.ini | 62 +++ .../test/win64UnwindInfoTests.asm | 382 ++++++++++++++++++ 31 files changed, 1606 insertions(+), 103 deletions(-) create mode 100644 toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h create mode 100644 toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp create mode 100644 toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h create mode 100644 toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp create mode 100644 toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h create mode 100644 toolkit/crashreporter/test/unit/head_win64cfi.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.exe create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.exe create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.exe create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js create mode 100644 toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js create mode 100644 toolkit/crashreporter/test/win64UnwindInfoTests.asm diff --git a/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h new file mode 100644 index 000000000000..e2a8bd826bfa --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef MinidumpAnalyzerUtils_h +#define MinidumpAnalyzerUtils_h + +#ifdef XP_WIN +#include +#endif + +#include +#include +#include +#include + +namespace CrashReporter { + +struct MinidumpAnalyzerOptions { + bool fullMinidump; + std::string forceUseModule; +}; + +extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions; + +#ifdef XP_WIN + +#if !defined(_MSC_VER) +static inline std::string +WideToMBCP(const std::wstring& wide, unsigned int cp, bool* success = nullptr) +{ + char* buffer = nullptr; + int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(), + -1, nullptr, 0, nullptr, nullptr); + if (buffer_size == 0) { + if (success) { + *success = false; + } + + return ""; + } + + buffer = new char[buffer_size]; + if (buffer == nullptr) { + if (success) { + *success = false; + } + + return ""; + } + + WideCharToMultiByte(cp, 0, wide.c_str(), + -1, buffer, buffer_size, nullptr, nullptr); + std::string mb = buffer; + delete [] buffer; + + if (success) { + *success = true; + } + + return mb; +} +#endif /* !defined(_MSC_VER) */ + +static inline std::wstring +UTF8ToWide(const std::string& aUtf8Str, bool *aSuccess = nullptr) +{ + wchar_t* buffer = nullptr; + int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(), + -1, nullptr, 0); + if (buffer_size == 0) { + if (aSuccess) { + *aSuccess = false; + } + + return L""; + } + + buffer = new wchar_t[buffer_size]; + + if (buffer == nullptr) { + if (aSuccess) { + *aSuccess = false; + } + + return L""; + } + + MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(), + -1, buffer, buffer_size); + std::wstring str = buffer; + delete [] buffer; + + if (aSuccess) { + *aSuccess = true; + } + + return str; +} + +static inline std::string +WideToMBCS(const std::wstring &inp) { + int buffer_size = WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1, + nullptr, 0, NULL, NULL); + if (buffer_size == 0) { + return ""; + } + + std::vector buffer(buffer_size); + buffer[0] = 0; + + WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1, + buffer.data(), buffer_size, NULL, NULL); + + return buffer.data(); +} + +static inline std::string +UTF8toMBCS(const std::string &inp) { + std::wstring wide = UTF8ToWide(inp); + std::string ret = WideToMBCS(wide); + return ret; +} + +#endif // XP_WIN + +// Check if a file exists at the specified path + +static inline bool +FileExists(const std::string& aPath) +{ +#if defined(XP_WIN) + DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str()); + return (attrs != INVALID_FILE_ATTRIBUTES); +#else // Non-Windows + struct stat sb; + int ret = stat(aPath.c_str(), &sb); + if (ret == -1 || !(sb.st_mode & S_IFREG)) { + return false; + } + + return true; +#endif // XP_WIN +} + +} // namespace + +#endif // MinidumpAnalyzerUtils_h diff --git a/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp new file mode 100644 index 000000000000..9683bc903e40 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#if XP_WIN && HAVE_64BIT_BUILD + +#include "MozStackFrameSymbolizer.h" + +#include "MinidumpAnalyzerUtils.h" + +#include "processor/cfi_frame_info.h" + +#include +#include +#include + +namespace CrashReporter { + + extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions; + + using google_breakpad::CFIFrameInfo; + +MozStackFrameSymbolizer::MozStackFrameSymbolizer() : + StackFrameSymbolizer(nullptr, nullptr) +{ +} + +MozStackFrameSymbolizer::SymbolizerResult +MozStackFrameSymbolizer::FillSourceLineInfo(const CodeModules* modules, + const SystemInfo* system_info, + StackFrame* stack_frame) +{ + SymbolizerResult ret = StackFrameSymbolizer::FillSourceLineInfo( + modules, system_info, stack_frame); + + if (ret == kNoError && this->HasImplementation() && + stack_frame->function_name.empty()) { + // Breakpad's Stackwalker::InstructionAddressSeemsValid only considers an + // address valid if it has associated symbols. + // + // This makes sense for complete & accurate symbols, but ours may be + // incomplete or wrong. Returning a function name tells Breakpad we + // recognize this address as code, so it's OK to use in stack scanning. + // This function is only called with addresses that land in this module. + // + // This allows us to fall back to stack scanning in the case where we were + // unable to provide CFI. + stack_frame->function_name = ""; + } + return ret; +} + +CFIFrameInfo* +MozStackFrameSymbolizer::FindCFIFrameInfo(const StackFrame* frame) +{ + std::string modulePath; + + // For unit testing, support loading a specified module instead of + // the real one. + bool moduleHasBeenReplaced = false; + if (gMinidumpAnalyzerOptions.forceUseModule.size() > 0) { + modulePath = gMinidumpAnalyzerOptions.forceUseModule; + moduleHasBeenReplaced = true; + } else { + if (!frame->module) { + return nullptr; + } + modulePath = frame->module->code_file(); + } + + // Get/create the unwind parser. + auto itMod = mModuleMap.find(modulePath); + std::shared_ptr unwindParser; + if (itMod != mModuleMap.end()) { + unwindParser = itMod->second; + } else { + unwindParser.reset(new ModuleUnwindParser(modulePath)); + mModuleMap[modulePath] = unwindParser; + } + + UnwindCFI cfi; + DWORD offsetAddr; + + if (moduleHasBeenReplaced) { + // If we are replacing a module, addresses will never line up. + // So just act like the 1st entry is correct. + offsetAddr = unwindParser->GetAnyOffsetAddr(); + } else { + offsetAddr = frame->instruction - frame->module->base_address(); + } + + if (!unwindParser->GetCFI(offsetAddr, cfi)) { + return nullptr; + } + + std::unique_ptr rules(new CFIFrameInfo()); + + static const size_t exprSize = 50; + char expr[exprSize]; + if (cfi.stackSize == 0) { + snprintf(expr, exprSize, "$rsp"); + } else { + snprintf(expr, exprSize, "$rsp %d +", cfi.stackSize); + } + rules->SetCFARule(expr); + + if (cfi.ripOffset == 0) { + snprintf(expr, exprSize, ".cfa ^"); + } else { + snprintf(expr, exprSize, ".cfa %d - ^", cfi.ripOffset); + } + rules->SetRARule(expr); + + return rules.release(); +} + +} // namespace CrashReporter + +#endif // XP_WIN && HAVE_64BIT_BUILD diff --git a/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h new file mode 100644 index 000000000000..49d2b75dcc12 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef MozStackFrameSymbolizer_h +#define MozStackFrameSymbolizer_h + +#if XP_WIN && HAVE_64BIT_BUILD + +#include "Win64ModuleUnwindMetadata.h" + +#include "google_breakpad/processor/stack_frame_symbolizer.h" +#include "google_breakpad/processor/stack_frame.h" + +#include + +namespace CrashReporter { + +using google_breakpad::CodeModule; +using google_breakpad::CodeModules; +using google_breakpad::SourceLineResolverInterface; +using google_breakpad::StackFrame; +using google_breakpad::StackFrameSymbolizer; +using google_breakpad::SymbolSupplier; +using google_breakpad::SystemInfo; + +class MozStackFrameSymbolizer : public StackFrameSymbolizer { + using google_breakpad::StackFrameSymbolizer::SymbolizerResult; + + std::map> mModuleMap; + +public: + MozStackFrameSymbolizer(); + + virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules, + const SystemInfo* system_info, + StackFrame* stack_frame); + + virtual class google_breakpad::CFIFrameInfo* FindCFIFrameInfo( + const StackFrame* frame); +}; + +} // namespace CrashReporter + +#endif // XP_WIN && HAVE_64BIT_BUILD + +#endif // MozStackFrameSymbolizer_h diff --git a/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp new file mode 100644 index 000000000000..635218c5366d --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp @@ -0,0 +1,265 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#if XP_WIN && HAVE_64BIT_BUILD + +#include "Win64ModuleUnwindMetadata.h" + +#include "MinidumpAnalyzerUtils.h" + +#include +#include +#include +#include +#include +#include + +namespace CrashReporter { + +union UnwindCode { + struct { + uint8_t offset_in_prolog; + uint8_t unwind_operation_code : 4; + uint8_t operation_info : 4; + }; + USHORT frame_offset; +}; + +enum UnwindOperationCodes { + UWOP_PUSH_NONVOL = 0, // info == register number + UWOP_ALLOC_LARGE = 1, // no info, alloc size in next 2 slots + UWOP_ALLOC_SMALL = 2, // info == size of allocation / 8 - 1 + UWOP_SET_FPREG = 3, // no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 + UWOP_SAVE_NONVOL = 4, // info == register number, offset in next slot + UWOP_SAVE_NONVOL_FAR = 5, // info == register number, offset in next 2 slots + UWOP_SAVE_XMM = 6, // Version 1; undocumented + UWOP_EPILOG = 6, // Version 2; undocumented + UWOP_SAVE_XMM_FAR = 7, // Version 1; undocumented + UWOP_SPARE = 7, // Version 2; undocumented + UWOP_SAVE_XMM128 = 8, // info == XMM reg number, offset in next slot + UWOP_SAVE_XMM128_FAR = 9, // info == XMM reg number, offset in next 2 slots + UWOP_PUSH_MACHFRAME = 10 // info == 0: no error-code, 1: error-code +}; + +struct UnwindInfo { + uint8_t version : 3; + uint8_t flags : 5; + uint8_t size_of_prolog; + uint8_t count_of_codes; + uint8_t frame_register : 4; + uint8_t frame_offset : 4; + UnwindCode unwind_code[1]; +}; + +ModuleUnwindParser::~ModuleUnwindParser() +{ + if (mImg) { + ImageUnload(mImg); + } +} + +void* +ModuleUnwindParser::RvaToVa(ULONG aRva) +{ + return ImageRvaToVa( + mImg->FileHeader, mImg->MappedAddress, aRva, &mImg->LastRvaSection); +} + +ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath) + : mPath(aPath) +{ + // Convert wchar to native charset because ImageLoad only takes + // a PSTR as input. + std::string code_file = UTF8toMBCS(aPath); + + mImg = ImageLoad((PSTR)code_file.c_str(), NULL); + if (!mImg || !mImg->FileHeader) { + return; + } + + PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader; + if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + return; + } + + DWORD exception_rva = optional_header-> + DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress; + + DWORD exception_size = optional_header-> + DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size; + + auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva); + if (!funcs) { + return; + } + + for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) { + mUnwindMap[funcs[i].BeginAddress] = &funcs[i]; + } +} + +bool +ModuleUnwindParser::GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc, + UnwindCFI& aRet) +{ + DWORD unwind_rva = aFunc.UnwindInfoAddress; + // Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid + // circular references. + std::set visited; + + // Follow chained function entries + while (unwind_rva & 0x1) { + unwind_rva ^= 0x1; + + if (visited.end() != visited.find(unwind_rva)) { + return false; + } + visited.insert(unwind_rva); + + auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva); + if (!chained_func) { + return false; + } + unwind_rva = chained_func->UnwindInfoAddress; + } + + visited.insert(unwind_rva); + + auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva); + if (!unwind_info) { + return false; + } + + DWORD stack_size = 8; // minimal stack size is 8 for RIP + DWORD rip_offset = 8; + do { + for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) { + UnwindCode* unwind_code = &unwind_info->unwind_code[c]; + switch (unwind_code->unwind_operation_code) { + case UWOP_PUSH_NONVOL: { + stack_size += 8; + break; + } + case UWOP_ALLOC_LARGE: { + if (unwind_code->operation_info == 0) { + c++; + if (c < unwind_info->count_of_codes) { + stack_size += (unwind_code + 1)->frame_offset * 8; + } + } else { + c += 2; + if (c < unwind_info->count_of_codes) { + stack_size += (unwind_code + 1)->frame_offset | + ((unwind_code + 2)->frame_offset << 16); + } + } + break; + } + case UWOP_ALLOC_SMALL: { + stack_size += unwind_code->operation_info * 8 + 8; + break; + } + case UWOP_SET_FPREG: + // To correctly track RSP when it's been transferred to another + // register, we would need to emit CFI records for every unwind op. + // For simplicity, don't emit CFI records for this function as + // we know it will be incorrect after this point. + return false; + case UWOP_SAVE_NONVOL: + case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG + case UWOP_SAVE_XMM128: { + c++; // skip slot with offset + break; + } + case UWOP_SAVE_NONVOL_FAR: + case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE + case UWOP_SAVE_XMM128_FAR: { + c += 2; // skip 2 slots with offset + break; + } + case UWOP_PUSH_MACHFRAME: { + if (unwind_code->operation_info) { + stack_size += 88; + } else { + stack_size += 80; + } + rip_offset += 80; + break; + } + default: { + return false; + } + } + } + + if (unwind_info->flags & UNW_FLAG_CHAININFO) { + auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)( + (unwind_info->unwind_code + + ((unwind_info->count_of_codes + 1) & ~1))); + + if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) { + return false; // Circular reference + } + + visited.insert(chained_func->UnwindInfoAddress); + + unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress); + } else { + unwind_info = nullptr; + } + } while (unwind_info); + + aRet.beginAddress = aFunc.BeginAddress; + aRet.size = aFunc.EndAddress - aFunc.BeginAddress; + aRet.stackSize = stack_size; + aRet.ripOffset = rip_offset; + return true; +} + +// For unit testing we sometimes need any address that's valid in this module. +// Just return the first address we know of. +DWORD +ModuleUnwindParser::GetAnyOffsetAddr() const { + if (mUnwindMap.size() < 1) { + return 0; + } + return mUnwindMap.begin()->first; +} + +bool +ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet) +{ + // Figure out the begin address of the requested address. + auto itUW = mUnwindMap.lower_bound(aAddress + 1); + if (itUW == mUnwindMap.begin()) { + return false; // address before this module. + } + --itUW; + + // Ensure that the function entry is big enough to contain this address. + IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second; + if (aAddress > func.EndAddress) { + return false; + } + + // Do we have CFI for this function already? + auto itCFI = mCFIMap.find(aAddress); + if (itCFI != mCFIMap.end()) { + aRet = itCFI->second; + return true; + } + + // No, generate it. + if (!GenerateCFIForFunction(func, aRet)) { + return false; + } + + mCFIMap[func.BeginAddress] = aRet; + return true; +} + +} // namespace + +#endif // XP_WIN && HAVE_64BIT_BUILD diff --git a/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h new file mode 100644 index 000000000000..3abd80642fc7 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef Win64ModuleUnwindMetadata_h +#define Win64ModuleUnwindMetadata_h + +#if XP_WIN && HAVE_64BIT_BUILD + +#include +#include +#include + +#include +#include +#include + +namespace CrashReporter { + +struct UnwindCFI +{ + uint32_t beginAddress; + uint32_t size; + uint32_t stackSize; + uint32_t ripOffset; +}; + +// Does lazy-parsing of unwind info. +class ModuleUnwindParser { + PLOADED_IMAGE mImg; + std::string mPath; + + // Maps begin address to exception record. + // Populated upon construction. + std::map mUnwindMap; + + // Maps begin address to CFI. + // Populated as needed. + std::map mCFIMap; + + bool GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc, + UnwindCFI& aRet); + void* RvaToVa(ULONG aRva); + +public: + explicit ModuleUnwindParser(const std::string& aPath); + ~ModuleUnwindParser(); + bool GetCFI(DWORD aAddress, UnwindCFI& aRet); + DWORD GetAnyOffsetAddr() const; +}; + +} // namespace + +#endif // XP_WIN && HAVE_64BIT_BUILD + +#endif // Win64ModuleUnwindMetadata_h diff --git a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp index 3cdeee2edbb0..a68c4e585b43 100644 --- a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp +++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp @@ -32,12 +32,11 @@ #endif -// Path of the minidump to be analyzed. -static string gMinidumpPath; +#include "MinidumpAnalyzerUtils.h" -// When set to true print out the full minidump analysis, otherwise only -// include the crashing thread in the output. -static bool gFullMinidump = false; +#if XP_WIN && HAVE_64BIT_BUILD +#include "MozStackFrameSymbolizer.h" +#endif namespace CrashReporter { @@ -62,77 +61,10 @@ using google_breakpad::ProcessResult; using google_breakpad::ProcessState; using google_breakpad::StackFrame; -#ifdef XP_WIN +MinidumpAnalyzerOptions gMinidumpAnalyzerOptions; -#if !defined(_MSC_VER) -static string WideToMBCP(const wstring& wide, - unsigned int cp, - bool* success = nullptr) -{ - char* buffer = nullptr; - int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(), - -1, nullptr, 0, nullptr, nullptr); - if(buffer_size == 0) { - if (success) - *success = false; - return ""; - } - - buffer = new char[buffer_size]; - if(buffer == nullptr) { - if (success) - *success = false; - return ""; - } - - WideCharToMultiByte(cp, 0, wide.c_str(), - -1, buffer, buffer_size, nullptr, nullptr); - string mb = buffer; - delete [] buffer; - - if (success) - *success = true; - - return mb; -} -#endif /* !defined(_MSC_VER) */ - -static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr) -{ - wchar_t* buffer = nullptr; - int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(), - -1, nullptr, 0); - if (buffer_size == 0) { - if (aSuccess) { - *aSuccess = false; - } - - return L""; - } - - buffer = new wchar_t[buffer_size]; - - if (buffer == nullptr) { - if (aSuccess) { - *aSuccess = false; - } - - return L""; - } - - MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(), - -1, buffer, buffer_size); - wstring str = buffer; - delete [] buffer; - - if (aSuccess) { - *aSuccess = true; - } - - return str; -} - -#endif +// Path of the minidump to be analyzed. +static string gMinidumpPath; struct ModuleCompare { bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const { @@ -317,7 +249,8 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot) // Record the crashing thread index only if this is a full minidump // and all threads' stacks are present, otherwise only the crashing // thread stack is written out and this field is set to 0. - crashInfo["crashing_thread"] = gFullMinidump ? requestingThread : 0; + crashInfo["crashing_thread"] = + gMinidumpAnalyzerOptions.fullMinidump ? requestingThread : 0; } } else { crashInfo["type"] = Json::Value(Json::nullValue); @@ -345,7 +278,7 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot) Json::Value threads(Json::arrayValue); int threadCount = aProcessState.threads()->size(); - if (!gFullMinidump && (requestingThread != -1)) { + if (!gMinidumpAnalyzerOptions.fullMinidump && (requestingThread != -1)) { // Only add the crashing thread Json::Value thread; Json::Value stack(Json::arrayValue); @@ -374,9 +307,14 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot) static bool ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) { +#if XP_WIN && HAVE_64BIT_BUILD + MozStackFrameSymbolizer symbolizer; + MinidumpProcessor minidumpProcessor(&symbolizer, false); +#else BasicSourceLineResolver resolver; // We don't have a valid symbol resolver so we pass nullptr instead. MinidumpProcessor minidumpProcessor(nullptr, &resolver); +#endif // Process the minidump. Minidump dump(aDumpFile); @@ -415,25 +353,6 @@ OpenAppend(const string& aFilename) return file; } -// Check if a file exists at the specified path - -static bool -FileExists(const string& aPath) -{ -#if defined(XP_WIN) - DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str()); - return (attrs != INVALID_FILE_ATTRIBUTES); -#else // Non-Windows - struct stat sb; - int ret = stat(aPath.c_str(), &sb); - if (ret == -1 || !(sb.st_mode & S_IFREG)) { - return false; - } - - return true; -#endif // XP_WIN -} - // Update the extra data file by adding the StackTraces field holding the // JSON output of this program. @@ -473,7 +392,10 @@ ParseArguments(int argc, char** argv) { for (int i = 1; i < argc - 1; i++) { if (strcmp(argv[i], "--full") == 0) { - gFullMinidump = true; + gMinidumpAnalyzerOptions.fullMinidump = true; + } else if ((strcmp(argv[i], "--force-use-module") == 0) && (i < argc - 2)) { + gMinidumpAnalyzerOptions.forceUseModule = argv[i + 1]; + ++i; } else { exit(EXIT_FAILURE); } diff --git a/toolkit/crashreporter/minidump-analyzer/moz.build b/toolkit/crashreporter/minidump-analyzer/moz.build index 7c7923bb5ba7..f40d1c139494 100644 --- a/toolkit/crashreporter/minidump-analyzer/moz.build +++ b/toolkit/crashreporter/minidump-analyzer/moz.build @@ -27,6 +27,18 @@ if CONFIG['OS_TARGET'] != 'Android': if CONFIG['OS_TARGET'] == 'Darwin': DIST_SUBDIR = 'crashreporter.app/Contents/MacOS' +if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64': + UNIFIED_SOURCES += [ + 'MozStackFrameSymbolizer.cpp', + 'Win64ModuleUnwindMetadata.cpp', + ] + + OS_LIBS += [ + 'Dbghelp', + 'Imagehlp' + ] + + # Don't use the STL wrappers in the crashreporter clients; they don't # link with -lmozalloc, and it really doesn't matter here anyway. DisableStlWrapping() diff --git a/toolkit/crashreporter/test/CrashTestUtils.jsm b/toolkit/crashreporter/test/CrashTestUtils.jsm index 83be13562256..ba5b15742110 100644 --- a/toolkit/crashreporter/test/CrashTestUtils.jsm +++ b/toolkit/crashreporter/test/CrashTestUtils.jsm @@ -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); diff --git a/toolkit/crashreporter/test/moz.build b/toolkit/crashreporter/test/moz.build index 152a16bd9c50..0adaa52310cf 100644 --- a/toolkit/crashreporter/test/moz.build +++ b/toolkit/crashreporter/test/moz.build @@ -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', diff --git a/toolkit/crashreporter/test/nsTestCrasher.cpp b/toolkit/crashreporter/test/nsTestCrasher.cpp index dd62598b23b6..e69f1c2163f0 100644 --- a/toolkit/crashreporter/test/nsTestCrasher.cpp +++ b/toolkit/crashreporter/test/nsTestCrasher.cpp @@ -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 +GetWin64CFITestMap() { + std::map 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 +} diff --git a/toolkit/crashreporter/test/unit/head_crashreporter.js b/toolkit/crashreporter/test/unit/head_crashreporter.js index 8fbc78328297..026d8bb4f224 100644 --- a/toolkit/crashreporter/test/unit/head_crashreporter.js +++ b/toolkit/crashreporter/test/unit/head_crashreporter.js @@ -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,32 @@ 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 = []; + if (additionalArgs) { + args = args.concat(additionalArgs); + } + args.push(dumpFile.path); + process.run(true /* blocking */, args, args.length); +} + function handleMinidump(callback) { // find minidump let minidump = getMinidump(); @@ -131,7 +160,7 @@ function handleMinidump(callback) { let extra = parseKeyValuePairsFromFile(extrafile); if (callback) { - callback(minidump, extra); + callback(minidump, extra, extrafile); } if (minidump.exists()) { diff --git a/toolkit/crashreporter/test/unit/head_win64cfi.js b/toolkit/crashreporter/test/unit/head_win64cfi.js new file mode 100644 index 000000000000..042cadc64a5f --- /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); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js new file mode 100644 index 000000000000..83aa3625a941 --- /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" } + ]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js new file mode 100644 index 000000000000..f79d233e9183 --- /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" } + ]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js new file mode 100644 index 000000000000..2a9027810853 --- /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" } + ]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.exe b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.exe new file mode 100644 index 0000000000000000000000000000000000000000..5283cc5df7e0a7abb20dd540cc203c4266702fff GIT binary patch literal 1440 zcmah}%}*0i5T61?EcPP}n0j(+){m%Z3=|HYYAmwE0!>RoghpCu12yfoDT@N(U{f)H zjR`8;FzKJ5MhNjl0>&sU8ZjY=Ui?ZEIC%76vd(PxL8(S3nVosR`OUmHZ)dl?X9g+( zpbAw{0G7~_xYXyrAIlgv9e-|uXNU9lC7ma4?;45@bIDX8(X~&#pc1B%)n0tj7lgzlDKBQmcS6iOHY;p|M9)ZFWK@BwnK`HN{E`VIqH*oCtE5UK+us2 zholgI4P#W3(`+fvg{h8Ykw<5z9xiCMoF_~L_!?nK;w{}mdY8jpr?>9Bl4>&iNtJ3| zoZvb0qQv*@9GYG$H|2GuK964Jry_mz+?(eqyG`!3w8_J^Hu+26N-mdE z>vs#8-Jp3fz}uvb7vlyjxJjetdL|^z^ zmVSk$KhYJ=V(h?PV`b!HjNG^^$n*STTng#cESkO*@Zw#S3!6=P9WKPeW<-A#l}DVEgvCt7<=)fhcl+0Gzkh95TWFm;*YP6TY8^UfskC11^3W{NY^ig`$3=>>nRG;HR`@2u*j%$Y zqdskvr_LQeK0Y4qA4FaO`=FU}jB4|U_eT9|wHnZc^9Qj;!d!^accAf{gwdVb_OWKd z*muX~*lyK>HGCSZ&QoC3@lcxt&>5=Ka~PghCv`?W7@bGKsH=l&jL|+!deEm{|0d<{ xd!w>_B<7CZ1i0Gb>uB*bHaNnu*nTG6<0Dk+KZ|x7^%wf|f9k|NYD)H{tlv4%8B+iN literal 0 HcmV?d00001 diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js new file mode 100644 index 000000000000..1e475d588959 --- /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]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.exe b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.exe new file mode 100644 index 0000000000000000000000000000000000000000..de48576f6536cc6895f23297752690da1a694a10 GIT binary patch literal 1440 zcmah}-%C?*6hAke+H5}<8tbWR+jiJLm3xb*`ub=X=igb3W&Mzvp|;*4jM@ zWdKl)s;U6$a39j{Aiwaa=uxGUUJQ)>&2FtF?FZI^RXbFC@jxQV_HC-1K%9qm+&p8N-Vh z@|z{;O)UKxmp_TIJ!g%SkPk6(!-A;H2v2b6Wo;dG^t)mFC8M)*m17 zONl}0dQ=MghZ~)KUnCX@4McW8{Nz1a9lB?+TsYs^LbF7(rOlZTmnhC=(h;Ru5zY|C z=9m}fV_@S1s7?ZK#&R$^&0u_9k>pGUFgXu`iK~GMjL|tvy3waz z|0bp1d$p`}Fy@X90bKO>+B_`{^^R~Xwwp=s_yqMD*36>aK>dYpLH|$fct%yxxs>%A DNxT&; literal 0 HcmV?d00001 diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js new file mode 100644 index 000000000000..f2dbb81498b6 --- /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]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.exe b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.exe new file mode 100644 index 0000000000000000000000000000000000000000..ab4ce326bdf34b9e417107d2d22adb3d01b006ee GIT binary patch literal 1440 zcmah}-%C?*6hBv;+H5}<8tbWReEUJgC3O1G)1sMInmRT?vXPr^=wiDY^I9rCG?sy0 z0xNwp*q>05B0UKjQ96qZ#Ec&L#Xui=>>*g^+}*2lMIAWbbH1PRIp_O5-+Q+9o@pot zfC|)n9$*nIiBnzwy)0qaeC)Xyo*l~8FLLf|eb-=Qh)*Qr1Ib{F?+;2+T;}`2d~#Ug zBNFfG^zpHHC@h#vMy8eCteJoL@W%Fxt{t=1nQ3+ZVdeqa3dg(I`>NfVrM)-OKS*5t zW}X&+(A{#de)3(_u^(_8YRb%DB;({IXPpgE&l1I^dErKC{kDe$7{^NU)Uv%UjY(Wl>$cS37IqH*oCtEB$KtM=_ zf^rbRjxnmqYqpr@#8e?s;L+U^*wcBCse9s zVVv=n1)25kR86hsviKYq7m)?Zrm~)|%c7V5nU}x1@69pFZd0yV+ms=DoARZ1d2MY? zt=}!CcLSCMKdYy5*LMuLZ~JvNlsEah39Zg|QSq~cm|h8Bww9aP&0v($@gn1R5ku~C zN&00y{Rx*lgRui=jh2v)>g2{HQJG_paVw@)GH802!GmvAD*9~F=WwFF#djwq(PM|v zucbZy5J^e&mw-Tv>X+if z(yfRT@{cq-{N8Xh92^Sof%wV$)H-y}Vrjk9<)$JuTk4##m_%`XCWQ#i3Tq-vpKE>2 zs82iPsdLB1#>PT@1IWu^A2d_Kh&GS-Zq&b4s{vg&cMxkN%!wHN4m6&FFnV+QKGsYa z%`78Kn`6#a1-8&BusKeGjbl)g0N{)jV04(l__Q*?nG9fZ903zo3zZn7b9CuJpL+e9 zlz#7x^7i4VD{>RyN{hFn#ogE-grd>?OnS#hsMLQ3?KbK!^y&Yp6VIqFI+wD3127&K AdjJ3c literal 0 HcmV?d00001 diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js new file mode 100644 index 000000000000..90ba30b1442a --- /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]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe new file mode 100644 index 000000000000..5fa1d76ffc60 --- /dev/null +++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe @@ -0,0 +1 @@ +this is not a valid PE file. \ No newline at end of file diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js new file mode 100644 index 000000000000..02caa81534d4 --- /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]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js new file mode 100644 index 000000000000..8097dc858558 --- /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" } + ]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js new file mode 100644 index 000000000000..9e38e8ea7f34 --- /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" } + ]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js new file mode 100644 index 000000000000..2a1e47e7d667 --- /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" } + ]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js new file mode 100644 index 000000000000..6aef92324edb --- /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" } + ]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js new file mode 100644 index 000000000000..32e297548d24 --- /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" } + ]); +} diff --git a/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js b/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js new file mode 100644 index 000000000000..4447cbd9adb0 --- /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 } + ]); +} diff --git a/toolkit/crashreporter/test/unit/xpcshell.ini b/toolkit/crashreporter/test/unit/xpcshell.ini index 25bea51bd25c..0ac4cc209e3e 100644 --- a/toolkit/crashreporter/test/unit/xpcshell.ini +++ b/toolkit/crashreporter/test/unit/xpcshell.ini @@ -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 + + + + + + diff --git a/toolkit/crashreporter/test/win64UnwindInfoTests.asm b/toolkit/crashreporter/test/win64UnwindInfoTests.asm new file mode 100644 index 000000000000..905d395b943b --- /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