| //===-- StopInfoMachException.cpp -----------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "StopInfoMachException.h" | |
| #include "lldb/lldb-forward.h" | |
| #if defined(__APPLE__) | |
| // Needed for the EXC_RESOURCE interpretation macros | |
| #include <kern/exc_resource.h> | |
| #endif | |
| #include "lldb/Breakpoint/Watchpoint.h" | |
| #include "lldb/Symbol/Symbol.h" | |
| #include "lldb/Target/ABI.h" | |
| #include "lldb/Target/DynamicLoader.h" | |
| #include "lldb/Target/ExecutionContext.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Target/RegisterContext.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Target/ThreadPlan.h" | |
| #include "lldb/Target/UnixSignals.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/StreamString.h" | |
| #include <optional> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| /// Information about a pointer-authentication related instruction. | |
| struct PtrauthInstructionInfo { | |
| bool IsAuthenticated; | |
| bool IsLoad; | |
| bool DoesBranch; | |
| }; | |
| /// Get any pointer-authentication related information about the instruction | |
| /// at address \p at_addr. | |
| static std::optional<PtrauthInstructionInfo> | |
| GetPtrauthInstructionInfo(Target &target, const ArchSpec &arch, | |
| const Address &at_addr) { | |
| const char *plugin_name = nullptr; | |
| const char *flavor = nullptr; | |
| const char *cpu = nullptr; | |
| const char *features = nullptr; | |
| AddressRange range_bounds(at_addr, 4); | |
| const bool prefer_file_cache = true; | |
| DisassemblerSP disassembler_sp = | |
| Disassembler::DisassembleRange(arch, plugin_name, flavor, cpu, features, | |
| target, range_bounds, prefer_file_cache); | |
| if (!disassembler_sp) | |
| return std::nullopt; | |
| InstructionList &insn_list = disassembler_sp->GetInstructionList(); | |
| InstructionSP insn = insn_list.GetInstructionAtIndex(0); | |
| if (!insn) | |
| return std::nullopt; | |
| return PtrauthInstructionInfo{insn->IsAuthenticated(), insn->IsLoad(), | |
| insn->DoesBranch()}; | |
| } | |
| /// Describe the load address of \p addr using the format filename:line:col. | |
| static void DescribeAddressBriefly(Stream &strm, const Address &addr, | |
| Target &target) { | |
| strm.Printf("at address=0x%" PRIx64, addr.GetLoadAddress(&target)); | |
| StreamString s; | |
| if (addr.GetDescription(s, target, eDescriptionLevelBrief)) | |
| strm.Printf(" %s", s.GetString().data()); | |
| strm.Printf(".\n"); | |
| } | |
| std::optional<addr_t> StopInfoMachException::GetTagFaultAddress() const { | |
| const bool bad_access = | |
| (m_value == 1 || m_value == 12); // EXC_BAD_ACCESS or EXC_GUARD | |
| const bool tag_fault = (m_exc_code == 0x106); // EXC_ARM_MTE_TAG_FAULT | |
| // Whether the subcode (m_exc_subcode) holds the fault address. | |
| const bool has_fault_addr = (m_exc_data_count >= 2); | |
| if (bad_access && tag_fault && has_fault_addr) | |
| return m_exc_subcode; // The subcode is the fault address. | |
| return std::nullopt; | |
| } | |
| static constexpr uint8_t g_mte_tag_shift = 64 - 8; | |
| static constexpr addr_t g_mte_tag_mask = (addr_t)0x0f << g_mte_tag_shift; | |
| bool StopInfoMachException::DetermineTagMismatch() { | |
| std::optional<addr_t> fault_address = GetTagFaultAddress(); | |
| if (!fault_address) | |
| return false; | |
| const uint64_t bad_address = *fault_address; | |
| StreamString strm; | |
| strm.Printf("EXC_ARM_MTE_TAG_FAULT (code=%" PRIu64 ", address=0x%" PRIx64 | |
| ")\n", | |
| m_exc_code, bad_address); | |
| const uint8_t tag = (bad_address & g_mte_tag_mask) >> g_mte_tag_shift; | |
| const addr_t canonical_addr = bad_address & ~g_mte_tag_mask; | |
| strm.Printf( | |
| "Note: MTE tag mismatch detected: pointer tag=%d, address=0x%" PRIx64, | |
| tag, canonical_addr); | |
| m_description = std::string(strm.GetString()); | |
| return true; | |
| } | |
| bool StopInfoMachException::DeterminePtrauthFailure(ExecutionContext &exe_ctx) { | |
| bool IsBreakpoint = m_value == 6; // EXC_BREAKPOINT | |
| bool IsBadAccess = m_value == 1; // EXC_BAD_ACCESS | |
| if (!IsBreakpoint && !IsBadAccess) | |
| return false; | |
| // Check that we have a live process. | |
| if (!exe_ctx.HasProcessScope() || !exe_ctx.HasThreadScope() || | |
| !exe_ctx.HasTargetScope()) | |
| return false; | |
| Thread &thread = *exe_ctx.GetThreadPtr(); | |
| StackFrameSP current_frame = thread.GetStackFrameAtIndex(0); | |
| if (!current_frame) | |
| return false; | |
| Target &target = *exe_ctx.GetTargetPtr(); | |
| Process &process = *exe_ctx.GetProcessPtr(); | |
| const ArchSpec &arch = target.GetArchitecture(); | |
| // Check for a ptrauth-enabled target. | |
| const bool ptrauth_enabled_target = | |
| arch.GetCore() == ArchSpec::eCore_arm_arm64e; | |
| if (!ptrauth_enabled_target) | |
| return false; | |
| // Set up a stream we can write a diagnostic into. | |
| StreamString strm; | |
| auto emit_ptrauth_prologue = [&](uint64_t at_address) { | |
| strm.Printf("EXC_BAD_ACCESS (code=%" PRIu64 ", address=0x%" PRIx64 ")\n", | |
| m_exc_code, at_address); | |
| strm.Printf("Note: Possible pointer authentication failure detected.\n"); | |
| }; | |
| ABISP abi_sp = process.GetABI(); | |
| assert(abi_sp && "Missing ABI info"); | |
| // Check if we have a "brk 0xc47x" trap, where the value that failed to | |
| // authenticate is in x16. | |
| Address current_address = current_frame->GetFrameCodeAddress(); | |
| if (IsBreakpoint) { | |
| RegisterContext *reg_ctx = exe_ctx.GetRegisterContext(); | |
| if (!reg_ctx) | |
| return false; | |
| const RegisterInfo *X16Info = reg_ctx->GetRegisterInfoByName("x16"); | |
| RegisterValue X16Val; | |
| if (!reg_ctx->ReadRegister(X16Info, X16Val)) | |
| return false; | |
| uint64_t bad_address = X16Val.GetAsUInt64(); | |
| uint64_t fixed_bad_address = abi_sp->FixCodeAddress(bad_address); | |
| Address brk_address; | |
| if (!target.ResolveLoadAddress(fixed_bad_address, brk_address)) | |
| return false; | |
| auto brk_ptrauth_info = | |
| GetPtrauthInstructionInfo(target, arch, current_address); | |
| if (brk_ptrauth_info && brk_ptrauth_info->IsAuthenticated) { | |
| emit_ptrauth_prologue(bad_address); | |
| strm.Printf("Found value that failed to authenticate "); | |
| DescribeAddressBriefly(strm, brk_address, target); | |
| m_description = std::string(strm.GetString()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| assert(IsBadAccess && "Handle EXC_BAD_ACCESS only after this point"); | |
| // Check that we have the "bad address" from an EXC_BAD_ACCESS. | |
| if (m_exc_data_count < 2) | |
| return false; | |
| // Ok, we know the Target is valid and that it describes a ptrauth-enabled | |
| // device. Now, we need to determine whether this exception was caused by a | |
| // ptrauth failure. | |
| uint64_t bad_address = m_exc_subcode; | |
| uint64_t fixed_bad_address = abi_sp->FixCodeAddress(bad_address); | |
| uint64_t current_pc = current_address.GetLoadAddress(&target); | |
| // Detect: LDRAA, LDRAB (Load Register, with pointer authentication). | |
| // | |
| // If an authenticated load results in an exception, the instruction at the | |
| // current PC should be one of LDRAx. | |
| if (bad_address != current_pc && fixed_bad_address != current_pc) { | |
| auto ptrauth_info = | |
| GetPtrauthInstructionInfo(target, arch, current_address); | |
| if (ptrauth_info && ptrauth_info->IsAuthenticated && ptrauth_info->IsLoad) { | |
| emit_ptrauth_prologue(bad_address); | |
| strm.Printf("Found authenticated load instruction "); | |
| DescribeAddressBriefly(strm, current_address, target); | |
| m_description = std::string(strm.GetString()); | |
| return true; | |
| } | |
| } | |
| // Detect: BLRAA, BLRAAZ, BLRAB, BLRABZ (Branch with Link to Register, with | |
| // pointer authentication). | |
| // | |
| // TODO: Detect: BRAA, BRAAZ, BRAB, BRABZ (Branch to Register, with pointer | |
| // authentication). At a minimum, this requires call site info support for | |
| // indirect calls. | |
| // | |
| // If an authenticated call or tail call results in an exception, stripping | |
| // the bad address should give the current PC, which points to the address | |
| // we tried to branch to. | |
| if (bad_address != current_pc && fixed_bad_address == current_pc) { | |
| if (StackFrameSP parent_frame = thread.GetStackFrameAtIndex(1)) { | |
| addr_t return_pc = | |
| parent_frame->GetFrameCodeAddress().GetLoadAddress(&target); | |
| Address blr_address; | |
| if (!target.ResolveLoadAddress(return_pc - 4, blr_address)) | |
| return false; | |
| auto blr_ptrauth_info = | |
| GetPtrauthInstructionInfo(target, arch, blr_address); | |
| if (blr_ptrauth_info && blr_ptrauth_info->IsAuthenticated && | |
| blr_ptrauth_info->DoesBranch) { | |
| emit_ptrauth_prologue(bad_address); | |
| strm.Printf("Found authenticated indirect branch "); | |
| DescribeAddressBriefly(strm, blr_address, target); | |
| m_description = std::string(strm.GetString()); | |
| return true; | |
| } | |
| } | |
| } | |
| // TODO: Detect: RETAA, RETAB (Return from subroutine, with pointer | |
| // authentication). | |
| // | |
| // Is there a motivating, non-malicious code snippet that corrupts LR? | |
| return false; | |
| } | |
| const char *StopInfoMachException::GetDescription() { | |
| if (!m_description.empty()) | |
| return m_description.c_str(); | |
| if (GetValue() == eStopReasonInvalid) | |
| return "invalid stop reason!"; | |
| ExecutionContext exe_ctx(m_thread_wp.lock()); | |
| Target *target = exe_ctx.GetTargetPtr(); | |
| const llvm::Triple::ArchType cpu = | |
| target ? target->GetArchitecture().GetMachine() | |
| : llvm::Triple::UnknownArch; | |
| const char *exc_desc = nullptr; | |
| const char *code_label = "code"; | |
| const char *code_desc = nullptr; | |
| const char *subcode_label = "subcode"; | |
| const char *subcode_desc = nullptr; | |
| #if defined(__APPLE__) | |
| char code_desc_buf[32]; | |
| char subcode_desc_buf[32]; | |
| #endif | |
| switch (m_value) { | |
| case 1: // EXC_BAD_ACCESS | |
| exc_desc = "EXC_BAD_ACCESS"; | |
| subcode_label = "address"; | |
| switch (cpu) { | |
| case llvm::Triple::x86: | |
| case llvm::Triple::x86_64: | |
| switch (m_exc_code) { | |
| case 0xd: | |
| code_desc = "EXC_I386_GPFLT"; | |
| m_exc_data_count = 1; | |
| break; | |
| } | |
| break; | |
| case llvm::Triple::arm: | |
| case llvm::Triple::thumb: | |
| switch (m_exc_code) { | |
| case 0x101: | |
| code_desc = "EXC_ARM_DA_ALIGN"; | |
| break; | |
| case 0x102: | |
| code_desc = "EXC_ARM_DA_DEBUG"; | |
| break; | |
| } | |
| break; | |
| case llvm::Triple::aarch64: | |
| if (DeterminePtrauthFailure(exe_ctx)) | |
| return m_description.c_str(); | |
| if (DetermineTagMismatch()) | |
| return m_description.c_str(); | |
| break; | |
| default: | |
| break; | |
| } | |
| break; | |
| case 2: // EXC_BAD_INSTRUCTION | |
| exc_desc = "EXC_BAD_INSTRUCTION"; | |
| switch (cpu) { | |
| case llvm::Triple::x86: | |
| case llvm::Triple::x86_64: | |
| if (m_exc_code == 1) | |
| code_desc = "EXC_I386_INVOP"; | |
| break; | |
| case llvm::Triple::arm: | |
| case llvm::Triple::thumb: | |
| if (m_exc_code == 1) | |
| code_desc = "EXC_ARM_UNDEFINED"; | |
| break; | |
| default: | |
| break; | |
| } | |
| break; | |
| case 3: // EXC_ARITHMETIC | |
| exc_desc = "EXC_ARITHMETIC"; | |
| switch (cpu) { | |
| case llvm::Triple::x86: | |
| case llvm::Triple::x86_64: | |
| switch (m_exc_code) { | |
| case 1: | |
| code_desc = "EXC_I386_DIV"; | |
| break; | |
| case 2: | |
| code_desc = "EXC_I386_INTO"; | |
| break; | |
| case 3: | |
| code_desc = "EXC_I386_NOEXT"; | |
| break; | |
| case 4: | |
| code_desc = "EXC_I386_EXTOVR"; | |
| break; | |
| case 5: | |
| code_desc = "EXC_I386_EXTERR"; | |
| break; | |
| case 6: | |
| code_desc = "EXC_I386_EMERR"; | |
| break; | |
| case 7: | |
| code_desc = "EXC_I386_BOUND"; | |
| break; | |
| case 8: | |
| code_desc = "EXC_I386_SSEEXTERR"; | |
| break; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| break; | |
| case 4: // EXC_EMULATION | |
| exc_desc = "EXC_EMULATION"; | |
| break; | |
| case 5: // EXC_SOFTWARE | |
| exc_desc = "EXC_SOFTWARE"; | |
| if (m_exc_code == 0x10003) { | |
| subcode_desc = "EXC_SOFT_SIGNAL"; | |
| subcode_label = "signo"; | |
| } | |
| break; | |
| case 6: // EXC_BREAKPOINT | |
| { | |
| exc_desc = "EXC_BREAKPOINT"; | |
| switch (cpu) { | |
| case llvm::Triple::x86: | |
| case llvm::Triple::x86_64: | |
| switch (m_exc_code) { | |
| case 1: | |
| code_desc = "EXC_I386_SGL"; | |
| break; | |
| case 2: | |
| code_desc = "EXC_I386_BPT"; | |
| break; | |
| } | |
| break; | |
| case llvm::Triple::arm: | |
| case llvm::Triple::thumb: | |
| switch (m_exc_code) { | |
| case 0x101: | |
| code_desc = "EXC_ARM_DA_ALIGN"; | |
| break; | |
| case 0x102: | |
| code_desc = "EXC_ARM_DA_DEBUG"; | |
| break; | |
| case 1: | |
| code_desc = "EXC_ARM_BREAKPOINT"; | |
| break; | |
| // FIXME temporary workaround, exc_code 0 does not really mean | |
| // EXC_ARM_BREAKPOINT | |
| case 0: | |
| code_desc = "EXC_ARM_BREAKPOINT"; | |
| break; | |
| } | |
| break; | |
| case llvm::Triple::aarch64: | |
| if (DeterminePtrauthFailure(exe_ctx)) | |
| return m_description.c_str(); | |
| break; | |
| default: | |
| break; | |
| } | |
| } break; | |
| case 7: | |
| exc_desc = "EXC_SYSCALL"; | |
| break; | |
| case 8: | |
| exc_desc = "EXC_MACH_SYSCALL"; | |
| break; | |
| case 9: | |
| exc_desc = "EXC_RPC_ALERT"; | |
| break; | |
| case 10: | |
| exc_desc = "EXC_CRASH"; | |
| break; | |
| case 11: | |
| exc_desc = "EXC_RESOURCE"; | |
| #if defined(__APPLE__) | |
| { | |
| int resource_type = EXC_RESOURCE_DECODE_RESOURCE_TYPE(m_exc_code); | |
| code_label = "limit"; | |
| code_desc = code_desc_buf; | |
| subcode_label = "observed"; | |
| subcode_desc = subcode_desc_buf; | |
| switch (resource_type) { | |
| case RESOURCE_TYPE_CPU: | |
| exc_desc = | |
| "EXC_RESOURCE (RESOURCE_TYPE_CPU: CPU usage monitor tripped)"; | |
| snprintf(code_desc_buf, sizeof(code_desc_buf), "%d%%", | |
| (int)EXC_RESOURCE_CPUMONITOR_DECODE_PERCENTAGE(m_exc_code)); | |
| snprintf(subcode_desc_buf, sizeof(subcode_desc_buf), "%d%%", | |
| (int)EXC_RESOURCE_CPUMONITOR_DECODE_PERCENTAGE_OBSERVED( | |
| m_exc_subcode)); | |
| break; | |
| case RESOURCE_TYPE_WAKEUPS: | |
| exc_desc = "EXC_RESOURCE (RESOURCE_TYPE_WAKEUPS: idle wakeups monitor " | |
| "tripped)"; | |
| snprintf( | |
| code_desc_buf, sizeof(code_desc_buf), "%d w/s", | |
| (int)EXC_RESOURCE_CPUMONITOR_DECODE_WAKEUPS_PERMITTED(m_exc_code)); | |
| snprintf(subcode_desc_buf, sizeof(subcode_desc_buf), "%d w/s", | |
| (int)EXC_RESOURCE_CPUMONITOR_DECODE_WAKEUPS_OBSERVED( | |
| m_exc_subcode)); | |
| break; | |
| case RESOURCE_TYPE_MEMORY: | |
| exc_desc = "EXC_RESOURCE (RESOURCE_TYPE_MEMORY: high watermark memory " | |
| "limit exceeded)"; | |
| snprintf(code_desc_buf, sizeof(code_desc_buf), "%d MB", | |
| (int)EXC_RESOURCE_HWM_DECODE_LIMIT(m_exc_code)); | |
| subcode_desc = nullptr; | |
| subcode_label = nullptr; | |
| break; | |
| #if defined(RESOURCE_TYPE_IO) | |
| // RESOURCE_TYPE_IO is introduced in macOS SDK 10.12. | |
| case RESOURCE_TYPE_IO: | |
| exc_desc = "EXC_RESOURCE RESOURCE_TYPE_IO"; | |
| snprintf(code_desc_buf, sizeof(code_desc_buf), "%d MB", | |
| (int)EXC_RESOURCE_IO_DECODE_LIMIT(m_exc_code)); | |
| snprintf(subcode_desc_buf, sizeof(subcode_desc_buf), "%d MB", | |
| (int)EXC_RESOURCE_IO_OBSERVED(m_exc_subcode)); | |
| ; | |
| break; | |
| #endif | |
| } | |
| } | |
| #endif | |
| break; | |
| case 12: | |
| if (DetermineTagMismatch()) | |
| return m_description.c_str(); | |
| exc_desc = "EXC_GUARD"; | |
| break; | |
| } | |
| StreamString strm; | |
| if (exc_desc) | |
| strm.PutCString(exc_desc); | |
| else | |
| strm.Printf("EXC_??? (%" PRIu64 ")", m_value); | |
| if (m_exc_data_count >= 1) { | |
| if (code_desc) | |
| strm.Printf(" (%s=%s", code_label, code_desc); | |
| else | |
| strm.Printf(" (%s=%" PRIu64, code_label, m_exc_code); | |
| } | |
| if (m_exc_data_count >= 2) { | |
| if (subcode_label && subcode_desc) | |
| strm.Printf(", %s=%s", subcode_label, subcode_desc); | |
| else if (subcode_label) | |
| strm.Printf(", %s=0x%" PRIx64, subcode_label, m_exc_subcode); | |
| } | |
| if (m_exc_data_count > 0) | |
| strm.PutChar(')'); | |
| m_description = std::string(strm.GetString()); | |
| return m_description.c_str(); | |
| } | |
| #if defined(__APPLE__) | |
| const char * | |
| StopInfoMachException::MachException::Name(exception_type_t exc_type) { | |
| switch (exc_type) { | |
| case EXC_BAD_ACCESS: | |
| return "EXC_BAD_ACCESS"; | |
| case EXC_BAD_INSTRUCTION: | |
| return "EXC_BAD_INSTRUCTION"; | |
| case EXC_ARITHMETIC: | |
| return "EXC_ARITHMETIC"; | |
| case EXC_EMULATION: | |
| return "EXC_EMULATION"; | |
| case EXC_SOFTWARE: | |
| return "EXC_SOFTWARE"; | |
| case EXC_BREAKPOINT: | |
| return "EXC_BREAKPOINT"; | |
| case EXC_SYSCALL: | |
| return "EXC_SYSCALL"; | |
| case EXC_MACH_SYSCALL: | |
| return "EXC_MACH_SYSCALL"; | |
| case EXC_RPC_ALERT: | |
| return "EXC_RPC_ALERT"; | |
| #ifdef EXC_CRASH | |
| case EXC_CRASH: | |
| return "EXC_CRASH"; | |
| #endif | |
| case EXC_RESOURCE: | |
| return "EXC_RESOURCE"; | |
| #ifdef EXC_GUARD | |
| case EXC_GUARD: | |
| return "EXC_GUARD"; | |
| #endif | |
| #ifdef EXC_CORPSE_NOTIFY | |
| case EXC_CORPSE_NOTIFY: | |
| return "EXC_CORPSE_NOTIFY"; | |
| #endif | |
| #ifdef EXC_CORPSE_VARIANT_BIT | |
| case EXC_CORPSE_VARIANT_BIT: | |
| return "EXC_CORPSE_VARIANT_BIT"; | |
| #endif | |
| default: | |
| break; | |
| } | |
| return NULL; | |
| } | |
| std::optional<exception_type_t> | |
| StopInfoMachException::MachException::ExceptionCode(const char *name) { | |
| return llvm::StringSwitch<std::optional<exception_type_t>>(name) | |
| .Case("EXC_BAD_ACCESS", EXC_BAD_ACCESS) | |
| .Case("EXC_BAD_INSTRUCTION", EXC_BAD_INSTRUCTION) | |
| .Case("EXC_ARITHMETIC", EXC_ARITHMETIC) | |
| .Case("EXC_EMULATION", EXC_EMULATION) | |
| .Case("EXC_SOFTWARE", EXC_SOFTWARE) | |
| .Case("EXC_BREAKPOINT", EXC_BREAKPOINT) | |
| .Case("EXC_SYSCALL", EXC_SYSCALL) | |
| .Case("EXC_MACH_SYSCALL", EXC_MACH_SYSCALL) | |
| .Case("EXC_RPC_ALERT", EXC_RPC_ALERT) | |
| #ifdef EXC_CRASH | |
| .Case("EXC_CRASH", EXC_CRASH) | |
| #endif | |
| .Case("EXC_RESOURCE", EXC_RESOURCE) | |
| #ifdef EXC_GUARD | |
| .Case("EXC_GUARD", EXC_GUARD) | |
| #endif | |
| #ifdef EXC_CORPSE_NOTIFY | |
| .Case("EXC_CORPSE_NOTIFY", EXC_CORPSE_NOTIFY) | |
| #endif | |
| .Default(std::nullopt); | |
| } | |
| #endif | |
| StopInfoSP StopInfoMachException::CreateStopReasonWithMachException( | |
| Thread &thread, uint32_t exc_type, uint32_t exc_data_count, | |
| uint64_t exc_code, uint64_t exc_sub_code, uint64_t exc_sub_sub_code, | |
| bool pc_already_adjusted, bool adjust_pc_if_needed) { | |
| if (exc_type == 0) | |
| return StopInfoSP(); | |
| bool not_stepping_but_got_singlestep_exception = false; | |
| uint32_t pc_decrement = 0; | |
| ExecutionContext exe_ctx(thread.shared_from_this()); | |
| Target *target = exe_ctx.GetTargetPtr(); | |
| const llvm::Triple::ArchType cpu = | |
| target ? target->GetArchitecture().GetMachine() | |
| : llvm::Triple::UnknownArch; | |
| ProcessSP process_sp(thread.GetProcess()); | |
| RegisterContextSP reg_ctx_sp(thread.GetRegisterContext()); | |
| // Caveat: with x86 KDP if we've hit a breakpoint, the pc we | |
| // receive is past the breakpoint instruction. | |
| // If we have a breakpoints at 0x100 and 0x101, we hit the | |
| // 0x100 breakpoint and the pc is reported at 0x101. | |
| // We will initially mark this thread as being stopped at an | |
| // unexecuted breakpoint at 0x101. Later when we see that | |
| // we stopped for a Breakpoint reason, we will decrement the | |
| // pc, and update the thread to record that we hit the | |
| // breakpoint at 0x100. | |
| // The fact that the pc may be off by one at this point | |
| // (for an x86 KDP breakpoint hit) is not a problem. | |
| addr_t pc = reg_ctx_sp->GetPC(); | |
| BreakpointSiteSP bp_site_sp = | |
| process_sp->GetBreakpointSiteList().FindByAddress(pc); | |
| if (bp_site_sp && process_sp->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) | |
| thread.SetThreadStoppedAtUnexecutedBP(pc); | |
| switch (exc_type) { | |
| case 1: // EXC_BAD_ACCESS | |
| case 2: // EXC_BAD_INSTRUCTION | |
| case 3: // EXC_ARITHMETIC | |
| case 4: // EXC_EMULATION | |
| break; | |
| case 5: // EXC_SOFTWARE | |
| if (exc_code == 0x10003) // EXC_SOFT_SIGNAL | |
| { | |
| if (exc_sub_code == 5) { | |
| // On MacOSX, a SIGTRAP can signify that a process has called exec, | |
| // so we should check with our dynamic loader to verify. | |
| ProcessSP process_sp(thread.GetProcess()); | |
| if (process_sp) { | |
| DynamicLoader *dynamic_loader = process_sp->GetDynamicLoader(); | |
| if (dynamic_loader && dynamic_loader->ProcessDidExec()) { | |
| // The program was re-exec'ed | |
| return StopInfo::CreateStopReasonWithExec(thread); | |
| } | |
| } | |
| } | |
| return StopInfo::CreateStopReasonWithSignal(thread, exc_sub_code); | |
| } | |
| break; | |
| // A mach exception comes with 2-4 pieces of data. | |
| // The sub-codes are only provided for certain types | |
| // of mach exceptions. | |
| // [exc_type, exc_code, exc_sub_code, exc_sub_sub_code] | |
| // | |
| // Here are all of the EXC_BREAKPOINT, exc_type==6, | |
| // exceptions we can receive. | |
| // | |
| // Instruction step: | |
| // [6, 1, 0] | |
| // Intel KDP [6, 3, ??] | |
| // armv7 [6, 0x102, <stop-pc>] Same as software breakpoint! | |
| // | |
| // Software breakpoint: | |
| // x86 [6, 2, 0] | |
| // Intel KDP [6, 2, <bp-addr + 1>] | |
| // arm64 [6, 1, <bp-addr>] | |
| // armv7 [6, 0x102, <bp-addr>] Same as instruction step! | |
| // | |
| // Hardware breakpoint: | |
| // x86 [6, 1, <bp-addr>, 0] | |
| // x86/Rosetta not implemented, see software breakpoint | |
| // arm64 [6, 1, <bp-addr>] | |
| // armv7 not implemented, see software breakpoint | |
| // | |
| // Hardware watchpoint: | |
| // x86 [6, 1, <accessed-addr>, 0] (both Intel hw and Rosetta) | |
| // arm64 [6, 0x102, <accessed-addr>, 0] | |
| // armv7 [6, 0x102, <accessed-addr>, 0] | |
| // | |
| // arm64 BRK instruction (imm arg not reflected in the ME) | |
| // [ 6, 1, <addr-of-BRK-insn>] | |
| // | |
| // In order of codes mach exceptions: | |
| // [6, 1, 0] - instruction step | |
| // [6, 1, <bp-addr>] - hardware breakpoint or watchpoint | |
| // | |
| // [6, 2, 0] - software breakpoint | |
| // [6, 2, <bp-addr + 1>] - software breakpoint | |
| // | |
| // [6, 3] - instruction step | |
| // | |
| // [6, 0x102, <stop-pc>] armv7 instruction step | |
| // [6, 0x102, <bp-addr>] armv7 software breakpoint | |
| // [6, 0x102, <accessed-addr>, 0] arm64/armv7 watchpoint | |
| case 6: // EXC_BREAKPOINT | |
| { | |
| bool stopped_by_hitting_breakpoint = false; | |
| bool stopped_by_completing_stepi = false; | |
| bool stopped_watchpoint = false; | |
| std::optional<addr_t> address; | |
| // exc_code 1 | |
| if (exc_code == 1) { | |
| if (exc_sub_code == 0) { | |
| stopped_by_completing_stepi = true; | |
| } else { | |
| // Ambiguous: could be signalling a | |
| // breakpoint or watchpoint hit. | |
| stopped_by_hitting_breakpoint = true; | |
| stopped_watchpoint = true; | |
| address = exc_sub_code; | |
| } | |
| } | |
| // exc_code 2 | |
| if (exc_code == 2) { | |
| if (exc_sub_code == 0) | |
| stopped_by_hitting_breakpoint = true; | |
| else { | |
| stopped_by_hitting_breakpoint = true; | |
| // Intel KDP software breakpoint | |
| if (!pc_already_adjusted) | |
| pc_decrement = 1; | |
| } | |
| } | |
| // exc_code 3 | |
| if (exc_code == 3) | |
| stopped_by_completing_stepi = true; | |
| // exc_code 0x102 | |
| if (exc_code == 0x102 && exc_sub_code != 0) { | |
| if (cpu == llvm::Triple::arm || cpu == llvm::Triple::thumb) { | |
| stopped_by_hitting_breakpoint = true; | |
| stopped_by_completing_stepi = true; | |
| } | |
| stopped_watchpoint = true; | |
| address = exc_sub_code; | |
| } | |
| // The Mach Exception may have been ambiguous -- | |
| // e.g. we stopped either because of a breakpoint | |
| // or a watchpoint. We'll disambiguate which it | |
| // really was. | |
| if (stopped_by_hitting_breakpoint) { | |
| addr_t pc = reg_ctx_sp->GetPC() - pc_decrement; | |
| if (address) | |
| bp_site_sp = | |
| process_sp->GetBreakpointSiteList().FindByAddress(*address); | |
| if (!bp_site_sp && reg_ctx_sp) { | |
| bp_site_sp = process_sp->GetBreakpointSiteList().FindByAddress(pc); | |
| } | |
| if (bp_site_sp && | |
| process_sp->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) { | |
| // We've hit this breakpoint, whether it was intended for this thread | |
| // or not. Clear this in the Tread object so we step past it on resume. | |
| thread.SetThreadHitBreakpointSite(); | |
| if (bp_site_sp->ValidForThisThread(thread)) { | |
| // Update the PC if we were asked to do so, but only do so if we find | |
| // a breakpoint that we know about because this could be a trap | |
| // instruction in the code. | |
| if (pc_decrement > 0 && adjust_pc_if_needed && reg_ctx_sp) | |
| reg_ctx_sp->SetPC(pc); | |
| return StopInfo::CreateStopReasonWithBreakpointSiteID( | |
| thread, bp_site_sp->GetID()); | |
| } else { | |
| return StopInfoSP(); | |
| } | |
| } | |
| } | |
| // Breakpoint-hit events are handled. | |
| // Now handle watchpoints. | |
| if (stopped_watchpoint && address) { | |
| WatchpointResourceSP wp_rsrc_sp = | |
| target->GetProcessSP()->GetWatchpointResourceList().FindByAddress( | |
| *address); | |
| if (wp_rsrc_sp && wp_rsrc_sp->GetNumberOfConstituents() > 0) { | |
| return StopInfo::CreateStopReasonWithWatchpointID( | |
| thread, wp_rsrc_sp->GetConstituentAtIndex(0)->GetID()); | |
| } | |
| } | |
| // Finally, handle instruction step. | |
| if (stopped_by_completing_stepi) { | |
| if (thread.GetTemporaryResumeState() != eStateStepping) | |
| not_stepping_but_got_singlestep_exception = true; | |
| else | |
| return StopInfo::CreateStopReasonToTrace(thread); | |
| } | |
| } break; | |
| case 7: // EXC_SYSCALL | |
| case 8: // EXC_MACH_SYSCALL | |
| case 9: // EXC_RPC_ALERT | |
| case 10: // EXC_CRASH | |
| break; | |
| } | |
| return std::make_shared<StopInfoMachException>( | |
| thread, exc_type, exc_data_count, exc_code, exc_sub_code, | |
| not_stepping_but_got_singlestep_exception); | |
| } | |
| void StopInfoMachException::PerformAction([[maybe_unused]] Event *event_ptr) { | |
| // This action currently only fires if the exception is an ARM breakpoint | |
| // and if the PC is still at the instruction that caused the exception. | |
| if (!(m_value == 6 /*EXC_BREAKPOINT*/ && | |
| m_exc_code == 1 /*EXC_ARM_BREAKPOINT*/) || | |
| m_exc_subcode != m_thread_wp.lock()->GetRegisterContext()->GetPC()) | |
| return; | |
| SkipOverTrapInstruction(); | |
| } | |
| // Detect an unusual situation on Darwin where: | |
| // | |
| // 0. We did an instruction-step before this. | |
| // 1. We have a hardware breakpoint or watchpoint set. | |
| // 2. We resumed the process, but not with an instruction-step. | |
| // 3. The thread gets an "instruction-step completed" mach exception. | |
| // 4. The pc has not advanced - it is the same as before. | |
| // | |
| // This method returns true for that combination of events. | |
| bool StopInfoMachException::WasContinueInterrupted(Thread &thread) { | |
| Log *log = GetLog(LLDBLog::Step); | |
| // We got an instruction-step completed mach exception but we were not | |
| // doing an instruction step on this thread. | |
| if (!m_not_stepping_but_got_singlestep_exception) | |
| return false; | |
| RegisterContextSP reg_ctx_sp(thread.GetRegisterContext()); | |
| std::optional<addr_t> prev_pc = thread.GetPreviousFrameZeroPC(); | |
| if (!reg_ctx_sp || !prev_pc) | |
| return false; | |
| // The previous pc value and current pc value are the same. | |
| if (*prev_pc != reg_ctx_sp->GetPC()) | |
| return false; | |
| // We have a watchpoint -- this is the kernel bug. | |
| ProcessSP process_sp = thread.GetProcess(); | |
| if (process_sp->GetWatchpointResourceList().GetSize()) { | |
| LLDB_LOGF(log, | |
| "Thread stopped with insn-step completed mach exception but " | |
| "thread was not stepping; there is a hardware watchpoint set."); | |
| return true; | |
| } | |
| // We have a hardware breakpoint -- this is the kernel bug. | |
| auto &bp_site_list = process_sp->GetBreakpointSiteList(); | |
| for (auto &site : bp_site_list.Sites()) { | |
| if (site->IsHardware() && | |
| process_sp->IsBreakpointSitePhysicallyEnabled(*site)) { | |
| LLDB_LOGF(log, | |
| "Thread stopped with insn-step completed mach exception but " | |
| "thread was not stepping; there is a hardware breakpoint set."); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } |