summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp')
-rw-r--r--toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp226
1 files changed, 226 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..efe67aa021
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
@@ -0,0 +1,226 @@
+/* -*- 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