205 lines
7.8 KiB
Python
205 lines
7.8 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):
|
|
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.
|
|
|
|
# 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"])
|
|
|
|
# --- 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"])
|