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 *(const DataExtractor &, 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 | |
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(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 | |
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(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 | |
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(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 | |
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 | args: EHInstruction{.offset: 0, .type: EHInstruction::Type::PUSH_REGISTER, .reg: lldb_rip_x86_64, .frame_offset: 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(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 | |
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(rva: exception_dir_rva, |
440 | size: exception_dir_size)) {} |
441 | |
442 | bool 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 | |
458 | bool PECallFrameInfo::GetUnwindPlan(const Address &addr, |
459 | UnwindPlan &unwind_plan) { |
460 | return GetUnwindPlan(range: 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(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 | |
506 | const 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 | |