Skip to content

Commit 9d05187

Browse files
authored
Merge pull request #11667 from felipepiovezan/felipe/multimem_cherry_picks_62
🍒 [lldb] Cherry pick patches for MultiMemoryRead packet (6.2)
2 parents d57002f + b499cca commit 9d05187

File tree

12 files changed

+457
-138
lines changed

12 files changed

+457
-138
lines changed

lldb/docs/resources/lldbgdbremote.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,57 @@ This is a performance optimization, which speeds up debugging by avoiding
735735
multiple round-trips for retrieving thread information. The information from this
736736
packet can be retrieved using a combination of `qThreadStopInfo` and `m` packets.
737737
738+
### MultiMemRead
739+
740+
Read memory from multiple memory ranges.
741+
742+
This packet has one argument:
743+
744+
* `ranges`: a list of pairs of numbers, formatted in base-16. Each pair is
745+
separated by a `,`, as is each number in each pair. The first number of the
746+
pair denotes the base address of the memory read, the second denotes the number
747+
of bytes to be read. The list must end with a `;`.
748+
749+
The reply packet starts with a comma-separated list of numbers formatted in
750+
base-16, denoting how many bytes were read from each range, in the same order
751+
as the request packet. The list is followed by a `;`, followed by a sequence of
752+
bytes containing binary encoded data for all memory that was read. The length
753+
of the binary encodeed data, after being decoded as required by the GDB remote
754+
protocol, must be equal to the sum of the numbers provided at the start of the
755+
reply. The order of the binary data is the same as the order of the ranges in
756+
the request packet.
757+
758+
If some part of a range is not readable, the stub may perform a partial read of
759+
a prefix of the range. In other words, partial reads will only ever be from the
760+
start of the range, never the middle or end. Support for partial reads depends
761+
on the debug stub.
762+
763+
If, by applying the rules above, the stub has read zero bytes from a range, it
764+
must return a length of zero for that range in the reply packet; no bytes for
765+
this memory range are included in the sequence of bytes that follows.
766+
767+
A stub that supports this packet must include `MultiMemRead+` in the reply to
768+
`qSupported`.
769+
770+
```
771+
send packet: $MultiMemRead:ranges:100a00,4,200200,a0,400000,4;
772+
read packet: $4,0,2;<binary encoding of abcd1000><binary encoding of eeff>
773+
```
774+
775+
In the example above, the first read produced `abcd1000`, the read of `a0`
776+
bytes from address `200200` failed to read any bytes, and the third read
777+
produced two bytes – `eeff` – out of the four requested.
778+
779+
```
780+
send packet: $MultiMemRead:ranges:100a00,0;
781+
read packet: $0;
782+
```
783+
784+
In the example above, a read of zero bytes was requested.
785+
786+
**Priority to Implement:** Only required for performance, the debugger will
787+
fall back to doing separate read requests if this packet is unavailable.
788+
738789
## QEnvironment:NAME=VALUE
739790
740791
Setup the environment up for a new child process that will soon be

lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,7 @@ def add_qSupported_packets(self, client_features=[]):
923923
"QNonStop",
924924
"SupportedWatchpointTypes",
925925
"SupportedCompressions",
926+
"MultiMemRead",
926927
]
927928

928929
def parse_qSupported_response(self, context):

lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "lldb/Utility/LLDBLog.h"
1515
#include "lldb/Utility/Log.h"
1616
#include "lldb/lldb-enumerations.h"
17+
#include "llvm/ADT/Sequence.h"
1718

1819
using namespace lldb;
1920
using namespace lldb_private;
@@ -266,22 +267,47 @@ bool ClassDescriptorV2::method_list_t::Read(Process *process,
266267
return true;
267268
}
268269

