-
Couldn't load subscription status.
- Fork 15k
[lldb] Implement Process::ReadMemoryRanges #163651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[lldb] Implement Process::ReadMemoryRanges #163651
Conversation
|
@llvm/pr-subscribers-lldb Author: Felipe de Azevedo Piovezan (felipepiovezan) ChangesThis commit introduces a base-class implementation for a method that reads memory from multiple ranges at once. This implementation simply calls the underlying
As an example, this commit changes AppleObjCClassDescriptorV2 to use the new API. Note about the APIIn the RFC, we discussed having the API return some kind of class As can be seen in the added test and in the added use-case (AppleObjCClassDescriptorV2), users of this API will just iterate over the vector of memory buffers. So they want a return type that can be iterated over, and the vector seems more natural than creating a new class and defining iterators for it. Likewise, in the RFC, we discussed wrapping the result into an Full diff: https://github.com/llvm/llvm-project/pull/163651.diff 4 Files Affected:
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index dc75d98acea70..7a260323b5a3d 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -1571,6 +1571,25 @@ class Process : public std::enable_shared_from_this<Process>,
virtual size_t ReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
Status &error);
+ /// Read from multiple memory ranges and write the results into buffer.
+ ///
+ /// \param[in] ranges
+ /// A collection of ranges (base address + size) to read from.
+ ///
+ /// \param[out] buffer
+ /// A buffer where the read memory will be written to. It must be at least
+ /// as long as the sum of the sizes of each range.
+ ///
+ /// \return
+ /// A vector of MutableArrayRef, where each MutableArrayRef is a slice of
+ /// the input buffer into which the memory contents were copied. The size
+ /// of the slice indicates how many bytes were read successfully. Partial
+ /// reads are always performed from the start of the requested range,
+ /// never from the middle or end.
+ virtual llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
+ ReadMemoryRanges(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
+ llvm::MutableArrayRef<uint8_t> buffer);
+
/// Read of memory from a process.
///
/// This function has the same semantics of ReadMemory except that it
diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp
index 6d8f41aef1ffc..7ce4cbf4c61a4 100644
--- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp
@@ -279,22 +279,23 @@ ClassDescriptorV2::ReadMethods(llvm::ArrayRef<lldb::addr_t> addresses,
const size_t num_methods = addresses.size();
llvm::SmallVector<uint8_t, 0> buffer(num_methods * size, 0);
- llvm::DenseSet<uint32_t> failed_indices;
- for (auto [idx, addr] : llvm::enumerate(addresses)) {
- Status error;
- process->ReadMemory(addr, buffer.data() + idx * size, size, error);
- if (error.Fail())
- failed_indices.insert(idx);
- }
+ llvm::SmallVector<Range<addr_t, size_t>> mem_ranges =
+ llvm::to_vector(llvm::map_range(llvm::seq(num_methods), [&](size_t idx) {
+ return Range<addr_t, size_t>(addresses[idx], size);
+ }));
+
+ llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> read_results =
+ process->ReadMemoryRanges(mem_ranges, buffer);
llvm::SmallVector<method_t, 0> methods;
methods.reserve(num_methods);
- for (auto [idx, addr] : llvm::enumerate(addresses)) {
- if (failed_indices.contains(idx))
+ for (auto [addr, memory] : llvm::zip(addresses, read_results)) {
+ // Ignore partial reads.
+ if (memory.size() != size)
continue;
- DataExtractor extractor(buffer.data() + idx * size, size,
- process->GetByteOrder(),
+
+ DataExtractor extractor(memory.data(), size, process->GetByteOrder(),
process->GetAddressByteSize());
methods.push_back(method_t());
methods.back().Read(extractor, process, addr, relative_selector_base_addr,
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 3176852f0b724..9692a7fece7e5 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1971,6 +1971,34 @@ size_t Process::ReadMemory(addr_t addr, void *buf, size_t size, Status &error) {
}
}
+llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
+Process::ReadMemoryRanges(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
+ llvm::MutableArrayRef<uint8_t> buffer) {
+ llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> results;
+
+ for (auto [addr, len] : ranges) {
+ // This is either a programmer error, or a protocol violation.
+ // In production builds, gracefully fail.
+ assert(buffer.size() >= len);
+ if (buffer.size() < len) {
+ results.push_back(buffer.take_front(0));
+ continue;
+ }
+
+ Status status;
+ size_t num_bytes_read =
+ ReadMemoryFromInferior(addr, buffer.data(), len, status);
+ // FIXME: ReadMemoryFromInferior promises to return 0 in case of errors, but
+ // it doesn't; it never checks for errors.
+ if (status.Fail())
+ num_bytes_read = 0;
+ results.push_back(buffer.take_front(num_bytes_read));
+ buffer = buffer.drop_front(num_bytes_read);
+ }
+
+ return results;
+}
+
void Process::DoFindInMemory(lldb::addr_t start_addr, lldb::addr_t end_addr,
const uint8_t *buf, size_t size,
AddressRanges &matches, size_t alignment,
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index 4a96730e00464..1317dd27b256e 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -17,6 +17,8 @@
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "gtest/gtest.h"
+#include <cstdint>
+#include <random>
using namespace lldb_private;
using namespace lldb;
@@ -225,3 +227,63 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) {
// instead of using an
// old cache
}
+
+/// A process class that reads `lower_byte(address)` for each `address` it
+/// reads.
+class DummyReaderProcess : public Process {
+public:
+ size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
+ Status &error) override {
+ uint8_t *buffer = static_cast<uint8_t*>(buf);
+ for(size_t addr = vm_addr; addr < vm_addr + size; addr++)
+ buffer[addr - vm_addr] = addr;
+ return size;
+ }
+ // Boilerplate, nothing interesting below.
+ DummyReaderProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp)
+ : Process(target_sp, listener_sp) {}
+ bool CanDebug(lldb::TargetSP, bool) override { return true; }
+ Status DoDestroy() override { return {}; }
+ void RefreshStateAfterStop() override {}
+ bool DoUpdateThreadList(ThreadList &, ThreadList &) override { return false; }
+ llvm::StringRef GetPluginName() override { return "Dummy"; }
+};
+
+TEST_F(MemoryTest, TestReadMemoryRanges) {
+ ArchSpec arch("x86_64-apple-macosx-");
+
+ Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch));
+
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ TargetSP target_sp = CreateTarget(debugger_sp, arch);
+ ASSERT_TRUE(target_sp);
+
+ ListenerSP listener_sp(Listener::MakeListener("dummy"));
+ ProcessSP process_sp =
+ std::make_shared<DummyReaderProcess>(target_sp, listener_sp);
+ ASSERT_TRUE(process_sp);
+
+ DummyProcess *process = static_cast<DummyProcess *>(process_sp.get());
+ process->SetMaxReadSize(1024);
+
+ llvm::SmallVector<uint8_t, 0> buffer(1024, 0);
+
+ // Read 8 ranges of 128 bytes, starting at random addresses
+ std::mt19937 rng(42);
+ std::uniform_int_distribution<addr_t> distribution(1, 100000);
+ llvm::SmallVector<Range<addr_t, size_t>> ranges;
+ for (unsigned i = 0; i < 1024; i += 128)
+ ranges.emplace_back(distribution(rng), 128);
+
+ llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> read_results =
+ process->ReadMemoryRanges(ranges, buffer);
+
+ for (auto [range, memory] : llvm::zip(ranges, read_results)) {
+ ASSERT_EQ(memory.size(), 128u);
+ addr_t range_base = range.GetRangeBase();
+ for (auto [idx, byte] : llvm::enumerate(memory))
+ ASSERT_EQ(byte, static_cast<uint8_t>(range_base + idx));
+ }
+}
|
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
4f47055 to
d6f54b4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the thinking for doing this in "ptrace style" where the caller allocates a buffer and gives it to the function, vs. allocating the buffer on behalf of the caller?
The latter would make it impossible to get the buffer size wrong, but on the other hand, the caller would know best how to allocate a given size buffer (stack, heap, and so on).
If the main use case (and the only one right now) is a small number of bytes from a large set of addresses, we're probably talking stack sized allocations not heap. So allowing the caller to set that up would be logical.
I also thought what about a MultiMemWrite, would it make sense to have a symmetric API for that hypothetical packet. If the caller allocates the buffer, the two would be symmetric. And you could do a "gather read" from N places and "scatter write" to N other places.
(no real use case for this, just theory crafting)
It's an internal API so we can always swap later but please include your thinking in the PR description even if it's an arbitrary choice, might be interesting in future.
lldb/source/Target/Process.cpp
Outdated
| for (auto [addr, len] : ranges) { | ||
| // This is either a programmer error, or a protocol violation. | ||
| // In production builds, gracefully fail. | ||
| assert(buffer.size() >= len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your algorithm here is:
- For each range
- Take that much from the front of buffer, making buffer smaller
- So if at any point, there aren't "len" bytes left to take, the caller made a mistake and didn't provide enough buffer
I was confused at first because I read the assert backwards. We expect that the remaining buffer size will be >= len of the current range, if it's not then it fails.
But then I see below, take_front(0). And now I'm confused again.
I think some comments would help like:
when the remaining buffer is > range we do this
when it's exactly the same we do this
when it's less than we decide that's an error so we do this
lldb/source/Target/Process.cpp
Outdated
| // In production builds, gracefully fail. | ||
| assert(buffer.size() >= len); | ||
| if (buffer.size() < len) { | ||
| results.push_back(buffer.take_front(0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For instance, this is a bit cryptic without a comment.
I suppose it's because there has to be some pointer into the original buffer, but you are returning 0 bytes for this. Perhaps you can use buffer.data instead to make it clear we do not intend to move the start of the buffer at all?
Seeing 0 here makes me wonder if you meant to type another number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I should have just done emplace_back(nullptr, 0) to be honest, would have made the point more evident.
lldb/unittests/Target/MemoryTest.cpp
Outdated
|
|
||
| llvm::SmallVector<uint8_t, 0> buffer(1024, 0); | ||
|
|
||
| // Read 8 ranges of 128 bytes, starting at random addresses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random in tests rings alarm bells for me but hopefully once I read the rest I'll understand why you need this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reading this, I think I understand. The addresses and sizes you put in are somewhat arbitrary.
I think I would prefer to see a few "carefully chosen" sequences instead, or as well. Boundary conditions at least.
This is a relatively simple algorithm to test but if it did fail, I'm not sure the failure reporting would be good enough to know what inputs it was given. Maybe GoogleTest will surprise me though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random in tests rings alarm bells for me but hopefully once I read the rest I'll understand why you need this.
Worth mentioning that this is deterministic, as it uses a fixed seed. So every platform and every run will use the exact same data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can write some arbitrary sequences instead, it was just simpler to generate the sequence this way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok part of this is me being lazy, but future me and anyone else will be even more lazy, so add a comment that it is in fact a fixed seed.
A fixed arbitrary seed that is.
lldb/unittests/Target/MemoryTest.cpp
Outdated
| // old cache | ||
| } | ||
|
|
||
| /// A process class that reads `lower_byte(address)` for each `address` it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is lower_byte? Do you mean literally it returns the least significant byte of the address? This looks like a function call the way you wrote it.
(the idea is fine overall just the way it's written)
lldb/unittests/Target/MemoryTest.cpp
Outdated
| Status &error) override { | ||
| uint8_t *buffer = static_cast<uint8_t *>(buf); | ||
| for (size_t addr = vm_addr; addr < vm_addr + size; addr++) | ||
| buffer[addr - vm_addr] = addr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a truncation, right? I'd add a static cast to make that clear and keep the compiler happy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also move the comment about the lower byte to here, or repeat it.
No real motivation other than "the other APIs in |
I'm fine with it. "when in Rome" is a decent argument when there's no particular reason to go one way or the other. |
This commit introduces a base-class implementation for a method that
reads memory from multiple ranges at once. This implementation simply
calls the underlying `ReadMemoryFromInferior` method on each requested
range, intentionally bypassing the memory caching mechanism (though this
may be easily changed in the future).
`Process` implementations that can be perform this operation more
efficiently - e.g. with the MultiMemPacket described in [1] - are
expected to override this method.
As an example, this commit changes AppleObjCClassDescriptorV2 to use the
new API.
Note about the API
------------------
In the RFC, we discussed having the API return some kind of class
`ReadMemoryRangesResult`. However, while writing such a class, it became
clear that it was merely wrapping a vector, without providing anything
useful. For example, this class:
```
struct ReadMemoryRangesResult {
ReadMemoryRangesResult(
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> ranges)
: ranges(std::move(ranges)) {}
llvm::ArrayRef<llvm::MutableArrayRef<uint8_t>> getRanges() const {
return ranges;
}
private:
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> ranges;
};
```
As can be seen in the added test and in the added use-case
(AppleObjCClassDescriptorV2), users of this API will just iterate over
the vector of memory buffers. So they want a return type that can be
iterated over, and the vector seems more natural than creating a new
class and defining iterators for it.
Likewise, in the RFC, we discussed wrapping the result into an
`Expected`. Upon experimenting with the code, this feels like it limits
what the API is able to do as the base class implementation never needs
to fail the entire result, it's the individual reads that may fail and
this is expressed through a zero-length result. Any derived classes
overriding `ReadMemoryRanges` should also never produce a top level
failure: if they did, they can just fall back to the base class
implementation, which would produce a better result.
The choice of having the caller allocate a buffer and pass it to
`Process::ReadMemoryRanges` is done mostly to follow conventions already
done in the Process class.
[1]: https://discourse.llvm.org/t/rfc-a-new-vectorized-memory-read-packet/
d6f54b4 to
58d7eda
Compare
|
Addressed review comments |
|
I moved some of the assertions out of the main loop to simplify the algorithm a bit. |
|
Btw, the ProcessGDBRemote impl is here: #164311 |
The AArch64 bot seems to be having some infra issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, I think you addressed David's feedback in the commit today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with all the behaviour as is, needs a few more tests.
lldb/source/Target/Process.cpp
Outdated
| // In production builds, gracefully fail by returning empty chunks. | ||
| assert(buffer.size() >= total_ranges_len); | ||
| if (buffer.size() < total_ranges_len) | ||
| return llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>(ranges.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an explicit zero value here, it took me ages to parse what this meant (which is a "C++ be like" sort of issue, but still, make it clearer).
Also you say "chunks" but these are not chunks, we don't return chunks as such we return a description of a chunk. I suggest "returning a length of 0 for all ranges".
a10d92a to
1b470fb
Compare
|
Added death tests, removed references to "chunks" in the comments, rewrote |
lldb/unittests/Target/MemoryTest.cpp
Outdated
|
|
||
| llvm::SmallVector<uint8_t, 0> too_short_buffer(10, 0); | ||
| llvm::SmallVector<Range<addr_t, size_t>> ranges = {{0x12345, 128}}; | ||
| ASSERT_DEATH( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure whether ASSERT_DEATH incorporates a check whether assertions are on, so please check that if you didn't already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though I would like to see the non-assert path anyway. So you'll need an ifdef for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to see the non-assert side of the ASSERT_DEATH paths tested (in non-asserts builds, not in the same build of course).
Otherwise LGTM so in the interests of time I'm approving and I'm sure you'll figure it out.
|
Added release-mode testing. I found out that gtest has a |
|
The AArch bot is still failing with the message below, so I am going to bypass it.
|
|
The unit tests are failing on Arm 32-bit, I'll figure it out. |
Tests added by #163651. Use lldb::addr_t (which is always 64-bit) for all addresses so that we don't calculate an invalid address on 32-bit and segfault. As happened on Linaro's Arm 32-bit buildbot.
This commit makes use of the newly created MultiMemRead packet to provide an efficient implementation of MultiMemRead inside ProcessGDBRemote. Testing is tricky, but it is accomplished two ways: 1. Some Objective-C tests would fail if this were implemented incorrectly, as there is already an in-tree use of the base class implementation of MultiMemRead, which is now getting replaced by the derived class. 2. One Objective-C test is modified so that we ensure the packet is being sent by looking at the packet logs. While not the most elegant solution, it is a strategy adopted in other tests as well. This gets around the fact that we cannot instantiate / unittest a mock ProcessGDBRemote. Depends on #163651
…nges (#164311) This commit makes use of the newly created MultiMemRead packet to provide an efficient implementation of MultiMemRead inside ProcessGDBRemote. Testing is tricky, but it is accomplished two ways: 1. Some Objective-C tests would fail if this were implemented incorrectly, as there is already an in-tree use of the base class implementation of MultiMemRead, which is now getting replaced by the derived class. 2. One Objective-C test is modified so that we ensure the packet is being sent by looking at the packet logs. While not the most elegant solution, it is a strategy adopted in other tests as well. This gets around the fact that we cannot instantiate / unittest a mock ProcessGDBRemote. Depends on llvm/llvm-project#163651
thank you! |
This commit introduces a base-class implementation for a method that
reads memory from multiple ranges at once. This implementation simply
calls the underlying `ReadMemoryFromInferior` method on each requested
range, intentionally bypassing the memory caching mechanism (though this
may be easily changed in the future).
`Process` implementations that can be perform this operation more
efficiently - e.g. with the MultiMemPacket described in [1] - are
expected to override this method.
As an example, this commit changes AppleObjCClassDescriptorV2 to use the
new API.
Note about the API
------------------
In the RFC, we discussed having the API return some kind of class
`ReadMemoryRangesResult`. However, while writing such a class, it became
clear that it was merely wrapping a vector, without providing anything
useful. For example, this class:
```
struct ReadMemoryRangesResult {
ReadMemoryRangesResult(
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> ranges)
: ranges(std::move(ranges)) {}
llvm::ArrayRef<llvm::MutableArrayRef<uint8_t>> getRanges() const {
return ranges;
}
private:
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> ranges;
};
```
As can be seen in the added test and in the added use-case
(AppleObjCClassDescriptorV2), users of this API will just iterate over
the vector of memory buffers. So they want a return type that can be
iterated over, and the vector seems more natural than creating a new
class and defining iterators for it.
Likewise, in the RFC, we discussed wrapping the result into an
`Expected`. Upon experimenting with the code, this feels like it limits
what the API is able to do as the base class implementation never needs
to fail the entire result, it's the individual reads that may fail and
this is expressed through a zero-length result. Any derived classes
overriding `ReadMemoryRanges` should also never produce a top level
failure: if they did, they can just fall back to the base class
implementation, which would produce a better result.
The choice of having the caller allocate a buffer and pass it to
`Process::ReadMemoryRanges` is done mostly to follow conventions already
done in the Process class.
[1]:
https://discourse.llvm.org/t/rfc-a-new-vectorized-memory-read-packet/
(cherry picked from commit f2cb997)
…164311) This commit makes use of the newly created MultiMemRead packet to provide an efficient implementation of MultiMemRead inside ProcessGDBRemote. Testing is tricky, but it is accomplished two ways: 1. Some Objective-C tests would fail if this were implemented incorrectly, as there is already an in-tree use of the base class implementation of MultiMemRead, which is now getting replaced by the derived class. 2. One Objective-C test is modified so that we ensure the packet is being sent by looking at the packet logs. While not the most elegant solution, it is a strategy adopted in other tests as well. This gets around the fact that we cannot instantiate / unittest a mock ProcessGDBRemote. Depends on llvm#163651 (cherry picked from commit 276bccd)
This commit introduces a base-class implementation for a method that
reads memory from multiple ranges at once. This implementation simply
calls the underlying `ReadMemoryFromInferior` method on each requested
range, intentionally bypassing the memory caching mechanism (though this
may be easily changed in the future).
`Process` implementations that can be perform this operation more
efficiently - e.g. with the MultiMemPacket described in [1] - are
expected to override this method.
As an example, this commit changes AppleObjCClassDescriptorV2 to use the
new API.
Note about the API
------------------
In the RFC, we discussed having the API return some kind of class
`ReadMemoryRangesResult`. However, while writing such a class, it became
clear that it was merely wrapping a vector, without providing anything
useful. For example, this class:
```
struct ReadMemoryRangesResult {
ReadMemoryRangesResult(
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> ranges)
: ranges(std::move(ranges)) {}
llvm::ArrayRef<llvm::MutableArrayRef<uint8_t>> getRanges() const {
return ranges;
}
private:
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> ranges;
};
```
As can be seen in the added test and in the added use-case
(AppleObjCClassDescriptorV2), users of this API will just iterate over
the vector of memory buffers. So they want a return type that can be
iterated over, and the vector seems more natural than creating a new
class and defining iterators for it.
Likewise, in the RFC, we discussed wrapping the result into an
`Expected`. Upon experimenting with the code, this feels like it limits
what the API is able to do as the base class implementation never needs
to fail the entire result, it's the individual reads that may fail and
this is expressed through a zero-length result. Any derived classes
overriding `ReadMemoryRanges` should also never produce a top level
failure: if they did, they can just fall back to the base class
implementation, which would produce a better result.
The choice of having the caller allocate a buffer and pass it to
`Process::ReadMemoryRanges` is done mostly to follow conventions already
done in the Process class.
[1]:
https://discourse.llvm.org/t/rfc-a-new-vectorized-memory-read-packet/
(cherry picked from commit f2cb997)
(cherry picked from commit bccb899)
…164311) This commit makes use of the newly created MultiMemRead packet to provide an efficient implementation of MultiMemRead inside ProcessGDBRemote. Testing is tricky, but it is accomplished two ways: 1. Some Objective-C tests would fail if this were implemented incorrectly, as there is already an in-tree use of the base class implementation of MultiMemRead, which is now getting replaced by the derived class. 2. One Objective-C test is modified so that we ensure the packet is being sent by looking at the packet logs. While not the most elegant solution, it is a strategy adopted in other tests as well. This gets around the fact that we cannot instantiate / unittest a mock ProcessGDBRemote. Depends on llvm#163651 (cherry picked from commit 276bccd) (cherry picked from commit 8e1255f)
Tests added by llvm#163651. Use lldb::addr_t (which is always 64-bit) for all addresses so that we don't calculate an invalid address on 32-bit and segfault. As happened on Linaro's Arm 32-bit buildbot.
…164311) This commit makes use of the newly created MultiMemRead packet to provide an efficient implementation of MultiMemRead inside ProcessGDBRemote. Testing is tricky, but it is accomplished two ways: 1. Some Objective-C tests would fail if this were implemented incorrectly, as there is already an in-tree use of the base class implementation of MultiMemRead, which is now getting replaced by the derived class. 2. One Objective-C test is modified so that we ensure the packet is being sent by looking at the packet logs. While not the most elegant solution, it is a strategy adopted in other tests as well. This gets around the fact that we cannot instantiate / unittest a mock ProcessGDBRemote. Depends on llvm#163651
This commit introduces a base-class implementation for a method that reads memory from multiple ranges at once. This implementation simply calls the underlying
ReadMemoryFromInferiormethod on each requested range, intentionally bypassing the memory caching mechanism (though this may be easily changed in the future).Processimplementations that can be perform this operation more efficiently - e.g. with the MultiMemPacket described in 1 - are expected to override this method.As an example, this commit changes AppleObjCClassDescriptorV2 to use the new API.
Note about the API
In the RFC, we discussed having the API return some kind of class
ReadMemoryRangesResult. However, while writing such a class, it became clear that it was merely wrapping a vector, without providing anything useful. For example, this class:As can be seen in the added test and in the added use-case (AppleObjCClassDescriptorV2), users of this API will just iterate over the vector of memory buffers. So they want a return type that can be iterated over, and the vector seems more natural than creating a new class and defining iterators for it.
Likewise, in the RFC, we discussed wrapping the result into an
Expected. Upon experimenting with the code, this feels like it limits what the API is able to do as the base class implementation never needs to fail the entire result, it's the individual reads that may fail and this is expressed through a zero-length result. Any derived classes overridingReadMemoryRangesshould also never produce a top level failure: if they did, they can just fall back to the base class implementation, which would produce a better result.The choice of having the caller allocate a buffer and pass it to
Process::ReadMemoryRangesis done mostly to follow conventions already done in the Process class.