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
9using namespace lldb;
10using namespace lldb_private;
11using namespace llvm::Win64EH;
12
13template <typename T>
14static 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_ptr: &offset, length: size));
17}
18
19struct 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
33using EHProgram = std::vector<EHInstruction>;
34
35class UnwindCodesIterator {
36public:
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
46private:
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
62UnwindCodesIterator::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
67bool 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(rva: m_unwind_info_rva, size: UNWIND_INFO_SIZE);
76
77 offset_t offset = 0;
78 m_unwind_info =
79 TypedRead<UnwindInfo>(data_extractor: m_unwind_info_data, offset, size: 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 rva: m_unwind_info_rva + UNWIND_INFO_SIZE,
87 size: 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>(data_extractor: m_unwind_code_data, offset&: 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 rva: runtime_function_rva, size: sizeof(RuntimeFunction));
106
107 offset_t offset = 0;
108 const auto *runtime_function =
109 TypedRead<RuntimeFunction>(data_extractor: 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
123class EHProgramBuilder {
124public:
125 EHProgramBuilder(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva);
126
127 bool Build();
128
129 const EHProgram &GetProgram() const { return m_program; }
130
131private:
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
146EHProgramBuilder::EHProgramBuilder(ObjectFilePECOFF &object_file,
147 uint32_t unwind_info_rva)
148 : m_iterator(object_file, unwind_info_rva) {}
149
150bool EHProgramBuilder::Build() {
151 while (m_iterator.GetNext())
152 if (!ProcessUnwindCode(code: *m_iterator.GetUnwindCode()))
153 return false;
154
155 if (m_iterator.IsError())
156 return false;
157
158 Finalize();
159
160 return true;
161}
162
163uint32_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
176uint32_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
191bool 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(machine_reg: operation_info);
199 if (r == LLDB_INVALID_REGNUM)
200 return false;
201
202 m_program.emplace_back(
203 args: EHInstruction{.offset: o, .type: EHInstruction::Type::PUSH_REGISTER, .reg: r, .frame_offset: 8});
204
205 return true;
206 }
207 case UOP_AllocLarge: {
208 uint32_t fo;
209 if (!ParseBigOrScaledFrameOffset(result&: fo, big: operation_info, scale: 8))
210 return false;
211
212 m_program.emplace_back(args: EHInstruction{.offset: o, .type: EHInstruction::Type::ALLOCATE,
213 LLDB_INVALID_REGNUM, .frame_offset: fo});
214
215 return true;
216 }
217 case UOP_AllocSmall: {
218 m_program.emplace_back(
219 args: EHInstruction{.offset: o, .type: EHInstruction::Type::ALLOCATE, LLDB_INVALID_REGNUM,
220 .frame_offset: 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 machine_reg: 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(args: EHInstruction{
236 .offset: o, .type: EHInstruction::Type::SET_FRAME_POINTER_REGISTER, .reg: fpr, .frame_offset: fpro});
237
238 return true;
239 }
240 case UOP_SaveNonVol:
241 case UOP_SaveNonVolBig: {
242 uint32_t r = ConvertMachineToLLDBRegister(machine_reg: operation_info);
243 if (r == LLDB_INVALID_REGNUM)
244 return false;
245
246 uint32_t fo;
247 if (!ParseBigOrScaledFrameOffset(result&: fo, big: unwind_operation == UOP_SaveNonVolBig,
248 scale: 8))
249 return false;
250
251 m_program.emplace_back(
252 args: EHInstruction{.offset: o, .type: EHInstruction::Type::SAVE_REGISTER, .reg: r, .frame_offset: 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(xmm_reg: operation_info);
266 if (r == LLDB_INVALID_REGNUM)
267 return false;
268
269 uint32_t fo;
270 if (!ParseBigOrScaledFrameOffset(result&: fo, big: unwind_operation == UOP_SaveXMM128Big,
271 scale: 16))
272 return false;
273
274 m_program.emplace_back(
275 args: EHInstruction{.offset: o, .type: EHInstruction::Type::SAVE_REGISTER, .reg: r, .frame_offset: fo});
276
277 return true;
278 }
279 case UOP_PushMachFrame: {
280 if (operation_info)
281 m_program.emplace_back(args: EHInstruction{.offset: o, .type: EHInstruction::Type::ALLOCATE,
282 LLDB_INVALID_REGNUM, .frame_offset: 8});
283 m_program.emplace_back(args: EHInstruction{.offset: o, .type: EHInstruction::Type::PUSH_REGISTER,
284 .reg: lldb_rip_x86_64, .frame_offset: 8});
285 m_program.emplace_back(args: EHInstruction{.offset: o, .type: EHInstruction::Type::PUSH_REGISTER,
286 .reg: lldb_cs_x86_64, .frame_offset: 8});
287 m_program.emplace_back(args: EHInstruction{.offset: o, .type: EHInstruction::Type::PUSH_REGISTER,
288 .reg: lldb_rflags_x86_64, .frame_offset: 8});
289 m_program.emplace_back(args: EHInstruction{.offset: o, .type: EHInstruction::Type::PUSH_REGISTER,
290 .reg: lldb_rsp_x86_64, .frame_offset: 8});
291 m_program.emplace_back(args: EHInstruction{.offset: o, .type: EHInstruction::Type::PUSH_REGISTER,
292 .reg: lldb_ss_x86_64, .frame_offset: 8});
293
294 return true;
295 }
296 default:
297 return false;
298 }
299}
300
301void 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 args: EHInstruction{.offset: 0, .type: EHInstruction::Type::PUSH_REGISTER, .reg: lldb_rip_x86_64, .frame_offset: 8});
308}
309
310bool 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
325bool 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
340bool 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
349class EHProgramRange {
350public:
351 EHProgramRange(EHProgram::const_iterator begin,
352 EHProgram::const_iterator end);
353
354 std::unique_ptr<UnwindPlan::Row> BuildUnwindPlanRow() const;
355
356private:
357 int32_t GetCFAFrameOffset() const;
358
359 EHProgram::const_iterator m_begin;
360 EHProgram::const_iterator m_end;
361};
362
363EHProgramRange::EHProgramRange(EHProgram::const_iterator begin,
364 EHProgram::const_iterator end)
365 : m_begin(begin), m_end(end) {}
366
367std::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(reg_num: it->reg, offset: 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(reg_num: lldb_rsp_x86_64,
391 offset: 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 reg_num: it->reg, offset: rsp_frame_offset - cfa_frame_offset, can_replace: 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 reg_num: it->reg, offset: it->frame_offset - cfa_frame_offset, can_replace: false);
407 break;
408 default:
409 break;
410 }
411 }
412
413 row->SetRegisterLocationToIsCFAPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0, can_replace: false);
414
415 return row;
416}
417
418int32_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
435PECallFrameInfo::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(rva: exception_dir_rva,
440 size: exception_dir_size)) {}
441
442bool PECallFrameInfo::GetAddressRange(Address addr, AddressRange &range) {
443 range.Clear();
444
445 const RuntimeFunction *runtime_function =
446 FindRuntimeFunctionIntersectsWithRange(range: AddressRange(addr, 1));
447 if (!runtime_function)
448 return false;
449
450 range.GetBaseAddress() =
451 m_object_file.GetAddress(rva: runtime_function->StartAddress);
452 range.SetByteSize(runtime_function->EndAddress -
453 runtime_function->StartAddress);
454
455 return true;
456}
457
458bool PECallFrameInfo::GetUnwindPlan(const Address &addr,
459 UnwindPlan &unwind_plan) {
460 return GetUnwindPlan(range: AddressRange(addr, 1), unwind_plan);
461}
462
463bool 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(x: 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(row_sp: *it);
497
498 unwind_plan.SetPlanValidAddressRange(AddressRange(
499 m_object_file.GetAddress(rva: runtime_function->StartAddress),
500 runtime_function->EndAddress - runtime_function->StartAddress));
501 unwind_plan.SetUnwindPlanValidAtAllInstructions(eLazyBoolNo);
502
503 return true;
504}
505
506const RuntimeFunction *PECallFrameInfo::FindRuntimeFunctionIntersectsWithRange(
507 const AddressRange &range) const {
508 uint32_t rva = m_object_file.GetRVA(addr: 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>(data_extractor: 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

source code of lldb/source/Plugins/ObjectFile/PECOFF/PECallFrameInfo.cpp