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