Skip to content

Commit e96d0b5

Browse files
Merge pull request #11674 from felipepiovezan/felipe/multimem_cherry_picks_p2_21x
🍒 [lldb] Cherry pick patches for MultiMemoryRead packet p2 (21.x)
2 parents 13c5753 + 8e1255f commit e96d0b5

File tree

7 files changed

+365
-11
lines changed

7 files changed

+365
-11
lines changed

lldb/include/lldb/Target/Process.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,28 @@ class Process : public std::enable_shared_from_this<Process>,
15851585
virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
15861586
Status &error);
15871587

1588+
/// Read from multiple memory ranges and write the results into buffer.
1589+
/// This calls ReadMemoryFromInferior multiple times, once per range,
1590+
/// bypassing the read cache. Process implementations that can perform this
1591+
/// operation more efficiently should override this.
1592+
///
1593+
/// \param[in] ranges
1594+
/// A collection of ranges (base address + size) to read from.
1595+
///
1596+
/// \param[out] buffer
1597+
/// A buffer where the read memory will be written to. It must be at least
1598+
/// as long as the sum of the sizes of each range.
1599+
///
1600+
/// \return
1601+
/// A vector of MutableArrayRef, where each MutableArrayRef is a slice of
1602+
/// the input buffer into which the memory contents were copied. The size
1603+
/// of the slice indicates how many bytes were read successfully. Partial
1604+
/// reads are always performed from the start of the requested range,
1605+
/// never from the middle or end.
1606+
virtual llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
1607+
ReadMemoryRanges(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
1608+
llvm::MutableArrayRef<uint8_t> buffer);
1609+
15881610
/// Read of memory from a process.
15891611
///
15901612
/// This function has the same semantics of ReadMemory except that it

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -279,22 +279,23 @@ ClassDescriptorV2::ReadMethods(llvm::ArrayRef<lldb::addr_t> addresses,
279279
const size_t num_methods = addresses.size();
280280

281281
llvm::SmallVector<uint8_t, 0> buffer(num_methods * size, 0);
282-
llvm::DenseSet<uint32_t> failed_indices;
283282

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);
289-
}
283+
llvm::SmallVector<Range<addr_t, size_t>> mem_ranges =
284+
llvm::to_vector(llvm::map_range(llvm::seq(num_methods), [&](size_t idx) {
285+
return Range<addr_t, size_t>(addresses[idx], size);
286+
}));
287+
288+
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> read_results =
289+
process->ReadMemoryRanges(mem_ranges, buffer);
290290

