https://github.com/DavidSpickett updated https://github.com/llvm/llvm-project/pull/222009
>From 4fa5b647b20684ef88ba12b35887ef58ae09e930 Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Tue, 8 Sep 2026 13:59:04 +0000 Subject: [PATCH 1/2] [lldb] Fix handling of lldb managed software breakpoints Fixes #217910. When the debug server can manage its own breakpoints this is called an "external" breakpoint. When it cannot, lldb must manage the breakpoint itself from the client side. Part of doing that is FindInRange which is used to patch reads and writes over software breakpoint sites. It had an issue where it would not account for a breakpoint that started before the range, but extended into the range. For example: ``` Memory content: ABCDEFGHIJKL Read range: ----[--]---- Breakpoint location: -BKPT------- Overlap with read: ----@------- Expected read result: ----EFGH---- Would actually get: ----TFGH---- ``` This change fixes that mistake: * Exit early if there are no sites to check. * Exit early if the lower_bound result is beyond the upper bound of the range - only if the result is not the end of the list. In the result was end(), there may be one site before which we must also check. The existing "Over end of breakpoint" test covers the bug I'm fixing. The tests have been refactored to run normally and via. a proxy that refuses breakpoint packets. We could instead use two different proxies where one supports the packets and one doesn't. However, that's more overhead per test and you can't run proxy tests remotely. --- .../lldb/Breakpoint/StopPointSiteList.h | 8 ++- .../TestWriteOverSoftwareBreakpoint.py | 72 +++++++++---------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/lldb/include/lldb/Breakpoint/StopPointSiteList.h b/lldb/include/lldb/Breakpoint/StopPointSiteList.h index 6371c7a7fb877..fd0b17839f3d2 100644 --- a/lldb/include/lldb/Breakpoint/StopPointSiteList.h +++ b/lldb/include/lldb/Breakpoint/StopPointSiteList.h @@ -194,10 +194,11 @@ template <typename StopPointSite> class StopPointSiteList { return false; std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (m_site_list.empty()) + return false; + typename collection::const_iterator lower, upper, pos; lower = m_site_list.lower_bound(lower_bound); - if (lower == m_site_list.end() || (*lower).first >= upper_bound) - return false; // This is one tricky bit. The site might overlap the bottom end of // the range. So we grab the site prior to the lower bound, and check @@ -212,6 +213,9 @@ template <typename StopPointSite> class StopPointSiteList { bp_site_list.Add(prev_site); } + if (lower != m_site_list.end() && lower->first >= upper_bound) + return !bp_site_list.IsEmpty(); + upper = m_site_list.upper_bound(upper_bound); for (pos = lower; pos != upper; pos++) diff --git a/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py index 1821e0fbfe7f3..d89220d1db5e0 100644 --- a/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py +++ b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py @@ -8,37 +8,42 @@ from lldbsuite.test.lldbtest import * import lldbsuite.test.lldbutil as lldbutil from lldbsuite.test.decorators import * +from lldbsuite.test.lldbgdbproxy import GDBProxyTestBase - -class WriteOverSoftwareBreakpoint(TestBase): +class TestCases(object): NO_DEBUG_INFO_TESTCASE = True - # Could not find a way to make place_break_here visible to lldb on Windows. - @skipIfWindows - @skipIfOutOfTreeDebugserver - def test_write_over_breakpoint(self): - TestBase.setUp(self) - self.line = line_number("main.c", "// break here") + def run_to_start(self): + is_proxy = isinstance(self, GDBProxyTestBase) + self.build() exe = self.getBuildArtifact("a.out") - self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + if is_proxy: + self.connect(target) + self.line = line_number("main.c", "// break here") lldbutil.run_break_set_by_file_and_line( self, "main.c", self.line, num_expected_locations=1, loc_exact=True ) - self.runCmd("run", RUN_SUCCEEDED) + + self.runCmd("continue" if is_proxy else "run") self.expect( "thread list", STOPPED_DUE_TO_BREAKPOINT, substrs=["stopped", "stop reason = breakpoint"], ) - target = self.dbg.GetSelectedTarget() - process = target.GetProcess() - loop_start_breakpoint_addr = ( target.breakpoints[0].GetLocationAtIndex(0).GetLoadAddress() ) + return target, target.GetProcess(), loop_start_breakpoint_addr + + # Could not find a way to make place_break_here visible to lldb on Windows. + @skipIfWindows + @skipIfOutOfTreeDebugserver + def test_write_over_breakpoint(self): + target, process, loop_start_breakpoint_addr = self.run_to_start() # Memory operations and breakpoint actions must be sent to the server # right away instead of waiting for the next continue event. @@ -158,30 +163,8 @@ def test_write_over_breakpoint(self): self.assertEqual(loop_start_breakpoint_addr, thread.selected_frame.GetPC()) def test_write_over_uncommitted_breakpoint(self): - TestBase.setUp(self) - self.line = line_number("main.c", "// break here") - self.build() - exe = self.getBuildArtifact("a.out") - self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) - self.runCmd("settings set target.process.use-delayed-breakpoints true") - - lldbutil.run_break_set_by_file_and_line( - self, "main.c", self.line, num_expected_locations=1, loc_exact=True - ) - self.runCmd("run", RUN_SUCCEEDED) - self.expect( - "thread list", - STOPPED_DUE_TO_BREAKPOINT, - substrs=["stopped", "stop reason = breakpoint"], - ) - - target = self.dbg.GetSelectedTarget() - process = target.GetProcess() - - loop_start_breakpoint_addr = ( - target.breakpoints[0].GetLocationAtIndex(0).GetLoadAddress() - ) + target, process, loop_start_breakpoint_addr = self.run_to_start() bkpt = target.BreakpointCreateByName("foo") self.assertTrue(bkpt.IsValid()) @@ -244,3 +227,20 @@ def check_memory(): # should be managing the breakpoint, but this checks that the handover # was done correctly. check_memory() + + +@skipIfRemote +class WriteOverLLDBManagedSoftwareBreakpoint(GDBProxyTestBase, TestCases): + def respond(self, packet): + # Pretend that the server cannot handle breakpoints, which means lldb + # must manage them. + if packet.startswith("jMultiBreakpoint"): + return "" + elif packet.startswith("Z"): + return "" + + return super().respond(packet) + + +class WriteOverExternalSoftwareBreakpoint(TestBase, TestCases): + pass >From 9f824d2d28255fc70a7410fe8c79830536365b16 Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Wed, 9 Sep 2026 09:49:58 +0000 Subject: [PATCH 2/2] Skip internal test when arch reports pc after break --- .../TestWriteOverSoftwareBreakpoint.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py index d89220d1db5e0..894b5e4d31782 100644 --- a/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py +++ b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py @@ -230,6 +230,11 @@ def check_memory(): @skipIfRemote +# x86, x86_64, s390x and all versions of Windows report a breakpoint address +# after the breakpoint and the test fails due to +# https://github.com/llvm/llvm-project/issues/222284. +@skipIf(archs=["i386", "x86_64", "s390x"]) +@skipIfWindows class WriteOverLLDBManagedSoftwareBreakpoint(GDBProxyTestBase, TestCases): def respond(self, packet): # Pretend that the server cannot handle breakpoints, which means lldb _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
