1 | //===-- StopInfoMachException.cpp -----------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "StopInfoMachException.h" |
10 | |
11 | #include "lldb/lldb-forward.h" |
12 | |
13 | #if defined(__APPLE__) |
14 | // Needed for the EXC_RESOURCE interpretation macros |
15 | #include <kern/exc_resource.h> |
16 | #endif |
17 | |
18 | #include "lldb/Breakpoint/Watchpoint.h" |
19 | #include "lldb/Symbol/Symbol.h" |
20 | #include "lldb/Target/ABI.h" |
21 | #include "lldb/Target/DynamicLoader.h" |
22 | #include "lldb/Target/ExecutionContext.h" |
23 | #include "lldb/Target/Process.h" |
24 | #include "lldb/Target/RegisterContext.h" |
25 | #include "lldb/Target/Target.h" |
26 | #include "lldb/Target/Thread.h" |
27 | #include "lldb/Target/ThreadPlan.h" |
28 | #include "lldb/Target/UnixSignals.h" |
29 | #include "lldb/Utility/LLDBLog.h" |
30 | #include "lldb/Utility/Log.h" |
31 | #include "lldb/Utility/StreamString.h" |
32 | #include <optional> |
33 | |
34 | using namespace lldb; |
35 | using namespace lldb_private; |
36 | |
37 | /// Information about a pointer-authentication related instruction. |
38 | struct PtrauthInstructionInfo { |
39 | bool IsAuthenticated; |
40 | bool IsLoad; |
41 | bool DoesBranch; |
42 | }; |
43 | |
44 | /// Get any pointer-authentication related information about the instruction |
45 | /// at address \p at_addr. |
46 | static std::optional<PtrauthInstructionInfo> |
47 | GetPtrauthInstructionInfo(Target &target, const ArchSpec &arch, |
48 | const Address &at_addr) { |
49 | const char *plugin_name = nullptr; |
50 | const char *flavor = nullptr; |
51 | const char *cpu = nullptr; |
52 | const char *features = nullptr; |
53 | AddressRange range_bounds(at_addr, 4); |
54 | const bool prefer_file_cache = true; |
55 | DisassemblerSP disassembler_sp = |
56 | Disassembler::DisassembleRange(arch, plugin_name, flavor, cpu, features, |
57 | target, disasm_ranges: range_bounds, force_live_memory: prefer_file_cache); |
58 | if (!disassembler_sp) |
59 | return std::nullopt; |
60 | |
61 | InstructionList &insn_list = disassembler_sp->GetInstructionList(); |
62 | InstructionSP insn = insn_list.GetInstructionAtIndex(idx: 0); |
63 | if (!insn) |
64 | return std::nullopt; |
65 | |
66 | return PtrauthInstructionInfo{.IsAuthenticated: insn->IsAuthenticated(), .IsLoad: insn->IsLoad(), |
67 | .DoesBranch: insn->DoesBranch()}; |
68 | } |
69 | |
70 | /// Describe the load address of \p addr using the format filename:line:col. |
71 | static void DescribeAddressBriefly(Stream &strm, const Address &addr, |
72 | Target &target) { |
73 | strm.Printf(format: "at address=0x%" PRIx64, addr.GetLoadAddress(target: &target)); |
74 | StreamString s; |
75 | if (addr.GetDescription(s, target, level: eDescriptionLevelBrief)) |
76 | strm.Printf(format: " %s" , s.GetString().data()); |
77 | strm.Printf(format: ".\n" ); |
78 | } |
79 | |
80 | bool StopInfoMachException::DeterminePtrauthFailure(ExecutionContext &exe_ctx) { |
81 | bool IsBreakpoint = m_value == 6; // EXC_BREAKPOINT |
82 | bool IsBadAccess = m_value == 1; // EXC_BAD_ACCESS |
83 | if (!IsBreakpoint && !IsBadAccess) |
84 | return false; |
85 | |
86 | // Check that we have a live process. |
87 | if (!exe_ctx.HasProcessScope() || !exe_ctx.HasThreadScope() || |
88 | !exe_ctx.HasTargetScope()) |
89 | return false; |
90 | |
91 | Thread &thread = *exe_ctx.GetThreadPtr(); |
92 | StackFrameSP current_frame = thread.GetStackFrameAtIndex(idx: 0); |
93 | if (!current_frame) |
94 | return false; |
95 | |
96 | Target &target = *exe_ctx.GetTargetPtr(); |
97 | Process &process = *exe_ctx.GetProcessPtr(); |
98 | const ArchSpec &arch = target.GetArchitecture(); |
99 | |
100 | // Check for a ptrauth-enabled target. |
101 | const bool ptrauth_enabled_target = |
102 | arch.GetCore() == ArchSpec::eCore_arm_arm64e; |
103 | if (!ptrauth_enabled_target) |
104 | return false; |
105 | |
106 | // Set up a stream we can write a diagnostic into. |
107 | StreamString strm; |
108 | auto emit_ptrauth_prologue = [&](uint64_t at_address) { |
109 | strm.Printf(format: "EXC_BAD_ACCESS (code=%" PRIu64 ", address=0x%" PRIx64 ")\n" , |
110 | m_exc_code, at_address); |
111 | strm.Printf(format: "Note: Possible pointer authentication failure detected.\n" ); |
112 | }; |
113 | |
114 | ABISP abi_sp = process.GetABI(); |
115 | assert(abi_sp && "Missing ABI info" ); |
116 | |
117 | // Check if we have a "brk 0xc47x" trap, where the value that failed to |
118 | // authenticate is in x16. |
119 | Address current_address = current_frame->GetFrameCodeAddress(); |
120 | if (IsBreakpoint) { |
121 | RegisterContext *reg_ctx = exe_ctx.GetRegisterContext(); |
122 | if (!reg_ctx) |
123 | return false; |
124 | |
125 | const RegisterInfo *X16Info = reg_ctx->GetRegisterInfoByName(reg_name: "x16" ); |
126 | RegisterValue X16Val; |
127 | if (!reg_ctx->ReadRegister(reg_info: X16Info, reg_value&: X16Val)) |
128 | return false; |
129 | uint64_t bad_address = X16Val.GetAsUInt64(); |
130 | |
131 | uint64_t fixed_bad_address = abi_sp->FixCodeAddress(pc: bad_address); |
132 | Address brk_address; |
133 | if (!target.ResolveLoadAddress(load_addr: fixed_bad_address, so_addr&: brk_address)) |
134 | return false; |
135 | |
136 | auto brk_ptrauth_info = |
137 | GetPtrauthInstructionInfo(target, arch, at_addr: current_address); |
138 | if (brk_ptrauth_info && brk_ptrauth_info->IsAuthenticated) { |
139 | emit_ptrauth_prologue(bad_address); |
140 | strm.Printf(format: "Found value that failed to authenticate " ); |
141 | DescribeAddressBriefly(strm, addr: brk_address, target); |
142 | m_description = std::string(strm.GetString()); |
143 | return true; |
144 | } |
145 | return false; |
146 | } |
147 | |
148 | assert(IsBadAccess && "Handle EXC_BAD_ACCESS only after this point" ); |
149 | |
150 | // Check that we have the "bad address" from an EXC_BAD_ACCESS. |
151 | if (m_exc_data_count < 2) |
152 | return false; |
153 | |
154 | // Ok, we know the Target is valid and that it describes a ptrauth-enabled |
155 | // device. Now, we need to determine whether this exception was caused by a |
156 | // ptrauth failure. |
157 | |
158 | uint64_t bad_address = m_exc_subcode; |
159 | uint64_t fixed_bad_address = abi_sp->FixCodeAddress(pc: bad_address); |
160 | uint64_t current_pc = current_address.GetLoadAddress(target: &target); |
161 | |
162 | // Detect: LDRAA, LDRAB (Load Register, with pointer authentication). |
163 | // |
164 | // If an authenticated load results in an exception, the instruction at the |
165 | // current PC should be one of LDRAx. |
166 | if (bad_address != current_pc && fixed_bad_address != current_pc) { |
167 | auto ptrauth_info = |
168 | GetPtrauthInstructionInfo(target, arch, at_addr: current_address); |
169 | if (ptrauth_info && ptrauth_info->IsAuthenticated && ptrauth_info->IsLoad) { |
170 | emit_ptrauth_prologue(bad_address); |
171 | strm.Printf(format: "Found authenticated load instruction " ); |
172 | DescribeAddressBriefly(strm, addr: current_address, target); |
173 | m_description = std::string(strm.GetString()); |
174 | return true; |
175 | } |
176 | } |
177 | |
178 | // Detect: BLRAA, BLRAAZ, BLRAB, BLRABZ (Branch with Link to Register, with |
179 | // pointer authentication). |
180 | // |
181 | // TODO: Detect: BRAA, BRAAZ, BRAB, BRABZ (Branch to Register, with pointer |
182 | // authentication). At a minimum, this requires call site info support for |
183 | // indirect calls. |
184 | // |
185 | // If an authenticated call or tail call results in an exception, stripping |
186 | // the bad address should give the current PC, which points to the address |
187 | // we tried to branch to. |
188 | if (bad_address != current_pc && fixed_bad_address == current_pc) { |
189 | if (StackFrameSP parent_frame = thread.GetStackFrameAtIndex(idx: 1)) { |
190 | addr_t return_pc = |
191 | parent_frame->GetFrameCodeAddress().GetLoadAddress(target: &target); |
192 | Address blr_address; |
193 | if (!target.ResolveLoadAddress(load_addr: return_pc - 4, so_addr&: blr_address)) |
194 | return false; |
195 | |
196 | auto blr_ptrauth_info = |
197 | GetPtrauthInstructionInfo(target, arch, at_addr: blr_address); |
198 | if (blr_ptrauth_info && blr_ptrauth_info->IsAuthenticated && |
199 | blr_ptrauth_info->DoesBranch) { |
200 | emit_ptrauth_prologue(bad_address); |
201 | strm.Printf(format: "Found authenticated indirect branch " ); |
202 | DescribeAddressBriefly(strm, addr: blr_address, target); |
203 | m_description = std::string(strm.GetString()); |
204 | return true; |
205 | } |
206 | } |
207 | } |
208 | |
209 | // TODO: Detect: RETAA, RETAB (Return from subroutine, with pointer |
210 | // authentication). |
211 | // |
212 | // Is there a motivating, non-malicious code snippet that corrupts LR? |
213 | |
214 | return false; |
215 | } |
216 | |
217 | const char *StopInfoMachException::GetDescription() { |
218 | if (!m_description.empty()) |
219 | return m_description.c_str(); |
220 | if (GetValue() == eStopReasonInvalid) |
221 | return "invalid stop reason!" ; |
222 | |
223 | ExecutionContext exe_ctx(m_thread_wp.lock()); |
224 | Target *target = exe_ctx.GetTargetPtr(); |
225 | const llvm::Triple::ArchType cpu = |
226 | target ? target->GetArchitecture().GetMachine() |
227 | : llvm::Triple::UnknownArch; |
228 | |
229 | const char *exc_desc = nullptr; |
230 | const char *code_label = "code" ; |
231 | const char *code_desc = nullptr; |
232 | const char *subcode_label = "subcode" ; |
233 | const char *subcode_desc = nullptr; |
234 | |
235 | #if defined(__APPLE__) |
236 | char code_desc_buf[32]; |
237 | char subcode_desc_buf[32]; |
238 | #endif |
239 | |
240 | switch (m_value) { |
241 | case 1: // EXC_BAD_ACCESS |
242 | exc_desc = "EXC_BAD_ACCESS" ; |
243 | subcode_label = "address" ; |
244 | switch (cpu) { |
245 | case llvm::Triple::x86: |
246 | case llvm::Triple::x86_64: |
247 | switch (m_exc_code) { |
248 | case 0xd: |
249 | code_desc = "EXC_I386_GPFLT" ; |
250 | m_exc_data_count = 1; |
251 | break; |
252 | } |
253 | break; |
254 | case llvm::Triple::arm: |
255 | case llvm::Triple::thumb: |
256 | switch (m_exc_code) { |
257 | case 0x101: |
258 | code_desc = "EXC_ARM_DA_ALIGN" ; |
259 | break; |
260 | case 0x102: |
261 | code_desc = "EXC_ARM_DA_DEBUG" ; |
262 | break; |
263 | } |
264 | break; |
265 | |
266 | case llvm::Triple::aarch64: |
267 | if (DeterminePtrauthFailure(exe_ctx)) |
268 | return m_description.c_str(); |
269 | break; |
270 | |
271 | default: |
272 | break; |
273 | } |
274 | break; |
275 | |
276 | case 2: // EXC_BAD_INSTRUCTION |
277 | exc_desc = "EXC_BAD_INSTRUCTION" ; |
278 | switch (cpu) { |
279 | case llvm::Triple::x86: |
280 | case llvm::Triple::x86_64: |
281 | if (m_exc_code == 1) |
282 | code_desc = "EXC_I386_INVOP" ; |
283 | break; |
284 | |
285 | case llvm::Triple::arm: |
286 | case llvm::Triple::thumb: |
287 | if (m_exc_code == 1) |
288 | code_desc = "EXC_ARM_UNDEFINED" ; |
289 | break; |
290 | |
291 | default: |
292 | break; |
293 | } |
294 | break; |
295 | |
296 | case 3: // EXC_ARITHMETIC |
297 | exc_desc = "EXC_ARITHMETIC" ; |
298 | switch (cpu) { |
299 | case llvm::Triple::x86: |
300 | case llvm::Triple::x86_64: |
301 | switch (m_exc_code) { |
302 | case 1: |
303 | code_desc = "EXC_I386_DIV" ; |
304 | break; |
305 | case 2: |
306 | code_desc = "EXC_I386_INTO" ; |
307 | break; |
308 | case 3: |
309 | code_desc = "EXC_I386_NOEXT" ; |
310 | break; |
311 | case 4: |
312 | code_desc = "EXC_I386_EXTOVR" ; |
313 | break; |
314 | case 5: |
315 | code_desc = "EXC_I386_EXTERR" ; |
316 | break; |
317 | case 6: |
318 | code_desc = "EXC_I386_EMERR" ; |
319 | break; |
320 | case 7: |
321 | code_desc = "EXC_I386_BOUND" ; |
322 | break; |
323 | case 8: |
324 | code_desc = "EXC_I386_SSEEXTERR" ; |
325 | break; |
326 | } |
327 | break; |
328 | |
329 | default: |
330 | break; |
331 | } |
332 | break; |
333 | |
334 | case 4: // EXC_EMULATION |
335 | exc_desc = "EXC_EMULATION" ; |
336 | break; |
337 | |
338 | case 5: // EXC_SOFTWARE |
339 | exc_desc = "EXC_SOFTWARE" ; |
340 | if (m_exc_code == 0x10003) { |
341 | subcode_desc = "EXC_SOFT_SIGNAL" ; |
342 | subcode_label = "signo" ; |
343 | } |
344 | break; |
345 | |
346 | case 6: // EXC_BREAKPOINT |
347 | { |
348 | exc_desc = "EXC_BREAKPOINT" ; |
349 | switch (cpu) { |
350 | case llvm::Triple::x86: |
351 | case llvm::Triple::x86_64: |
352 | switch (m_exc_code) { |
353 | case 1: |
354 | code_desc = "EXC_I386_SGL" ; |
355 | break; |
356 | case 2: |
357 | code_desc = "EXC_I386_BPT" ; |
358 | break; |
359 | } |
360 | break; |
361 | |
362 | case llvm::Triple::arm: |
363 | case llvm::Triple::thumb: |
364 | switch (m_exc_code) { |
365 | case 0x101: |
366 | code_desc = "EXC_ARM_DA_ALIGN" ; |
367 | break; |
368 | case 0x102: |
369 | code_desc = "EXC_ARM_DA_DEBUG" ; |
370 | break; |
371 | case 1: |
372 | code_desc = "EXC_ARM_BREAKPOINT" ; |
373 | break; |
374 | // FIXME temporary workaround, exc_code 0 does not really mean |
375 | // EXC_ARM_BREAKPOINT |
376 | case 0: |
377 | code_desc = "EXC_ARM_BREAKPOINT" ; |
378 | break; |
379 | } |
380 | break; |
381 | |
382 | case llvm::Triple::aarch64: |
383 | if (DeterminePtrauthFailure(exe_ctx)) |
384 | return m_description.c_str(); |
385 | break; |
386 | |
387 | default: |
388 | break; |
389 | } |
390 | } break; |
391 | |
392 | case 7: |
393 | exc_desc = "EXC_SYSCALL" ; |
394 | break; |
395 | |
396 | case 8: |
397 | exc_desc = "EXC_MACH_SYSCALL" ; |
398 | break; |
399 | |
400 | case 9: |
401 | exc_desc = "EXC_RPC_ALERT" ; |
402 | break; |
403 | |
404 | case 10: |
405 | exc_desc = "EXC_CRASH" ; |
406 | break; |
407 | case 11: |
408 | exc_desc = "EXC_RESOURCE" ; |
409 | #if defined(__APPLE__) |
410 | { |
411 | int resource_type = EXC_RESOURCE_DECODE_RESOURCE_TYPE(m_exc_code); |
412 | |
413 | code_label = "limit" ; |
414 | code_desc = code_desc_buf; |
415 | subcode_label = "observed" ; |
416 | subcode_desc = subcode_desc_buf; |
417 | |
418 | switch (resource_type) { |
419 | case RESOURCE_TYPE_CPU: |
420 | exc_desc = |
421 | "EXC_RESOURCE (RESOURCE_TYPE_CPU: CPU usage monitor tripped)" ; |
422 | snprintf(code_desc_buf, sizeof(code_desc_buf), "%d%%" , |
423 | (int)EXC_RESOURCE_CPUMONITOR_DECODE_PERCENTAGE(m_exc_code)); |
424 | snprintf(subcode_desc_buf, sizeof(subcode_desc_buf), "%d%%" , |
425 | (int)EXC_RESOURCE_CPUMONITOR_DECODE_PERCENTAGE_OBSERVED( |
426 | m_exc_subcode)); |
427 | break; |
428 | case RESOURCE_TYPE_WAKEUPS: |
429 | exc_desc = "EXC_RESOURCE (RESOURCE_TYPE_WAKEUPS: idle wakeups monitor " |
430 | "tripped)" ; |
431 | snprintf( |
432 | code_desc_buf, sizeof(code_desc_buf), "%d w/s" , |
433 | (int)EXC_RESOURCE_CPUMONITOR_DECODE_WAKEUPS_PERMITTED(m_exc_code)); |
434 | snprintf(subcode_desc_buf, sizeof(subcode_desc_buf), "%d w/s" , |
435 | (int)EXC_RESOURCE_CPUMONITOR_DECODE_WAKEUPS_OBSERVED( |
436 | m_exc_subcode)); |
437 | break; |
438 | case RESOURCE_TYPE_MEMORY: |
439 | exc_desc = "EXC_RESOURCE (RESOURCE_TYPE_MEMORY: high watermark memory " |
440 | "limit exceeded)" ; |
441 | snprintf(code_desc_buf, sizeof(code_desc_buf), "%d MB" , |
442 | (int)EXC_RESOURCE_HWM_DECODE_LIMIT(m_exc_code)); |
443 | subcode_desc = nullptr; |
444 | subcode_label = nullptr; |
445 | break; |
446 | #if defined(RESOURCE_TYPE_IO) |
447 | // RESOURCE_TYPE_IO is introduced in macOS SDK 10.12. |
448 | case RESOURCE_TYPE_IO: |
449 | exc_desc = "EXC_RESOURCE RESOURCE_TYPE_IO" ; |
450 | snprintf(code_desc_buf, sizeof(code_desc_buf), "%d MB" , |
451 | (int)EXC_RESOURCE_IO_DECODE_LIMIT(m_exc_code)); |
452 | snprintf(subcode_desc_buf, sizeof(subcode_desc_buf), "%d MB" , |
453 | (int)EXC_RESOURCE_IO_OBSERVED(m_exc_subcode)); |
454 | ; |
455 | break; |
456 | #endif |
457 | } |
458 | } |
459 | #endif |
460 | break; |
461 | case 12: |
462 | exc_desc = "EXC_GUARD" ; |
463 | break; |
464 | } |
465 | |
466 | StreamString strm; |
467 | |
468 | if (exc_desc) |
469 | strm.PutCString(cstr: exc_desc); |
470 | else |
471 | strm.Printf(format: "EXC_??? (%" PRIu64 ")" , m_value); |
472 | |
473 | if (m_exc_data_count >= 1) { |
474 | if (code_desc) |
475 | strm.Printf(format: " (%s=%s" , code_label, code_desc); |
476 | else |
477 | strm.Printf(format: " (%s=%" PRIu64, code_label, m_exc_code); |
478 | } |
479 | |
480 | if (m_exc_data_count >= 2) { |
481 | if (subcode_label && subcode_desc) |
482 | strm.Printf(format: ", %s=%s" , subcode_label, subcode_desc); |
483 | else if (subcode_label) |
484 | strm.Printf(format: ", %s=0x%" PRIx64, subcode_label, m_exc_subcode); |
485 | } |
486 | |
487 | if (m_exc_data_count > 0) |
488 | strm.PutChar(ch: ')'); |
489 | |
490 | m_description = std::string(strm.GetString()); |
491 | return m_description.c_str(); |
492 | } |
493 | |
494 | #if defined(__APPLE__) |
495 | const char * |
496 | StopInfoMachException::MachException::Name(exception_type_t exc_type) { |
497 | switch (exc_type) { |
498 | case EXC_BAD_ACCESS: |
499 | return "EXC_BAD_ACCESS" ; |
500 | case EXC_BAD_INSTRUCTION: |
501 | return "EXC_BAD_INSTRUCTION" ; |
502 | case EXC_ARITHMETIC: |
503 | return "EXC_ARITHMETIC" ; |
504 | case EXC_EMULATION: |
505 | return "EXC_EMULATION" ; |
506 | case EXC_SOFTWARE: |
507 | return "EXC_SOFTWARE" ; |
508 | case EXC_BREAKPOINT: |
509 | return "EXC_BREAKPOINT" ; |
510 | case EXC_SYSCALL: |
511 | return "EXC_SYSCALL" ; |
512 | case EXC_MACH_SYSCALL: |
513 | return "EXC_MACH_SYSCALL" ; |
514 | case EXC_RPC_ALERT: |
515 | return "EXC_RPC_ALERT" ; |
516 | #ifdef EXC_CRASH |
517 | case EXC_CRASH: |
518 | return "EXC_CRASH" ; |
519 | #endif |
520 | case EXC_RESOURCE: |
521 | return "EXC_RESOURCE" ; |
522 | #ifdef EXC_GUARD |
523 | case EXC_GUARD: |
524 | return "EXC_GUARD" ; |
525 | #endif |
526 | #ifdef EXC_CORPSE_NOTIFY |
527 | case EXC_CORPSE_NOTIFY: |
528 | return "EXC_CORPSE_NOTIFY" ; |
529 | #endif |
530 | #ifdef EXC_CORPSE_VARIANT_BIT |
531 | case EXC_CORPSE_VARIANT_BIT: |
532 | return "EXC_CORPSE_VARIANT_BIT" ; |
533 | #endif |
534 | default: |
535 | break; |
536 | } |
537 | return NULL; |
538 | } |
539 | |
540 | std::optional<exception_type_t> |
541 | StopInfoMachException::MachException::ExceptionCode(const char *name) { |
542 | return llvm::StringSwitch<std::optional<exception_type_t>>(name) |
543 | .Case("EXC_BAD_ACCESS" , EXC_BAD_ACCESS) |
544 | .Case("EXC_BAD_INSTRUCTION" , EXC_BAD_INSTRUCTION) |
545 | .Case("EXC_ARITHMETIC" , EXC_ARITHMETIC) |
546 | .Case("EXC_EMULATION" , EXC_EMULATION) |
547 | .Case("EXC_SOFTWARE" , EXC_SOFTWARE) |
548 | .Case("EXC_BREAKPOINT" , EXC_BREAKPOINT) |
549 | .Case("EXC_SYSCALL" , EXC_SYSCALL) |
550 | .Case("EXC_MACH_SYSCALL" , EXC_MACH_SYSCALL) |
551 | .Case("EXC_RPC_ALERT" , EXC_RPC_ALERT) |
552 | #ifdef EXC_CRASH |
553 | .Case("EXC_CRASH" , EXC_CRASH) |
554 | #endif |
555 | .Case("EXC_RESOURCE" , EXC_RESOURCE) |
556 | #ifdef EXC_GUARD |
557 | .Case("EXC_GUARD" , EXC_GUARD) |
558 | #endif |
559 | #ifdef EXC_CORPSE_NOTIFY |
560 | .Case("EXC_CORPSE_NOTIFY" , EXC_CORPSE_NOTIFY) |
561 | #endif |
562 | .Default(std::nullopt); |
563 | } |
564 | #endif |
565 | |
566 | StopInfoSP StopInfoMachException::CreateStopReasonWithMachException( |
567 | Thread &thread, uint32_t exc_type, uint32_t exc_data_count, |
568 | uint64_t exc_code, uint64_t exc_sub_code, uint64_t exc_sub_sub_code, |
569 | bool pc_already_adjusted, bool adjust_pc_if_needed) { |
570 | if (exc_type == 0) |
571 | return StopInfoSP(); |
572 | |
573 | bool not_stepping_but_got_singlestep_exception = false; |
574 | uint32_t pc_decrement = 0; |
575 | ExecutionContext exe_ctx(thread.shared_from_this()); |
576 | Target *target = exe_ctx.GetTargetPtr(); |
577 | const llvm::Triple::ArchType cpu = |
578 | target ? target->GetArchitecture().GetMachine() |
579 | : llvm::Triple::UnknownArch; |
580 | |
581 | ProcessSP process_sp(thread.GetProcess()); |
582 | RegisterContextSP reg_ctx_sp(thread.GetRegisterContext()); |
583 | // Caveat: with x86 KDP if we've hit a breakpoint, the pc we |
584 | // receive is past the breakpoint instruction. |
585 | // If we have a breakpoints at 0x100 and 0x101, we hit the |
586 | // 0x100 breakpoint and the pc is reported at 0x101. |
587 | // We will initially mark this thread as being stopped at an |
588 | // unexecuted breakpoint at 0x101. Later when we see that |
589 | // we stopped for a Breakpoint reason, we will decrement the |
590 | // pc, and update the thread to record that we hit the |
591 | // breakpoint at 0x100. |
592 | // The fact that the pc may be off by one at this point |
593 | // (for an x86 KDP breakpoint hit) is not a problem. |
594 | addr_t pc = reg_ctx_sp->GetPC(); |
595 | BreakpointSiteSP bp_site_sp = |
596 | process_sp->GetBreakpointSiteList().FindByAddress(addr: pc); |
597 | if (bp_site_sp && bp_site_sp->IsEnabled()) |
598 | thread.SetThreadStoppedAtUnexecutedBP(pc); |
599 | |
600 | switch (exc_type) { |
601 | case 1: // EXC_BAD_ACCESS |
602 | case 2: // EXC_BAD_INSTRUCTION |
603 | case 3: // EXC_ARITHMETIC |
604 | case 4: // EXC_EMULATION |
605 | break; |
606 | |
607 | case 5: // EXC_SOFTWARE |
608 | if (exc_code == 0x10003) // EXC_SOFT_SIGNAL |
609 | { |
610 | if (exc_sub_code == 5) { |
611 | // On MacOSX, a SIGTRAP can signify that a process has called exec, |
612 | // so we should check with our dynamic loader to verify. |
613 | ProcessSP process_sp(thread.GetProcess()); |
614 | if (process_sp) { |
615 | DynamicLoader *dynamic_loader = process_sp->GetDynamicLoader(); |
616 | if (dynamic_loader && dynamic_loader->ProcessDidExec()) { |
617 | // The program was re-exec'ed |
618 | return StopInfo::CreateStopReasonWithExec(thread); |
619 | } |
620 | } |
621 | } |
622 | return StopInfo::CreateStopReasonWithSignal(thread, signo: exc_sub_code); |
623 | } |
624 | break; |
625 | |
626 | // A mach exception comes with 2-4 pieces of data. |
627 | // The sub-codes are only provided for certain types |
628 | // of mach exceptions. |
629 | // [exc_type, exc_code, exc_sub_code, exc_sub_sub_code] |
630 | // |
631 | // Here are all of the EXC_BREAKPOINT, exc_type==6, |
632 | // exceptions we can receive. |
633 | // |
634 | // Instruction step: |
635 | // [6, 1, 0] |
636 | // Intel KDP [6, 3, ??] |
637 | // armv7 [6, 0x102, <stop-pc>] Same as software breakpoint! |
638 | // |
639 | // Software breakpoint: |
640 | // x86 [6, 2, 0] |
641 | // Intel KDP [6, 2, <bp-addr + 1>] |
642 | // arm64 [6, 1, <bp-addr>] |
643 | // armv7 [6, 0x102, <bp-addr>] Same as instruction step! |
644 | // |
645 | // Hardware breakpoint: |
646 | // x86 [6, 1, <bp-addr>, 0] |
647 | // x86/Rosetta not implemented, see software breakpoint |
648 | // arm64 [6, 1, <bp-addr>] |
649 | // armv7 not implemented, see software breakpoint |
650 | // |
651 | // Hardware watchpoint: |
652 | // x86 [6, 1, <accessed-addr>, 0] (both Intel hw and Rosetta) |
653 | // arm64 [6, 0x102, <accessed-addr>, 0] |
654 | // armv7 [6, 0x102, <accessed-addr>, 0] |
655 | // |
656 | // arm64 BRK instruction (imm arg not reflected in the ME) |
657 | // [ 6, 1, <addr-of-BRK-insn>] |
658 | // |
659 | // In order of codes mach exceptions: |
660 | // [6, 1, 0] - instruction step |
661 | // [6, 1, <bp-addr>] - hardware breakpoint or watchpoint |
662 | // |
663 | // [6, 2, 0] - software breakpoint |
664 | // [6, 2, <bp-addr + 1>] - software breakpoint |
665 | // |
666 | // [6, 3] - instruction step |
667 | // |
668 | // [6, 0x102, <stop-pc>] armv7 instruction step |
669 | // [6, 0x102, <bp-addr>] armv7 software breakpoint |
670 | // [6, 0x102, <accessed-addr>, 0] arm64/armv7 watchpoint |
671 | |
672 | case 6: // EXC_BREAKPOINT |
673 | { |
674 | bool stopped_by_hitting_breakpoint = false; |
675 | bool stopped_by_completing_stepi = false; |
676 | bool stopped_watchpoint = false; |
677 | std::optional<addr_t> address; |
678 | |
679 | // exc_code 1 |
680 | if (exc_code == 1) { |
681 | if (exc_sub_code == 0) { |
682 | stopped_by_completing_stepi = true; |
683 | } else { |
684 | // Ambiguous: could be signalling a |
685 | // breakpoint or watchpoint hit. |
686 | stopped_by_hitting_breakpoint = true; |
687 | stopped_watchpoint = true; |
688 | address = exc_sub_code; |
689 | } |
690 | } |
691 | |
692 | // exc_code 2 |
693 | if (exc_code == 2) { |
694 | if (exc_sub_code == 0) |
695 | stopped_by_hitting_breakpoint = true; |
696 | else { |
697 | stopped_by_hitting_breakpoint = true; |
698 | // Intel KDP software breakpoint |
699 | if (!pc_already_adjusted) |
700 | pc_decrement = 1; |
701 | } |
702 | } |
703 | |
704 | // exc_code 3 |
705 | if (exc_code == 3) |
706 | stopped_by_completing_stepi = true; |
707 | |
708 | // exc_code 0x102 |
709 | if (exc_code == 0x102 && exc_sub_code != 0) { |
710 | if (cpu == llvm::Triple::arm || cpu == llvm::Triple::thumb) { |
711 | stopped_by_hitting_breakpoint = true; |
712 | stopped_by_completing_stepi = true; |
713 | } |
714 | stopped_watchpoint = true; |
715 | address = exc_sub_code; |
716 | } |
717 | |
718 | // The Mach Exception may have been ambiguous -- |
719 | // e.g. we stopped either because of a breakpoint |
720 | // or a watchpoint. We'll disambiguate which it |
721 | // really was. |
722 | |
723 | if (stopped_by_hitting_breakpoint) { |
724 | addr_t pc = reg_ctx_sp->GetPC() - pc_decrement; |
725 | |
726 | if (address) |
727 | bp_site_sp = |
728 | process_sp->GetBreakpointSiteList().FindByAddress(addr: *address); |
729 | if (!bp_site_sp && reg_ctx_sp) { |
730 | bp_site_sp = process_sp->GetBreakpointSiteList().FindByAddress(addr: pc); |
731 | } |
732 | if (bp_site_sp && bp_site_sp->IsEnabled()) { |
733 | // We've hit this breakpoint, whether it was intended for this thread |
734 | // or not. Clear this in the Tread object so we step past it on resume. |
735 | thread.SetThreadHitBreakpointSite(); |
736 | |
737 | if (bp_site_sp->ValidForThisThread(thread)) { |
738 | // Update the PC if we were asked to do so, but only do so if we find |
739 | // a breakpoint that we know about because this could be a trap |
740 | // instruction in the code. |
741 | if (pc_decrement > 0 && adjust_pc_if_needed && reg_ctx_sp) |
742 | reg_ctx_sp->SetPC(pc); |
743 | |
744 | return StopInfo::CreateStopReasonWithBreakpointSiteID( |
745 | thread, break_id: bp_site_sp->GetID()); |
746 | } else { |
747 | return StopInfoSP(); |
748 | } |
749 | } |
750 | } |
751 | |
752 | // Breakpoint-hit events are handled. |
753 | // Now handle watchpoints. |
754 | |
755 | if (stopped_watchpoint && address) { |
756 | WatchpointResourceSP wp_rsrc_sp = |
757 | target->GetProcessSP()->GetWatchpointResourceList().FindByAddress( |
758 | addr: *address); |
759 | if (wp_rsrc_sp && wp_rsrc_sp->GetNumberOfConstituents() > 0) { |
760 | return StopInfo::CreateStopReasonWithWatchpointID( |
761 | thread, watch_id: wp_rsrc_sp->GetConstituentAtIndex(idx: 0)->GetID()); |
762 | } |
763 | } |
764 | |
765 | // Finally, handle instruction step. |
766 | |
767 | if (stopped_by_completing_stepi) { |
768 | if (thread.GetTemporaryResumeState() != eStateStepping) |
769 | not_stepping_but_got_singlestep_exception = true; |
770 | else |
771 | return StopInfo::CreateStopReasonToTrace(thread); |
772 | } |
773 | |
774 | } break; |
775 | |
776 | case 7: // EXC_SYSCALL |
777 | case 8: // EXC_MACH_SYSCALL |
778 | case 9: // EXC_RPC_ALERT |
779 | case 10: // EXC_CRASH |
780 | break; |
781 | } |
782 | |
783 | return std::make_shared<StopInfoMachException>( |
784 | args&: thread, args&: exc_type, args&: exc_data_count, args&: exc_code, args&: exc_sub_code, |
785 | args&: not_stepping_but_got_singlestep_exception); |
786 | } |
787 | |
788 | // Detect an unusual situation on Darwin where: |
789 | // |
790 | // 0. We did an instruction-step before this. |
791 | // 1. We have a hardware breakpoint or watchpoint set. |
792 | // 2. We resumed the process, but not with an instruction-step. |
793 | // 3. The thread gets an "instruction-step completed" mach exception. |
794 | // 4. The pc has not advanced - it is the same as before. |
795 | // |
796 | // This method returns true for that combination of events. |
797 | bool StopInfoMachException::WasContinueInterrupted(Thread &thread) { |
798 | Log *log = GetLog(mask: LLDBLog::Step); |
799 | |
800 | // We got an instruction-step completed mach exception but we were not |
801 | // doing an instruction step on this thread. |
802 | if (!m_not_stepping_but_got_singlestep_exception) |
803 | return false; |
804 | |
805 | RegisterContextSP reg_ctx_sp(thread.GetRegisterContext()); |
806 | std::optional<addr_t> prev_pc = thread.GetPreviousFrameZeroPC(); |
807 | if (!reg_ctx_sp || !prev_pc) |
808 | return false; |
809 | |
810 | // The previous pc value and current pc value are the same. |
811 | if (*prev_pc != reg_ctx_sp->GetPC()) |
812 | return false; |
813 | |
814 | // We have a watchpoint -- this is the kernel bug. |
815 | ProcessSP process_sp = thread.GetProcess(); |
816 | if (process_sp->GetWatchpointResourceList().GetSize()) { |
817 | LLDB_LOGF(log, |
818 | "Thread stopped with insn-step completed mach exception but " |
819 | "thread was not stepping; there is a hardware watchpoint set." ); |
820 | return true; |
821 | } |
822 | |
823 | // We have a hardware breakpoint -- this is the kernel bug. |
824 | auto &bp_site_list = process_sp->GetBreakpointSiteList(); |
825 | for (auto &site : bp_site_list.Sites()) { |
826 | if (site->IsHardware() && site->IsEnabled()) { |
827 | LLDB_LOGF(log, |
828 | "Thread stopped with insn-step completed mach exception but " |
829 | "thread was not stepping; there is a hardware breakpoint set." ); |
830 | return true; |
831 | } |
832 | } |
833 | |
834 | return false; |
835 | } |
836 | |