mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 07:45:30 +00:00
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
This commit is contained in:
parent
cbb83dfe9a
commit
6f4615d236
149
toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
Normal file
149
toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
Normal file
@ -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 <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
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<char> 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
|
@ -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 <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
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 = "<unknown code>";
|
||||||
|
}
|
||||||
|
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<ModuleUnwindParser> 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<CFIFrameInfo> 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
|
@ -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 <memory>
|
||||||
|
|
||||||
|
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<std::string, std::shared_ptr<ModuleUnwindParser>> 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
|
@ -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 <windows.h>
|
||||||
|
#include <winnt.h>
|
||||||
|
#include <ImageHlp.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<DWORD> 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
|
@ -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 <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winnt.h>
|
||||||
|
#include <ImageHlp.h>
|
||||||
|
|
||||||
|
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<DWORD, PIMAGE_RUNTIME_FUNCTION_ENTRY> mUnwindMap;
|
||||||
|
|
||||||
|
// Maps begin address to CFI.
|
||||||
|
// Populated as needed.
|
||||||
|
std::map<DWORD, UnwindCFI> 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
|
@ -32,12 +32,11 @@
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Path of the minidump to be analyzed.
|
#include "MinidumpAnalyzerUtils.h"
|
||||||
static string gMinidumpPath;
|
|
||||||
|
|
||||||
// When set to true print out the full minidump analysis, otherwise only
|
#if XP_WIN && HAVE_64BIT_BUILD
|
||||||
// include the crashing thread in the output.
|
#include "MozStackFrameSymbolizer.h"
|
||||||
static bool gFullMinidump = false;
|
#endif
|
||||||
|
|
||||||
namespace CrashReporter {
|
namespace CrashReporter {
|
||||||
|
|
||||||
@ -62,77 +61,10 @@ using google_breakpad::ProcessResult;
|
|||||||
using google_breakpad::ProcessState;
|
using google_breakpad::ProcessState;
|
||||||
using google_breakpad::StackFrame;
|
using google_breakpad::StackFrame;
|
||||||
|
|
||||||
#ifdef XP_WIN
|
MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
|
||||||
|
|
||||||
#if !defined(_MSC_VER)
|
// Path of the minidump to be analyzed.
|
||||||
static string WideToMBCP(const wstring& wide,
|
static string gMinidumpPath;
|
||||||
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
|
|
||||||
|
|
||||||
struct ModuleCompare {
|
struct ModuleCompare {
|
||||||
bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const {
|
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
|
// Record the crashing thread index only if this is a full minidump
|
||||||
// and all threads' stacks are present, otherwise only the crashing
|
// and all threads' stacks are present, otherwise only the crashing
|
||||||
// thread stack is written out and this field is set to 0.
|
// 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 {
|
} else {
|
||||||
crashInfo["type"] = Json::Value(Json::nullValue);
|
crashInfo["type"] = Json::Value(Json::nullValue);
|
||||||
@ -345,7 +278,7 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot)
|
|||||||
Json::Value threads(Json::arrayValue);
|
Json::Value threads(Json::arrayValue);
|
||||||
int threadCount = aProcessState.threads()->size();
|
int threadCount = aProcessState.threads()->size();
|
||||||
|
|
||||||
if (!gFullMinidump && (requestingThread != -1)) {
|
if (!gMinidumpAnalyzerOptions.fullMinidump && (requestingThread != -1)) {
|
||||||
// Only add the crashing thread
|
// Only add the crashing thread
|
||||||
Json::Value thread;
|
Json::Value thread;
|
||||||
Json::Value stack(Json::arrayValue);
|
Json::Value stack(Json::arrayValue);
|
||||||
@ -374,9 +307,14 @@ ConvertProcessStateToJSON(const ProcessState& aProcessState, Json::Value& aRoot)
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
|
ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
|
||||||
|
#if XP_WIN && HAVE_64BIT_BUILD
|
||||||
|
MozStackFrameSymbolizer symbolizer;
|
||||||
|
MinidumpProcessor minidumpProcessor(&symbolizer, false);
|
||||||
|
#else
|
||||||
BasicSourceLineResolver resolver;
|
BasicSourceLineResolver resolver;
|
||||||
// We don't have a valid symbol resolver so we pass nullptr instead.
|
// We don't have a valid symbol resolver so we pass nullptr instead.
|
||||||
MinidumpProcessor minidumpProcessor(nullptr, &resolver);
|
MinidumpProcessor minidumpProcessor(nullptr, &resolver);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Process the minidump.
|
// Process the minidump.
|
||||||
Minidump dump(aDumpFile);
|
Minidump dump(aDumpFile);
|
||||||
@ -415,25 +353,6 @@ OpenAppend(const string& aFilename)
|
|||||||
return file;
|
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
|
// Update the extra data file by adding the StackTraces field holding the
|
||||||
// JSON output of this program.
|
// JSON output of this program.
|
||||||
|
|
||||||
@ -473,7 +392,10 @@ ParseArguments(int argc, char** argv) {
|
|||||||
|
|
||||||
for (int i = 1; i < argc - 1; i++) {
|
for (int i = 1; i < argc - 1; i++) {
|
||||||
if (strcmp(argv[i], "--full") == 0) {
|
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 {
|
} else {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,18 @@ if CONFIG['OS_TARGET'] != 'Android':
|
|||||||
if CONFIG['OS_TARGET'] == 'Darwin':
|
if CONFIG['OS_TARGET'] == 'Darwin':
|
||||||
DIST_SUBDIR = 'crashreporter.app/Contents/MacOS'
|
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
|
# Don't use the STL wrappers in the crashreporter clients; they don't
|
||||||
# link with -lmozalloc, and it really doesn't matter here anyway.
|
# link with -lmozalloc, and it really doesn't matter here anyway.
|
||||||
DisableStlWrapping()
|
DisableStlWrapping()
|
||||||
|
@ -8,6 +8,7 @@ this.CrashTestUtils = {
|
|||||||
crash: null,
|
crash: null,
|
||||||
dumpHasStream: null,
|
dumpHasStream: null,
|
||||||
dumpHasInstructionPointerMemory: null,
|
dumpHasInstructionPointerMemory: null,
|
||||||
|
dumpWin64CFITestSymbols: null,
|
||||||
|
|
||||||
// Constants for crash()
|
// Constants for crash()
|
||||||
// Keep these in sync with nsTestCrasher.cpp!
|
// Keep these in sync with nsTestCrasher.cpp!
|
||||||
@ -18,6 +19,18 @@ this.CrashTestUtils = {
|
|||||||
CRASH_MOZ_CRASH: 4,
|
CRASH_MOZ_CRASH: 4,
|
||||||
CRASH_ABORT: 5,
|
CRASH_ABORT: 5,
|
||||||
CRASH_UNCAUGHT_EXCEPTION: 6,
|
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()
|
// Constants for dumpHasStream()
|
||||||
// From google_breakpad/common/minidump_format.h
|
// From google_breakpad/common/minidump_format.h
|
||||||
@ -63,3 +76,9 @@ CrashTestUtils.dumpCheckMemory = lib.declare("DumpCheckMemory",
|
|||||||
ctypes.default_abi,
|
ctypes.default_abi,
|
||||||
ctypes.bool,
|
ctypes.bool,
|
||||||
ctypes.char.ptr);
|
ctypes.char.ptr);
|
||||||
|
|
||||||
|
CrashTestUtils.getWin64CFITestFnAddrOffset =
|
||||||
|
lib.declare("GetWin64CFITestFnAddrOffset",
|
||||||
|
ctypes.default_abi,
|
||||||
|
ctypes.int32_t,
|
||||||
|
ctypes.int16_t);
|
||||||
|
@ -24,6 +24,11 @@ SOURCES += [
|
|||||||
'ExceptionThrower.cpp',
|
'ExceptionThrower.cpp',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
|
||||||
|
SOURCES += [
|
||||||
|
'win64UnwindInfoTests.asm',
|
||||||
|
]
|
||||||
|
|
||||||
if CONFIG['CLANG_CL']:
|
if CONFIG['CLANG_CL']:
|
||||||
SOURCES['ExceptionThrower.cpp'].flags += [
|
SOURCES['ExceptionThrower.cpp'].flags += [
|
||||||
'-Xclang',
|
'-Xclang',
|
||||||
|
@ -43,6 +43,24 @@ void PureVirtualCall()
|
|||||||
b.use(); // make sure b's actually used
|
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!
|
// Keep these in sync with CrashTestUtils.jsm!
|
||||||
const int16_t CRASH_INVALID_POINTER_DEREF = 0;
|
const int16_t CRASH_INVALID_POINTER_DEREF = 0;
|
||||||
const int16_t CRASH_PURE_VIRTUAL_CALL = 1;
|
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_MOZ_CRASH = 4;
|
||||||
const int16_t CRASH_ABORT = 5;
|
const int16_t CRASH_ABORT = 5;
|
||||||
const int16_t CRASH_UNCAUGHT_EXCEPTION = 6;
|
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
|
extern "C" NS_EXPORT
|
||||||
void Crash(int16_t how)
|
void Crash(int16_t how)
|
||||||
@ -84,6 +151,27 @@ void Crash(int16_t how)
|
|||||||
ThrowException();
|
ThrowException();
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -119,3 +207,20 @@ void TryOverrideExceptionHandler()
|
|||||||
SetUnhandledExceptionFilter(HandleException);
|
SetUnhandledExceptionFilter(HandleException);
|
||||||
}
|
}
|
||||||
#endif
|
#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/osfile.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||||
Cu.import("resource://testing-common/AppData.jsm", this);
|
Cu.import("resource://testing-common/AppData.jsm", this);
|
||||||
|
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||||
|
|
||||||
function getEventDir() {
|
function getEventDir() {
|
||||||
return OS.Path.join(do_get_tempdir().path, "crash-events");
|
return OS.Path.join(do_get_tempdir().path, "crash-events");
|
||||||
@ -24,15 +25,17 @@ function getEventDir() {
|
|||||||
*
|
*
|
||||||
* @param callback
|
* @param callback
|
||||||
* A JavaScript function to be called after the subprocess
|
* A JavaScript function to be called after the subprocess
|
||||||
* crashes. It will be passed (minidump, extra), where
|
* crashes. It will be passed (minidump, extra, extrafile), where
|
||||||
* minidump is an nsIFile of the minidump file produced,
|
* - minidump is an nsIFile of the minidump file produced,
|
||||||
* and extra is an object containing the key,value pairs from
|
* - extra is an object containing the key,value pairs from
|
||||||
* the .extra file.
|
* the .extra file.
|
||||||
|
* - extrafile is an nsIFile of the extra file
|
||||||
*
|
*
|
||||||
* @param canReturnZero
|
* @param canReturnZero
|
||||||
* If true, the subprocess may return with a zero exit code.
|
* If true, the subprocess may return with a zero exit code.
|
||||||
* Certain types of crashes may not cause the process to
|
* Certain types of crashes may not cause the process to
|
||||||
* exit with an error.
|
* exit with an error.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
function do_crash(setup, callback, canReturnZero) {
|
function do_crash(setup, callback, canReturnZero) {
|
||||||
// get current process filename (xpcshell)
|
// get current process filename (xpcshell)
|
||||||
@ -100,6 +103,32 @@ function getMinidump() {
|
|||||||
return null;
|
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) {
|
function handleMinidump(callback) {
|
||||||
// find minidump
|
// find minidump
|
||||||
let minidump = getMinidump();
|
let minidump = getMinidump();
|
||||||
@ -131,7 +160,7 @@ function handleMinidump(callback) {
|
|||||||
let extra = parseKeyValuePairsFromFile(extrafile);
|
let extra = parseKeyValuePairsFromFile(extrafile);
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(minidump, extra);
|
callback(minidump, extra, extrafile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minidump.exists()) {
|
if (minidump.exists()) {
|
||||||
|
193
toolkit/crashreporter/test/unit/head_win64cfi.js
Normal file
193
toolkit/crashreporter/test/unit/head_win64cfi.js
Normal file
@ -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" }
|
||||||
|
]);
|
||||||
|
}
|
Binary file not shown.
@ -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]);
|
||||||
|
}
|
Binary file not shown.
@ -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]);
|
||||||
|
}
|
Binary file not shown.
@ -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_crash_AsyncShutdown.js]
|
||||||
[test_event_files.js]
|
[test_event_files.js]
|
||||||
[test_crash_terminator.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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
382
toolkit/crashreporter/test/win64UnwindInfoTests.asm
Normal file
382
toolkit/crashreporter/test/win64UnwindInfoTests.asm
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user