[Lldb-commits] [lldb] [debugserver] Implement MultiBreakpoint (PR #192914)

Felipe de Azevedo Piovezan via lldb-commits lldb-commits at lists.llvm.org
Mon Apr 27 07:06:42 PDT 2026


================
@@ -0,0 +1,203 @@
+"""
+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 *
+
+
+ at skipUnlessDarwin  # Remove once lldbsever support is implemented.
+ at skipIfOutOfTreeDebugserver
+ at skipIf(archs=no_match(["x86_64", "arm64", "aarch64"]))
+class TestMultiBreakpoint(TestBase):
+    def send_packet(self, packet_str):
+        packet_str = escape_binary(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.
----------------
felipepiovezan wrote:

It is pre-existing, basically the differences were in the code that handles individual `zZ` packet parsing (which we are re-using in both debugserver and lldb-server).

Example: when parsing the breakpoint size, lldb-server will use `StringExtractor::GetHexMaxU32`, whereas debugserver uses `strtoul(p, &c, 16);`:

```
const uint32_t size =
      packet.GetHexMaxU32(false, std::numeric_limits<uint32_t>::max());
  if (size == std::numeric_limits<uint32_t>::max())
    ....
```

If the input is empty, the above will _not_ return `max()`, it will return 0. So it never errors out.
By contrast:

```
  auto byte_size = strtoul(p, &c, 16);
  if (errno != 0 && byte_size == 0)
    return BreakpointResult::CreateIllFormed("Invalid length in z packet");
```

here we explicitly check for 0.

We should fix this (probably `lldb-server`) separately.

https://github.com/llvm/llvm-project/pull/192914


More information about the lldb-commits mailing list