Backed out changeset bcbab342eed8 (bug 1889658) for causing wpt reftest failures...
[gecko.git] / toolkit / crashreporter / minidump-analyzer / Win64ModuleUnwindMetadata.cpp
blobefe67aa021de820175c208aea5c575a67e2eef42
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #if XP_WIN && HAVE_64BIT_BUILD
8 # include "Win64ModuleUnwindMetadata.h"
10 # include "MinidumpAnalyzerUtils.h"
12 # include <windows.h>
13 # include <winnt.h>
14 # include <imagehlp.h>
15 # include <set>
16 # include <sstream>
17 # include <string>
19 # include "mozilla/WindowsUnwindInfo.h"
21 using namespace mozilla;
23 namespace CrashReporter {
25 ModuleUnwindParser::~ModuleUnwindParser() {
26 if (mImg) {
27 ImageUnload(mImg);
31 void* ModuleUnwindParser::RvaToVa(ULONG aRva) {
32 return ImageRvaToVa(mImg->FileHeader, mImg->MappedAddress, aRva,
33 &mImg->LastRvaSection);
36 ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath)
37 : mPath(aPath) {
38 // Convert wchar to native charset because ImageLoad only takes
39 // a PSTR as input.
40 std::string code_file = UTF8ToMBCS(aPath);
42 mImg = ImageLoad((PSTR)code_file.c_str(), NULL);
43 if (!mImg || !mImg->FileHeader) {
44 return;
47 PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader;
48 if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
49 return;
52 DWORD exception_rva =
53 optional_header->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]
54 .VirtualAddress;
56 DWORD exception_size =
57 optional_header->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
59 auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva);
60 if (!funcs) {
61 return;
64 for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) {
65 mUnwindMap[funcs[i].BeginAddress] = &funcs[i];
69 bool ModuleUnwindParser::GenerateCFIForFunction(
70 IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc, UnwindCFI& aRet) {
71 DWORD unwind_rva = aFunc.UnwindInfoAddress;
72 // Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid
73 // circular references.
74 std::set<DWORD> visited;
76 // Follow chained function entries
77 while (unwind_rva & 0x1) {
78 unwind_rva ^= 0x1;
80 if (visited.end() != visited.find(unwind_rva)) {
81 return false;
83 visited.insert(unwind_rva);
85 auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva);
86 if (!chained_func) {
87 return false;
89 unwind_rva = chained_func->UnwindInfoAddress;
92 visited.insert(unwind_rva);
94 auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva);
95 if (!unwind_info) {
96 return false;
99 DWORD stack_size = 8; // minimal stack size is 8 for RIP
100 DWORD rip_offset = 8;
101 do {
102 for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) {
103 UnwindCode* unwind_code = &unwind_info->unwind_code[c];
104 switch (unwind_code->unwind_operation_code) {
105 case UWOP_PUSH_NONVOL: {
106 stack_size += 8;
107 break;
109 case UWOP_ALLOC_LARGE: {
110 if (unwind_code->operation_info == 0) {
111 c++;
112 if (c < unwind_info->count_of_codes) {
113 stack_size += (unwind_code + 1)->frame_offset * 8;
115 } else {
116 c += 2;
117 if (c < unwind_info->count_of_codes) {
118 stack_size += (unwind_code + 1)->frame_offset |
119 ((unwind_code + 2)->frame_offset << 16);
122 break;
124 case UWOP_ALLOC_SMALL: {
125 stack_size += unwind_code->operation_info * 8 + 8;
126 break;
128 case UWOP_SET_FPREG:
129 // To correctly track RSP when it's been transferred to another
130 // register, we would need to emit CFI records for every unwind op.
131 // For simplicity, don't emit CFI records for this function as
132 // we know it will be incorrect after this point.
133 return false;
134 case UWOP_SAVE_NONVOL:
135 case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG
136 case UWOP_SAVE_XMM128: {
137 c++; // skip slot with offset
138 break;
140 case UWOP_SAVE_NONVOL_FAR:
141 case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE
142 case UWOP_SAVE_XMM128_FAR: {
143 c += 2; // skip 2 slots with offset
144 break;
146 case UWOP_PUSH_MACHFRAME: {
147 if (unwind_code->operation_info) {
148 stack_size += 88;
149 } else {
150 stack_size += 80;
152 rip_offset += 80;
153 break;
155 default: {
156 return false;
161 if (unwind_info->flags & UNW_FLAG_CHAININFO) {
162 auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)((
163 unwind_info->unwind_code + ((unwind_info->count_of_codes + 1) & ~1)));
165 if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) {
166 return false; // Circular reference
169 visited.insert(chained_func->UnwindInfoAddress);
171 unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress);
172 } else {
173 unwind_info = nullptr;
175 } while (unwind_info);
177 aRet.beginAddress = aFunc.BeginAddress;
178 aRet.size = aFunc.EndAddress - aFunc.BeginAddress;
179 aRet.stackSize = stack_size;
180 aRet.ripOffset = rip_offset;
181 return true;
184 // For unit testing we sometimes need any address that's valid in this module.
185 // Just return the first address we know of.
186 DWORD
187 ModuleUnwindParser::GetAnyOffsetAddr() const {
188 if (mUnwindMap.size() < 1) {
189 return 0;
191 return mUnwindMap.begin()->first;
194 bool ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet) {
195 // Figure out the begin address of the requested address.
196 auto itUW = mUnwindMap.lower_bound(aAddress + 1);
197 if (itUW == mUnwindMap.begin()) {
198 return false; // address before this module.
200 --itUW;
202 // Ensure that the function entry is big enough to contain this address.
203 IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second;
204 if (aAddress > func.EndAddress) {
205 return false;
208 // Do we have CFI for this function already?
209 auto itCFI = mCFIMap.find(aAddress);
210 if (itCFI != mCFIMap.end()) {
211 aRet = itCFI->second;
212 return true;
215 // No, generate it.
216 if (!GenerateCFIForFunction(func, aRet)) {
217 return false;
220 mCFIMap[func.BeginAddress] = aRet;
221 return true;
224 } // namespace CrashReporter
226 #endif // XP_WIN && HAVE_64BIT_BUILD