Index: include/lldb/Host/common/NativeBreakpointList.h =================================================================== --- include/lldb/Host/common/NativeBreakpointList.h +++ include/lldb/Host/common/NativeBreakpointList.h @@ -19,6 +19,14 @@ #include namespace lldb_private { + +struct HardwareBreakpoint { + lldb::addr_t m_addr; + size_t m_size; +}; + +using HardwareBreakpointMap = std::map; + class NativeBreakpointList { public: typedef std::function> + GetHardwareDebugSupportInfo() const; virtual Error SetWatchpoint(lldb::addr_t addr, size_t size, uint32_t watch_flags, bool hardware); @@ -305,6 +315,7 @@ std::vector m_delegates; NativeBreakpointList m_breakpoint_list; NativeWatchpointList m_watchpoint_list; + HardwareBreakpointMap m_hw_breakpoints_map; int m_terminal_fd; uint32_t m_stop_id; Index: include/lldb/Host/common/NativeRegisterContext.h =================================================================== --- include/lldb/Host/common/NativeRegisterContext.h +++ include/lldb/Host/common/NativeRegisterContext.h @@ -75,6 +75,11 @@ virtual bool ClearHardwareBreakpoint(uint32_t hw_idx); + virtual Error ClearAllHardwareBreakpoints(); + + virtual Error GetHardwareBreakHitIndex(uint32_t &bp_index, + lldb::addr_t trap_addr); + virtual uint32_t NumSupportedHardwareWatchpoints(); virtual uint32_t SetHardwareWatchpoint(lldb::addr_t addr, size_t size, Index: include/lldb/Host/common/NativeThreadProtocol.h =================================================================== --- include/lldb/Host/common/NativeThreadProtocol.h +++ include/lldb/Host/common/NativeThreadProtocol.h @@ -56,6 +56,13 @@ virtual Error RemoveWatchpoint(lldb::addr_t addr) = 0; + // --------------------------------------------------------------------- + // Thread-specific Hardware Breakpoint routines + // --------------------------------------------------------------------- + virtual Error SetHardwareBreakpoint(lldb::addr_t addr, size_t size) = 0; + + virtual Error RemoveHardwareBreakpoint(lldb::addr_t addr) = 0; + protected: NativeProcessProtocolWP m_process_wp; lldb::tid_t m_tid; Index: packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/Makefile =================================================================== --- packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/Makefile +++ packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/Makefile @@ -0,0 +1,6 @@ +LEVEL = ../../../../make + +ENABLE_THREADS := YES +CXX_SOURCES := main.cpp + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/TestHWBreakMultiThread.py =================================================================== --- packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/TestHWBreakMultiThread.py +++ packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/TestHWBreakMultiThread.py @@ -0,0 +1,108 @@ +""" +Test hardware breakpoints for multiple threads. +""" + +from __future__ import print_function + + +import os +import time +import re +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +# Hardware breakpoints are supported only by platforms mentioned in oslist. +@skipUnlessPlatform(oslist=['linux']) +class HardwareBreakpointMultiThreadTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + mydir = TestBase.compute_mydir(__file__) + + # LLDB supports hardware breakpoints for arm and aarch64 architectures. + @skipIf(archs=no_match(['arm', 'aarch64'])) + def test_hw_break_set_delete_multi_thread(self): + self.build() + self.setTearDownCleanup() + self.break_multi_thread('delete') + + # LLDB supports hardware breakpoints for arm and aarch64 architectures. + @skipIf(archs=no_match(['arm', 'aarch64'])) + def test_hw_break_set_disable_multi_thread(self): + self.build() + self.setTearDownCleanup() + self.break_multi_thread('disable') + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Our simple source filename. + self.source = 'main.cpp' + # Find the line number to break inside main(). + self.first_stop = line_number( + self.source, 'Starting thread creation with hardware breakpoint set') + + def break_multi_thread(self, removal_type): + """Test that lldb hardware breakpoints work for multiple threads.""" + self.runCmd( + "file %s" % + os.path.join( + os.getcwd(), + 'a.out'), + CURRENT_EXECUTABLE_SET) + + # Stop in main before creating any threads. + lldbutil.run_break_set_by_file_and_line( + self, None, self.first_stop, num_expected_locations=1) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # We should be stopped again due to the breakpoint. + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', + 'stop reason = breakpoint']) + + # Now set a hardware breakpoint in thread function. + self.expect("breakpoint set -b hw_break_function --hardware", + substrs=[ + 'Breakpoint', + 'hw_break_function', + 'address = 0x']) + + # We should stop in hw_break_function function for 4 threads. + count = 0 + + while count < 4 : + + self.runCmd("process continue") + + # We should be stopped in hw_break_function + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=[ + 'stop reason = breakpoint', + 'hw_break_function']) + + # Continue the loop and test that we are stopped 4 times. + count += 1 + + if removal_type == 'delete': + self.runCmd("settings set auto-confirm true") + + # Now 'breakpoint delete' should just work fine without confirmation + # prompt from the command interpreter. + self.expect("breakpoint delete", + startstr="All breakpoints removed") + + # Restore the original setting of auto-confirm. + self.runCmd("settings clear auto-confirm") + + elif removal_type == 'disable': + self.expect("breakpoint disable", + startstr="All breakpoints disabled.") + + # Continue. Program should exit without stopping anywhere. + self.runCmd("process continue") Index: packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/main.cpp =================================================================== --- packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/main.cpp +++ packages/Python/lldbsuite/test/functionalities/breakpoint/hardware_breakpoints/hardware_breakpoint_on_multiple_threads/main.cpp @@ -0,0 +1,45 @@ +//===-- main.cpp ------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include + +#define NUM_OF_THREADS 8 + +void +hw_break_function (uint32_t thread_index) { + printf ("%s called by Thread #%u...\n", __FUNCTION__, thread_index); +} + + +void +thread_func (uint32_t thread_index) { + printf ("%s (thread index = %u) starting...\n", __FUNCTION__, thread_index); + + hw_break_function(thread_index); // Call hw_break_function +} + + +int main (int argc, char const *argv[]) +{ + std::thread threads[NUM_OF_THREADS]; + + printf ("Starting thread creation with hardware breakpoint set...\n"); + + for (auto &thread : threads) + thread = std::thread{thread_func, std::distance(threads, &thread)}; + + for (auto &thread : threads) + thread.join(); + + return 0; +} Index: packages/Python/lldbsuite/test/tools/lldb-server/TestLldbGdbServer.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-server/TestLldbGdbServer.py +++ packages/Python/lldbsuite/test/tools/lldb-server/TestLldbGdbServer.py @@ -1082,7 +1082,7 @@ self.set_inferior_startup_launch() self.qMemoryRegionInfo_reports_heap_address_as_readable_writeable() - def software_breakpoint_set_and_remove_work(self): + def breakpoint_set_and_remove_work(self, want_hardware=False): # Start up the inferior. procs = self.prep_debug_monitor_and_inferior( inferior_args=[ @@ -1126,15 +1126,27 @@ self.assertIsNotNone(context.get("function_address")) function_address = int(context.get("function_address"), 16) + # Get current target architecture + target_arch = self.getArchitecture() + # Set the breakpoint. - if self.getArchitecture() == "arm": + if (target_arch == "arm") or (target_arch == "aarch64"): # TODO: Handle case when setting breakpoint in thumb code BREAKPOINT_KIND = 4 else: BREAKPOINT_KIND = 1 + + # Set default packet type to Z0 (software breakpoint) + z_packet_type = 0 + + # If hardware breakpoint is requested set packet type to Z1 + if want_hardware == True: + z_packet_type = 1 + self.reset_test_sequence() self.add_set_breakpoint_packets( function_address, + z_packet_type, do_continue=True, breakpoint_kind=BREAKPOINT_KIND) @@ -1182,13 +1194,15 @@ # Verify that a breakpoint remove and continue gets us the expected # output. self.reset_test_sequence() + + # Add breakpoint remove packets + self.add_remove_breakpoint_packets( + function_address, + z_packet_type, + breakpoint_kind=BREAKPOINT_KIND) + self.test_sequence.add_log_lines( [ - # Remove the breakpoint. - "read packet: $z0,{0:x},{1}#00".format( - function_address, BREAKPOINT_KIND), - # Verify the stub could unset it. - "send packet: $OK#00", # Continue running. "read packet: $c#63", # We should now receive the output from the call. @@ -1209,7 +1223,7 @@ else: self.build() self.set_inferior_startup_launch() - self.software_breakpoint_set_and_remove_work() + self.breakpoint_set_and_remove_work(want_hardware=False) @llgs_test @expectedFlakeyLinux("llvm.org/pr25652") @@ -1221,7 +1235,33 @@ else: self.build() self.set_inferior_startup_launch() - self.software_breakpoint_set_and_remove_work() + self.breakpoint_set_and_remove_work(want_hardware=False) + + @debugserver_test + @skipUnlessPlatform(oslist=['linux']) + @skipIf(archs=no_match(['arm', 'aarch64'])) + def test_hardware_breakpoint_set_and_remove_work_debugserver(self): + self.init_debugserver_test() + if self.getArchitecture() == "arm": + # TODO: Handle case when setting breakpoint in thumb code + self.build(dictionary={'CFLAGS_EXTRAS': '-marm'}) + else: + self.build() + self.set_inferior_startup_launch() + self.breakpoint_set_and_remove_work(want_hardware=True) + + @llgs_test + @skipUnlessPlatform(oslist=['linux']) + @skipIf(archs=no_match(['arm', 'aarch64'])) + def test_hardware_breakpoint_set_and_remove_work_llgs(self): + self.init_llgs_test() + if self.getArchitecture() == "arm": + # TODO: Handle case when setting breakpoint in thumb code + self.build(dictionary={'CFLAGS_EXTRAS': '-marm'}) + else: + self.build() + self.set_inferior_startup_launch() + self.breakpoint_set_and_remove_work(want_hardware=True) def qSupported_returns_known_stub_features(self): # Start up the stub and start/prep the inferior. Index: packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py =================================================================== --- packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -883,12 +883,13 @@ def add_set_breakpoint_packets( self, address, + z_packet_type=0, do_continue=True, breakpoint_kind=1): self.test_sequence.add_log_lines( [ # Set the breakpoint. - "read packet: $Z0,{0:x},{1}#00".format( - address, breakpoint_kind), + "read packet: $Z{2},{0:x},{1}#00".format( + address, breakpoint_kind, z_packet_type), # Verify the stub could set it. "send packet: $OK#00", ], True) @@ -904,11 +905,15 @@ 2: "stop_thread_id"}}, ], True) - def add_remove_breakpoint_packets(self, address, breakpoint_kind=1): + def add_remove_breakpoint_packets( + self, + address, + z_packet_type=0, + breakpoint_kind=1): self.test_sequence.add_log_lines( [ # Remove the breakpoint. - "read packet: $z0,{0:x},{1}#00".format( - address, breakpoint_kind), + "read packet: $z{2},{0:x},{1}#00".format( + address, breakpoint_kind, z_packet_type), # Verify the stub could unset it. "send packet: $OK#00", ], True) Index: source/Host/common/NativeProcessProtocol.cpp =================================================================== --- source/Host/common/NativeProcessProtocol.cpp +++ source/Host/common/NativeProcessProtocol.cpp @@ -139,11 +139,8 @@ return m_watchpoint_list.GetWatchpointMap(); } -uint32_t NativeProcessProtocol::GetMaxWatchpoints() const { - // This default implementation will return the number of - // *hardware* breakpoints available. MacOSX and other OS - // implementations that support software breakpoints will want to - // override this correctly for their implementation. +llvm::Optional> +NativeProcessProtocol::GetHardwareDebugSupportInfo() const { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); // get any thread @@ -154,7 +151,7 @@ log->Warning("NativeProcessProtocol::%s (): failed to find a thread to " "grab a NativeRegisterContext!", __FUNCTION__); - return 0; + return llvm::None; } NativeRegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext()); @@ -163,10 +160,11 @@ log->Warning("NativeProcessProtocol::%s (): failed to get a " "RegisterContextNativeProcess from the first thread!", __FUNCTION__); - return 0; + return llvm::None; } - return reg_ctx_sp->NumSupportedHardwareWatchpoints(); + return std::make_pair(reg_ctx_sp->NumSupportedHardwareBreakpoints(), + reg_ctx_sp->NumSupportedHardwareWatchpoints()); } Error NativeProcessProtocol::SetWatchpoint(lldb::addr_t addr, size_t size, @@ -263,6 +261,92 @@ return overall_error.Fail() ? overall_error : error; } +const HardwareBreakpointMap & +NativeProcessProtocol::GetHardwareBreakpointMap() const { + return m_hw_breakpoints_map; +} + +Error NativeProcessProtocol::SetHardwareBreakpoint(lldb::addr_t addr, + size_t size) { + // This default implementation assumes setting a hardware breakpoint for + // this process will require setting same hardware breakpoint for each + // of its existing threads. New thread will do the same once created. + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Update the thread list + UpdateThreads(); + + // Exit here if target does not have required hardware breakpoint capability. + auto hw_debug_cap = GetHardwareDebugSupportInfo(); + + if (hw_debug_cap == llvm::None || hw_debug_cap->first == 0 || + hw_debug_cap->first <= m_hw_breakpoints_map.size()) + return Error("Target does not have required no of hardware breakpoints"); + + // Vector below stores all thread pointer for which we have we successfully + // set this hardware breakpoint. If any of the current process threads fails + // to set this hardware breakpoint then roll back and remove this breakpoint + // for all the threads that had already set it successfully. + std::vector breakpoint_established_threads; + + // Request to set a hardware breakpoint for each of current process threads. + std::lock_guard guard(m_threads_mutex); + for (auto thread_sp : m_threads) { + assert(thread_sp && "thread list should not have a NULL thread!"); + if (!thread_sp) + continue; + + Error thread_error = thread_sp->SetHardwareBreakpoint(addr, size); + if (thread_error.Success()) { + // Remember that we set this breakpoint successfully in + // case we need to clear it later. + breakpoint_established_threads.push_back(thread_sp); + } else { + // Unset the breakpoint for each thread we successfully + // set so that we get back to a consistent state of "not + // set" for this hardware breakpoint. + for (auto rollback_thread_sp : breakpoint_established_threads) { + Error remove_error = rollback_thread_sp->RemoveHardwareBreakpoint(addr); + if (remove_error.Fail() && log) { + log->Warning("NativeProcessProtocol::%s (): RemoveHardwareBreakpoint" + " failed for pid=%" PRIu64 ", tid=%" PRIu64 ": %s", + __FUNCTION__, GetID(), rollback_thread_sp->GetID(), + remove_error.AsCString()); + } + } + + return thread_error; + } + } + + // Register new hardware breakpoint into hardware breakpoints map of current + // process. + m_hw_breakpoints_map[addr] = {addr, size}; + + return Error(); +} + +Error NativeProcessProtocol::RemoveHardwareBreakpoint(lldb::addr_t addr) { + // Update the thread list + UpdateThreads(); + + Error error; + + std::lock_guard guard(m_threads_mutex); + for (auto thread_sp : m_threads) { + assert(thread_sp && "thread list should not have a NULL thread!"); + if (!thread_sp) + continue; + + error = thread_sp->RemoveHardwareBreakpoint(addr); + } + + // Also remove from hardware breakpoint map of current process. + m_hw_breakpoints_map.erase(addr); + + return error; +} + bool NativeProcessProtocol::RegisterNativeDelegate( NativeDelegate &native_delegate) { std::lock_guard guard(m_delegates_mutex); @@ -339,8 +423,12 @@ }); } -Error NativeProcessProtocol::RemoveBreakpoint(lldb::addr_t addr) { - return m_breakpoint_list.DecRef(addr); +Error NativeProcessProtocol::RemoveBreakpoint(lldb::addr_t addr, + bool hardware) { + if (hardware) + return RemoveHardwareBreakpoint(addr); + else + return m_breakpoint_list.DecRef(addr); } Error NativeProcessProtocol::EnableBreakpoint(lldb::addr_t addr) { Index: source/Host/common/NativeRegisterContext.cpp =================================================================== --- source/Host/common/NativeRegisterContext.cpp +++ source/Host/common/NativeRegisterContext.cpp @@ -246,10 +246,20 @@ return LLDB_INVALID_INDEX32; } +Error NativeRegisterContext::ClearAllHardwareBreakpoints() { + return Error("not implemented"); +} + bool NativeRegisterContext::ClearHardwareBreakpoint(uint32_t hw_idx) { return false; } +Error NativeRegisterContext::GetHardwareBreakHitIndex(uint32_t &bp_index, + lldb::addr_t trap_addr) { + bp_index = LLDB_INVALID_INDEX32; + return Error("not implemented"); +} + uint32_t NativeRegisterContext::NumSupportedHardwareWatchpoints() { return 0; } uint32_t NativeRegisterContext::SetHardwareWatchpoint(lldb::addr_t addr, Index: source/Plugins/Process/Linux/NativeProcessLinux.h =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.h +++ source/Plugins/Process/Linux/NativeProcessLinux.h @@ -86,6 +86,8 @@ Error SetBreakpoint(lldb::addr_t addr, uint32_t size, bool hardware) override; + Error RemoveBreakpoint(lldb::addr_t addr, bool hardware = false) override; + void DoStopIDBumped(uint32_t newBumpId) override; Error GetLoadedModuleFileSpec(const char *module_path, Index: source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -870,6 +870,19 @@ break; } + // If a breakpoint was hit, report it + uint32_t bp_index; + error = thread.GetRegisterContext()->GetHardwareBreakHitIndex( + bp_index, (uintptr_t)info.si_addr); + if (error.Fail()) + LLDB_LOG(log, "received error while checking for hardware " + "breakpoint hits, pid = {0}, error = {1}", + thread.GetID(), error); + if (bp_index != LLDB_INVALID_INDEX32) { + MonitorBreakpoint(thread); + break; + } + // Otherwise, report step over MonitorTrace(thread); break; @@ -1719,11 +1732,18 @@ Error NativeProcessLinux::SetBreakpoint(lldb::addr_t addr, uint32_t size, bool hardware) { if (hardware) - return Error("NativeProcessLinux does not support hardware breakpoints"); + return SetHardwareBreakpoint(addr, size); else return SetSoftwareBreakpoint(addr, size); } +Error NativeProcessLinux::RemoveBreakpoint(lldb::addr_t addr, bool hardware) { + if (hardware) + return RemoveHardwareBreakpoint(addr); + else + return NativeProcessProtocol::RemoveBreakpoint(addr); +} + Error NativeProcessLinux::GetSoftwareBreakpointTrapOpcode( size_t trap_opcode_size_hint, size_t &actual_opcode_size, const uint8_t *&trap_opcode_bytes) { Index: source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h =================================================================== --- source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h +++ source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.h @@ -46,10 +46,17 @@ // Hardware breakpoints/watchpoint mangement functions //------------------------------------------------------------------ + uint32_t NumSupportedHardwareBreakpoints() override; + uint32_t SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override; bool ClearHardwareBreakpoint(uint32_t hw_idx) override; + Error ClearAllHardwareBreakpoints() override; + + Error 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, Index: source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp =================================================================== --- source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp +++ source/Plugins/Process/Linux/NativeRegisterContextLinux_arm.cpp @@ -130,6 +130,7 @@ ::memset(&m_fpr, 0, sizeof(m_fpr)); ::memset(&m_gpr_arm, 0, sizeof(m_gpr_arm)); ::memset(&m_hwp_regs, 0, sizeof(m_hwp_regs)); + ::memset(&m_hbr_regs, 0, sizeof(m_hbr_regs)); // 16 is just a maximum value, query hardware for actual watchpoint count m_max_hwp_supported = 16; @@ -354,10 +355,28 @@ return (m_reg_info.first_fpr <= reg && reg <= m_reg_info.last_fpr); } +uint32_t NativeRegisterContextLinux_arm::NumSupportedHardwareBreakpoints() { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); + + if (log) + log->Printf("NativeRegisterContextLinux_arm::%s()", __FUNCTION__); + + Error error; + + // Read hardware breakpoint and watchpoint information. + error = ReadHardwareDebugInfo(); + + if (error.Fail()) + return 0; + + LLDB_LOG(log, "{0}", m_max_hbp_supported); + return m_max_hbp_supported; +} + uint32_t NativeRegisterContextLinux_arm::SetHardwareBreakpoint(lldb::addr_t addr, size_t size) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); LLDB_LOG(log, "addr: {0:x}, size: {1:x}", addr, size); // Read hardware breakpoint and watchpoint information. @@ -368,71 +387,58 @@ uint32_t control_value = 0, bp_index = 0; - // Check if size has a valid hardware breakpoint length. - // Thumb instructions are 2-bytes but we have no way here to determine - // if target address is a thumb or arm instruction. - // TODO: Add support for setting thumb mode hardware breakpoints - if (size != 4 && size != 2) + // Setup address and control values. + // Use size to get a hint of arm vs thumb modes. + switch (size) { + case 2: + control_value = (0x3 << 5) | 7; + addr &= ~1; + break; + case 4: + control_value = (0xfu << 5) | 7; + addr &= ~3; + break; + default: return LLDB_INVALID_INDEX32; + } - // Setup control value - // Make the byte_mask into a valid Byte Address Select mask - control_value = 0xfu << 5; - - // Enable this breakpoint and make it stop in privileged or user mode; - control_value |= 7; - - // Make sure bits 1:0 are clear in our address - // This should be different once we support thumb here. - addr &= ~((lldb::addr_t)3); - - // Iterate over stored hardware breakpoints - // Find a free bp_index or update reference count if duplicate. + // 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 && - m_hbr_regs[i].control == control_value) { - bp_index = i; // Mark duplicate index - break; // Stop searching here + } 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; - // Add new or update existing breakpoint - if ((m_hbr_regs[bp_index].control & 1) == 0) { - m_hbr_regs[bp_index].address = addr; - m_hbr_regs[bp_index].control = control_value; - m_hbr_regs[bp_index].refcount = 1; - - // PTRACE call to set corresponding hardware breakpoint register. - error = WriteHardwareDebugRegs(eDREGTypeBREAK, bp_index); - - if (error.Fail()) { - m_hbr_regs[bp_index].address = 0; - m_hbr_regs[bp_index].control &= ~1; - m_hbr_regs[bp_index].refcount = 0; + // 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; - return LLDB_INVALID_INDEX32; - } - } else - m_hbr_regs[bp_index].refcount++; + // PTRACE call to set corresponding hardware breakpoint register. + error = WriteHardwareDebugRegs(eDREGTypeBREAK, bp_index); + + 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_arm::ClearHardwareBreakpoint(uint32_t hw_idx) { - Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); LLDB_LOG(log, "hw_idx: {0}", hw_idx); - Error error; - // Read hardware breakpoint and watchpoint information. - error = ReadHardwareDebugInfo(); + Error error = ReadHardwareDebugInfo(); if (error.Fail()) return false; @@ -440,35 +446,88 @@ if (hw_idx >= m_max_hbp_supported) return false; - // Update reference count if multiple references. - if (m_hbr_regs[hw_idx].refcount > 1) { - m_hbr_regs[hw_idx].refcount--; - return true; - } else if (m_hbr_regs[hw_idx].refcount == 1) { - // 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; - uint32_t tempRefCount = m_hbr_regs[hw_idx].refcount; - - m_hbr_regs[hw_idx].control &= ~1; - m_hbr_regs[hw_idx].address = 0; - m_hbr_regs[hw_idx].refcount = 0; - - // PTRACE call to clear corresponding hardware breakpoint register. - WriteHardwareDebugRegs(eDREGTypeBREAK, hw_idx); - - if (error.Fail()) { - m_hbr_regs[hw_idx].control = tempControl; - m_hbr_regs[hw_idx].address = tempAddr; - m_hbr_regs[hw_idx].refcount = tempRefCount; + // 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, hw_idx); + + if (error.Fail()) { + m_hbr_regs[hw_idx].control = tempControl; + m_hbr_regs[hw_idx].address = tempAddr; + + return false; + } + + return true; +} + +Error NativeRegisterContextLinux_arm::GetHardwareBreakHitIndex( + uint32_t &bp_index, lldb::addr_t trap_addr) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); + + if (log) + log->Printf("NativeRegisterContextLinux_arm64::%s()", __FUNCTION__); - return false; + 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 Error(); } + } - return true; + bp_index = LLDB_INVALID_INDEX32; + return Error(); +} + +Error NativeRegisterContextLinux_arm::ClearAllHardwareBreakpoints() { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); + + if (log) + log->Printf("NativeRegisterContextLinux_arm::%s()", __FUNCTION__); + + Error 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 breakpoints 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, i); + + if (error.Fail()) { + m_hbr_regs[i].control = tempControl; + m_hbr_regs[i].address = tempAddr; + + return error; + } + } } - return false; + return Error(); } uint32_t NativeRegisterContextLinux_arm::NumSupportedHardwareWatchpoints() { @@ -784,8 +843,8 @@ (PTRACE_TYPE_ARG3)(intptr_t) - ((hwb_index << 1) + 2), ctrl_buf, sizeof(unsigned int)); } else { - addr_buf = &m_hwp_regs[hwb_index].address; - ctrl_buf = &m_hwp_regs[hwb_index].control; + addr_buf = &m_hbr_regs[hwb_index].address; + ctrl_buf = &m_hbr_regs[hwb_index].control; error = NativeProcessLinux::PtraceWrapper( PTRACE_SETHBPREGS, m_thread.GetID(), Index: source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h =================================================================== --- source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -46,10 +46,17 @@ // Hardware breakpoints/watchpoint mangement functions //------------------------------------------------------------------ + uint32_t NumSupportedHardwareBreakpoints() override; + uint32_t SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override; bool ClearHardwareBreakpoint(uint32_t hw_idx) override; + Error ClearAllHardwareBreakpoints() override; + + Error 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, Index: source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp =================================================================== --- source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -153,6 +153,7 @@ ::memset(&m_fpr, 0, sizeof(m_fpr)); ::memset(&m_gpr_arm64, 0, sizeof(m_gpr_arm64)); ::memset(&m_hwp_regs, 0, sizeof(m_hwp_regs)); + ::memset(&m_hbr_regs, 0, sizeof(m_hbr_regs)); // 16 is just a maximum value, query hardware for actual watchpoint count m_max_hwp_supported = 16; @@ -360,10 +361,27 @@ return (m_reg_info.first_fpr <= reg && reg <= m_reg_info.last_fpr); } +uint32_t NativeRegisterContextLinux_arm64::NumSupportedHardwareBreakpoints() { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); + + if (log) + log->Printf("NativeRegisterContextLinux_arm64::%s()", __FUNCTION__); + + Error 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_WATCHPOINTS)); + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); LLDB_LOG(log, "addr: {0:x}, size: {1:x}", addr, size); // Read hardware breakpoint and watchpoint information. @@ -388,47 +406,40 @@ control_value |= ((1 << size) - 1) << 5; control_value |= (2 << 1) | 1; - // Iterate over stored hardware breakpoints - // Find a free bp_index or update reference count if duplicate. + // 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 && - m_hbr_regs[i].control == control_value) { - bp_index = i; // Mark duplicate index - break; // Stop searching here + } 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; - // Add new or update existing breakpoint - if ((m_hbr_regs[bp_index].control & 1) == 0) { - m_hbr_regs[bp_index].address = addr; - m_hbr_regs[bp_index].control = control_value; - m_hbr_regs[bp_index].refcount = 1; - - // 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; - m_hbr_regs[bp_index].refcount = 0; + // 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; - return LLDB_INVALID_INDEX32; - } - } else - m_hbr_regs[bp_index].refcount++; + // 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_WATCHPOINTS)); + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); LLDB_LOG(log, "hw_idx: {0}", hw_idx); // Read hardware breakpoint and watchpoint information. @@ -440,35 +451,88 @@ if (hw_idx >= m_max_hbp_supported) return false; - // Update reference count if multiple references. - if (m_hbr_regs[hw_idx].refcount > 1) { - m_hbr_regs[hw_idx].refcount--; - return true; - } else if (m_hbr_regs[hw_idx].refcount == 1) { - // 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; - uint32_t tempRefCount = m_hbr_regs[hw_idx].refcount; - - m_hbr_regs[hw_idx].control &= ~1; - m_hbr_regs[hw_idx].address = 0; - m_hbr_regs[hw_idx].refcount = 0; - - // PTRACE call to clear corresponding hardware breakpoint register. - WriteHardwareDebugRegs(eDREGTypeBREAK); - - if (error.Fail()) { - m_hbr_regs[hw_idx].control = tempControl; - m_hbr_regs[hw_idx].address = tempAddr; - m_hbr_regs[hw_idx].refcount = tempRefCount; + // 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; +} + +Error NativeRegisterContextLinux_arm64::GetHardwareBreakHitIndex( + uint32_t &bp_index, lldb::addr_t trap_addr) { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); + + if (log) + log->Printf("NativeRegisterContextLinux_arm64::%s()", __FUNCTION__); - return false; + 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 Error(); } + } - return true; + bp_index = LLDB_INVALID_INDEX32; + return Error(); +} + +Error NativeRegisterContextLinux_arm64::ClearAllHardwareBreakpoints() { + Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); + + if (log) + log->Printf("NativeRegisterContextLinux_arm64::%s()", __FUNCTION__); + + Error 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 false; + return Error(); } uint32_t NativeRegisterContextLinux_arm64::NumSupportedHardwareWatchpoints() { Index: source/Plugins/Process/Linux/NativeThreadLinux.h =================================================================== --- source/Plugins/Process/Linux/NativeThreadLinux.h +++ source/Plugins/Process/Linux/NativeThreadLinux.h @@ -46,6 +46,10 @@ Error RemoveWatchpoint(lldb::addr_t addr) override; + Error SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override; + + Error RemoveHardwareBreakpoint(lldb::addr_t addr) override; + private: // --------------------------------------------------------------------- // Interface for friend classes @@ -102,6 +106,7 @@ std::string m_stop_description; using WatchpointIndexMap = std::map; WatchpointIndexMap m_watchpoint_index_map; + WatchpointIndexMap m_hw_break_index_map; llvm::Optional m_step_workaround; }; Index: source/Plugins/Process/Linux/NativeThreadLinux.cpp =================================================================== --- source/Plugins/Process/Linux/NativeThreadLinux.cpp +++ source/Plugins/Process/Linux/NativeThreadLinux.cpp @@ -190,6 +190,38 @@ return Error("Clearing hardware watchpoint failed."); } +Error NativeThreadLinux::SetHardwareBreakpoint(lldb::addr_t addr, size_t size) { + if (m_state == eStateLaunching) + return Error(); + + Error error = RemoveHardwareBreakpoint(addr); + if (error.Fail()) + return error; + + NativeRegisterContextSP reg_ctx = GetRegisterContext(); + uint32_t bp_index = reg_ctx->SetHardwareBreakpoint(addr, size); + + if (bp_index == LLDB_INVALID_INDEX32) + return Error("Setting hardware breakpoint failed."); + + m_hw_break_index_map.insert({addr, bp_index}); + return Error(); +} + +Error NativeThreadLinux::RemoveHardwareBreakpoint(lldb::addr_t addr) { + auto bp = m_hw_break_index_map.find(addr); + if (bp == m_hw_break_index_map.end()) + return Error(); + + uint32_t bp_index = bp->second; + if (GetRegisterContext()->ClearHardwareBreakpoint(bp_index)) { + m_hw_break_index_map.erase(bp); + return Error(); + } + + return Error("Clearing hardware breakpoint failed."); +} + Error NativeThreadLinux::Resume(uint32_t signo) { const StateType new_state = StateType::eStateRunning; MaybeLogStateChange(new_state); @@ -211,6 +243,18 @@ } } + // Set all active hardware breakpoint on all threads. + if (m_hw_break_index_map.empty()) { + NativeProcessLinux &process = GetProcess(); + + const auto &hw_breakpoint_map = process.GetHardwareBreakpointMap(); + GetRegisterContext()->ClearAllHardwareBreakpoints(); + for (const auto &pair : hw_breakpoint_map) { + const auto &bp = pair.second; + SetHardwareBreakpoint(bp.m_addr, bp.m_size); + } + } + intptr_t data = 0; if (signo != LLDB_INVALID_SIGNAL_NUMBER) Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -2531,12 +2531,14 @@ packet, "Too short z packet, missing software/hardware specifier"); bool want_breakpoint = true; + bool want_hardware = false; const GDBStoppointType stoppoint_type = GDBStoppointType(packet.GetS32(eStoppointInvalid)); switch (stoppoint_type) { case eBreakpointHardware: want_breakpoint = true; + want_hardware = true; break; case eBreakpointSoftware: want_breakpoint = true; @@ -2579,7 +2581,8 @@ if (want_breakpoint) { // Try to clear the breakpoint. - const Error error = m_debugged_process_sp->RemoveBreakpoint(addr); + const Error error = + m_debugged_process_sp->RemoveBreakpoint(addr, want_hardware); if (error.Success()) return SendOKResponse(); Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_BREAKPOINTS)); @@ -3037,9 +3040,14 @@ if (packet.GetChar() != ':') return SendErrorResponse(67); - uint32_t num = m_debugged_process_sp->GetMaxWatchpoints(); + auto hw_debug_cap = m_debugged_process_sp->GetHardwareDebugSupportInfo(); + StreamGDBRemote response; - response.Printf("num:%d;", num); + if (hw_debug_cap == llvm::None) + response.Printf("num:0;"); + else + response.Printf("num:%d;", hw_debug_cap->second); + return SendPacketNoLock(response.GetString()); }