[debugserver] Implement MultiBreakpoint (#192914)
This implements the packet as described in https://github.com/llvm/llvm-project/pull/192910 The following PRs are related to the MultiBreakpoint feature: * https://github.com/llvm/llvm-project/pull/192910 * https://github.com/llvm/llvm-project/pull/192914 * https://github.com/llvm/llvm-project/pull/192915 * https://github.com/llvm/llvm-project/pull/192919 * https://github.com/llvm/llvm-project/pull/192962 * https://github.com/llvm/llvm-project/pull/192964 * https://github.com/llvm/llvm-project/pull/192971 * https://github.com/llvm/llvm-project/pull/192988
This commit is contained in:
committed by
GitHub
parent
dd383b4610
commit
314c655470
3
lldb/test/API/functionalities/multi-breakpoint/Makefile
Normal file
3
lldb/test/API/functionalities/multi-breakpoint/Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
C_SOURCES := main.c
|
||||
|
||||
include Makefile.rules
|
||||
@@ -0,0 +1,204 @@
|
||||
"""
|
||||
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 *
|
||||
|
||||
|
||||
@skipUnlessDarwin # Remove once lldbsever support is implemented.
|
||||
@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"])
|
||||
7
lldb/test/API/functionalities/multi-breakpoint/main.c
Normal file
7
lldb/test/API/functionalities/multi-breakpoint/main.c
Normal file
@@ -0,0 +1,7 @@
|
||||
void func_a() {}
|
||||
void func_b() {}
|
||||
void func_c() {}
|
||||
|
||||
int main() {
|
||||
return 0; // break here
|
||||
}
|
||||
@@ -258,6 +258,8 @@ public:
|
||||
|
||||
Size GetNumElements();
|
||||
|
||||
const Vector &Elements() const { return m_elements; }
|
||||
|
||||
~JSONArray() override = default;
|
||||
|
||||
Vector m_elements;
|
||||
|
||||
@@ -481,6 +481,9 @@ void RNBRemote::CreatePacketTable() {
|
||||
"jGetSharedCacheInfo", "Replies with JSON data about the "
|
||||
"location and uuid of the shared "
|
||||
"cache in the inferior process."));
|
||||
t.push_back(Packet(
|
||||
json_multi_breakpoint, &RNBRemote::HandlePacket_jMultiBreakpoint, NULL,
|
||||
"jMultiBreakpoint", "Set/remove multiple breakpoints at once"));
|
||||
t.push_back(Packet(start_noack_mode, &RNBRemote::HandlePacket_QStartNoAckMode,
|
||||
NULL, "QStartNoAckMode",
|
||||
"Request that " DEBUGSERVER_PROGRAM_NAME
|
||||
@@ -3306,6 +3309,73 @@ rnb_err_t RNBRemote::HandlePacket_MultiMemRead(const char *p) {
|
||||
return SendPacket(reply_stream.str());
|
||||
}
|
||||
|
||||
rnb_err_t RNBRemote::HandlePacket_jMultiBreakpoint(const char *p) {
|
||||
const std::string_view packet_name("jMultiBreakpoint:");
|
||||
std::string_view packet(p);
|
||||
|
||||
if (!starts_with(packet, packet_name))
|
||||
return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
|
||||
"Invalid MultiBreakpoint packet prefix");
|
||||
|
||||
packet.remove_prefix(packet_name.size());
|
||||
|
||||
JSONParser parser(packet.cbegin());
|
||||
JSONValue::SP parsed = parser.ParseJSONValue();
|
||||
if (!parsed || parsed->GetKind() != JSONValue::Kind::Object)
|
||||
return HandlePacket_ILLFORMED(
|
||||
__FILE__, __LINE__, p,
|
||||
"MultiBreakpoint did not contain a JSON dictionary");
|
||||
|
||||
auto *request_dict = static_cast<JSONObject *>(parsed.get());
|
||||
JSONValue::SP request_array_sp =
|
||||
request_dict->GetObject("breakpoint_requests");
|
||||
|
||||
if (!request_array_sp ||
|
||||
request_array_sp->GetKind() != JSONValue::Kind::Array)
|
||||
return HandlePacket_ILLFORMED(
|
||||
__FILE__, __LINE__, p,
|
||||
"MultiBreakpoint did not contain a valid 'breakpoint_requests' field");
|
||||
|
||||
auto *request_array = static_cast<JSONArray *>(request_array_sp.get());
|
||||
std::vector<std::string> requests;
|
||||
requests.reserve(request_array->GetNumElements());
|
||||
for (JSONValue::SP value : request_array->Elements()) {
|
||||
if (!value || value->GetKind() != JSONValue::Kind::String)
|
||||
return HandlePacket_ILLFORMED(__FILE__, __LINE__, p,
|
||||
"MultiBreakpoint had a non-string entry");
|
||||
auto *request_str = static_cast<JSONString *>(value.get());
|
||||
requests.push_back(request_str->GetData());
|
||||
}
|
||||
|
||||
auto reply_array = std::make_shared<JSONGenerator::Array>();
|
||||
for (const std::string &request : requests) {
|
||||
BreakpointResult result = ExecuteBreakpointRequest(request.c_str());
|
||||
std::string reply_str;
|
||||
switch (result.kind) {
|
||||
case BreakpointResult::Kind::OK:
|
||||
reply_str = "OK";
|
||||
break;
|
||||
case BreakpointResult::Kind::Error: {
|
||||
char error_str[8];
|
||||
snprintf(error_str, sizeof(error_str), "E%02x", result.error_code);
|
||||
reply_str = error_str;
|
||||
break;
|
||||
}
|
||||
case BreakpointResult::Kind::IllFormed:
|
||||
case BreakpointResult::Kind::Unimplemented:
|
||||
reply_str = "E03";
|
||||
break;
|
||||
}
|
||||
reply_array->AddItem(std::make_shared<JSONGenerator::String>(reply_str));
|
||||
}
|
||||
|
||||
JSONGenerator::Dictionary reply_dict;
|
||||
reply_dict.AddItem("results", reply_array);
|
||||
std::ostringstream reply_stream;
|
||||
reply_dict.DumpBinaryEscaped(reply_stream);
|
||||
return SendPacket(reply_stream.str());
|
||||
}
|
||||
|
||||
// Read memory, sent it up as binary data.
|
||||
// Usage: xADDR,LEN
|
||||
// ADDR and LEN are both base 16.
|
||||
@@ -3660,6 +3730,7 @@ rnb_err_t RNBRemote::HandlePacket_qSupported(const char *p) {
|
||||
reply << "memory-tagging+;";
|
||||
|
||||
reply << "MultiMemRead+;";
|
||||
reply << "jMultiBreakpoint+;";
|
||||
return SendPacket(reply.str().c_str());
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ public:
|
||||
query_process_info, // 'qProcessInfo'
|
||||
json_query_thread_extended_info, // 'jThreadExtendedInfo'
|
||||
json_query_get_loaded_dynamic_libraries_infos, // 'jGetLoadedDynamicLibrariesInfos'
|
||||
json_multi_breakpoint, // 'jMultiBreakpoint'
|
||||
json_query_threads_info, // 'jThreadsInfo'
|
||||
json_query_get_shared_cache_info, // 'jGetSharedCacheInfo'
|
||||
pass_signals_to_inferior, // 'QPassSignals'
|
||||
@@ -190,6 +191,7 @@ public:
|
||||
rnb_err_t HandlePacket_jThreadExtendedInfo(const char *p);
|
||||
rnb_err_t HandlePacket_jGetLoadedDynamicLibrariesInfos(const char *p);
|
||||
rnb_err_t HandlePacket_jThreadsInfo(const char *p);
|
||||
rnb_err_t HandlePacket_jMultiBreakpoint(const char *p);
|
||||
rnb_err_t HandlePacket_jGetSharedCacheInfo(const char *p);
|
||||
rnb_err_t HandlePacket_qThreadExtraInfo(const char *p);
|
||||
rnb_err_t HandlePacket_qThreadStopInfo(const char *p);
|
||||
|
||||
Reference in New Issue
Block a user