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