269-
bool ClassDescriptorV2::method_t::Read(Process *process, lldb::addr_t addr,
270-
lldb::addr_t relative_selector_base_addr,
271-
bool is_small, bool has_direct_sel) {
272-
size_t ptr_size = process->GetAddressByteSize();
273-
size_t size = GetSize(process, is_small);
270+
llvm::SmallVector<ClassDescriptorV2::method_t, 0>
271+
ClassDescriptorV2::ReadMethods(llvm::ArrayRef<lldb::addr_t> addresses,
272+
lldb::addr_t relative_selector_base_addr,
273+
bool is_small, bool has_direct_sel) const {
274+
lldb_private::Process *process = m_runtime.GetProcess();
275+
if (!process)
276+
return {};
274277

275-
DataBufferHeap buffer(size, '\0');
276-
Status error;
278+
const size_t size = method_t::GetSize(process, is_small);
279+
const size_t num_methods = addresses.size();
277280

278-
process->ReadMemory(addr, buffer.GetBytes(), size, error);
279-
if (error.Fail()) {
280-
return false;
281+
llvm::SmallVector<uint8_t, 0> buffer(num_methods * size, 0);
282+
llvm::DenseSet<uint32_t> failed_indices;
283+
284+
for (auto [idx, addr] : llvm::enumerate(addresses)) {
285+
Status error;
286+
process->ReadMemory(addr, buffer.data() + idx * size, size, error);
287+
if (error.Fail())
288+
failed_indices.insert(idx);
281289
}
282290

283-
DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(),
284-
ptr_size);
291+
llvm::SmallVector<method_t, 0> methods;
292+
methods.reserve(num_methods);
293+
for (auto [idx, addr] : llvm::enumerate(addresses)) {
294+
if (failed_indices.contains(idx))
295+
continue;
296+
DataExtractor extractor(buffer.data() + idx * size, size,
297+
process->GetByteOrder(),
298+
process->GetAddressByteSize());
299+
methods.push_back(method_t());
300+
methods.back().Read(extractor, process, addr, relative_selector_base_addr,
301+
is_small, has_direct_sel);
302+
}
303+
304+
return methods;
305+
}
306+
307+
bool ClassDescriptorV2::method_t::Read(DataExtractor &extractor,
308+
Process *process, lldb::addr_t addr,
309+
lldb::addr_t relative_selector_base_addr,
310+
bool is_small, bool has_direct_sel) {
285311
lldb::offset_t cursor = 0;
286312

287313
if (is_small) {
@@ -291,11 +317,11 @@ bool ClassDescriptorV2::method_t::Read(Process *process, lldb::addr_t addr,
291317

292318
m_name_ptr = addr + nameref_offset;
293319

320+
Status error;
294321
if (!has_direct_sel) {
295322
// The SEL offset points to a SELRef. We need to dereference twice.
296-
m_name_ptr = process->ReadUnsignedIntegerFromMemory(m_name_ptr, ptr_size,
297-
0, error);
298-
if (!error.Success())
323+
m_name_ptr = process->ReadPointerFromMemory(m_name_ptr, error);
324+
if (error.Fail())
299325
return false;
300326
} else if (relative_selector_base_addr != LLDB_INVALID_ADDRESS) {
301327
m_name_ptr = relative_selector_base_addr + nameref_offset;
@@ -308,13 +334,13 @@ bool ClassDescriptorV2::method_t::Read(Process *process, lldb::addr_t addr,
308334
m_imp_ptr = extractor.GetAddress_unchecked(&cursor);
309335
}
310336

337+
Status error;
311338
process->ReadCStringFromMemory(m_name_ptr, m_name, error);
312-
if (error.Fail()) {
339+
if (error.Fail())
313340
return false;
314-
}
315341

316342
process->ReadCStringFromMemory(m_types_ptr, m_types, error);
317-
return !error.Fail();
343+
return error.Success();
318344
}
319345

320346
bool ClassDescriptorV2::ivar_list_t::Read(Process *process, lldb::addr_t addr) {
@@ -447,17 +473,19 @@ ClassDescriptorV2::GetMethodList(Process *process,
447473
bool ClassDescriptorV2::ProcessMethodList(
448474
std::function<bool(const char *, const char *)> const &instance_method_func,
449475
ClassDescriptorV2::method_list_t &method_list) const {
450-
lldb_private::Process *process = m_runtime.GetProcess();
451-
auto method = std::make_unique<method_t>();
452-
lldb::addr_t relative_selector_base_addr =
453-
m_runtime.GetRelativeSelectorBaseAddr();
454-
for (uint32_t i = 0, e = method_list.m_count; i < e; ++i) {
455-
method->Read(process, method_list.m_first_ptr + (i * method_list.m_entsize),
456-
relative_selector_base_addr, method_list.m_is_small,
457-
method_list.m_has_direct_selector);
458-
if (instance_method_func(method->m_name.c_str(), method->m_types.c_str()))
476+
auto idx_to_method_addr = [&](uint32_t idx) {
477+
return method_list.m_first_ptr + (idx * method_list.m_entsize);
478+
};
479+
llvm::SmallVector<addr_t> addresses = llvm::to_vector(llvm::map_range(
480+
llvm::seq<uint32_t>(method_list.m_count), idx_to_method_addr));
481+
482+
llvm::SmallVector<method_t, 0> methods =
483+
ReadMethods(addresses, m_runtime.GetRelativeSelectorBaseAddr(),
484+
method_list.m_is_small, method_list.m_has_direct_selector);
485+
486+
for (const auto &method : methods)
487+
if (instance_method_func(method.m_name.c_str(), method.m_types.c_str()))
459488
break;
460-
}
461489
return true;
462490
}
463491

lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,16 @@ class ClassDescriptorV2 : public ObjCLanguageRuntime::ClassDescriptor {
172172
+ field_size; // IMP imp;
173173
}
174174

175-
bool Read(Process *process, lldb::addr_t addr,
175+
bool Read(DataExtractor &extractor, Process *process, lldb::addr_t addr,
176176
lldb::addr_t relative_selector_base_addr, bool is_small,
177177
bool has_direct_sel);
178178
};
179179

180+
llvm::SmallVector<method_t, 0>
181+
ReadMethods(llvm::ArrayRef<lldb::addr_t> addresses,
182+
lldb::addr_t relative_selector_base_addr, bool is_small,
183+
bool has_direct_sel) const;
184+
180185
struct ivar_list_t {
181186
uint32_t m_entsize;
182187
uint32_t m_count;

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ uint64_t GDBRemoteCommunicationClient::GetRemoteMaxPacketSize() {
198198
return m_max_packet_size;
199199
}
200200

201+
bool GDBRemoteCommunicationClient::GetMultiMemReadSupported() {
202+
if (m_supports_multi_mem_read == eLazyBoolCalculate)
203+
GetRemoteQSupported();
204+
return m_supports_multi_mem_read == eLazyBoolYes;
205+
}
206+
201207
bool GDBRemoteCommunicationClient::QueryNoAckModeSupported() {
202208
if (m_supports_not_sending_acks == eLazyBoolCalculate) {
203209
m_send_acks = true;
@@ -324,6 +330,7 @@ void GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
324330
m_supported_async_json_packets_is_valid = false;
325331
m_supported_async_json_packets_sp.reset();
326332
m_supports_jModulesInfo = true;
333+
m_supports_multi_mem_read = eLazyBoolCalculate;
327334
}
328335

329336
// These flags should be reset when we first connect to a GDB server and when
@@ -347,6 +354,7 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
347354
m_supports_memory_tagging = eLazyBoolNo;
348355
m_supports_qSaveCore = eLazyBoolNo;
349356
m_uses_native_signals = eLazyBoolNo;
357+
m_supports_multi_mem_read = eLazyBoolNo;
350358

351359
m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if
352360
// not, we assume no limit
@@ -397,6 +405,8 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
397405
m_supports_qSaveCore = eLazyBoolYes;
398406
else if (x == "native-signals+")
399407
m_uses_native_signals = eLazyBoolYes;
408+
else if (x == "MultiMemRead+")
409+
m_supports_multi_mem_read = eLazyBoolYes;
400410
// Look for a list of compressions in the features list e.g.
401411
// qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib-
402412
// deflate,lzma

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
331331

332332
bool GetMultiprocessSupported();
333333

334+
bool GetMultiMemReadSupported();
335+
334336
LazyBool SupportsAllocDeallocMemory() // const
335337
{
336338
// Uncomment this to have lldb pretend the debug server doesn't respond to
@@ -561,6 +563,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
561563
LazyBool m_supports_memory_tagging = eLazyBoolCalculate;
562564
LazyBool m_supports_qSaveCore = eLazyBoolCalculate;
563565
LazyBool m_uses_native_signals = eLazyBoolCalculate;
566+
LazyBool m_supports_multi_mem_read = eLazyBoolCalculate;
564567

565568
bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1,
566569
m_supports_qUserName : 1, m_supports_qGroupName : 1,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Tests debugserver support for MultiMemRead.
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test import lldbutil
9+
10+
11+
@skipUnlessDarwin
12+
@skipIfOutOfTreeDebugserver
13+
class TestCase(TestBase):
14+
def send_process_packet(self, packet_str):
15+
self.runCmd(f"proc plugin packet send {packet_str}", check=False)
16+
# The output is of the form:
17+
# packet: <packet_str>
18+
# response: <response>
19+
reply = self.res.GetOutput().split("\n")
20+
packet = reply[0].strip()
21+
response = reply[1].strip()
22+
23+
self.assertTrue(packet.startswith("packet: "))
24+
self.assertTrue(response.startswith("response: "))
25+
return response[len("response: ") :]
26+
27+
def check_invalid_packet(self, packet_str):
28+
reply = self.send_process_packet("packet_str")
29+
self.assertEqual(reply, "E03")
30+
31+
def test_packets(self):
32+
self.build()
33+
source_file = lldb.SBFileSpec("main.c")
34+
target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
35+
self, "break here", source_file
36+
)
37+
38+
reply = self.send_process_packet("qSupported")
39+
self.assertIn("MultiMemRead+", reply)
40+
41+
mem_address_var = thread.frames[0].FindVariable("memory")
42+
self.assertTrue(mem_address_var)
43+
mem_address = mem_address_var.GetValueAsUnsigned() + 42
44+
45+
# no ":"
46+
self.check_invalid_packet("MultiMemRead")
47+
# missing ranges
48+
self.check_invalid_packet("MultiMemRead:")
49+
# needs at least one range
50+
self.check_invalid_packet("MultiMemRead:ranges:")
51+
# needs at least one range
52+
self.check_invalid_packet("MultiMemRead:ranges:,")
53+
# a range is a pair of numbers
54+
self.check_invalid_packet("MultiMemRead:ranges:10")
55+
# a range is a pair of numbers
56+
self.check_invalid_packet("MultiMemRead:ranges:10,")
57+
# range list must end with ;
58+
self.check_invalid_packet("MultiMemRead:ranges:10,2")
59+
self.check_invalid_packet("MultiMemRead:ranges:10,2,")
60+
self.check_invalid_packet("MultiMemRead:ranges:10,2,3")
61+
# ranges are pairs of numbers.
62+
self.check_invalid_packet("MultiMemRead:ranges:10,2,3;")
63+
# unrecognized field
64+
self.check_invalid_packet("MultiMemRead:ranges:10,2;blah:;")
65+
# unrecognized field
66+
self.check_invalid_packet("MultiMemRead:blah:;ranges:10,2;")
67+
68+
# Zero-length reads are ok.
69+
reply = self.send_process_packet("MultiMemRead:ranges:0,0;")
70+
self.assertEqual(reply, "0;")
71+
72+
# Debugserver is permissive with trailing commas.
73+
reply = self.send_process_packet("MultiMemRead:ranges:10,2,;")
74+
self.assertEqual(reply, "0;")
75+
reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},2,;")
76+
self.assertEqual(reply, "2;ab")
77+
78+
reply = self.send_process_packet("MultiMemRead:ranges:10,2;")
79+
self.assertEqual(reply, "0;")
80+
reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},0;")
81+
self.assertEqual(reply, "0;")
82+
reply = self.send_process_packet(f"MultiMemRead:ranges:{mem_address:x},2;")
83+
self.assertEqual(reply, "2;ab")
84+
reply = self.send_process_packet(
85+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4;"
86+
)
87+
self.assertEqual(reply, "2,4;abcdef")
88+
reply = self.send_process_packet(
89+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4,{mem_address+6:x},8;"
90+
)
91+
self.assertEqual(reply, "2,4,8;abcdefghijklmn")
92+
93+
# Test zero length in the middle.
94+
reply = self.send_process_packet(
95+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},0,{mem_address+6:x},8;"
96+
)
97+
self.assertEqual(reply, "2,0,8;abghijklmn")
98+
# Test zero length in the end.
99+
reply = self.send_process_packet(
100+
f"MultiMemRead:ranges:{mem_address:x},2,{mem_address+2:x},4,{mem_address+6:x},0;"
101+
)
102+
self.assertEqual(reply, "2,4,0;abcdef")
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <stdlib.h>
2+
#include <string.h>
3+
4+
int main(int argc, char **argv) {
5+
char *memory = malloc(1024);
6+
memset(memory, '-', 1024);
7+
// Write "interesting" characters at an offset from the memory filled with
8+
// `-`. This way, if we read outside the range in either direction, we should
9+
// find `-`s`.
10+
int offset = 42;
11+
for (int i = offset; i < offset + 14; i++)
12+
memory[i] = 'a' + (i - offset);
13+
return 0; // break here
14+
}

0 commit comments

Comments
 (0)