/* -*- 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 # include # 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 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