[Lldb-commits] [lldb] [debugserver] Implement MultiBreakpoint (PR #192914)
David Spickett via lldb-commits
lldb-commits at lists.llvm.org
Fri Apr 24 09:02:40 PDT 2026
================
@@ -0,0 +1,191 @@
+"""
+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
+
+
+ at skipUnlessDarwin # Remove once lldbsever support is implemented.
+ at skipIfOutOfTreeDebugserver
+class TestMultiBreakpoint(TestBase):
+ def send_packet(self, packet_str):
+ self.runCmd(f"process plugin packet send '{packet_str}'", check=False)
+ output = self.res.GetOutput()
+ reply = output.split("\n")
+ # The output is of the form:
+ # packet: <packet_str>
+ # response: <response>
+ packet_line = None
+ response_line = None
+ for line in reply:
+ line = line.strip()
+ if line.startswith("packet:"):
+ packet_line = line
+ elif line.startswith("response:"):
+ response_line = line
+ self.assertIsNotNone(packet_line, f'No "packet:" line in output: {output}')
+ self.assertIsNotNone(response_line, f'No "response:" line in output: {output}')
+ return response_line[len("response:") :].strip()
+
+ def check_invalid_packet(self, packet_str):
+ reply = self.send_packet(packet_str)
+ if reply.startswith("E"):
+ return
+ else:
+ self.assertMultiResponse(reply, ["error"])
+
+ 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):
+ 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.
+ reply = self.send_packet("qSupported")
+ self.assertIn("jMultiBreakpoint+", reply)
+
+ 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"])
+
+ # --- 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", "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"])
----------------
DavidSpickett wrote:
One of these should be looking for [OK, error, OK] to check that the last one is executed. Fine to do that as part of an existing one, perhaps the set and remove in one packet test.
https://github.com/llvm/llvm-project/pull/192914
More information about the lldb-commits
mailing list