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