This reapplies #195858 with a fix for 32-bit arm (and generally, any architecture that uses software single-stepping). The problem was that the temporary breakpoints used for single-stepping were interfering with the breakpoints set by the client. The fix is to check for existing breakpoints before setting the temporary ones. To achieve this, I've separated the notion of "next PC candidates for a thread" from "step breakpoints we've actually set". The freebsd code had some software single stepping code, but: - this was [introduced](https://reviews.llvm.org/D95802) for mips64 support, which was [removed](https://github.com/llvm/llvm-project/pull/179582) earlier this year - AFAICT, this never worked since the original patch only checked `m_threads_stepping_with_breakpoint`, but never set it to anything. This is why I'm removing the remnants of the single step support instead of trying to adapt it. The original commit message was: We did not say so explictly, but I'd argue that via https://github.com/llvm/llvm-project/pull/195815, we are supporting stubs which do not refcount breakpoints. In these stubs the set/clear breakpoint packets are idempotent: - setting a breakpoint for the second time is a no-op (returns OK) - clearing a breakpoint clears it, regardless of how many times it has been set - clearing a non-existent breakpoint (either because it was already cleared, or because it was never set) returns an error This makes lldb-server one of those stubs, which makes the code slightly simpler, but more importantly, ensures we do not regress this behavior.
197 lines
7.4 KiB
Python
197 lines
7.4 KiB
Python
"""
|
|
Tests the jMultiBreakpoint packet, this test runs against whichever debug server
|
|
the platform provides (debugserver on macOS, lldb-server elsewhere).
|
|
"""
|
|
|
|
import json
|
|
|
|
import lldb
|
|
from lldbsuite.test.decorators import *
|
|
from lldbsuite.test.lldbtest import *
|
|
from lldbsuite.test import lldbutil
|
|
from lldbsuite.test.gdbclientutils import *
|
|
|
|
|
|
@skipIfWindows # No server on Windows.
|
|
@skipIfOutOfTreeDebugserver
|
|
# Runs on systems where we can always predict the software break size
|
|
@skipIf(archs=no_match(["x86_64", "arm64", "aarch64"]))
|
|
class TestMultiBreakpoint(TestBase):
|
|
NO_DEBUG_INFO_TESTCASE = True
|
|
|
|
def check_invalid_packet(self, packet_str):
|
|
reply = lldbutil.send_packet_get_reply(self, packet_str)
|
|
if reply.startswith("E"):
|
|
return
|
|
else:
|
|
self.assertMultiResponse(reply, ["error"])
|
|
|
|
def send_packet(self, packet_str):
|
|
return lldbutil.send_packet_get_reply(self, packet_str)
|
|
|
|
def assertMultiResponse(self, reply, expected):
|
|
"""Assert a JSON-array multi-response matches the expected pattern.
|
|
|
|
Each element of `expected` is either 'OK' for an exact match, or
|
|
'error' to accept any error response (starting with 'E')."""
|
|
parts = json.loads(reply)["results"]
|
|
self.assertEqual(
|
|
len(parts),
|
|
len(expected),
|
|
f"Expected {len(expected)} responses, got {len(parts)}: {reply}",
|
|
)
|
|
for i, (actual, exp) in enumerate(zip(parts, expected)):
|
|
if exp == "OK":
|
|
self.assertEqual(
|
|
actual, "OK", f"Response {i}: expected OK, got {actual}"
|
|
)
|
|
elif exp == "error":
|
|
self.assertTrue(
|
|
actual.startswith("E"),
|
|
f"Response {i}: expected error, got {actual}",
|
|
)
|
|
else:
|
|
self.fail(f'Bad expected value "{exp}" at index {i}')
|
|
|
|
def get_function_address(self, name):
|
|
"""Return the hex address of a function as a string (no 0x prefix)."""
|
|
funcs = self.target.FindFunctions(name)
|
|
self.assertGreater(len(funcs), 0, f'Could not find function "{name}"')
|
|
addr = funcs[0].GetSymbol().GetStartAddress().GetLoadAddress(self.target)
|
|
self.assertNotEqual(addr, lldb.LLDB_INVALID_ADDRESS)
|
|
return f"{addr:x}"
|
|
|
|
def test_multi_breakpoint(self):
|
|
# Debugserver uses refcounted breakpoints
|
|
breakpoints_are_refcounted = self.platformIsDarwin()
|
|
|
|
self.build()
|
|
source_file = lldb.SBFileSpec("main.c")
|
|
self.target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
|
|
self, "break here", source_file
|
|
)
|
|
|
|
# Verify the server advertises jMultiBreakpoint support.
|
|
capabilities = lldbutil.get_qsupported_capabilities(self)
|
|
self.assertIn("jMultiBreakpoint+", capabilities)
|
|
|
|
addr_a = self.get_function_address("func_a")
|
|
addr_b = self.get_function_address("func_b")
|
|
addr_c = self.get_function_address("func_c")
|
|
|
|
# For breakpoint kind, use 4 on AArch64 (4-byte instruction), 1 elsewhere.
|
|
arch = self.getArchitecture()
|
|
if arch in ["arm64", "aarch64"]:
|
|
bp_kind = "4"
|
|
else:
|
|
bp_kind = "1"
|
|
|
|
# --- Malformed packets ---
|
|
# Very light error testing, as debugserver and lldb-server behave
|
|
# somewhat differently under malformed input.
|
|
|
|
# Empty body after colon.
|
|
self.check_invalid_packet("jMultiBreakpoint:")
|
|
# Not a dictionary
|
|
self.check_invalid_packet((f'jMultiBreakpoint:["Z0,{addr_a},{bp_kind}"]'))
|
|
|
|
def make_packet(array):
|
|
key = "breakpoint_requests"
|
|
json_str = json.dumps({key: array})
|
|
return f"jMultiBreakpoint: {json_str}"
|
|
|
|
# --- Set a single breakpoint ---
|
|
array = [f"Z0,{addr_a},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK"])
|
|
|
|
# --- Remove the breakpoint we just set ---
|
|
array = [f"z0,{addr_a},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK"])
|
|
|
|
# --- Set multiple breakpoints at once ---
|
|
array = [
|
|
f"Z0,{addr_a},{bp_kind}",
|
|
f"Z0,{addr_b},{bp_kind}",
|
|
f"Z0,{addr_c},{bp_kind}",
|
|
]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK", "OK", "OK"])
|
|
|
|
# --- Remove multiple breakpoints at once ---
|
|
array = [
|
|
f"z0,{addr_a},{bp_kind}",
|
|
f"z0,{addr_b},{bp_kind}",
|
|
f"z0,{addr_c},{bp_kind}",
|
|
]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK", "OK", "OK"])
|
|
|
|
# --- Mixed set and remove in one packet ---
|
|
# Set two breakpoints first.
|
|
array = [f"Z0,{addr_a},{bp_kind}", f"Z0,{addr_b},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK", "OK"])
|
|
|
|
# Remove one, set another, remove the other.
|
|
array = [
|
|
f"z0,{addr_a},{bp_kind}",
|
|
f"Z0,{addr_c},{bp_kind}",
|
|
f"z0,{addr_b},{bp_kind}",
|
|
]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK", "OK", "OK"])
|
|
|
|
# Clean up.
|
|
array = [f"z0,{addr_c},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK"])
|
|
|
|
# --- Set the same breakpoint twice
|
|
array = [f"Z0,{addr_a},{bp_kind}", f"Z0,{addr_a},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK", "OK"])
|
|
|
|
# Clean up both.
|
|
array = [f"z0,{addr_a},{bp_kind}", f"z0,{addr_a},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(
|
|
reply, ["OK", "OK" if breakpoints_are_refcounted else "error"]
|
|
)
|
|
|
|
# --- Set the same breakpoint twice, but remove it thrice.
|
|
array = [f"Z0,{addr_a},{bp_kind}", f"Z0,{addr_a},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK", "OK"])
|
|
array = [
|
|
f"z0,{addr_a},{bp_kind}",
|
|
f"z0,{addr_a},{bp_kind}",
|
|
f"z0,{addr_a},{bp_kind}",
|
|
]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(
|
|
reply, ["OK", "OK" if breakpoints_are_refcounted else "error", "error"]
|
|
)
|
|
|
|
# --- Set and remove the same address in a single packet ---
|
|
# The spec requires requests to be executed in order, so the set
|
|
# should succeed and the subsequent remove should find and clear it.
|
|
array = [f"Z0,{addr_a},{bp_kind}", f"z0,{addr_a},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK", "OK"])
|
|
|
|
# --- Remove a breakpoint that was never set ---
|
|
array = [f"z0,{addr_b},{bp_kind}"]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["error"])
|
|
|
|
# --- A failure in the middle should not prevent later requests from succeeding ---
|
|
array = [
|
|
f"Z0,{addr_a},{bp_kind}",
|
|
f"z0,{addr_b},{bp_kind}",
|
|
f"z0,{addr_a},{bp_kind}",
|
|
]
|
|
reply = self.send_packet(make_packet(array))
|
|
self.assertMultiResponse(reply, ["OK", "error", "OK"])
|