diff options
Diffstat (limited to 'toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp')
-rw-r--r-- | toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp new file mode 100644 index 0000000000..4978415e86 --- /dev/null +++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp @@ -0,0 +1,227 @@ +/* -*- 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 <set> +# include <sstream> +# include <string> + +# include "mozilla/WindowsUnwindInfo.h" + +using namespace mozilla; + +namespace CrashReporter { + +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 CrashReporter + +#endif // XP_WIN && HAVE_64BIT_BUILD |