291291
llvm::SmallVector<method_t, 0> methods;
292292
methods.reserve(num_methods);
293-
for (auto [idx, addr] : llvm::enumerate(addresses)) {
294-
if (failed_indices.contains(idx))
293+
for (auto [addr, memory] : llvm::zip(addresses, read_results)) {
294+
// Ignore partial reads.
295+
if (memory.size() != size)
295296
continue;
296-
DataExtractor extractor(buffer.data() + idx * size, size,
297-
process->GetByteOrder(),
297+
298+
DataExtractor extractor(memory.data(), size, process->GetByteOrder(),
298299
process->GetAddressByteSize());
299300
methods.push_back(method_t());
300301
methods.back().Read(extractor, process, addr, relative_selector_base_addr,

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
#include "lldb/Host/Host.h"
8787
#include "lldb/Utility/StringExtractorGDBRemote.h"
8888

89+
#include "llvm/ADT/STLExtras.h"
8990
#include "llvm/ADT/ScopeExit.h"
9091
#include "llvm/ADT/StringMap.h"
9192
#include "llvm/ADT/StringSwitch.h"
@@ -2735,6 +2736,108 @@ size_t ProcessGDBRemote::DoReadMemory(addr_t addr, void *buf, size_t size,
27352736
return 0;
27362737
}
27372738

2739+
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
2740+
ProcessGDBRemote::ReadMemoryRanges(
2741+
llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
2742+
llvm::MutableArrayRef<uint8_t> buffer) {
2743+
if (!m_gdb_comm.GetMultiMemReadSupported())
2744+
return Process::ReadMemoryRanges(ranges, buffer);
2745+
2746+
llvm::Expected<StringExtractorGDBRemote> response =
2747+
SendMultiMemReadPacket(ranges);
2748+
if (!response) {
2749+
LLDB_LOG_ERROR(GetLog(GDBRLog::Process), response.takeError(),
2750+
"MultiMemRead error response: {0}");
2751+
return Process::ReadMemoryRanges(ranges, buffer);
2752+
}
2753+
2754+
llvm::StringRef response_str = response->GetStringRef();
2755+
const unsigned expected_num_ranges = ranges.size();
2756+
llvm::Expected<llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>>
2757+
parsed_response =
2758+
ParseMultiMemReadPacket(response_str, buffer, expected_num_ranges);
2759+
if (!parsed_response) {
2760+
LLDB_LOG_ERROR(GetLog(GDBRLog::Process), parsed_response.takeError(),
2761+
"MultiMemRead error parsing response: {0}");
2762+
return Process::ReadMemoryRanges(ranges, buffer);
2763+
}
2764+
return std::move(*parsed_response);
2765+
}
2766+
2767+
llvm::Expected<StringExtractorGDBRemote>
2768+
ProcessGDBRemote::SendMultiMemReadPacket(
2769+
llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges) {
2770+
std::string packet_str;
2771+
llvm::raw_string_ostream stream(packet_str);
2772+
stream << "MultiMemRead:ranges:";
2773+
2774+
auto range_to_stream = [&](auto range) {
2775+
// the "-" marker omits the '0x' prefix.
2776+
stream << llvm::formatv("{0:x-},{1:x-}", range.base, range.size);
2777+
};
2778+
llvm::interleave(ranges, stream, range_to_stream, ",");
2779+
stream << ";";
2780+
2781+
StringExtractorGDBRemote response;
2782+
GDBRemoteCommunication::PacketResult packet_result =
2783+
m_gdb_comm.SendPacketAndWaitForResponse(packet_str.data(), response,
2784+
GetInterruptTimeout());
2785+
if (packet_result != GDBRemoteCommunication::PacketResult::Success)
2786+
return llvm::createStringError(
2787+
llvm::formatv("MultiMemRead failed to send packet: '{0}'", packet_str));
2788+
2789+
if (response.IsErrorResponse())
2790+
return llvm::createStringError(
2791+
llvm::formatv("MultiMemRead failed: '{0}'", response.GetStringRef()));
2792+
2793+
if (!response.IsNormalResponse())
2794+
return llvm::createStringError(llvm::formatv(
2795+
"MultiMemRead unexpected response: '{0}'", response.GetStringRef()));
2796+
2797+
return response;
2798+
}
2799+
2800+
llvm::Expected<llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>>
2801+
ProcessGDBRemote::ParseMultiMemReadPacket(llvm::StringRef response_str,
2802+
llvm::MutableArrayRef<uint8_t> buffer,
2803+
unsigned expected_num_ranges) {
2804+
// The sizes and the data are separated by a `;`.
2805+
auto [sizes_str, memory_data] = response_str.split(';');
2806+
if (sizes_str.size() == response_str.size())
2807+
return llvm::createStringError(llvm::formatv(
2808+
"MultiMemRead response missing field separator ';' in: '{0}'",
2809+
response_str));
2810+
2811+
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> read_results;
2812+
2813+
// Sizes are separated by a `,`.
2814+
for (llvm::StringRef size_str : llvm::split(sizes_str, ',')) {
2815+
uint64_t read_size;
2816+
if (size_str.getAsInteger(16, read_size))
2817+
return llvm::createStringError(llvm::formatv(
2818+
"MultiMemRead response has invalid size string: {0}", size_str));
2819+
2820+
if (memory_data.size() < read_size)
2821+
return llvm::createStringError(
2822+
llvm::formatv("MultiMemRead response did not have enough data, "
2823+
"requested sizes: {0}",
2824+
sizes_str));
2825+
2826+
llvm::StringRef region_to_read = memory_data.take_front(read_size);
2827+
memory_data = memory_data.drop_front(read_size);
2828+
2829+
assert(buffer.size() >= read_size);
2830+
llvm::MutableArrayRef<uint8_t> region_to_write =
2831+
buffer.take_front(read_size);
2832+
buffer = buffer.drop_front(read_size);
2833+
2834+
memcpy(region_to_write.data(), region_to_read.data(), read_size);
2835+
read_results.push_back(region_to_write);
2836+
}
2837+
2838+
return read_results;
2839+
}
2840+
27382841
bool ProcessGDBRemote::SupportsMemoryTagging() {
27392842
return m_gdb_comm.GetMemoryTaggingSupported();
27402843
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,22 @@ class ProcessGDBRemote : public Process,
137137
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
138138
Status &error) override;
139139

140+
/// Override of ReadMemoryRanges that uses MultiMemRead to optimize this
141+
/// operation.
142+
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
143+
ReadMemoryRanges(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
144+
llvm::MutableArrayRef<uint8_t> buf) override;
145+
146+
private:
147+
llvm::Expected<StringExtractorGDBRemote>
148+
SendMultiMemReadPacket(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges);
149+
150+
llvm::Expected<llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>>
151+
ParseMultiMemReadPacket(llvm::StringRef response_str,
152+
llvm::MutableArrayRef<uint8_t> buffer,
153+
unsigned expected_num_ranges);
154+
155+
public:
140156
Status
141157
WriteObjectFile(std::vector<ObjectFile::LoadableData> entries) override;
142158

lldb/source/Target/Process.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,49 @@ size_t Process::ReadMemory(addr_t addr, void *buf, size_t size, Status &error) {
21112111
}
21122112
}
21132113

2114+
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
2115+
Process::ReadMemoryRanges(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
2116+
llvm::MutableArrayRef<uint8_t> buffer) {
2117+
auto total_ranges_len = llvm::sum_of(
2118+
llvm::map_range(ranges, [](auto range) { return range.size; }));
2119+
// If the buffer is not large enough, this is a programmer error.
2120+
// In production builds, gracefully fail by returning a length of 0 for all
2121+
// ranges.
2122+
assert(buffer.size() >= total_ranges_len && "provided buffer is too short");
2123+
if (buffer.size() < total_ranges_len) {
2124+
llvm::MutableArrayRef<uint8_t> empty;
2125+
return {ranges.size(), empty};
2126+
}
2127+
2128+
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> results;
2129+
2130+
// While `buffer` has space, take the next requested range and read
2131+
// memory into a `buffer` piece, then slice it to remove the used memory.
2132+
for (auto [addr, range_len] : ranges) {
2133+
Status status;
2134+
size_t num_bytes_read =
2135+
ReadMemoryFromInferior(addr, buffer.data(), range_len, status);
2136+
// FIXME: ReadMemoryFromInferior promises to return 0 in case of errors, but
2137+
// it doesn't; it never checks for errors.
2138+
if (status.Fail())
2139+
num_bytes_read = 0;
2140+
2141+
assert(num_bytes_read <= range_len && "read more than requested bytes");
2142+
if (num_bytes_read > range_len) {
2143+
// In production builds, gracefully fail by returning length zero for this
2144+
// range.
2145+
results.emplace_back();
2146+
continue;
2147+
}
2148+
2149+
results.push_back(buffer.take_front(num_bytes_read));
2150+
// Slice buffer to remove the used memory.
2151+
buffer = buffer.drop_front(num_bytes_read);
2152+
}
2153+
2154+
return results;
2155+
}
2156+
21142157
void Process::DoFindInMemory(lldb::addr_t start_addr, lldb::addr_t end_addr,
21152158
const uint8_t *buf, size_t size,
21162159
AddressRanges &matches, size_t alignment,

lldb/test/API/lang/objc/foundation/TestObjCMethodsNSError.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,30 @@ def test_NSError_p(self):
4545
],
4646
)
4747
self.runCmd("process continue")
48+
49+
@skipIfOutOfTreeDebugserver
50+
def test_runtime_types_efficient_memreads(self):
51+
# Test that we use an efficient reading of memory when reading
52+
# Objective-C method descriptions.
53+
logfile = os.path.join(self.getBuildDir(), "log.txt")
54+
self.runCmd(f"log enable -f {logfile} gdb-remote packets process")
55+
self.addTearDownHook(lambda: self.runCmd("log disable gdb-remote packets"))
56+
57+
self.build()
58+
self.target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
59+
self, "// Break here for NSString tests", lldb.SBFileSpec("main.m", False)
60+
)
61+
62+
self.runCmd(f"proc plugin packet send StartTesting", check=False)
63+
self.expect('expression str = [NSString stringWithCString: "new"]')
64+
self.runCmd(f"proc plugin packet send EndTesting", check=False)
65+
66+
self.assertTrue(os.path.exists(logfile))
67+
log_text = open(logfile).read()
68+
log_text = log_text.split("StartTesting", 1)[-1].split("EndTesting", 1)[0]
69+
70+
# This test is only checking that the packet it used at all (and that
71+
# no errors are produced). It doesn't check that the packet is being
72+
# used to solve a problem in an optimal way.
73+
self.assertIn("MultiMemRead:", log_text)
74+
self.assertNotIn("MultiMemRead error", log_text)

0 commit comments

Comments
 (0)