226 lines
6.3 KiB
C++
226 lines
6.3 KiB
C++
/* -*- 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
|