Source code

Revision control

Copy as Markdown

Other Tools

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