https://github.com/DavidSpickett created https://github.com/llvm/llvm-project/pull/221244
https://github.com/llvm/llvm-project/pull/192971 added a delayed breakpoints feature, which delays sending the breakpoint management packets until we actually continue the process. The feature itself is working, but it has made it easier to hit a bunch of pre-existing issues handling reads and writes around breakpoints. https://github.com/llvm/llvm-project/issues/217359, https://github.com/llvm/llvm-project/issues/217840, https://github.com/llvm/llvm-project/issues/217359, https://github.com/llvm/llvm-project/issues/205120 and https://github.com/llvm/llvm-project/issues/202672 (reported by a downstream project as https://github.com/llvm/llvm-project/issues/216000). This PR backports to 23 a set of fixes for those issues. It is quite large, but ~560 of the ~660 new lines are tests. 89ad6f51f475 [lldb][test] Fix skip in TestWriteMemoryWithHWBreakpoint.py (#216723) b955ea912ac1 [lldb][debugserver] Fix a bug in FindBreakpointsThatOverlapRange (#217009) a132493da2f6 [LLDB][lldb-server] Handle writes over software breakpoint sites (#217348) 76d27c889614 [lldb][lldb-server] Fix compilation on 32-bit (#217934) 938e6446b039 [lldb][debugserver] Handle breakpoint prior to addr in RemoveTrapsFromBuffer (#217851) d8ec70dc8db9 [lldb][debugserver] Fix bugs in FindBreakpointsThatOverlapRange (#217837) 9cda4668bc97 [lldb] Handle 0 size sites in StopPointSiteList::FindInRange (#217919) 0b98a7cd6353 [lldb][test] Enable TestWriteOverSoftwareBreakpoint for debugserver (#218362) 7ea6d83f49d9 [lldb][test] Disable two breakpoint tests for debugserver (#218636) dfa87f357528 Reland "[lldb][test] Disable two breakpoint tests for debugserver" (#220845) bf1874e420a3 [lldb] Update decorator in write-over-breakpoint tests (#220919) >From 2416b034eb8c3949ccaf0a9c3f51e46ab8a2fae5 Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Mon, 17 Aug 2026 14:13:17 +0100 Subject: [PATCH 01/11] [lldb][test] Fix skip in TestWriteMemoryWithHWBreakpoint.py (#216723) In a87b27fd5161ec43527fc3356852046a321ea82c, the opposite skip was put in. It should skip if hardware breakpoints are *not* supported. Also that commit added a stray "skip". I have removed that and fixed the incorrect variable name. --- .../TestWriteMemoryWithHWBreakpoint.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py index c82ae24a6d9ab..c7c4c09454ef0 100644 --- a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py +++ b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py @@ -13,8 +13,7 @@ class WriteMemoryWithHWBreakpoint(HardwareBreakpointTestBase): - @skipTestIfFn(HardwareBreakpointTestBase.supports_hw_breakpoints) - @skip + @skipTestIfFn(HardwareBreakpointTestBase.hw_breakpoints_unsupported) def test_copy_memory_with_hw_break(self): self.build() exe = self.getBuildArtifact("a.out") @@ -46,4 +45,4 @@ def test_copy_memory_with_hw_break(self): error = lldb.SBError() result = process.WriteMemory(address, data, error) - self.assertTrue(error.Success() and result == len(bytes)) + self.assertTrue(error.Success() and result == len(data)) >From 2aaef556b4569d843516f3c4b94009fafc98b946 Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Thu, 20 Aug 2026 08:57:44 +0100 Subject: [PATCH 02/11] [lldb][debugserver] Fix a bug in FindBreakpointsThatOverlapRange (#217009) This reverts commit a7ef89ad9796e6de3a085ec75a13f0dfec5a8059 to reland with a fix for the reported failure on MacOS. I think this was due to a bug in `DNBBreakpointList::FindBreakpointsThatOverlapRange`. In the second loop, `pos` is only incremented if `IntersectsRange` returns true. It will never return true for a hardware breakpoint because hardware breakpoints are not placed into actual memory. This means that it gets stuck in that final while. I think if I had access to the logs, I would see lldb timing out the write memory request. I made some small changes to the test case: * Split the final assertion so we can tell if it failed entirely or partially. * Renamed the test case, since it's not "copying" anything. --- .../TestWriteMemoryWithHWBreakpoint.py | 5 +++-- lldb/tools/debugserver/source/DNBBreakpoint.cpp | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py index c7c4c09454ef0..341ef444c66e0 100644 --- a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py +++ b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py @@ -14,7 +14,7 @@ class WriteMemoryWithHWBreakpoint(HardwareBreakpointTestBase): @skipTestIfFn(HardwareBreakpointTestBase.hw_breakpoints_unsupported) - def test_copy_memory_with_hw_break(self): + def test_write_memory_with_hw_break(self): self.build() exe = self.getBuildArtifact("a.out") @@ -45,4 +45,5 @@ def test_copy_memory_with_hw_break(self): error = lldb.SBError() result = process.WriteMemory(address, data, error) - self.assertTrue(error.Success() and result == len(data)) + self.assertTrue(error.Success()) + self.assertEqual(result, len(data)) diff --git a/lldb/tools/debugserver/source/DNBBreakpoint.cpp b/lldb/tools/debugserver/source/DNBBreakpoint.cpp index e41bf9b4fd905..74f0fb17129f3 100644 --- a/lldb/tools/debugserver/source/DNBBreakpoint.cpp +++ b/lldb/tools/debugserver/source/DNBBreakpoint.cpp @@ -147,10 +147,10 @@ size_t DNBBreakpointList::FindBreakpointsThatOverlapRange( break; // Check if this breakpoint overlaps, and if it does, add it to the list - if (pos->second.IntersectsRange(addr, size, NULL, NULL, NULL)) { + if (pos->second.IntersectsRange(addr, size, NULL, NULL, NULL)) bps.push_back(&pos->second); - ++pos; - } + + ++pos; } } return bps.size(); >From 5173a907a2a5532103e29adbf6a0f48aadb76478 Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Fri, 21 Aug 2026 13:23:58 +0100 Subject: [PATCH 03/11] [LLDB][lldb-server] Handle writes over software breakpoint sites (#217348) Fixes #202672 lldb-server was not handling them at all, and I found bugs in debugserver's implementation. This change only fixes lldb-server, the debugserver issue is tracked by #217359 and #217840 (though the added API tests can be run for debugserver once that's done). When data is written over a software breakpoint location, it should not be written to memory but instead to the saved copy of the original data at that location. If this is not done, writes corrupt the software break instruction and it's no longer a valid breakpoint. This is how it should work: ``` Memory contents: ----abcd---- Place a breakpoint: ----bkpt---- (saved bytes: abcd) Then we write over it: --abcdefgh-- The result: --abbkptgh-- (saved bytes: cdef) Remove the breakpoint: --abcdefgh-- ``` When the breakpoint is removed, the saved bytes are written to memory and it looks just like you had written it all in the first place. This is what was happening with lldb-server: ``` Memory contents: ----abcd---- Place a breakpoint: ----bkpt---- (saved bytes: abcd) Then we write over it: --abcdefgh-- The result: --abcdefgh-- (saved bytes: bkpt) (break no longer valid) Remove the breakpoint: Errors because it doesn't see the break instruction in memory. ``` debugserver already has an algorithm to work out how to do this. It's in MachProcess::WriteMemory and what I've added to NativeProcessProtocol follows the same basic logic. * Assume breakpoints are sorted in ascending order of address (hence the change from std::unordered_map to std::map). * Skip forward to the first breakpoint that starts > the write address. * If that breakpoint is not the first, go back one breakpoint. This catches the case where a breakpoint starts earlier than the write but still over laps it, or starts exactly when the write does. * If the write address is before the first breakpoint, write up to that breakpoint. * If the write address is at or in a breakpoint, update that breakpoint's saved bytes until you reach the end of it, or the write is complete. * If there's write data left, move the write address forward to just beyond the breakpoint. * Repeat for all breakpoints from that point on in the list, or until the write is complete. * Finally, check for any remaining bytes to be written after the last breakpoint. This means a single write is split into multiple sometimes. The alternative is to make a copy of the write data, and patch the breakpoint instructions into that to create 1 write request. I decided not to do that because: * debugserver does not do this, and we may need to compare them later. * It is harder to reason about recovery if the single write fails. NativeProcessProtocol ReadMemory is no longer abstract, instead there is a lower level DoWriteMemory which sub-classes implement. DoWriteMemory is what WriteMemory used to be, so some places call it directly because they want to ignore breakpoint sites. I tried to avoid this extra abstraction layer, but I do not see a good alternative. Tests have been added at the NativeProccess level for different layouts. The expected number of write calls is checked and breakpoint insertion is tested in 2 orders if there is > 1. There is an API test based on the steps reported in #216000. This test should run on all architectures. For now it won't run for debugserver. I have had to take a small risk to get some dead code immediately before the breakpoint site. Short of using target specific assembly, I think what I've got is as safe as we can be. Limiting this test to AArch64 would not be a big problem, as the same write splitting algorithm is used on all architectures. However just on principle I would like it to be as generic as it can be. I did have to skip Windows, because I could not find a way to make the breakpoint location label visible to lldb. The code being tested should work on Windows, if someone can figure out the symbol part. The loss of coverage is minimal because the algorithm under test is not OS specific. --- .../lldb/Host/common/NativeProcessProtocol.h | 19 +- .../Host/common/NativeProcessProtocol.cpp | 95 ++++++- .../Plugins/Process/AIX/NativeProcessAIX.cpp | 4 +- .../Plugins/Process/AIX/NativeProcessAIX.h | 4 +- .../Process/FreeBSD/NativeProcessFreeBSD.cpp | 4 +- .../Process/FreeBSD/NativeProcessFreeBSD.h | 4 +- .../Process/Linux/NativeProcessLinux.cpp | 15 +- .../Process/Linux/NativeProcessLinux.h | 4 +- .../Process/NetBSD/NativeProcessNetBSD.cpp | 4 +- .../Process/NetBSD/NativeProcessNetBSD.h | 4 +- .../Windows/Common/NativeProcessWindows.cpp | 4 +- .../Windows/Common/NativeProcessWindows.h | 4 +- .../write_over_software_breakpoint/Makefile | 3 + .../TestWriteOverSoftwareBreakpoint.py | 161 +++++++++++ .../write_over_software_breakpoint/main.c | 34 +++ .../Mock/ProcessMockAccelerator.cpp | 5 +- .../Accelerator/Mock/ProcessMockAccelerator.h | 4 +- .../Host/NativeProcessProtocolTest.cpp | 264 ++++++++++++++++++ .../Host/NativeProcessTestUtils.h | 22 +- 19 files changed, 610 insertions(+), 48 deletions(-) create mode 100644 lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/Makefile create mode 100644 lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py create mode 100644 lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/main.c diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index 435185a38f3f9..c70bd4d011383 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -26,9 +26,9 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/MemoryBuffer.h" +#include <map> #include <mutex> #include <optional> -#include <unordered_map> #include <vector> namespace lldb_private { @@ -133,8 +133,10 @@ class NativeProcessProtocol { ReadCStringFromMemory(lldb::addr_t addr, char *buffer, size_t max_size, size_t &total_bytes_read); - virtual Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, - size_t &bytes_written) = 0; + /// Write memory while not overwriting breakpoints in memory. The breakpoints' + /// saved bytes are updated with what would have been written. + Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written); virtual llvm::Expected<lldb::addr_t> AllocateMemory(size_t size, uint32_t permissions) { @@ -456,7 +458,9 @@ class NativeProcessProtocol { llvm::ArrayRef<uint8_t> breakpoint_opcodes; }; - std::unordered_map<lldb::addr_t, SoftwareBreakpoint> m_software_breakpoints; + // Using std::map so that breakpoints are sorted in ascending address order. + // WriteMemory relies on this. + std::map<lldb::addr_t, SoftwareBreakpoint> m_software_breakpoints; lldb::pid_t m_pid; std::vector<std::unique_ptr<NativeThreadProtocol>> m_threads; @@ -481,6 +485,13 @@ class NativeProcessProtocol { // Extensions enabled per the last SetEnabledExtensions() call. Extension m_enabled_extensions; + // Write to memory, with no awareness of software breakpoint sites. Used to + // implement WriteMemory. May be called directly for use cases that should + // ignore software breakpoint sites, for example adding or removing those + // breakpoints. + virtual Status DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) = 0; + // lldb_private::Host calls should be used to launch a process for debugging, // and then the process should be attached to. When attaching to a process // lldb_private::Host calls should be used to locate the process to attach diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp index dbffdc619ef42..b996424541907 100644 --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -18,6 +18,7 @@ #include "lldb/lldb-enumerations.h" #include "llvm/Support/Process.h" +#include <iterator> #include <optional> using namespace lldb; @@ -396,7 +397,7 @@ Status NativeProcessProtocol::RemoveSoftwareBreakpoint(lldb::addr_t addr) { // We found a valid breakpoint opcode at this address, now restore the // saved opcode. size_t bytes_written = 0; - error = WriteMemory(addr, saved.data(), saved.size(), bytes_written); + error = DoWriteMemory(addr, saved.data(), saved.size(), bytes_written); if (error.Fail() || bytes_written < saved.size()) { return Status::FromErrorStringWithFormat( "addr=0x%" PRIx64 ": tried to write %zu bytes but only wrote %zu", @@ -454,8 +455,8 @@ NativeProcessProtocol::EnableSoftwareBreakpoint(lldb::addr_t addr, // Write a software breakpoint in place of the original opcode. size_t bytes_written = 0; - error = WriteMemory(addr, expected_trap->data(), expected_trap->size(), - bytes_written); + error = DoWriteMemory(addr, expected_trap->data(), expected_trap->size(), + bytes_written); if (error.Fail()) return error.ToError(); @@ -649,9 +650,91 @@ Status NativeProcessProtocol::RemoveBreakpoint(lldb::addr_t addr, return RemoveSoftwareBreakpoint(addr); } -Status NativeProcessProtocol::ReadMemoryWithoutTrap(lldb::addr_t addr, - void *buf, size_t size, - size_t &bytes_read) { +Status NativeProcessProtocol::WriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) { + Status error; + bytes_written = 0; + + if (!size) + return error; + + if (m_software_breakpoints.empty()) + return DoWriteMemory(addr, buf, size, bytes_written); + + // Find first breakpoint that starts >= addr. + std::map<lldb::addr_t, SoftwareBreakpoint>::iterator bkpt = + m_software_breakpoints.lower_bound(addr); + + // it points to the first breakpoint starting at >= addr, but the one + // immediately before it may extend over addr, or begin exactly at addr. + if (bkpt != m_software_breakpoints.begin()) + bkpt = std::prev(bkpt); + + const uint8_t *byte_buf = static_cast<const uint8_t *>(buf); + for (; bkpt != m_software_breakpoints.end(); ++bkpt) { + auto &[sbp_addr, sbp_data] = *bkpt; + // If the address is before a breakpoint site, write up to the site, or to + // the end of the write. Whichever comes first. + if (addr < sbp_addr) { + const size_t to_write = std::min(size, size_t{sbp_addr - addr}); + size_t part_bytes_written = 0; + error = DoWriteMemory(addr, byte_buf, to_write, part_bytes_written); + bytes_written += part_bytes_written; + + if (error.Fail() || part_bytes_written < to_write) { + return Status::FromErrorStringWithFormat( + "addr=0x%" PRIx64 ": tried to write %zu bytes but only wrote %zu", + addr, to_write, part_bytes_written); + } + + byte_buf += to_write; + addr += to_write; + size -= to_write; + + if (!size) + break; + } + + // If the address is within a breakpoint site, update the saved opcodes + // for that site. + if ((addr >= sbp_addr) && + (addr < (sbp_addr + sbp_data.saved_opcodes.size()))) { + // Instead of writing this chunk, update the saved bytes in the + // breakpoint. + const size_t idx = addr - sbp_addr; + const size_t to_write = + std::min(size, sbp_data.saved_opcodes.size() - idx); + for (size_t copied = 0; copied < to_write; + ++bytes_written, ++byte_buf, ++addr, --size, ++copied) + sbp_data.saved_opcodes[idx + copied] = *byte_buf; + } + + if (!size) + break; + } + + // If the write range extends beyond the last breakpoint site, write the + // remaining data. + if (size) { + // Write the remaining part after the last breakpoint, or the whole range + // in the case that there were no breakpoints. + size_t part_bytes_written = 0; + error = DoWriteMemory(addr, byte_buf, size, part_bytes_written); + bytes_written += part_bytes_written; + if (error.Fail() || part_bytes_written < size) { + return Status::FromErrorStringWithFormat( + "addr=0x%" PRIx64 ": tried to write %zu bytes but only wrote %zu", + addr, size, part_bytes_written); + } + } + + return Status(); +} + +Status +NativeProcessProtocol::ReadMemoryWithoutTrap(const lldb::addr_t addr, + void *buf, size_t size, + size_t &bytes_read) { Status error = ReadMemory(addr, buf, size, bytes_read); if (error.Fail()) return error; diff --git a/lldb/source/Plugins/Process/AIX/NativeProcessAIX.cpp b/lldb/source/Plugins/Process/AIX/NativeProcessAIX.cpp index 9c7e66cb79028..9792e217cdfa4 100644 --- a/lldb/source/Plugins/Process/AIX/NativeProcessAIX.cpp +++ b/lldb/source/Plugins/Process/AIX/NativeProcessAIX.cpp @@ -243,8 +243,8 @@ Status NativeProcessAIX::ReadMemory(lldb::addr_t addr, void *buf, size_t size, return Status("unsupported"); } -Status NativeProcessAIX::WriteMemory(lldb::addr_t addr, const void *buf, - size_t size, size_t &bytes_written) { +Status NativeProcessAIX::DoWriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) { return Status("unsupported"); } diff --git a/lldb/source/Plugins/Process/AIX/NativeProcessAIX.h b/lldb/source/Plugins/Process/AIX/NativeProcessAIX.h index bc44f2b02af98..4de7cb02f1426 100644 --- a/lldb/source/Plugins/Process/AIX/NativeProcessAIX.h +++ b/lldb/source/Plugins/Process/AIX/NativeProcessAIX.h @@ -80,8 +80,8 @@ class NativeProcessAIX : public NativeProcessProtocol { Status ReadMemory(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read) override; - Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, - size_t &bytes_written) override; + Status DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; size_t UpdateThreads() override; diff --git a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp index 4853ab2827d9e..82ac352e85cd5 100644 --- a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp +++ b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.cpp @@ -898,8 +898,8 @@ Status NativeProcessFreeBSD::ReadMemory(lldb::addr_t addr, void *buf, return Status(); } -Status NativeProcessFreeBSD::WriteMemory(lldb::addr_t addr, const void *buf, - size_t size, size_t &bytes_written) { +Status NativeProcessFreeBSD::DoWriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) { const unsigned char *src = static_cast<const unsigned char *>(buf); Status error; struct ptrace_io_desc io; diff --git a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h index 7e8bdc527f420..90c830b8e5f66 100644 --- a/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h +++ b/lldb/source/Plugins/Process/FreeBSD/NativeProcessFreeBSD.h @@ -62,8 +62,8 @@ class NativeProcessFreeBSD : public NativeProcessELF { Status ReadMemory(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read) override; - Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, - size_t &bytes_written) override; + Status DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; size_t UpdateThreads() override; diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp index 80f1b5662ba61..9b309fee41dde 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -1347,8 +1347,9 @@ NativeProcessLinux::Syscall(llvm::ArrayRef<uint64_t> args) { return std::move(Err); } - llvm::scope_exit restore_mem( - [&] { WriteMemory(exe_addr, memory.data(), memory.size(), bytes_read); }); + llvm::scope_exit restore_mem([&] { + DoWriteMemory(exe_addr, memory.data(), memory.size(), bytes_read); + }); if (llvm::Error Err = reg_ctx.SetPC(exe_addr).ToError()) return std::move(Err); @@ -1361,8 +1362,8 @@ NativeProcessLinux::Syscall(llvm::ArrayRef<uint64_t> args) { return std::move(Err); } } - if (llvm::Error Err = WriteMemory(exe_addr, syscall_data.Insn.data(), - syscall_data.Insn.size(), bytes_read) + if (llvm::Error Err = DoWriteMemory(exe_addr, syscall_data.Insn.data(), + syscall_data.Insn.size(), bytes_read) .ToError()) return std::move(Err); @@ -1665,8 +1666,8 @@ Status NativeProcessLinux::ReadMemory(lldb::addr_t addr, void *buf, size_t size, return Status(); } -Status NativeProcessLinux::WriteMemory(lldb::addr_t addr, const void *buf, - size_t size, size_t &bytes_written) { +Status NativeProcessLinux::DoWriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) { const unsigned char *src = static_cast<const unsigned char *>(buf); size_t remainder; Status error; @@ -1697,7 +1698,7 @@ Status NativeProcessLinux::WriteMemory(lldb::addr_t addr, const void *buf, memcpy(buff, src, remainder); size_t bytes_written_rec; - error = WriteMemory(addr, buff, k_ptrace_word_size, bytes_written_rec); + error = DoWriteMemory(addr, buff, k_ptrace_word_size, bytes_written_rec); if (error.Fail()) return error; diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h index 936d690e42ae7..d255c1e0df18b 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -99,8 +99,8 @@ class NativeProcessLinux : public NativeProcessELF, Status ReadMemory(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read) override; - Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, - size_t &bytes_written) override; + Status DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; llvm::Expected<lldb::addr_t> AllocateMemory(size_t size, uint32_t permissions) override; diff --git a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp index 3fd14c4c43071..702b2bbca4440 100644 --- a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp +++ b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp @@ -926,8 +926,8 @@ Status NativeProcessNetBSD::ReadMemory(lldb::addr_t addr, void *buf, return Status(); } -Status NativeProcessNetBSD::WriteMemory(lldb::addr_t addr, const void *buf, - size_t size, size_t &bytes_written) { +Status NativeProcessNetBSD::DoWriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) { const unsigned char *src = static_cast<const unsigned char *>(buf); Status error; struct ptrace_io_desc io; diff --git a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h index 976d48e74854e..d27042c1a6a79 100644 --- a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h +++ b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h @@ -60,8 +60,8 @@ class NativeProcessNetBSD : public NativeProcessELF { Status ReadMemory(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read) override; - Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, - size_t &bytes_written) override; + Status DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; lldb::addr_t GetSharedLibraryInfoAddress() override; diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp index f87fd23f5a047..1601d3f2f0f1f 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp @@ -237,8 +237,8 @@ Status NativeProcessWindows::ReadMemory(lldb::addr_t addr, void *buf, return ProcessDebugger::ReadMemory(addr, buf, size, bytes_read); } -Status NativeProcessWindows::WriteMemory(lldb::addr_t addr, const void *buf, - size_t size, size_t &bytes_written) { +Status NativeProcessWindows::DoWriteMemory(lldb::addr_t addr, const void *buf, + size_t size, size_t &bytes_written) { return ProcessDebugger::WriteMemory(addr, buf, size, bytes_written); } diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h index 17469f18fbc73..82b82d109b1e4 100644 --- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h +++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h @@ -71,8 +71,8 @@ class NativeProcessWindows : public NativeProcessProtocol, Status ReadMemory(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read) override; - Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, - size_t &bytes_written) override; + Status DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; llvm::Expected<lldb::addr_t> AllocateMemory(size_t size, uint32_t permissions) override; diff --git a/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/Makefile b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules 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 new file mode 100644 index 0000000000000..4d33382fe3bd9 --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/TestWriteOverSoftwareBreakpoint.py @@ -0,0 +1,161 @@ +""" +Check that when data is written over a software breakpoint site, it does not +corrupt the breakpoint instruction, and is later written to memory when the +breakpoint is removed. +""" + +import lldb +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.decorators import * + + +class WriteOverSoftwareBreakpoint(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + # Could not find a way to make place_break_here visible to lldb on Windows. + @skipIfWindows + # debugserver needs fixing, see: + # https://github.com/llvm/llvm-project/issues/217359 + # https://github.com/llvm/llvm-project/issues/217840 + @llgs_test + def test_write_over_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) + + 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() + ) + + # Memory operations and breakpoint actions must be sent to the server + # right away instead of waiting for the next continue event. + self.runCmd("settings set target.process.disable-memory-cache on") + # Breakpoints set by address are never delayed but to make this test + # less fragile if later changed, disable delayed breakpoints entirely. + self.runCmd("settings set target.process.use-delayed-breakpoints false") + + # At this point we are stopped at the start of the for loop. + # We will set a further breakpoint in foo, and this is the one we will + # test by writing over it then continuing to it. + # We could use the same breakpoint, but arranging for dead code + # immediately before it is more risky. + + # The breakpoint must be on an exact address, because we do not want + # lldb to adjust it based on the debug information for prologues + # and epilogues. + symbol_contexts = target.FindSymbols("place_break_here") + self.assertEqual(1, len(symbol_contexts)) + label_symbol = symbol_contexts[0].GetSymbol() + self.assertTrue(label_symbol.IsValid()) + bkpt_address = label_symbol.GetStartAddress().GetLoadAddress(target) + + # lldb-server has its algorithm unit tested as part of + # NativeProcessProtocol, but debugserver does not use that. So we will + # do a few different types of overwrite here so we have coverage for both + # debug servers. + # + # We cannot be sure what the software break size will be, so I'm assuming + # 4 bytes because that's what Arm/AArch64 use. This means we may not be + # getting full coverage on Thumb or x86 but the test should still pass + # there. + # + # We assume that the instruction immediately before the breakpoint in + # foo is dead code, so we are allowed to corrupt it. + writes = [ + # Up to but not over breakpoint. + (-4, 4), + # Over start of breakpoint. + (-2, 4), + # Exactly over breakpoint. + (0, 4), + # Over end of breakpoint. + (2, 4), + # Immediately after breakpoint. + (4, 4), + # From before to after breakpoint. + (-4, 12), + ] + + for write_offset, write_size in writes: + # Place a breakpoint immediately after the dead code in foo. + bkpt = target.BreakpointCreateByAddress(bkpt_address) + self.assertTrue(bkpt.IsValid()) + self.assertEqual(bkpt.GetNumLocations(), 1) + self.assertFalse(bkpt.IsHardware()) + self.assertEqual(bkpt.GetLocationAtIndex(0).GetLoadAddress(), bkpt_address) + + check_address = bkpt_address + write_offset + + # Read around the breakpoint site. We assume that read subsitution + # is working, so the data here is the original contents of memory + # without the trap instruction. + err = lldb.SBError() + original_data = bytearray( + process.ReadMemory(check_address, write_size, err) + ) + self.assertSuccess(err) + self.assertEqual(len(original_data), write_size) + + # Write around/in/over the breakpoint site. + write_data = bytearray(range(write_size)) + wrote = process.WriteMemory(check_address, write_data, err) + self.assertSuccess(err) + self.assertEqual(wrote, write_size) + + # The data overlapping the breakpoint site should be in that breakpoint's + # saved data. So it will appear as if all the data was written to memory, + # even though it was not yet. + after_write = bytearray(process.ReadMemory(check_address, write_size, err)) + self.assertSuccess(err) + self.assertEqual(after_write, write_data) + + # The instruction in memory should still be intact so we can continue + # to the breakpoint. + process.Continue() + + thread = process.thread[0] + self.assertState(process.GetState(), lldb.eStateStopped) + self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint) + # Should be stopped at the breakpoint we placed in foo. This proves that + # the breakpoint instruction was intact. + self.assertEqual( + bkpt_address, + thread.selected_frame.GetPC(), + ) + + # When the breakpoint is removed the saved bytes will be written to + # memory. + self.assertTrue(target.BreakpointDelete(bkpt.GetID())) + + data = process.ReadMemory(check_address, write_size, err) + self.assertSuccess(err) + self.assertEqual(bytearray(data), write_data) + + # Restore the original instruction data. The dead code before the + # breakpoint is ok but the instructions after it must be put back + # so we can continue. + wrote = process.WriteMemory(check_address, original_data, err) + self.assertSuccess(err) + self.assertEqual(wrote, write_size) + + # Continue back to the start of the loop. + process.Continue() + self.assertState(process.GetState(), lldb.eStateStopped) + self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint) + self.assertEqual(loop_start_breakpoint_addr, thread.selected_frame.GetPC()) diff --git a/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/main.c b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/main.c new file mode 100644 index 0000000000000..4c9100dda604a --- /dev/null +++ b/lldb/test/API/functionalities/breakpoint/write_over_software_breakpoint/main.c @@ -0,0 +1,34 @@ +volatile int always_false; + +int foo() { + // This nop is deliberately dead code so that we have an instruction + // immediately prior to the breakpoint that we are allowed to corrupt. + if (always_false) { + // This dead code must be at least 4 bytes even on an architecture where + // nop is 1 byte. + asm volatile("nop\n" + "nop\n" + "nop\n" + "nop\n"); + } + // We are assuming that there are no instructions placed between the dead code + // above and the assembly below. This is not guaranteed but it's safer than + // assuming that this function will have no prologue instructions or breaking + // in the very first instruction of foo and hoping whatever comes before is + // not important. + asm volatile(".globl place_break_here\n" + "place_break_here:\n" + // The test will repeatedly add and remove a breakpoint here. + "nop"); + + return 0; +} + +int main() { + volatile int sum = 0; + // Pick a number of loops >= the number of write patterns being tested. + for (unsigned i = 0; i < 10; ++i) + sum += foo(); // break here + + return 0; +} diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp index 174ab1a0143d4..e6ed3b695452c 100644 --- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp +++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.cpp @@ -65,8 +65,9 @@ Status ProcessMockAccelerator::ReadMemory(lldb::addr_t addr, void *buf, return Status::FromErrorString("unimplemented"); } -Status ProcessMockAccelerator::WriteMemory(lldb::addr_t addr, const void *buf, - size_t size, size_t &bytes_written) { +Status ProcessMockAccelerator::DoWriteMemory(lldb::addr_t addr, const void *buf, + size_t size, + size_t &bytes_written) { bytes_written = 0; return Status::FromErrorString("unimplemented"); } diff --git a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h index 6fc07b5ac011a..d475e0d983775 100644 --- a/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h +++ b/lldb/tools/lldb-server/Plugins/Accelerator/Mock/ProcessMockAccelerator.h @@ -42,8 +42,8 @@ class ProcessMockAccelerator : public NativeProcessProtocol { Status ReadMemory(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read) override; - Status WriteMemory(lldb::addr_t addr, const void *buf, size_t size, - size_t &bytes_written) override; + Status DoWriteMemory(lldb::addr_t addr, const void *buf, size_t size, + size_t &bytes_written) override; lldb::addr_t GetSharedLibraryInfoAddress() override; size_t UpdateThreads() override; diff --git a/lldb/unittests/Host/NativeProcessProtocolTest.cpp b/lldb/unittests/Host/NativeProcessProtocolTest.cpp index 91c4fd69d6e54..e5756d1db9f10 100644 --- a/lldb/unittests/Host/NativeProcessProtocolTest.cpp +++ b/lldb/unittests/Host/NativeProcessProtocolTest.cpp @@ -238,3 +238,267 @@ TEST(NativeProcessProtocolTest, ReadCStringFromMemory_CrossPageBoundary) { llvm::HasValue(llvm::StringRef("hello"))); EXPECT_EQ(bytes_read, 6UL); } + +void DoTestWriteMemoryPreservingTrap( + const std::vector<lldb::addr_t> &bp_addrs, + std::optional<lldb::addr_t> write_addr, + const std::vector<uint8_t> &write_data, uint32_t expected_number_of_writes, + const std::vector<uint8_t> expected_after_write_read_memory, + const std::vector<uint8_t> expected_after_write_read_memory_without_trap) { + NiceMock<MockDelegate> DummyDelegate; + MockProcess<NativeProcessProtocol> Process(DummyDelegate, + ArchSpec("aarch64-pc-linux")); + const std::vector<uint8_t> fake_memory{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + FakeMemory M{fake_memory}; + ON_CALL(Process, ReadMemory(_, _)) + .WillByDefault(Invoke(&M, &FakeMemory::Read)); + ON_CALL(Process, WriteMemory(_, _)) + .WillByDefault(Invoke(&M, &FakeMemory::Write)); + + for (auto bp_addr : bp_addrs) + EXPECT_THAT_ERROR( + Process.SetBreakpoint(bp_addr, 0, /*hardware=*/false).ToError(), + llvm::Succeeded()); + + if (expected_number_of_writes) + EXPECT_CALL(Process, WriteMemory(_, _)) + .Times(expected_number_of_writes) + .WillRepeatedly(DoDefault()); + else + EXPECT_CALL(Process, WriteMemory(_, _)).Times(0); + + if (write_addr) { + size_t bytes_written = 0; + Status err = Process.WriteMemory(*write_addr, write_data.data(), + write_data.size(), bytes_written); + EXPECT_THAT_ERROR(err.ToError(), llvm::Succeeded()); + EXPECT_EQ(bytes_written, write_data.size()); + } + + Mock::VerifyAndClearExpectations(&Process); + + // Anything written over a breakpoint should go into the saved bytes instead + // of into memory. + + // ReadMemory should show that the breakpoint instructions are unchanged. + auto memory_or_err = Process.ReadMemory(0, fake_memory.size()); + EXPECT_THAT_EXPECTED(memory_or_err, llvm::Succeeded()); + EXPECT_EQ(*memory_or_err, expected_after_write_read_memory); + + // ReadMemoryWithoutTrap should show that writes to the breakpoints have + // updated the saved data in the breakpoint. + memory_or_err = Process.ReadMemoryWithoutTrap(0, fake_memory.size()); + EXPECT_THAT_EXPECTED(memory_or_err, llvm::Succeeded()); + EXPECT_EQ(*memory_or_err, expected_after_write_read_memory_without_trap); + + // When the breakpoint is removed, the saved bytes are actually written + // to memory. + for (auto bp_addr : bp_addrs) + EXPECT_THAT_ERROR(Process.RemoveBreakpoint(bp_addr, false).ToError(), + llvm::Succeeded()); + + // The memory should contain the written data now. + memory_or_err = Process.ReadMemory(0, fake_memory.size()); + EXPECT_THAT_EXPECTED(memory_or_err, llvm::Succeeded()); + EXPECT_EQ(*memory_or_err, expected_after_write_read_memory_without_trap); + + // As there are no breakpoints, the result of ReadMemoryWithoutTrap should + // be the same. + memory_or_err = Process.ReadMemoryWithoutTrap(0, fake_memory.size()); + EXPECT_THAT_EXPECTED(memory_or_err, llvm::Succeeded()); + EXPECT_EQ(*memory_or_err, expected_after_write_read_memory_without_trap); +} + +void TestWriteMemoryPreservingTrap( + const std::vector<lldb::addr_t> &bp_addrs, + std::optional<lldb::addr_t> write_addr, + const std::vector<uint8_t> &write_data, uint32_t expected_number_of_writes, + const std::vector<uint8_t> expected_after_write_read_memory, + const std::vector<uint8_t> expected_after_write_read_memory_without_trap) { + auto bp_addrs_in_order = bp_addrs; + DoTestWriteMemoryPreservingTrap( + bp_addrs_in_order, write_addr, write_data, expected_number_of_writes, + expected_after_write_read_memory, + expected_after_write_read_memory_without_trap); + + // WriteMemoryPreservingTrap should not care in what order the breakpoints + // were inserted. + if (bp_addrs_in_order.size()) { + std::reverse(bp_addrs_in_order.begin(), bp_addrs_in_order.end()); + DoTestWriteMemoryPreservingTrap( + bp_addrs_in_order, write_addr, write_data, expected_number_of_writes, + expected_after_write_read_memory, + expected_after_write_read_memory_without_trap); + } +} + +TEST(NativeProcessProtocolTest, WriteMemoryPreservingTrap) { +// Software breakpoint instruction encoding for AArch64. +#define S__W__B__P 0x0, 0x0, 0x20, 0xd4 + + // In these tests, numbers are used for the initial memory contents and + // letters for the data being written. These variables are used for letters + // so that the inputs can be vertically aligned. + uint8_t a = 'a'; + uint8_t b = 'b'; + uint8_t c = 'c'; + uint8_t d = 'd'; + uint8_t e = 'e'; + uint8_t f = 'f'; + uint8_t g = 'g'; + uint8_t h = 'h'; + uint8_t i = 'i'; + uint8_t j = 'j'; + uint8_t k = 'k'; + + // Write nothing, set no breakpoints, nothing changes. + TestWriteMemoryPreservingTrap({}, std::nullopt, {}, 0, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + // Write nothing, set a breakpoint. Breakpoint encoding should be visible in + // memory. + TestWriteMemoryPreservingTrap({0}, std::nullopt, {}, 0, + {S__W__B__P, 4, 5, 6, 7, 8, 9, 10}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + // 0 size write with no breakpoints set. + TestWriteMemoryPreservingTrap({}, 0, {}, 0, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + // 0 size write not within a breakpoint. + TestWriteMemoryPreservingTrap({4}, 0, {}, 0, + {0, 1, 2, 3, S__W__B__P, 8, 9, 10}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + // 0 size write at start of a breakpoint. + TestWriteMemoryPreservingTrap({4}, 4, {}, 0, + {0, 1, 2, 3, S__W__B__P, 8, 9, 10}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + // 0 size write at end of a breakpoint. + TestWriteMemoryPreservingTrap({4}, 7, {}, 0, + {0, 1, 2, 3, S__W__B__P, 8, 9, 10}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + // 0 size write one beyond the end of a breakpoint. + TestWriteMemoryPreservingTrap({4}, 8, {}, 0, + {0, 1, 2, 3, S__W__B__P, 8, 9, 10}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + // Write something but set no breakpoints. Data is written directly to memory. + TestWriteMemoryPreservingTrap({}, 2, {a, b}, 1, + {0, 1, a, b, 4, 5, 6, 7, 8, 9, 10}, + {0, 1, a, b, 4, 5, 6, 7, 8, 9, 10}); + + // Write before a breakpoint and do not overlap it. + TestWriteMemoryPreservingTrap({4}, 1, {a, b, c}, 1, + {0, a, b, c, S__W__B__P, 8, 9, 10}, + {0, a, b, c, 4, 5, 6, 7, 8, 9, 10}); + + // Write within a breakpoint, without writing outside of it. Data kept in + // saved bytes until removal. + TestWriteMemoryPreservingTrap({4}, 4, {a, b, c, d}, 0, + {0, 1, 2, 3, S__W__B__P, 8, 9, 10}, + {0, 1, 2, 3, a, b, c, d, 8, 9, 10}); + + // Write immediately after a breakpoint. Data is written directly to memory. + TestWriteMemoryPreservingTrap({2}, 6, {a, b, c}, 1, + {0, 1, S__W__B__P, a, b, c, 9, 10}, + {0, 1, 2, 3, 4, 5, a, b, c, 9, 10}); + + // Write a range overlapping the beginning of a breakpoint. First part will + // go direct to memory, the other to the saved bytes. + TestWriteMemoryPreservingTrap({2}, 0, {a, b, c, d}, 1, + {a, b, S__W__B__P, 6, 7, 8, 9, 10}, + {a, b, c, d, 4, 5, 6, 7, 8, 9, 10}); + + // Write overlapping end of breakpoint. First part goes to saved bytes, + // second part direct to memory. + TestWriteMemoryPreservingTrap({3}, 5, {a, b, c, d}, 1, + {0, 1, 2, S__W__B__P, c, d, 9, 10}, + {0, 1, 2, 3, 4, a, b, c, d, 9, 10}); + + // Write from before to after break. Ends go to memory, middle to saved bytes. + TestWriteMemoryPreservingTrap({2}, 0, {a, b, c, d, e, f, g, h}, 2, + {a, b, S__W__B__P, g, h, 8, 9, 10}, + {a, b, c, d, e, f, g, h, 8, 9, 10}); + + // Overlap a breakpoint at the very start of memory. + TestWriteMemoryPreservingTrap({0}, 0, {a, b, c, d, e, f}, 1, + {S__W__B__P, e, f, 6, 7, 8, 9, 10}, + {a, b, c, d, e, f, 6, 7, 8, 9, 10}); + + // Overlap a breakpoint at the very end of memory. + TestWriteMemoryPreservingTrap({7}, 5, {a, b, c, d, e, f}, 1, + {0, 1, 2, 3, 4, a, b, S__W__B__P}, + {0, 1, 2, 3, 4, a, b, c, d, e, f}); + + // Write up to a breakpoint. + TestWriteMemoryPreservingTrap({5}, 1, {a, b, c, d}, 1, + {0, a, b, c, d, S__W__B__P, 9, 10}, + {0, a, b, c, d, 5, 6, 7, 8, 9, 10}); + + // Write starting immediately after a breakpoint. + TestWriteMemoryPreservingTrap({2}, 6, {a, b, c, d}, 1, + {0, 1, S__W__B__P, a, b, c, d, 10}, + {0, 1, 2, 3, 4, 5, a, b, c, d, 10}); + + // Overlap 2 breakpoints, write extends before and after them. + TestWriteMemoryPreservingTrap({1, 6}, 0, {a, b, c, d, e, f, g, h, i, j, k}, 3, + {a, S__W__B__P, f, S__W__B__P, k}, + {a, b, c, d, e, f, g, h, i, j, k}); + + // Write starts within the first one, and ends after the second one. + TestWriteMemoryPreservingTrap({1, 6}, 2, {a, b, c, d, e, f, g, h, i}, 2, + {0, S__W__B__P, d, S__W__B__P, i}, + {0, 1, a, b, c, d, e, f, g, h, i}); + + // Write range from before first breakpoint to within second breakpoint. + TestWriteMemoryPreservingTrap({1, 6}, 0, {a, b, c, d, e, f, g, h, i}, 2, + {a, S__W__B__P, f, S__W__B__P, 10}, + {a, b, c, d, e, f, g, h, i, 9, 10}); + + // Write range from within first to beyond second. + TestWriteMemoryPreservingTrap({1, 6}, 2, {a, b, c, d, e, f, g, h, i}, 2, + {0, S__W__B__P, d, S__W__B__P, i}, + {0, 1, a, b, c, d, e, f, g, h, i}); + + // Write range from within first to within second. + TestWriteMemoryPreservingTrap({1, 6}, 2, {a, b, c, d, e, f, g}, 1, + {0, S__W__B__P, d, S__W__B__P, 10}, + {0, 1, a, b, c, d, e, f, g, 9, 10}); + + // Write in range between 2 breakpoints. + TestWriteMemoryPreservingTrap({0, 7}, 4, {a, b, c}, 1, + {S__W__B__P, a, b, c, S__W__B__P}, + {0, 1, 2, 3, a, b, c, 7, 8, 9, 10}); + + // 2 breakpoints with no gap between them, write overlaps the first one. + TestWriteMemoryPreservingTrap({2, 6}, 0, {a, b, c, d}, 1, + {a, b, S__W__B__P, S__W__B__P, 10}, + {a, b, c, d, 4, 5, 6, 7, 8, 9, 10}); + + // 2 breakpoints with no gap between them, write is within the first one. + TestWriteMemoryPreservingTrap({2, 6}, 3, {a, b}, 0, + {0, 1, S__W__B__P, S__W__B__P, 10}, + {0, 1, 2, a, b, 5, 6, 7, 8, 9, 10}); + + // 2 breakpoints with no gap between them, write is across both. + TestWriteMemoryPreservingTrap({2, 6}, 4, {a, b, c, d}, 0, + {0, 1, S__W__B__P, S__W__B__P, 10}, + {0, 1, 2, 3, a, b, c, d, 8, 9, 10}); + + // 2 breakpoints with no gap between them, write is within second one. + TestWriteMemoryPreservingTrap({2, 6}, 7, {a, b}, 0, + {0, 1, S__W__B__P, S__W__B__P, 10}, + {0, 1, 2, 3, 4, 5, 6, a, b, 9, 10}); + + // 2 breakpoints with no gap between them, write overlaps second one. + TestWriteMemoryPreservingTrap({2, 6}, 8, {a, b, c}, 1, + {0, 1, S__W__B__P, S__W__B__P, c}, + {0, 1, 2, 3, 4, 5, 6, 7, a, b, c}); + +#undef S__W__B__P +} diff --git a/lldb/unittests/TestingSupport/Host/NativeProcessTestUtils.h b/lldb/unittests/TestingSupport/Host/NativeProcessTestUtils.h index 1a017122411a8..005d199febaf1 100644 --- a/lldb/unittests/TestingSupport/Host/NativeProcessTestUtils.h +++ b/lldb/unittests/TestingSupport/Host/NativeProcessTestUtils.h @@ -83,10 +83,20 @@ template <typename T> class MockProcess : public T { return Status(); } - Status WriteMemory(addr_t Addr, const void *Buf, size_t Size, - size_t &BytesWritten) /*override*/ { + using T::WriteMemory; + + MOCK_METHOD2(ReadMemory, + llvm::Expected<std::vector<uint8_t>>(addr_t Addr, size_t Size)); + MOCK_METHOD2(WriteMemory, + llvm::Expected<size_t>(addr_t Addr, + llvm::ArrayRef<uint8_t> Data)); + + Status DoWriteMemory(addr_t Addr, const void *Buf, size_t Size, + size_t &BytesWritten) /*override*/ { auto ExpectedBytes = this->WriteMemory( - Addr, llvm::ArrayRef(static_cast<const uint8_t *>(Buf), Size)); + Addr, + llvm::ArrayRef(const_cast<uint8_t *>(static_cast<const uint8_t *>(Buf)), + Size)); if (!ExpectedBytes) { BytesWritten = 0; return Status::FromError(ExpectedBytes.takeError()); @@ -95,12 +105,6 @@ template <typename T> class MockProcess : public T { return Status(); } - MOCK_METHOD2(ReadMemory, - llvm::Expected<std::vector<uint8_t>>(addr_t Addr, size_t Size)); - MOCK_METHOD2(WriteMemory, - llvm::Expected<size_t>(addr_t Addr, - llvm::ArrayRef<uint8_t> Data)); - using T::GetSoftwareBreakpointTrapOpcode; llvm::Expected<std::vector<uint8_t>> ReadMemoryWithoutTrap(addr_t Addr, size_t Size) { >From a8f070829d97fc1095b3298a3111c09ce68342fa Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Fri, 21 Aug 2026 16:09:46 +0100 Subject: [PATCH 04/11] [lldb][lldb-server] Fix compilation on 32-bit (#217934) Fixes #217348 / a132493da2f60a08cc66c75fa0dd12ccb3fe9c26 NativeProcessProtocol.cpp:679:53: error: non-constant-expression cannot be narrowed from type 'unsigned long long' to 'size_t' (aka 'unsigned int') in initializer list [-Wc++11-narrowing] 679 | const size_t to_write = std::min(size, size_t{sbp_addr - addr}); | ^~~~~~~~~~~~~~~ On 32-bit, size_t is 32-bit but addr_t is still a 64-bit type. sbp_addr and addr are addr_t, so it makes more sense to cast size to that. Which is a widening on 32-bit and a nop on 64-bit. --- lldb/source/Host/common/NativeProcessProtocol.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp index b996424541907..2d905d0539801 100644 --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -676,7 +676,8 @@ Status NativeProcessProtocol::WriteMemory(lldb::addr_t addr, const void *buf, // If the address is before a breakpoint site, write up to the site, or to // the end of the write. Whichever comes first. if (addr < sbp_addr) { - const size_t to_write = std::min(size, size_t{sbp_addr - addr}); + const size_t to_write = + std::min(static_cast<addr_t>(size), sbp_addr - addr); size_t part_bytes_written = 0; error = DoWriteMemory(addr, byte_buf, to_write, part_bytes_written); bytes_written += part_bytes_written; >From ce9e5c551cb97dc7e3f68ab0da6ae7b206b274be Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Mon, 24 Aug 2026 09:25:58 +0100 Subject: [PATCH 05/11] [lldb][debugserver] Handle breakpoint prior to addr in RemoveTrapsFromBuffer (#217851) Fixes #217840 lower_bound finds the first breakpoint that starts >= addr. There might be a breakpoint before that which starts before addr but extends past addr. Therefore it might need to be patched out of the buffer. This fix is intentionally minimal as I don't have a Mac to test it in. A proper fix would reuse FindBreakpointsThatOverlapRange, which also has this bug but is getting fixed. The problem with that is that RemoveTrapsFromBuffer is const, and FindBreakpointsThatOverlapRange returns non const pointers to the breakpoints. Not super complex to fix but more than I want to do at a distance. --- lldb/tools/debugserver/source/DNBBreakpoint.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lldb/tools/debugserver/source/DNBBreakpoint.cpp b/lldb/tools/debugserver/source/DNBBreakpoint.cpp index 74f0fb17129f3..531a049bfd803 100644 --- a/lldb/tools/debugserver/source/DNBBreakpoint.cpp +++ b/lldb/tools/debugserver/source/DNBBreakpoint.cpp @@ -171,9 +171,18 @@ void DNBBreakpointList::DisableAll() { void DNBBreakpointList::RemoveTrapsFromBuffer(nub_addr_t addr, nub_size_t size, void *p) const { + if (m_breakpoints.empty()) + return; + uint8_t *buf = (uint8_t *)p; const_iterator end = m_breakpoints.end(); const_iterator pos = m_breakpoints.lower_bound(addr); + + // lower_bound finds a breakpoint starting >= addr. The breakpoint prior to + // that may start before addr but extend beyond it, so it must be checked too. + if (pos != m_breakpoints.begin()) + --pos; + while (pos != end && (pos->first < (addr + size))) { nub_addr_t intersect_addr; nub_size_t intersect_size; >From f36d2d4e31768c35ca132bef3221197c9b92e880 Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Mon, 24 Aug 2026 09:26:21 +0100 Subject: [PATCH 06/11] [lldb][debugserver] Fix bugs in FindBreakpointsThatOverlapRange (#217837) Fixes #217359 The first issue is obvious, when checking the previous breakpoint we should push back prev_pos->second, instead of pos->second. The second problem is what happens when lower_bound returns end(). Before, `if (pos != end)` would stop us checking the previous breakpoint. If lower_bound returned end() but there were breakpoints, the last breakpoint may start before addr and extend past it. This was being missed. To fix that: * Remove the `if (pos != end)` check. * Return early if there are no breakpoints. * If lower_bound does not find the first one, look at the previous one. * Look at the rest from pos onwards. The earlly return means we know that m_breakpoints.begin() != m_breakpoints.end() and so even if lower_bound returns end(), we are safe to decrement that iterator. I think this will fix the API tests in #217348, when those are enabled from debugserver. --- .../debugserver/source/DNBBreakpoint.cpp | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/lldb/tools/debugserver/source/DNBBreakpoint.cpp b/lldb/tools/debugserver/source/DNBBreakpoint.cpp index 531a049bfd803..ce390a85f54e0 100644 --- a/lldb/tools/debugserver/source/DNBBreakpoint.cpp +++ b/lldb/tools/debugserver/source/DNBBreakpoint.cpp @@ -125,34 +125,38 @@ DNBBreakpointList::FindNearestWatchpoint(nub_addr_t addr) const { size_t DNBBreakpointList::FindBreakpointsThatOverlapRange( nub_addr_t addr, nub_addr_t size, std::vector<DNBBreakpoint *> &bps) { bps.clear(); + + if (m_breakpoints.empty()) + return bps.size(); + iterator end = m_breakpoints.end(); // Find the first breakpoint with an address >= to "addr" iterator pos = m_breakpoints.lower_bound(addr); - if (pos != end) { - if (pos != m_breakpoints.begin()) { - // Watch out for a breakpoint at an address less than "addr" that might - // still overlap - iterator prev_pos = pos; - --prev_pos; - if (prev_pos->second.IntersectsRange(addr, size, NULL, NULL, NULL)) - bps.push_back(&pos->second); - } - while (pos != end) { - // When we hit a breakpoint whose start address is greater than "addr + - // size" we are done. - // Do the math in a way that doesn't risk unsigned overflow with bad - // input. - if ((pos->second.Address() - addr) >= size) - break; + if (pos != m_breakpoints.begin()) { + // Watch out for a breakpoint at an address less than "addr" that might + // still overlap + iterator prev_pos = pos; + --prev_pos; + if (prev_pos->second.IntersectsRange(addr, size, NULL, NULL, NULL)) + bps.push_back(&prev_pos->second); + } + + while (pos != end) { + // When we hit a breakpoint whose start address is greater than "addr + + // size" we are done. + // Do the math in a way that doesn't risk unsigned overflow with bad + // input. + if ((pos->second.Address() - addr) >= size) + break; - // Check if this breakpoint overlaps, and if it does, add it to the list - if (pos->second.IntersectsRange(addr, size, NULL, NULL, NULL)) - bps.push_back(&pos->second); + // Check if this breakpoint overlaps, and if it does, add it to the list + if (pos->second.IntersectsRange(addr, size, NULL, NULL, NULL)) + bps.push_back(&pos->second); - ++pos; - } + ++pos; } + return bps.size(); } >From a5f852ee5054148746662ab276fe356dc042e61d Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Mon, 24 Aug 2026 10:15:56 +0100 Subject: [PATCH 07/11] [lldb] Handle 0 size sites in StopPointSiteList::FindInRange (#217919) Fixes #205120 In which due to delayed breakpoints, a breakpoint that would become an external breakpoint later (meaning managed by the debug server) was temporarily stored as a software breakpoint (which is managed by lldb) with a zero size breakpoint site. That zero site site tripped an assertion when you tried to write over the site. To fix this, I've explicitly ignored zero size sites in FindInRange by defining them as never overlapping. FindInRange is only used for patching reads and writes, so I think this is safe to do. I have documented this in the docstring. I considered adding a breakpoint type "uncommitted", but software breakpoints are actually handled in the most conservative manner (reads and writes are always patched). So I think as a default it's fine (also I don't want to go and audit all the places that use that enum and end up with a way larger fix for this issue). Though I have a few debugserver fixes in flight, since this is a fix in lldb, I think the added API test will work for lldb-server and debugserver. --- .../lldb/Breakpoint/StopPointSiteList.h | 11 ++- .../TestWriteOverSoftwareBreakpoint.py | 88 +++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/lldb/include/lldb/Breakpoint/StopPointSiteList.h b/lldb/include/lldb/Breakpoint/StopPointSiteList.h index 101eccda4616b..088c2b3c1ee1d 100644 --- a/lldb/include/lldb/Breakpoint/StopPointSiteList.h +++ b/lldb/include/lldb/Breakpoint/StopPointSiteList.h @@ -182,6 +182,9 @@ template <typename StopPointSite> class StopPointSiteList { return false; } + /// Find breakpoint sites that in any way overlap the range starting at + /// \a lower_bound and ending at \a upper_bound (but not including it). + /// Zero sized sites are treated as never overlapping. bool FindInRange(lldb::addr_t lower_bound, lldb::addr_t upper_bound, StopPointSiteList &bp_site_list) const { if (lower_bound > upper_bound) @@ -200,14 +203,18 @@ template <typename StopPointSite> class StopPointSiteList { typename collection::const_iterator prev_pos = lower; prev_pos--; const StopPointSiteSP &prev_site = (*prev_pos).second; - if (prev_site->GetLoadAddress() + prev_site->GetByteSize() > lower_bound) + if (prev_site->GetByteSize() != 0 && + (prev_site->GetLoadAddress() + prev_site->GetByteSize() > + lower_bound)) bp_site_list.Add(prev_site); } upper = m_site_list.upper_bound(upper_bound); for (pos = lower; pos != upper; pos++) - bp_site_list.Add((*pos).second); + if (pos->second->GetByteSize() != 0) + bp_site_list.Add(pos->second); + return true; } 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 4d33382fe3bd9..6b9f599ef2bc2 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 @@ -159,3 +159,91 @@ def test_write_over_breakpoint(self): self.assertState(process.GetState(), lldb.eStateStopped) self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint) 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() + ) + + bkpt = target.BreakpointCreateByName("foo") + self.assertTrue(bkpt.IsValid()) + self.assertEqual(bkpt.GetNumLocations(), 1) + self.assertFalse(bkpt.IsHardware()) + + # At this point only lldb knows about the breakpoint, it has not been + # sent to the debug server yet. It is treated as software with a 0 size + # breakpoint site. + + bkpt_address = bkpt.GetLocationAtIndex(0).GetLoadAddress() + # The largest breakpoint instruction we know of is 4 bytes. + read_size = 4 + err = lldb.SBError() + original_data = bytearray(process.ReadMemory(bkpt_address, read_size, err)) + self.assertSuccess(err) + self.assertEqual(len(original_data), read_size) + + # The smallest break instruction we know about is 1 byte, so we will + # write only 1 byte. The value is ideally != byte 1 of the break and + # != to the value currently in memory. 0xcd is different to x86's 0xcc, + # and not used by any other platform we support. 0xdc is the fallback + # and if the original value is also 0xcd. + write_data = bytearray([0xCD if original_data[0] != 0xCD else 0xDC]) + + # Write something over the breakpoint. Use a single byte since x86's + # break is a single byte and we do not want to corrupt other code. + # LLDB used to crash at this point. + err = lldb.SBError() + wrote = process.WriteMemory(bkpt_address, write_data, err) + self.assertSuccess(err) + self.assertEqual(wrote, 1) + + def check_memory(): + err = lldb.SBError() + got = bytearray(process.ReadMemory(bkpt_address, read_size, err)) + self.assertSuccess(err) + self.assertEqual(len(got), read_size) + read_expected = write_data + original_data[1:] + self.assertEqual(got, read_expected) + + # We should see the original data with our new single byte at the start. + check_memory() + + # The instruction in memory should still be intact so we can continue + # to the breakpoint. + process.Continue() + + thread = process.thread[0] + self.assertState(process.GetState(), lldb.eStateStopped) + self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint) + # Should be stopped at the breakpoint we placed in foo. This proves that + # the breakpoint instruction was intact. + self.assertEqual( + bkpt_address, + thread.selected_frame.GetPC(), + ) + + # We should see the new first byte still. At this point the debug server + # should be managing the breakpoint, but this checks that the handover + # was done correctly. + check_memory() >From 1859194e790c9865d5402fae366b50ca61bb1c75 Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Mon, 24 Aug 2026 10:50:30 +0100 Subject: [PATCH 08/11] [lldb][test] Enable TestWriteOverSoftwareBreakpoint for debugserver (#218362) Both the mentioned issues have been fixed. --- .../TestWriteOverSoftwareBreakpoint.py | 4 ---- 1 file changed, 4 deletions(-) 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 6b9f599ef2bc2..2abae29c837ea 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 @@ -15,10 +15,6 @@ class WriteOverSoftwareBreakpoint(TestBase): # Could not find a way to make place_break_here visible to lldb on Windows. @skipIfWindows - # debugserver needs fixing, see: - # https://github.com/llvm/llvm-project/issues/217359 - # https://github.com/llvm/llvm-project/issues/217840 - @llgs_test def test_write_over_breakpoint(self): TestBase.setUp(self) self.line = line_number("main.c", "// break here") >From cb012d986d8aa58dbc99c845ec55d7d1031f24f5 Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Tue, 25 Aug 2026 09:30:31 +0100 Subject: [PATCH 09/11] [lldb][test] Disable two breakpoint tests for debugserver (#218636) These are failing in Green Dragon's LLDB:Sanitized build. --- .../TestWriteMemoryWithHWBreakpoint.py | 2 ++ .../TestWriteOverSoftwareBreakpoint.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py index 341ef444c66e0..1fb9060902b16 100644 --- a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py +++ b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py @@ -14,6 +14,8 @@ class WriteMemoryWithHWBreakpoint(HardwareBreakpointTestBase): @skipTestIfFn(HardwareBreakpointTestBase.hw_breakpoints_unsupported) + # Fails with a sanitized build of debugserver. + @llgs_test def test_write_memory_with_hw_break(self): self.build() exe = self.getBuildArtifact("a.out") 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 2abae29c837ea..471684f61e152 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 @@ -15,6 +15,8 @@ class WriteOverSoftwareBreakpoint(TestBase): # Could not find a way to make place_break_here visible to lldb on Windows. @skipIfWindows + # Fails with a sanitized build of debugserver. + @llgs_test def test_write_over_breakpoint(self): TestBase.setUp(self) self.line = line_number("main.c", "// break here") >From abeabec51b8c2902bf365d927cecec6656b6b2ec Mon Sep 17 00:00:00 2001 From: David Spickett <[email protected]> Date: Thu, 3 Sep 2026 09:37:49 +0100 Subject: [PATCH 10/11] Reland "[lldb][test] Disable two breakpoint tests for debugserver" (#220845) Reverts llvm/llvm-project#220615 These tests are still failing in Swift CI despite apparently working at desk: https://ci.swift.org/view/all/job/llvm.org/job/lldb-cmake-sanitized/1529/ Tracking this in https://github.com/llvm/llvm-project/issues/220844. --- .../TestWriteMemoryWithHWBreakpoint.py | 2 +- .../TestWriteOverSoftwareBreakpoint.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py index 1fb9060902b16..de90f8f65e218 100644 --- a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py +++ b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py @@ -14,7 +14,7 @@ class WriteMemoryWithHWBreakpoint(HardwareBreakpointTestBase): @skipTestIfFn(HardwareBreakpointTestBase.hw_breakpoints_unsupported) - # Fails with a sanitized build of debugserver. + # Fails with a sanitized build of debugserver. https://github.com/llvm/llvm-project/issues/220844 @llgs_test def test_write_memory_with_hw_break(self): self.build() 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 471684f61e152..d023624454443 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 @@ -15,7 +15,7 @@ class WriteOverSoftwareBreakpoint(TestBase): # Could not find a way to make place_break_here visible to lldb on Windows. @skipIfWindows - # Fails with a sanitized build of debugserver. + # Fails with a sanitized build of debugserver. https://github.com/llvm/llvm-project/issues/220844 @llgs_test def test_write_over_breakpoint(self): TestBase.setUp(self) >From 8c3d36216b17e73b2f46e8102ce20c4959720781 Mon Sep 17 00:00:00 2001 From: Felipe de Azevedo Piovezan <[email protected]> Date: Thu, 3 Sep 2026 08:52:30 -0400 Subject: [PATCH 11/11] [lldb] Update decorator in write-over-breakpoint tests (#220919) sanitized bots don't use the just-built debugserver, so any tests exercising new debugserver features won't work on them. --- .../TestWriteMemoryWithHWBreakpoint.py | 3 +-- .../TestWriteOverSoftwareBreakpoint.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py index de90f8f65e218..402c152c05224 100644 --- a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py +++ b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/write_memory_with_hw_breakpoint/TestWriteMemoryWithHWBreakpoint.py @@ -14,8 +14,7 @@ class WriteMemoryWithHWBreakpoint(HardwareBreakpointTestBase): @skipTestIfFn(HardwareBreakpointTestBase.hw_breakpoints_unsupported) - # Fails with a sanitized build of debugserver. https://github.com/llvm/llvm-project/issues/220844 - @llgs_test + @skipIfOutOfTreeDebugserver def test_write_memory_with_hw_break(self): self.build() exe = self.getBuildArtifact("a.out") 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 d023624454443..1821e0fbfe7f3 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 @@ -15,8 +15,7 @@ class WriteOverSoftwareBreakpoint(TestBase): # Could not find a way to make place_break_here visible to lldb on Windows. @skipIfWindows - # Fails with a sanitized build of debugserver. https://github.com/llvm/llvm-project/issues/220844 - @llgs_test + @skipIfOutOfTreeDebugserver def test_write_over_breakpoint(self): TestBase.setUp(self) self.line = line_number("main.c", "// break here") _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
