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/. */
5
6 #if XP_WIN && HAVE_64BIT_BUILD
7
8 # include "Win64ModuleUnwindMetadata.h"
9
10 # include "MinidumpAnalyzerUtils.h"
11
12 # include <windows.h>
13 # include <winnt.h>
14 # include <imagehlp.h>
15 # include <iostream>
16 # include <set>
17 # include <sstream>
18 # include <string>
19
20 namespace CrashReporter {
21
22 union UnwindCode {
23 struct {
24 uint8_t offset_in_prolog;
25 uint8_t unwind_operation_code : 4;
26 uint8_t operation_info : 4;
27 };
28 USHORT frame_offset;
29 };
30
31 enum UnwindOperationCodes {
32 UWOP_PUSH_NONVOL = 0, // info == register number
33 UWOP_ALLOC_LARGE = 1, // no info, alloc size in next 2 slots
34 UWOP_ALLOC_SMALL = 2, // info == size of allocation / 8 - 1
35 UWOP_SET_FPREG = 3, // no info, FP = RSP + UNWIND_INFO.FPRegOffset*16
36 UWOP_SAVE_NONVOL = 4, // info == register number, offset in next slot
37 UWOP_SAVE_NONVOL_FAR = 5, // info == register number, offset in next 2 slots
38 UWOP_SAVE_XMM = 6, // Version 1; undocumented
39 UWOP_EPILOG = 6, // Version 2; undocumented
40 UWOP_SAVE_XMM_FAR = 7, // Version 1; undocumented
41 UWOP_SPARE = 7, // Version 2; undocumented
42 UWOP_SAVE_XMM128 = 8, // info == XMM reg number, offset in next slot
43 UWOP_SAVE_XMM128_FAR = 9, // info == XMM reg number, offset in next 2 slots
44 UWOP_PUSH_MACHFRAME = 10 // info == 0: no error-code, 1: error-code
45 };
46
47 struct UnwindInfo {
48 uint8_t version : 3;
49 uint8_t flags : 5;
50 uint8_t size_of_prolog;
51 uint8_t count_of_codes;
52 uint8_t frame_register : 4;
53 uint8_t frame_offset : 4;
54 UnwindCode unwind_code[1];
55 };
56
~ModuleUnwindParser()57 ModuleUnwindParser::~ModuleUnwindParser() {
58 if (mImg) {
59 ImageUnload(mImg);
60 }
61 }
62
RvaToVa(ULONG aRva)63 void* ModuleUnwindParser::RvaToVa(ULONG aRva) {
64 return ImageRvaToVa(mImg->FileHeader, mImg->MappedAddress, aRva,
65 &mImg->LastRvaSection);
66 }
67
ModuleUnwindParser(const std::string & aPath)68 ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath)
69 : mPath(aPath) {
70 // Convert wchar to native charset because ImageLoad only takes
71 // a PSTR as input.
72 std::string code_file = UTF8ToMBCS(aPath);
73
74 mImg = ImageLoad((PSTR)code_file.c_str(), NULL);
75 if (!mImg || !mImg->FileHeader) {
76 return;
77 }
78
79 PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader;
80 if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
81 return;
82 }
83
84 DWORD exception_rva =
85 optional_header->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]
86 .VirtualAddress;
87
88 DWORD exception_size =
89 optional_header->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
90
91 auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva);
92 if (!funcs) {
93 return;
94 }
95
96 for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) {
97 mUnwindMap[funcs[i].BeginAddress] = &funcs[i];
98 }
99 }
100
GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY & aFunc,UnwindCFI & aRet)101 bool ModuleUnwindParser::GenerateCFIForFunction(
102 IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc, UnwindCFI& aRet) {
103 DWORD unwind_rva = aFunc.UnwindInfoAddress;
104 // Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid
105 // circular references.
106 std::set<DWORD> visited;
107
108 // Follow chained function entries
109 while (unwind_rva & 0x1) {
110 unwind_rva ^= 0x1;
111
112 if (visited.end() != visited.find(unwind_rva)) {
113 return false;
114 }
115 visited.insert(unwind_rva);
116
117 auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva);
118 if (!chained_func) {
119 return false;
120 }
121 unwind_rva = chained_func->UnwindInfoAddress;
122 }
123
124 visited.insert(unwind_rva);
125
126 auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva);
127 if (!unwind_info) {
128 return false;
129 }
130
131 DWORD stack_size = 8; // minimal stack size is 8 for RIP
132 DWORD rip_offset = 8;
133 do {
134 for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) {
135 UnwindCode* unwind_code = &unwind_info->unwind_code[c];
136 switch (unwind_code->unwind_operation_code) {
137 case UWOP_PUSH_NONVOL: {
138 stack_size += 8;
139 break;
140 }
141 case UWOP_ALLOC_LARGE: {
142 if (unwind_code->operation_info == 0) {
143 c++;
144 if (c < unwind_info->count_of_codes) {
145 stack_size += (unwind_code + 1)->frame_offset * 8;
146 }
147 } else {
148 c += 2;
149 if (c < unwind_info->count_of_codes) {
150 stack_size += (unwind_code + 1)->frame_offset |
151 ((unwind_code + 2)->frame_offset << 16);
152 }
153 }
154 break;
155 }
156 case UWOP_ALLOC_SMALL: {
157 stack_size += unwind_code->operation_info * 8 + 8;
158 break;
159 }
160 case UWOP_SET_FPREG:
161 // To correctly track RSP when it's been transferred to another
162 // register, we would need to emit CFI records for every unwind op.
163 // For simplicity, don't emit CFI records for this function as
164 // we know it will be incorrect after this point.
165 return false;
166 case UWOP_SAVE_NONVOL:
167 case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG
168 case UWOP_SAVE_XMM128: {
169 c++; // skip slot with offset
170 break;
171 }
172 case UWOP_SAVE_NONVOL_FAR:
173 case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE
174 case UWOP_SAVE_XMM128_FAR: {
175 c += 2; // skip 2 slots with offset
176 break;
177 }
178 case UWOP_PUSH_MACHFRAME: {
179 if (unwind_code->operation_info) {
180 stack_size += 88;
181 } else {
182 stack_size += 80;
183 }
184 rip_offset += 80;
185 break;
186 }
187 default: {
188 return false;
189 }
190 }
191 }
192
193 if (unwind_info->flags & UNW_FLAG_CHAININFO) {
194 auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)((
195 unwind_info->unwind_code + ((unwind_info->count_of_codes + 1) & ~1)));
196
197 if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) {
198 return false; // Circular reference
199 }
200
201 visited.insert(chained_func->UnwindInfoAddress);
202
203 unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress);
204 } else {
205 unwind_info = nullptr;
206 }
207 } while (unwind_info);
208
209 aRet.beginAddress = aFunc.BeginAddress;
210 aRet.size = aFunc.EndAddress - aFunc.BeginAddress;
211 aRet.stackSize = stack_size;
212 aRet.ripOffset = rip_offset;
213 return true;
214 }
215
216 // For unit testing we sometimes need any address that's valid in this module.
217 // Just return the first address we know of.
218 DWORD
GetAnyOffsetAddr() const219 ModuleUnwindParser::GetAnyOffsetAddr() const {
220 if (mUnwindMap.size() < 1) {
221 return 0;
222 }
223 return mUnwindMap.begin()->first;
224 }
225
GetCFI(DWORD aAddress,UnwindCFI & aRet)226 bool ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet) {
227 // Figure out the begin address of the requested address.
228 auto itUW = mUnwindMap.lower_bound(aAddress + 1);
229 if (itUW == mUnwindMap.begin()) {
230 return false; // address before this module.
231 }
232 --itUW;
233
234 // Ensure that the function entry is big enough to contain this address.
235 IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second;
236 if (aAddress > func.EndAddress) {
237 return false;
238 }
239
240 // Do we have CFI for this function already?
241 auto itCFI = mCFIMap.find(aAddress);
242 if (itCFI != mCFIMap.end()) {
243 aRet = itCFI->second;
244 return true;
245 }
246
247 // No, generate it.
248 if (!GenerateCFIForFunction(func, aRet)) {
249 return false;
250 }
251
252 mCFIMap[func.BeginAddress] = aRet;
253 return true;
254 }
255
256 } // namespace CrashReporter
257
258 #endif // XP_WIN && HAVE_64BIT_BUILD
259