Index: lldb/packages/Python/lldbsuite/test/dotest.py =================================================================== --- lldb/packages/Python/lldbsuite/test/dotest.py +++ lldb/packages/Python/lldbsuite/test/dotest.py @@ -807,6 +807,9 @@ except subprocess.CalledProcessError: pass return False, "security.models.extensions.user_set_dbregs disabled" + elif platform == "freebsd" and configuration.arch == "aarch64": + # TODO: add a version check + return False, "FreeBSD xx.x does not support watchpoints on arm64" return True, "watchpoint support available" def checkWatchpointSupport(): Index: lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp +++ lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp @@ -282,12 +282,15 @@ SetState(StateType::eStateStopped, true); return; case TRAP_TRACE: + LLDB_LOG(log, "SIGTRAP/TRAP_TRACE: si_addr: {0}", + info.pl_siginfo.si_addr); + if (thread) { auto ®ctx = static_cast( thread->GetRegisterContext()); uint32_t wp_index = LLDB_INVALID_INDEX32; - Status error = - regctx.GetWatchpointHitIndex(wp_index, LLDB_INVALID_ADDRESS); + Status error = regctx.GetWatchpointHitIndex( + wp_index, reinterpret_cast(info.pl_siginfo.si_addr)); if (error.Fail()) LLDB_LOG(log, "received error while checking for watchpoint hits, pid = " Index: lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h +++ lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.h @@ -17,6 +17,7 @@ // clang-format on #include "Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD.h" +#include "Plugins/Process/Utility/NativeRegisterContextBreakWatchpoint_arm64.h" #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" #include @@ -26,7 +27,9 @@ class NativeProcessFreeBSD; -class NativeRegisterContextFreeBSD_arm64 : public NativeRegisterContextFreeBSD { +class NativeRegisterContextFreeBSD_arm64 + : public NativeRegisterContextFreeBSD, + public NativeRegisterContextBreakWatchpoint_arm64 { public: NativeRegisterContextFreeBSD_arm64(const ArchSpec &target_arch, NativeThreadProtocol &native_thread); @@ -56,10 +59,15 @@ // and sizes, so we do not have to worry about these (and we have // a unittest to assert that). std::array m_reg_data; + dbreg m_dbreg; + bool m_read_dbreg; Status ReadRegisterSet(uint32_t set); Status WriteRegisterSet(uint32_t set); + llvm::Error ReadHardwareDebugInfo() override; + llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override; + RegisterInfoPOSIX_arm64 &GetRegisterInfo() const; }; Index: lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp =================================================================== --- lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp +++ lldb/source/Plugins/Process/FreeBSD/NativeRegisterContextFreeBSD_arm64.cpp @@ -15,6 +15,7 @@ #include "lldb/Utility/Status.h" #include "Plugins/Process/FreeBSD/NativeProcessFreeBSD.h" +#include "Plugins/Process/POSIX/ProcessPOSIXLog.h" #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" // clang-format off @@ -36,7 +37,8 @@ NativeRegisterContextFreeBSD_arm64::NativeRegisterContextFreeBSD_arm64( const ArchSpec &target_arch, NativeThreadProtocol &native_thread) : NativeRegisterContextRegisterInfo( - native_thread, new RegisterInfoPOSIX_arm64(target_arch)) { + native_thread, new RegisterInfoPOSIX_arm64(target_arch)), + m_read_dbreg(false) { GetRegisterInfo().ConfigureVectorRegisterInfos( RegisterInfoPOSIX_arm64::eVectorQuadwordAArch64); } @@ -206,4 +208,46 @@ return llvm::Error::success(); } +llvm::Error NativeRegisterContextFreeBSD_arm64::ReadHardwareDebugInfo() { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_REGISTERS)); + + // we're fully stateful, so no need to reread control registers ever + if (m_read_dbreg) + return llvm::Error::success(); + + Status res = NativeProcessFreeBSD::PtraceWrapper(PT_GETDBREGS, + m_thread.GetID(), &m_dbreg); + if (res.Fail()) + return res.ToError(); + + LLDB_LOG(log, "m_dbreg read: debug_ver={0}, nbkpts={1}, nwtpts={2}", + m_dbreg.db_debug_ver, m_dbreg.db_nbkpts, m_dbreg.db_nwtpts); + m_max_hbp_supported = m_dbreg.db_nbkpts; + m_max_hwp_supported = m_dbreg.db_nwtpts; + assert(m_max_hbp_supported <= m_hbr_regs.size()); + assert(m_max_hwp_supported <= m_hwp_regs.size()); + + m_read_dbreg = true; + return llvm::Error::success(); +} + +llvm::Error +NativeRegisterContextFreeBSD_arm64::WriteHardwareDebugRegs(DREGType hwbType) { + assert(m_read_dbreg && "dbregs must be read before writing them back"); + + // copy data from m_*_regs to m_dbreg before writing it back + for (uint32_t i = 0; i < m_max_hbp_supported; i++) { + m_dbreg.db_breakregs[i].dbr_addr = m_hbr_regs[i].address; + m_dbreg.db_breakregs[i].dbr_ctrl = m_hbr_regs[i].control; + } + for (uint32_t i = 0; i < m_max_hwp_supported; i++) { + m_dbreg.db_watchregs[i].dbw_addr = m_hwp_regs[i].address; + m_dbreg.db_watchregs[i].dbw_ctrl = m_hwp_regs[i].control; + } + + return NativeProcessFreeBSD::PtraceWrapper(PT_SETDBREGS, m_thread.GetID(), + &m_dbreg) + .ToError(); +} + #endif // defined (__aarch64__) Index: lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -12,6 +12,7 @@ #define lldb_NativeRegisterContextLinux_arm64_h #include "Plugins/Process/Linux/NativeRegisterContextLinux.h" +#include "Plugins/Process/Utility/NativeRegisterContextBreakWatchpoint_arm64.h" #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" #include @@ -21,7 +22,9 @@ class NativeProcessLinux; -class NativeRegisterContextLinux_arm64 : public NativeRegisterContextLinux { +class NativeRegisterContextLinux_arm64 + : public NativeRegisterContextLinux, + public NativeRegisterContextBreakWatchpoint_arm64 { public: NativeRegisterContextLinux_arm64(const ArchSpec &target_arch, NativeThreadProtocol &native_thread); @@ -49,44 +52,7 @@ bool RegisterOffsetIsDynamic() const override { return true; } - // Hardware breakpoints/watchpoint management functions - - uint32_t NumSupportedHardwareBreakpoints() override; - - uint32_t SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override; - - bool ClearHardwareBreakpoint(uint32_t hw_idx) override; - - Status ClearAllHardwareBreakpoints() override; - - Status GetHardwareBreakHitIndex(uint32_t &bp_index, - lldb::addr_t trap_addr) override; - - uint32_t NumSupportedHardwareWatchpoints() override; - - uint32_t SetHardwareWatchpoint(lldb::addr_t addr, size_t size, - uint32_t watch_flags) override; - - bool ClearHardwareWatchpoint(uint32_t hw_index) override; - - Status ClearAllHardwareWatchpoints() override; - - Status GetWatchpointHitIndex(uint32_t &wp_index, - lldb::addr_t trap_addr) override; - - lldb::addr_t GetWatchpointHitAddress(uint32_t wp_index) override; - - lldb::addr_t GetWatchpointAddress(uint32_t wp_index) override; - - uint32_t GetWatchpointSize(uint32_t wp_index); - - bool WatchpointIsEnabled(uint32_t wp_index); - - // Debug register type select - enum DREGType { eDREGTypeWATCH = 0, eDREGTypeBREAK }; - protected: - Status ReadGPR() override; Status WriteGPR() override; @@ -121,21 +87,6 @@ struct user_sve_header m_sve_header; std::vector m_sve_ptrace_payload; - // Debug register info for hardware breakpoints and watchpoints management. - struct DREG { - lldb::addr_t address; // Breakpoint/watchpoint address value. - lldb::addr_t hit_addr; // Address at which last watchpoint trigger exception - // occurred. - lldb::addr_t real_addr; // Address value that should cause target to stop. - uint32_t control; // Breakpoint/watchpoint control value. - uint32_t refcount; // Serves as enable/disable and reference counter. - }; - - struct DREG m_hbr_regs[16]; // Arm native linux hardware breakpoints - struct DREG m_hwp_regs[16]; // Arm native linux hardware watchpoints - - uint32_t m_max_hwp_supported; - uint32_t m_max_hbp_supported; bool m_refresh_hwdebug_info; bool IsGPR(unsigned reg) const; @@ -164,9 +115,9 @@ size_t GetSVEBufferSize() { return m_sve_ptrace_payload.size(); } - Status ReadHardwareDebugInfo(); + llvm::Error ReadHardwareDebugInfo() override; - Status WriteHardwareDebugRegs(int hwbType); + llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override; uint32_t CalculateFprOffset(const RegisterInfo *reg_info) const; Index: lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -457,430 +457,9 @@ return false; } -uint32_t NativeRegisterContextLinux_arm64::NumSupportedHardwareBreakpoints() { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); - - LLDB_LOGF(log, "NativeRegisterContextLinux_arm64::%s()", __FUNCTION__); - - Status error; - - // Read hardware breakpoint and watchpoint information. - error = ReadHardwareDebugInfo(); - - if (error.Fail()) - return 0; - - return m_max_hbp_supported; -} - -uint32_t -NativeRegisterContextLinux_arm64::SetHardwareBreakpoint(lldb::addr_t addr, - size_t size) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); - LLDB_LOG(log, "addr: {0:x}, size: {1:x}", addr, size); - - // Read hardware breakpoint and watchpoint information. - Status error = ReadHardwareDebugInfo(); - - if (error.Fail()) - return LLDB_INVALID_INDEX32; - - uint32_t control_value = 0, bp_index = 0; - - // Check if size has a valid hardware breakpoint length. - if (size != 4) - return LLDB_INVALID_INDEX32; // Invalid size for a AArch64 hardware - // breakpoint - - // Check 4-byte alignment for hardware breakpoint target address. - if (addr & 0x03) - return LLDB_INVALID_INDEX32; // Invalid address, should be 4-byte aligned. - - // Setup control value - control_value = 0; - control_value |= ((1 << size) - 1) << 5; - control_value |= (2 << 1) | 1; - - // Iterate over stored breakpoints and find a free bp_index - bp_index = LLDB_INVALID_INDEX32; - for (uint32_t i = 0; i < m_max_hbp_supported; i++) { - if ((m_hbr_regs[i].control & 1) == 0) { - bp_index = i; // Mark last free slot - } else if (m_hbr_regs[i].address == addr) { - return LLDB_INVALID_INDEX32; // We do not support duplicate breakpoints. - } - } - - if (bp_index == LLDB_INVALID_INDEX32) - return LLDB_INVALID_INDEX32; - - // Update breakpoint in local cache - m_hbr_regs[bp_index].real_addr = addr; - m_hbr_regs[bp_index].address = addr; - m_hbr_regs[bp_index].control = control_value; - - // PTRACE call to set corresponding hardware breakpoint register. - error = WriteHardwareDebugRegs(eDREGTypeBREAK); - - if (error.Fail()) { - m_hbr_regs[bp_index].address = 0; - m_hbr_regs[bp_index].control &= ~1; - - return LLDB_INVALID_INDEX32; - } - - return bp_index; -} - -bool NativeRegisterContextLinux_arm64::ClearHardwareBreakpoint( - uint32_t hw_idx) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); - LLDB_LOG(log, "hw_idx: {0}", hw_idx); - - // Read hardware breakpoint and watchpoint information. - Status error = ReadHardwareDebugInfo(); - - if (error.Fail()) - return false; - - if (hw_idx >= m_max_hbp_supported) - return false; - - // Create a backup we can revert to in case of failure. - lldb::addr_t tempAddr = m_hbr_regs[hw_idx].address; - uint32_t tempControl = m_hbr_regs[hw_idx].control; - - m_hbr_regs[hw_idx].control &= ~1; - m_hbr_regs[hw_idx].address = 0; - - // PTRACE call to clear corresponding hardware breakpoint register. - error = WriteHardwareDebugRegs(eDREGTypeBREAK); - - if (error.Fail()) { - m_hbr_regs[hw_idx].control = tempControl; - m_hbr_regs[hw_idx].address = tempAddr; - - return false; - } - - return true; -} - -Status NativeRegisterContextLinux_arm64::GetHardwareBreakHitIndex( - uint32_t &bp_index, lldb::addr_t trap_addr) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); - - LLDB_LOGF(log, "NativeRegisterContextLinux_arm64::%s()", __FUNCTION__); - - lldb::addr_t break_addr; - - for (bp_index = 0; bp_index < m_max_hbp_supported; ++bp_index) { - break_addr = m_hbr_regs[bp_index].address; - - if ((m_hbr_regs[bp_index].control & 0x1) && (trap_addr == break_addr)) { - m_hbr_regs[bp_index].hit_addr = trap_addr; - return Status(); - } - } - - bp_index = LLDB_INVALID_INDEX32; - return Status(); -} - -Status NativeRegisterContextLinux_arm64::ClearAllHardwareBreakpoints() { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); - - LLDB_LOGF(log, "NativeRegisterContextLinux_arm64::%s()", __FUNCTION__); - - Status error; - - // Read hardware breakpoint and watchpoint information. - error = ReadHardwareDebugInfo(); - - if (error.Fail()) - return error; - - lldb::addr_t tempAddr = 0; - uint32_t tempControl = 0; - - for (uint32_t i = 0; i < m_max_hbp_supported; i++) { - if (m_hbr_regs[i].control & 0x01) { - // Create a backup we can revert to in case of failure. - tempAddr = m_hbr_regs[i].address; - tempControl = m_hbr_regs[i].control; - - // Clear watchpoints in local cache - m_hbr_regs[i].control &= ~1; - m_hbr_regs[i].address = 0; - - // Ptrace call to update hardware debug registers - error = WriteHardwareDebugRegs(eDREGTypeBREAK); - - if (error.Fail()) { - m_hbr_regs[i].control = tempControl; - m_hbr_regs[i].address = tempAddr; - - return error; - } - } - } - - return Status(); -} - -uint32_t NativeRegisterContextLinux_arm64::NumSupportedHardwareWatchpoints() { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); - - // Read hardware breakpoint and watchpoint information. - Status error = ReadHardwareDebugInfo(); - - if (error.Fail()) - return 0; - - LLDB_LOG(log, "{0}", m_max_hwp_supported); - return m_max_hwp_supported; -} - -uint32_t NativeRegisterContextLinux_arm64::SetHardwareWatchpoint( - lldb::addr_t addr, size_t size, uint32_t watch_flags) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); - LLDB_LOG(log, "addr: {0:x}, size: {1:x} watch_flags: {2:x}", addr, size, - watch_flags); - - // Read hardware breakpoint and watchpoint information. - Status error = ReadHardwareDebugInfo(); - - if (error.Fail()) - return LLDB_INVALID_INDEX32; - - uint32_t control_value = 0, wp_index = 0; - lldb::addr_t real_addr = addr; - - // Check if we are setting watchpoint other than read/write/access Also - // update watchpoint flag to match AArch64 write-read bit configuration. - switch (watch_flags) { - case 1: - watch_flags = 2; - break; - case 2: - watch_flags = 1; - break; - case 3: - break; - default: - return LLDB_INVALID_INDEX32; - } - - // Check if size has a valid hardware watchpoint length. - if (size != 1 && size != 2 && size != 4 && size != 8) - return LLDB_INVALID_INDEX32; - - // Check 8-byte alignment for hardware watchpoint target address. Below is a - // hack to recalculate address and size in order to make sure we can watch - // non 8-byte aligned addresses as well. - if (addr & 0x07) { - uint8_t watch_mask = (addr & 0x07) + size; - - if (watch_mask > 0x08) - return LLDB_INVALID_INDEX32; - else if (watch_mask <= 0x02) - size = 2; - else if (watch_mask <= 0x04) - size = 4; - else - size = 8; - - addr = addr & (~0x07); - } - - // Setup control value - control_value = watch_flags << 3; - control_value |= ((1 << size) - 1) << 5; - control_value |= (2 << 1) | 1; - - // Iterate over stored watchpoints and find a free wp_index - wp_index = LLDB_INVALID_INDEX32; - for (uint32_t i = 0; i < m_max_hwp_supported; i++) { - if ((m_hwp_regs[i].control & 1) == 0) { - wp_index = i; // Mark last free slot - } else if (m_hwp_regs[i].address == addr) { - return LLDB_INVALID_INDEX32; // We do not support duplicate watchpoints. - } - } - - if (wp_index == LLDB_INVALID_INDEX32) - return LLDB_INVALID_INDEX32; - - // Update watchpoint in local cache - m_hwp_regs[wp_index].real_addr = real_addr; - m_hwp_regs[wp_index].address = addr; - m_hwp_regs[wp_index].control = control_value; - - // PTRACE call to set corresponding watchpoint register. - error = WriteHardwareDebugRegs(eDREGTypeWATCH); - - if (error.Fail()) { - m_hwp_regs[wp_index].address = 0; - m_hwp_regs[wp_index].control &= ~1; - - return LLDB_INVALID_INDEX32; - } - - return wp_index; -} - -bool NativeRegisterContextLinux_arm64::ClearHardwareWatchpoint( - uint32_t wp_index) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); - LLDB_LOG(log, "wp_index: {0}", wp_index); - - // Read hardware breakpoint and watchpoint information. - Status error = ReadHardwareDebugInfo(); - - if (error.Fail()) - return false; - - if (wp_index >= m_max_hwp_supported) - return false; - - // Create a backup we can revert to in case of failure. - lldb::addr_t tempAddr = m_hwp_regs[wp_index].address; - uint32_t tempControl = m_hwp_regs[wp_index].control; - - // Update watchpoint in local cache - m_hwp_regs[wp_index].control &= ~1; - m_hwp_regs[wp_index].address = 0; - - // Ptrace call to update hardware debug registers - error = WriteHardwareDebugRegs(eDREGTypeWATCH); - - if (error.Fail()) { - m_hwp_regs[wp_index].control = tempControl; - m_hwp_regs[wp_index].address = tempAddr; - - return false; - } - - return true; -} - -Status NativeRegisterContextLinux_arm64::ClearAllHardwareWatchpoints() { - // Read hardware breakpoint and watchpoint information. - Status error = ReadHardwareDebugInfo(); - - if (error.Fail()) - return error; - - lldb::addr_t tempAddr = 0; - uint32_t tempControl = 0; - - for (uint32_t i = 0; i < m_max_hwp_supported; i++) { - if (m_hwp_regs[i].control & 0x01) { - // Create a backup we can revert to in case of failure. - tempAddr = m_hwp_regs[i].address; - tempControl = m_hwp_regs[i].control; - - // Clear watchpoints in local cache - m_hwp_regs[i].control &= ~1; - m_hwp_regs[i].address = 0; - - // Ptrace call to update hardware debug registers - error = WriteHardwareDebugRegs(eDREGTypeWATCH); - - if (error.Fail()) { - m_hwp_regs[i].control = tempControl; - m_hwp_regs[i].address = tempAddr; - - return error; - } - } - } - - return Status(); -} - -uint32_t -NativeRegisterContextLinux_arm64::GetWatchpointSize(uint32_t wp_index) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); - LLDB_LOG(log, "wp_index: {0}", wp_index); - - switch ((m_hwp_regs[wp_index].control >> 5) & 0xff) { - case 0x01: - return 1; - case 0x03: - return 2; - case 0x0f: - return 4; - case 0xff: - return 8; - default: - return 0; - } -} -bool NativeRegisterContextLinux_arm64::WatchpointIsEnabled(uint32_t wp_index) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); - LLDB_LOG(log, "wp_index: {0}", wp_index); - - if ((m_hwp_regs[wp_index].control & 0x1) == 0x1) - return true; - else - return false; -} - -Status NativeRegisterContextLinux_arm64::GetWatchpointHitIndex( - uint32_t &wp_index, lldb::addr_t trap_addr) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); - LLDB_LOG(log, "wp_index: {0}, trap_addr: {1:x}", wp_index, trap_addr); - - uint32_t watch_size; - lldb::addr_t watch_addr; - - for (wp_index = 0; wp_index < m_max_hwp_supported; ++wp_index) { - watch_size = GetWatchpointSize(wp_index); - watch_addr = m_hwp_regs[wp_index].address; - - if (WatchpointIsEnabled(wp_index) && trap_addr >= watch_addr && - trap_addr < watch_addr + watch_size) { - m_hwp_regs[wp_index].hit_addr = trap_addr; - return Status(); - } - } - - wp_index = LLDB_INVALID_INDEX32; - return Status(); -} - -lldb::addr_t -NativeRegisterContextLinux_arm64::GetWatchpointAddress(uint32_t wp_index) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); - LLDB_LOG(log, "wp_index: {0}", wp_index); - - if (wp_index >= m_max_hwp_supported) - return LLDB_INVALID_ADDRESS; - - if (WatchpointIsEnabled(wp_index)) - return m_hwp_regs[wp_index].real_addr; - else - return LLDB_INVALID_ADDRESS; -} - -lldb::addr_t -NativeRegisterContextLinux_arm64::GetWatchpointHitAddress(uint32_t wp_index) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); - LLDB_LOG(log, "wp_index: {0}", wp_index); - - if (wp_index >= m_max_hwp_supported) - return LLDB_INVALID_ADDRESS; - - if (WatchpointIsEnabled(wp_index)) - return m_hwp_regs[wp_index].hit_addr; - else - return LLDB_INVALID_ADDRESS; -} - -Status NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() { +llvm::Error NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() { if (!m_refresh_hwdebug_info) { - return Status(); + return llvm::Error::success(); } ::pid_t tid = m_thread.GetID(); @@ -896,7 +475,7 @@ &ioVec, ioVec.iov_len); if (error.Fail()) - return error; + return error.ToError(); m_max_hwp_supported = dreg_state.dbg_info & 0xff; @@ -905,24 +484,26 @@ &ioVec, ioVec.iov_len); if (error.Fail()) - return error; + return error.ToError(); m_max_hbp_supported = dreg_state.dbg_info & 0xff; m_refresh_hwdebug_info = false; - return error; + return llvm::Error::success(); } -Status NativeRegisterContextLinux_arm64::WriteHardwareDebugRegs(int hwbType) { +llvm::Error +NativeRegisterContextLinux_arm64::WriteHardwareDebugRegs(DREGType hwbType) { struct iovec ioVec; struct user_hwdebug_state dreg_state; - Status error; + int regset; memset(&dreg_state, 0, sizeof(dreg_state)); ioVec.iov_base = &dreg_state; - if (hwbType == eDREGTypeWATCH) { - hwbType = NT_ARM_HW_WATCH; + switch (hwbType) { + case eDREGTypeWATCH: + regset = NT_ARM_HW_WATCH; ioVec.iov_len = sizeof(dreg_state.dbg_info) + sizeof(dreg_state.pad) + (sizeof(dreg_state.dbg_regs[0]) * m_max_hwp_supported); @@ -930,8 +511,9 @@ dreg_state.dbg_regs[i].addr = m_hwp_regs[i].address; dreg_state.dbg_regs[i].ctrl = m_hwp_regs[i].control; } - } else { - hwbType = NT_ARM_HW_BREAK; + break; + case eDREGTypeBREAK: + regset = NT_ARM_HW_BREAK; ioVec.iov_len = sizeof(dreg_state.dbg_info) + sizeof(dreg_state.pad) + (sizeof(dreg_state.dbg_regs[0]) * m_max_hbp_supported); @@ -939,10 +521,12 @@ dreg_state.dbg_regs[i].addr = m_hbr_regs[i].address; dreg_state.dbg_regs[i].ctrl = m_hbr_regs[i].control; } + break; } return NativeProcessLinux::PtraceWrapper(PTRACE_SETREGSET, m_thread.GetID(), - &hwbType, &ioVec, ioVec.iov_len); + ®set, &ioVec, ioVec.iov_len) + .ToError(); } Status NativeRegisterContextLinux_arm64::ReadGPR() { Index: lldb/source/Plugins/Process/Utility/CMakeLists.txt =================================================================== --- lldb/source/Plugins/Process/Utility/CMakeLists.txt +++ lldb/source/Plugins/Process/Utility/CMakeLists.txt @@ -10,6 +10,7 @@ LinuxSignals.cpp MipsLinuxSignals.cpp NativeProcessSoftwareSingleStep.cpp + NativeRegisterContextBreakWatchpoint_arm64.cpp NativeRegisterContextRegisterInfo.cpp NativeRegisterContextWatchpoint_x86.cpp NetBSDSignals.cpp Index: lldb/source/Plugins/Process/Utility/NativeRegisterContextBreakWatchpoint_arm64.h =================================================================== --- /dev/null +++ lldb/source/Plugins/Process/Utility/NativeRegisterContextBreakWatchpoint_arm64.h @@ -0,0 +1,77 @@ +//===-- NativeRegisterContextBreakWatchpoint_arm64.h ------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef lldb_NativeRegisterContextBreakWatchpoint_arm64_h +#define lldb_NativeRegisterContextBreakWatchpoint_arm64_h + +#include "Plugins/Process/Utility/NativeRegisterContextRegisterInfo.h" + +#include + +namespace lldb_private { + +class NativeRegisterContextBreakWatchpoint_arm64 + : public virtual NativeRegisterContextRegisterInfo { +public: + uint32_t NumSupportedHardwareBreakpoints() override; + + uint32_t SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override; + + bool ClearHardwareBreakpoint(uint32_t hw_idx) override; + + Status ClearAllHardwareBreakpoints() override; + + Status GetHardwareBreakHitIndex(uint32_t &bp_index, + lldb::addr_t trap_addr) override; + + uint32_t NumSupportedHardwareWatchpoints() override; + + uint32_t SetHardwareWatchpoint(lldb::addr_t addr, size_t size, + uint32_t watch_flags) override; + + bool ClearHardwareWatchpoint(uint32_t hw_index) override; + + Status ClearAllHardwareWatchpoints() override; + + Status GetWatchpointHitIndex(uint32_t &wp_index, + lldb::addr_t trap_addr) override; + + lldb::addr_t GetWatchpointHitAddress(uint32_t wp_index) override; + + lldb::addr_t GetWatchpointAddress(uint32_t wp_index) override; + + uint32_t GetWatchpointSize(uint32_t wp_index); + + bool WatchpointIsEnabled(uint32_t wp_index); + + // Debug register type select + enum DREGType { eDREGTypeWATCH = 0, eDREGTypeBREAK }; + +protected: + // Debug register info for hardware breakpoints and watchpoints management. + struct DREG { + lldb::addr_t address; // Breakpoint/watchpoint address value. + lldb::addr_t hit_addr; // Address at which last watchpoint trigger exception + // occurred. + lldb::addr_t real_addr; // Address value that should cause target to stop. + uint32_t control; // Breakpoint/watchpoint control value. + }; + + std::array m_hbr_regs; // hardware breakpoints + std::array m_hwp_regs; // hardware watchpoints + + uint32_t m_max_hbp_supported; + uint32_t m_max_hwp_supported; + + virtual llvm::Error ReadHardwareDebugInfo() = 0; + virtual llvm::Error WriteHardwareDebugRegs(DREGType hwbType) = 0; +}; + +} // namespace lldb_private + +#endif // #ifndef lldb_NativeRegisterContextBreakWatchpoint_arm64_h Index: lldb/source/Plugins/Process/Utility/NativeRegisterContextBreakWatchpoint_arm64.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/Process/Utility/NativeRegisterContextBreakWatchpoint_arm64.cpp @@ -0,0 +1,458 @@ +//===-- NativeRegisterContextBreakWatchpoint_arm64.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 "NativeRegisterContextBreakWatchpoint_arm64.h" + +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegisterValue.h" + +using namespace lldb_private; + +uint32_t +NativeRegisterContextBreakWatchpoint_arm64::NumSupportedHardwareBreakpoints() { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); + llvm::Error error = ReadHardwareDebugInfo(); + if (error) { + LLDB_LOG_ERROR(log, std::move(error), + "failed to read debug registers: {0}"); + return 0; + } + + return m_max_hbp_supported; +} + +uint32_t NativeRegisterContextBreakWatchpoint_arm64::SetHardwareBreakpoint( + lldb::addr_t addr, size_t size) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); + LLDB_LOG(log, "addr: {0:x}, size: {1:x}", addr, size); + + // Read hardware breakpoint and watchpoint information. + llvm::Error error = ReadHardwareDebugInfo(); + if (error) { + LLDB_LOG_ERROR( + log, std::move(error), + "unable to set breakpoint: failed to read debug registers: {0}"); + return LLDB_INVALID_INDEX32; + } + + uint32_t control_value = 0, bp_index = 0; + + // Check if size has a valid hardware breakpoint length. + if (size != 4) + return LLDB_INVALID_INDEX32; // Invalid size for a AArch64 hardware + // breakpoint + + // Check 4-byte alignment for hardware breakpoint target address. + if (addr & 0x03) + return LLDB_INVALID_INDEX32; // Invalid address, should be 4-byte aligned. + + // Setup control value + control_value = 0; + control_value |= ((1 << size) - 1) << 5; + control_value |= (2 << 1) | 1; + + // Iterate over stored breakpoints and find a free bp_index + bp_index = LLDB_INVALID_INDEX32; + for (uint32_t i = 0; i < m_max_hbp_supported; i++) { + if ((m_hbr_regs[i].control & 1) == 0) { + bp_index = i; // Mark last free slot + } else if (m_hbr_regs[i].address == addr) { + return LLDB_INVALID_INDEX32; // We do not support duplicate breakpoints. + } + } + + if (bp_index == LLDB_INVALID_INDEX32) + return LLDB_INVALID_INDEX32; + + // Update breakpoint in local cache + m_hbr_regs[bp_index].real_addr = addr; + m_hbr_regs[bp_index].address = addr; + m_hbr_regs[bp_index].control = control_value; + + // PTRACE call to set corresponding hardware breakpoint register. + error = WriteHardwareDebugRegs(eDREGTypeBREAK); + + if (error) { + m_hbr_regs[bp_index].address = 0; + m_hbr_regs[bp_index].control &= ~1; + + LLDB_LOG_ERROR( + log, std::move(error), + "unable to set breakpoint: failed to write debug registers: {0}"); + return LLDB_INVALID_INDEX32; + } + + return bp_index; +} + +bool NativeRegisterContextBreakWatchpoint_arm64::ClearHardwareBreakpoint( + uint32_t hw_idx) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); + LLDB_LOG(log, "hw_idx: {0}", hw_idx); + + // Read hardware breakpoint and watchpoint information. + llvm::Error error = ReadHardwareDebugInfo(); + if (error) { + LLDB_LOG_ERROR( + log, std::move(error), + "unable to clear breakpoint: failed to read debug registers: {0}"); + return false; + } + + if (hw_idx >= m_max_hbp_supported) + return false; + + // Create a backup we can revert to in case of failure. + lldb::addr_t tempAddr = m_hbr_regs[hw_idx].address; + uint32_t tempControl = m_hbr_regs[hw_idx].control; + + m_hbr_regs[hw_idx].control &= ~1; + m_hbr_regs[hw_idx].address = 0; + + // PTRACE call to clear corresponding hardware breakpoint register. + error = WriteHardwareDebugRegs(eDREGTypeBREAK); + + if (error) { + m_hbr_regs[hw_idx].control = tempControl; + m_hbr_regs[hw_idx].address = tempAddr; + + LLDB_LOG_ERROR( + log, std::move(error), + "unable to clear breakpoint: failed to write debug registers: {0}"); + return false; + } + + return true; +} + +Status NativeRegisterContextBreakWatchpoint_arm64::GetHardwareBreakHitIndex( + uint32_t &bp_index, lldb::addr_t trap_addr) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); + + LLDB_LOGF(log, "NativeRegisterContextBreakWatchpoint_arm64::%s()", + __FUNCTION__); + + lldb::addr_t break_addr; + + for (bp_index = 0; bp_index < m_max_hbp_supported; ++bp_index) { + break_addr = m_hbr_regs[bp_index].address; + + if ((m_hbr_regs[bp_index].control & 0x1) && (trap_addr == break_addr)) { + m_hbr_regs[bp_index].hit_addr = trap_addr; + return Status(); + } + } + + bp_index = LLDB_INVALID_INDEX32; + return Status(); +} + +Status +NativeRegisterContextBreakWatchpoint_arm64::ClearAllHardwareBreakpoints() { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); + + LLDB_LOGF(log, "NativeRegisterContextBreakWatchpoint_arm64::%s()", + __FUNCTION__); + + // Read hardware breakpoint and watchpoint information. + llvm::Error error = ReadHardwareDebugInfo(); + if (error) + return Status(std::move(error)); + + lldb::addr_t tempAddr = 0; + uint32_t tempControl = 0; + + for (uint32_t i = 0; i < m_max_hbp_supported; i++) { + if (m_hbr_regs[i].control & 0x01) { + // Create a backup we can revert to in case of failure. + tempAddr = m_hbr_regs[i].address; + tempControl = m_hbr_regs[i].control; + + // Clear watchpoints in local cache + m_hbr_regs[i].control &= ~1; + m_hbr_regs[i].address = 0; + + // Ptrace call to update hardware debug registers + error = WriteHardwareDebugRegs(eDREGTypeBREAK); + + if (error) { + m_hbr_regs[i].control = tempControl; + m_hbr_regs[i].address = tempAddr; + + return Status(std::move(error)); + } + } + } + + return Status(); +} + +uint32_t +NativeRegisterContextBreakWatchpoint_arm64::NumSupportedHardwareWatchpoints() { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); + llvm::Error error = ReadHardwareDebugInfo(); + if (error) { + LLDB_LOG_ERROR(log, std::move(error), + "failed to read debug registers: {0}"); + return 0; + } + + return m_max_hwp_supported; +} + +uint32_t NativeRegisterContextBreakWatchpoint_arm64::SetHardwareWatchpoint( + lldb::addr_t addr, size_t size, uint32_t watch_flags) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); + LLDB_LOG(log, "addr: {0:x}, size: {1:x} watch_flags: {2:x}", addr, size, + watch_flags); + + // Read hardware breakpoint and watchpoint information. + llvm::Error error = ReadHardwareDebugInfo(); + if (error) { + LLDB_LOG_ERROR( + log, std::move(error), + "unable to set watchpoint: failed to read debug registers: {0}"); + return LLDB_INVALID_INDEX32; + } + + uint32_t control_value = 0, wp_index = 0; + lldb::addr_t real_addr = addr; + + // Check if we are setting watchpoint other than read/write/access Also + // update watchpoint flag to match AArch64 write-read bit configuration. + switch (watch_flags) { + case 1: + watch_flags = 2; + break; + case 2: + watch_flags = 1; + break; + case 3: + break; + default: + return LLDB_INVALID_INDEX32; + } + + // Check if size has a valid hardware watchpoint length. + if (size != 1 && size != 2 && size != 4 && size != 8) + return LLDB_INVALID_INDEX32; + + // Check 8-byte alignment for hardware watchpoint target address. Below is a + // hack to recalculate address and size in order to make sure we can watch + // non 8-byte aligned addresses as well. + if (addr & 0x07) { + uint8_t watch_mask = (addr & 0x07) + size; + + if (watch_mask > 0x08) + return LLDB_INVALID_INDEX32; + else if (watch_mask <= 0x02) + size = 2; + else if (watch_mask <= 0x04) + size = 4; + else + size = 8; + + addr = addr & (~0x07); + } + + // Setup control value + control_value = watch_flags << 3; + control_value |= ((1 << size) - 1) << 5; + control_value |= (2 << 1) | 1; + + // Iterate over stored watchpoints and find a free wp_index + wp_index = LLDB_INVALID_INDEX32; + for (uint32_t i = 0; i < m_max_hwp_supported; i++) { + if ((m_hwp_regs[i].control & 1) == 0) { + wp_index = i; // Mark last free slot + } else if (m_hwp_regs[i].address == addr) { + return LLDB_INVALID_INDEX32; // We do not support duplicate watchpoints. + } + } + + if (wp_index == LLDB_INVALID_INDEX32) + return LLDB_INVALID_INDEX32; + + // Update watchpoint in local cache + m_hwp_regs[wp_index].real_addr = real_addr; + m_hwp_regs[wp_index].address = addr; + m_hwp_regs[wp_index].control = control_value; + + // PTRACE call to set corresponding watchpoint register. + error = WriteHardwareDebugRegs(eDREGTypeWATCH); + + if (error) { + m_hwp_regs[wp_index].address = 0; + m_hwp_regs[wp_index].control &= ~1; + + LLDB_LOG_ERROR( + log, std::move(error), + "unable to set watchpoint: failed to write debug registers: {0}"); + return LLDB_INVALID_INDEX32; + } + + return wp_index; +} + +bool NativeRegisterContextBreakWatchpoint_arm64::ClearHardwareWatchpoint( + uint32_t wp_index) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); + LLDB_LOG(log, "wp_index: {0}", wp_index); + + // Read hardware breakpoint and watchpoint information. + llvm::Error error = ReadHardwareDebugInfo(); + if (error) { + LLDB_LOG_ERROR( + log, std::move(error), + "unable to clear watchpoint: failed to read debug registers: {0}"); + return false; + } + + if (wp_index >= m_max_hwp_supported) + return false; + + // Create a backup we can revert to in case of failure. + lldb::addr_t tempAddr = m_hwp_regs[wp_index].address; + uint32_t tempControl = m_hwp_regs[wp_index].control; + + // Update watchpoint in local cache + m_hwp_regs[wp_index].control &= ~1; + m_hwp_regs[wp_index].address = 0; + + // Ptrace call to update hardware debug registers + error = WriteHardwareDebugRegs(eDREGTypeWATCH); + + if (error) { + m_hwp_regs[wp_index].control = tempControl; + m_hwp_regs[wp_index].address = tempAddr; + + LLDB_LOG_ERROR( + log, std::move(error), + "unable to clear watchpoint: failed to write debug registers: {0}"); + return false; + } + + return true; +} + +Status +NativeRegisterContextBreakWatchpoint_arm64::ClearAllHardwareWatchpoints() { + // Read hardware breakpoint and watchpoint information. + llvm::Error error = ReadHardwareDebugInfo(); + if (error) + return Status(std::move(error)); + + lldb::addr_t tempAddr = 0; + uint32_t tempControl = 0; + + for (uint32_t i = 0; i < m_max_hwp_supported; i++) { + if (m_hwp_regs[i].control & 0x01) { + // Create a backup we can revert to in case of failure. + tempAddr = m_hwp_regs[i].address; + tempControl = m_hwp_regs[i].control; + + // Clear watchpoints in local cache + m_hwp_regs[i].control &= ~1; + m_hwp_regs[i].address = 0; + + // Ptrace call to update hardware debug registers + error = WriteHardwareDebugRegs(eDREGTypeWATCH); + + if (error) { + m_hwp_regs[i].control = tempControl; + m_hwp_regs[i].address = tempAddr; + + return Status(std::move(error)); + } + } + } + + return Status(); +} + +uint32_t NativeRegisterContextBreakWatchpoint_arm64::GetWatchpointSize( + uint32_t wp_index) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); + LLDB_LOG(log, "wp_index: {0}", wp_index); + + switch ((m_hwp_regs[wp_index].control >> 5) & 0xff) { + case 0x01: + return 1; + case 0x03: + return 2; + case 0x0f: + return 4; + case 0xff: + return 8; + default: + return 0; + } +} + +bool NativeRegisterContextBreakWatchpoint_arm64::WatchpointIsEnabled( + uint32_t wp_index) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); + LLDB_LOG(log, "wp_index: {0}", wp_index); + + if ((m_hwp_regs[wp_index].control & 0x1) == 0x1) + return true; + else + return false; +} + +Status NativeRegisterContextBreakWatchpoint_arm64::GetWatchpointHitIndex( + uint32_t &wp_index, lldb::addr_t trap_addr) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); + LLDB_LOG(log, "wp_index: {0}, trap_addr: {1:x}", wp_index, trap_addr); + + uint32_t watch_size; + lldb::addr_t watch_addr; + + for (wp_index = 0; wp_index < m_max_hwp_supported; ++wp_index) { + watch_size = GetWatchpointSize(wp_index); + watch_addr = m_hwp_regs[wp_index].address; + + if (WatchpointIsEnabled(wp_index) && trap_addr >= watch_addr && + trap_addr < watch_addr + watch_size) { + m_hwp_regs[wp_index].hit_addr = trap_addr; + return Status(); + } + } + + wp_index = LLDB_INVALID_INDEX32; + return Status(); +} + +lldb::addr_t NativeRegisterContextBreakWatchpoint_arm64::GetWatchpointAddress( + uint32_t wp_index) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); + LLDB_LOG(log, "wp_index: {0}", wp_index); + + if (wp_index >= m_max_hwp_supported) + return LLDB_INVALID_ADDRESS; + + if (WatchpointIsEnabled(wp_index)) + return m_hwp_regs[wp_index].real_addr; + else + return LLDB_INVALID_ADDRESS; +} + +lldb::addr_t +NativeRegisterContextBreakWatchpoint_arm64::GetWatchpointHitAddress( + uint32_t wp_index) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_WATCHPOINTS)); + LLDB_LOG(log, "wp_index: {0}", wp_index); + + if (wp_index >= m_max_hwp_supported) + return LLDB_INVALID_ADDRESS; + + if (WatchpointIsEnabled(wp_index)) + return m_hwp_regs[wp_index].hit_addr; + else + return LLDB_INVALID_ADDRESS; +}