1 #include "PECallFrameInfo.h"
2 
3 #include "ObjectFilePECOFF.h"
4 
5 #include "Plugins/Process/Utility/lldb-x86-register-enums.h"
6 #include "lldb/Symbol/UnwindPlan.h"
7 #include "llvm/Support/Win64EH.h"
8 
9 using namespace lldb;
10 using namespace lldb_private;
11 using namespace llvm::Win64EH;
12 
13 template <typename T>
14 static const T *TypedRead(const DataExtractor &data_extractor, offset_t &offset,
15                           offset_t size = sizeof(T)) {
16   return static_cast<const T *>(data_extractor.GetData(&offset, size));
17 }
18 
19 struct EHInstruction {
20   enum class Type {
21     PUSH_REGISTER,
22     ALLOCATE,
23     SET_FRAME_POINTER_REGISTER,
24     SAVE_REGISTER
25   };
26 
27   uint8_t offset;
28   Type type;
29   uint32_t reg;
30   uint32_t frame_offset;
31 };
32 
33 using EHProgram = std::vector<EHInstruction>;
34 
35 class UnwindCodesIterator {
36 public:
37   UnwindCodesIterator(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva);
38 
39   bool GetNext();
40   bool IsError() const { return m_error; }
41 
42   const UnwindInfo *GetUnwindInfo() const { return m_unwind_info; }
43   const UnwindCode *GetUnwindCode() const { return m_unwind_code; }
44   bool IsChained() const { return m_chained; }
45 
46 private:
47   ObjectFilePECOFF &m_object_file;
48 
49   bool m_error = false;
50 
51   uint32_t m_unwind_info_rva;
52   DataExtractor m_unwind_info_data;
53   const UnwindInfo *m_unwind_info = nullptr;
54 
55   DataExtractor m_unwind_code_data;
56   offset_t m_unwind_code_offset;
57   const UnwindCode *m_unwind_code = nullptr;
58 
59   bool m_chained = false;
60 };
61 
62 UnwindCodesIterator::UnwindCodesIterator(ObjectFilePECOFF &object_file,
63                                          uint32_t unwind_info_rva)
64     : m_object_file(object_file),
65       m_unwind_info_rva(unwind_info_rva), m_unwind_code_offset{} {}
66 
67 bool UnwindCodesIterator::GetNext() {
68   static constexpr int UNWIND_INFO_SIZE = 4;
69 
70   m_error = false;
71   m_unwind_code = nullptr;
72   while (!m_unwind_code) {
73     if (!m_unwind_info) {
74       m_unwind_info_data =
75           m_object_file.ReadImageDataByRVA(m_unwind_info_rva, UNWIND_INFO_SIZE);
76 
77       offset_t offset = 0;
78       m_unwind_info =
79           TypedRead<UnwindInfo>(m_unwind_info_data, offset, UNWIND_INFO_SIZE);
80       if (!m_unwind_info) {
81         m_error = true;
82         break;
83       }
84 
85       m_unwind_code_data = m_object_file.ReadImageDataByRVA(
86           m_unwind_info_rva + UNWIND_INFO_SIZE,
87           m_unwind_info->NumCodes * sizeof(UnwindCode));
88       m_unwind_code_offset = 0;
89     }
90 
91     if (m_unwind_code_offset < m_unwind_code_data.GetByteSize()) {
92       m_unwind_code =
93           TypedRead<UnwindCode>(m_unwind_code_data, m_unwind_code_offset);
94       m_error = !m_unwind_code;
95       break;
96     }
97 
98     if (!(m_unwind_info->getFlags() & UNW_ChainInfo))
99       break;
100 
101     uint32_t runtime_function_rva =
102         m_unwind_info_rva + UNWIND_INFO_SIZE +
103         ((m_unwind_info->NumCodes + 1) & ~1) * sizeof(UnwindCode);
104     DataExtractor runtime_function_data = m_object_file.ReadImageDataByRVA(
105         runtime_function_rva, sizeof(RuntimeFunction));
106 
107     offset_t offset = 0;
108     const auto *runtime_function =
109         TypedRead<RuntimeFunction>(runtime_function_data, offset);
110     if (!runtime_function) {
111       m_error = true;
112       break;
113     }
114 
115     m_unwind_info_rva = runtime_function->UnwindInfoOffset;
116     m_unwind_info = nullptr;
117     m_chained = true;
118   }
119 
120   return !!m_unwind_code;
121 }
122 
123 class EHProgramBuilder {
124 public:
125   EHProgramBuilder(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva);
126 
127   bool Build();
128 
129   const EHProgram &GetProgram() const { return m_program; }
130 
131 private:
132   static uint32_t ConvertMachineToLLDBRegister(uint8_t machine_reg);
133   static uint32_t ConvertXMMToLLDBRegister(uint8_t xmm_reg);
134 
135   bool ProcessUnwindCode(UnwindCode code);
136   void Finalize();
137 
138   bool ParseBigOrScaledFrameOffset(uint32_t &result, bool big, uint32_t scale);
139   bool ParseBigFrameOffset(uint32_t &result);
140   bool ParseFrameOffset(uint32_t &result);
141 
142   UnwindCodesIterator m_iterator;
143   EHProgram m_program;
144 };
145 
146 EHProgramBuilder::EHProgramBuilder(ObjectFilePECOFF &object_file,
147                                    uint32_t unwind_info_rva)
148     : m_iterator(object_file, unwind_info_rva) {}
149 
150 bool EHProgramBuilder::Build() {
151   while (m_iterator.GetNext())
152     if (!ProcessUnwindCode(*m_iterator.GetUnwindCode()))
153       return false;
154 
155   if (m_iterator.IsError())
156     return false;
157 
158   Finalize();
159 
160   return true;
161 }
162 
163 uint32_t EHProgramBuilder::ConvertMachineToLLDBRegister(uint8_t machine_reg) {
164   static uint32_t machine_to_lldb_register[] = {
165       lldb_rax_x86_64, lldb_rcx_x86_64, lldb_rdx_x86_64, lldb_rbx_x86_64,
166       lldb_rsp_x86_64, lldb_rbp_x86_64, lldb_rsi_x86_64, lldb_rdi_x86_64,
167       lldb_r8_x86_64,  lldb_r9_x86_64,  lldb_r10_x86_64, lldb_r11_x86_64,
168       lldb_r12_x86_64, lldb_r13_x86_64, lldb_r14_x86_64, lldb_r15_x86_64};
169 
170   if (machine_reg >= std::size(machine_to_lldb_register))
171     return LLDB_INVALID_REGNUM;
172 
173   return machine_to_lldb_register[machine_reg];
174 }
175 
176 uint32_t EHProgramBuilder::ConvertXMMToLLDBRegister(uint8_t xmm_reg) {
177   static uint32_t xmm_to_lldb_register[] = {
178       lldb_xmm0_x86_64,  lldb_xmm1_x86_64,  lldb_xmm2_x86_64,
179       lldb_xmm3_x86_64,  lldb_xmm4_x86_64,  lldb_xmm5_x86_64,
180       lldb_xmm6_x86_64,  lldb_xmm7_x86_64,  lldb_xmm8_x86_64,
181       lldb_xmm9_x86_64,  lldb_xmm10_x86_64, lldb_xmm11_x86_64,
182       lldb_xmm12_x86_64, lldb_xmm13_x86_64, lldb_xmm14_x86_64,
183       lldb_xmm15_x86_64};
184 
185   if (xmm_reg >= std::size(xmm_to_lldb_register))
186     return LLDB_INVALID_REGNUM;
187 
188   return xmm_to_lldb_register[xmm_reg];
189 }
190 
191 bool EHProgramBuilder::ProcessUnwindCode(UnwindCode code) {
192   uint8_t o = m_iterator.IsChained() ? 0 : code.u.CodeOffset;
193   uint8_t unwind_operation = code.getUnwindOp();
194   uint8_t operation_info = code.getOpInfo();
195 
196   switch (unwind_operation) {
197   case UOP_PushNonVol: {
198     uint32_t r = ConvertMachineToLLDBRegister(operation_info);
199     if (r == LLDB_INVALID_REGNUM)
200       return false;
201 
202     m_program.emplace_back(
203         EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, r, 8});
204 
205     return true;
206   }
207   case UOP_AllocLarge: {
208     uint32_t fo;
209     if (!ParseBigOrScaledFrameOffset(fo, operation_info, 8))
210       return false;
211 
212     m_program.emplace_back(EHInstruction{o, EHInstruction::Type::ALLOCATE,
213                                          LLDB_INVALID_REGNUM, fo});
214 
215     return true;
216   }
217   case UOP_AllocSmall: {
218     m_program.emplace_back(
219         EHInstruction{o, EHInstruction::Type::ALLOCATE, LLDB_INVALID_REGNUM,
220                       static_cast<uint32_t>(operation_info) * 8 + 8});
221     return true;
222   }
223   case UOP_SetFPReg: {
224     uint32_t fpr = LLDB_INVALID_REGNUM;
225     if (m_iterator.GetUnwindInfo()->getFrameRegister())
226       fpr = ConvertMachineToLLDBRegister(
227           m_iterator.GetUnwindInfo()->getFrameRegister());
228     if (fpr == LLDB_INVALID_REGNUM)
229       return false;
230 
231     uint32_t fpro =
232         static_cast<uint32_t>(m_iterator.GetUnwindInfo()->getFrameOffset()) *
233         16;
234 
235     m_program.emplace_back(EHInstruction{
236         o, EHInstruction::Type::SET_FRAME_POINTER_REGISTER, fpr, fpro});
237 
238     return true;
239   }
240   case UOP_SaveNonVol:
241   case UOP_SaveNonVolBig: {
242     uint32_t r = ConvertMachineToLLDBRegister(operation_info);
243     if (r == LLDB_INVALID_REGNUM)
244       return false;
245 
246     uint32_t fo;
247     if (!ParseBigOrScaledFrameOffset(fo, unwind_operation == UOP_SaveNonVolBig,
248                                      8))
249       return false;
250 
251     m_program.emplace_back(
252         EHInstruction{o, EHInstruction::Type::SAVE_REGISTER, r, fo});
253 
254     return true;
255   }
256   case UOP_Epilog: {
257     return m_iterator.GetNext();
258   }
259   case UOP_SpareCode: {
260     // ReSharper disable once CppIdenticalOperandsInBinaryExpression
261     return m_iterator.GetNext() && m_iterator.GetNext();
262   }
263   case UOP_SaveXMM128:
264   case UOP_SaveXMM128Big: {
265     uint32_t r = ConvertXMMToLLDBRegister(operation_info);
266     if (r == LLDB_INVALID_REGNUM)
267       return false;
268 
269     uint32_t fo;
270     if (!ParseBigOrScaledFrameOffset(fo, unwind_operation == UOP_SaveXMM128Big,
271                                      16))
272       return false;
273 
274     m_program.emplace_back(
275         EHInstruction{o, EHInstruction::Type::SAVE_REGISTER, r, fo});
276 
277     return true;
278   }
279   case UOP_PushMachFrame: {
280     if (operation_info)
281       m_program.emplace_back(EHInstruction{o, EHInstruction::Type::ALLOCATE,
282                                            LLDB_INVALID_REGNUM, 8});
283     m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
284                                          lldb_rip_x86_64, 8});
285     m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
286                                          lldb_cs_x86_64, 8});
287     m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
288                                          lldb_rflags_x86_64, 8});
289     m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
290                                          lldb_rsp_x86_64, 8});
291     m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
292                                          lldb_ss_x86_64, 8});
293 
294     return true;
295   }
296   default:
297     return false;
298   }
299 }
300 
301 void EHProgramBuilder::Finalize() {
302   for (const EHInstruction &i : m_program)
303     if (i.reg == lldb_rip_x86_64)
304       return;
305 
306   m_program.emplace_back(
307       EHInstruction{0, EHInstruction::Type::PUSH_REGISTER, lldb_rip_x86_64, 8});
308 }
309 
310 bool EHProgramBuilder::ParseBigOrScaledFrameOffset(uint32_t &result, bool big,
311                                                    uint32_t scale) {
312   if (big) {
313     if (!ParseBigFrameOffset(result))
314       return false;
315   } else {
316     if (!ParseFrameOffset(result))
317       return false;
318 
319     result *= scale;
320   }
321 
322   return true;
323 }
324 
325 bool EHProgramBuilder::ParseBigFrameOffset(uint32_t &result) {
326   if (!m_iterator.GetNext())
327     return false;
328 
329   result = m_iterator.GetUnwindCode()->FrameOffset;
330 
331   if (!m_iterator.GetNext())
332     return false;
333 
334   result += static_cast<uint32_t>(m_iterator.GetUnwindCode()->FrameOffset)
335             << 16;
336 
337   return true;
338 }
339 
340 bool EHProgramBuilder::ParseFrameOffset(uint32_t &result) {
341   if (!m_iterator.GetNext())
342     return false;
343 
344   result = m_iterator.GetUnwindCode()->FrameOffset;
345 
346   return true;
347 }
348 
349 class EHProgramRange {
350 public:
351   EHProgramRange(EHProgram::const_iterator begin,
352                  EHProgram::const_iterator end);
353 
354   std::unique_ptr<UnwindPlan::Row> BuildUnwindPlanRow() const;
355 
356 private:
357   int32_t GetCFAFrameOffset() const;
358 
359   EHProgram::const_iterator m_begin;
360   EHProgram::const_iterator m_end;
361 };
362 
363 EHProgramRange::EHProgramRange(EHProgram::const_iterator begin,
364                                EHProgram::const_iterator end)
365     : m_begin(begin), m_end(end) {}
366 
367 std::unique_ptr<UnwindPlan::Row> EHProgramRange::BuildUnwindPlanRow() const {
368   std::unique_ptr<UnwindPlan::Row> row = std::make_unique<UnwindPlan::Row>();
369 
370   if (m_begin != m_end)
371     row->SetOffset(m_begin->offset);
372 
373   int32_t cfa_frame_offset = GetCFAFrameOffset();
374 
375   bool frame_pointer_found = false;
376   for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {
377     switch (it->type) {
378     case EHInstruction::Type::SET_FRAME_POINTER_REGISTER:
379       row->GetCFAValue().SetIsRegisterPlusOffset(it->reg, cfa_frame_offset -
380                                                               it->frame_offset);
381       frame_pointer_found = true;
382       break;
383     default:
384       break;
385     }
386     if (frame_pointer_found)
387       break;
388   }
389   if (!frame_pointer_found)
390     row->GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64,
391                                                cfa_frame_offset);
392 
393   int32_t rsp_frame_offset = 0;
394   for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {
395     switch (it->type) {
396     case EHInstruction::Type::PUSH_REGISTER:
397       row->SetRegisterLocationToAtCFAPlusOffset(
398           it->reg, rsp_frame_offset - cfa_frame_offset, false);
399       rsp_frame_offset += it->frame_offset;
400       break;
401     case EHInstruction::Type::ALLOCATE:
402       rsp_frame_offset += it->frame_offset;
403       break;
404     case EHInstruction::Type::SAVE_REGISTER:
405       row->SetRegisterLocationToAtCFAPlusOffset(
406           it->reg, it->frame_offset - cfa_frame_offset, false);
407       break;
408     default:
409       break;
410     }
411   }
412 
413   row->SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, false);
414 
415   return row;
416 }
417 
418 int32_t EHProgramRange::GetCFAFrameOffset() const {
419   int32_t result = 0;
420 
421   for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {
422     switch (it->type) {
423     case EHInstruction::Type::PUSH_REGISTER:
424     case EHInstruction::Type::ALLOCATE:
425       result += it->frame_offset;
426       break;
427     default:
428       break;
429     }
430   }
431 
432   return result;
433 }
434 
435 PECallFrameInfo::PECallFrameInfo(ObjectFilePECOFF &object_file,
436                                  uint32_t exception_dir_rva,
437                                  uint32_t exception_dir_size)
438     : m_object_file(object_file),
439       m_exception_dir(object_file.ReadImageDataByRVA(exception_dir_rva,
440                                                       exception_dir_size)) {}
441 
442 bool PECallFrameInfo::GetAddressRange(Address addr, AddressRange &range) {
443   range.Clear();
444 
445   const RuntimeFunction *runtime_function =
446       FindRuntimeFunctionIntersectsWithRange(AddressRange(addr, 1));
447   if (!runtime_function)
448     return false;
449 
450   range.GetBaseAddress() =
451       m_object_file.GetAddress(runtime_function->StartAddress);
452   range.SetByteSize(runtime_function->EndAddress -
453                     runtime_function->StartAddress);
454 
455   return true;
456 }
457 
458 bool PECallFrameInfo::GetUnwindPlan(const Address &addr,
459                                     UnwindPlan &unwind_plan) {
460   return GetUnwindPlan(AddressRange(addr, 1), unwind_plan);
461 }
462 
463 bool PECallFrameInfo::GetUnwindPlan(const AddressRange &range,
464                                     UnwindPlan &unwind_plan) {
465   unwind_plan.Clear();
466 
467   unwind_plan.SetSourceName("PE EH info");
468   unwind_plan.SetSourcedFromCompiler(eLazyBoolYes);
469   unwind_plan.SetRegisterKind(eRegisterKindLLDB);
470 
471   const RuntimeFunction *runtime_function =
472       FindRuntimeFunctionIntersectsWithRange(range);
473   if (!runtime_function)
474     return false;
475 
476   EHProgramBuilder builder(m_object_file, runtime_function->UnwindInfoOffset);
477   if (!builder.Build())
478     return false;
479 
480   std::vector<UnwindPlan::RowSP> rows;
481 
482   uint32_t last_offset = UINT32_MAX;
483   for (auto it = builder.GetProgram().begin(); it != builder.GetProgram().end();
484        ++it) {
485     if (it->offset == last_offset)
486       continue;
487 
488     EHProgramRange program_range =
489         EHProgramRange(it, builder.GetProgram().end());
490     rows.push_back(program_range.BuildUnwindPlanRow());
491 
492     last_offset = it->offset;
493   }
494 
495   for (auto it = rows.rbegin(); it != rows.rend(); ++it)
496     unwind_plan.AppendRow(*it);
497 
498   unwind_plan.SetPlanValidAddressRange(AddressRange(
499       m_object_file.GetAddress(runtime_function->StartAddress),
500       runtime_function->EndAddress - runtime_function->StartAddress));
501   unwind_plan.SetUnwindPlanValidAtAllInstructions(eLazyBoolNo);
502 
503   return true;
504 }
505 
506 const RuntimeFunction *PECallFrameInfo::FindRuntimeFunctionIntersectsWithRange(
507     const AddressRange &range) const {
508   uint32_t rva = m_object_file.GetRVA(range.GetBaseAddress());
509   addr_t size = range.GetByteSize();
510 
511   uint32_t begin = 0;
512   uint32_t end = m_exception_dir.GetByteSize() / sizeof(RuntimeFunction);
513   while (begin < end) {
514     uint32_t curr = (begin + end) / 2;
515 
516     offset_t offset = curr * sizeof(RuntimeFunction);
517     const auto *runtime_function =
518         TypedRead<RuntimeFunction>(m_exception_dir, offset);
519     if (!runtime_function)
520       break;
521 
522     if (runtime_function->StartAddress < rva + size &&
523         runtime_function->EndAddress > rva)
524       return runtime_function;
525 
526     if (runtime_function->StartAddress >= rva + size)
527       end = curr;
528 
529     if (runtime_function->EndAddress <= rva)
530       begin = curr + 1;
531   }
532 
533   return nullptr;
534 }
535