Skip to content
35 changes: 35 additions & 0 deletions lldb/include/lldb/Target/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,28 @@ 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.
/// This calls ReadMemoryFromInferior multiple times, once per range,
/// bypassing the read cache. Process implementations that can perform this
/// operation more efficiently should override this.
///
/// \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
Expand Down Expand Up @@ -1647,11 +1669,24 @@ class Process : public std::enable_shared_from_this<Process>,
size_t byte_size, uint64_t fail_value,
Status &error);

/// Use Process::ReadMemoryRanges to efficiently read multiple unsigned
/// integers from memory at once.
/// TODO: this should be upstream once there is a use for it there.
llvm::SmallVector<std::optional<uint64_t>>
ReadUnsignedIntegersFromMemory(llvm::ArrayRef<lldb::addr_t> addresses,
unsigned byte_size);

int64_t ReadSignedIntegerFromMemory(lldb::addr_t load_addr, size_t byte_size,
int64_t fail_value, Status &error);

lldb::addr_t ReadPointerFromMemory(lldb::addr_t vm_addr, Status &error);

/// Use Process::ReadMemoryRanges to efficiently read multiple pointers from
/// memory at once.
/// TODO: this should be upstream once there is a use for it there.
llvm::SmallVector<std::optional<lldb::addr_t>>
ReadPointersFromMemory(llvm::ArrayRef<lldb::addr_t> ptr_locs);

bool WritePointerToMemory(lldb::addr_t vm_addr, lldb::addr_t ptr_value,
Status &error);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
196 changes: 141 additions & 55 deletions lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2337,14 +2337,15 @@ class CommandObjectLanguageSwiftTaskInfo final : public CommandObjectParsed {
}

TaskInspector task_inspector;
auto task_addr_or_err = task_inspector.GetTaskAddrFromThreadLocalStorage(
m_exe_ctx.GetThreadRef());
if (auto error = task_addr_or_err.takeError()) {
result.AppendError(toString(std::move(error)));
std::optional<lldb::addr_t> maybe_task_addr =
task_inspector.GetTaskAddrFromThreadLocalStorage(
m_exe_ctx.GetThreadRef());
if (!task_addr) {
result.AppendError("could find the task address");
return;
}

task_addr = task_addr_or_err.get();
task_addr = *maybe_task_addr;
}

auto ts_or_err = m_exe_ctx.GetTargetRef().GetScratchTypeSystemForLanguage(
Expand Down Expand Up @@ -2935,19 +2936,6 @@ std::optional<lldb::addr_t> SwiftLanguageRuntime::TrySkipVirtualParentProlog(
return pc_value;
}

/// Attempts to read the memory location at `task_addr_location`, producing
/// the Task pointer if possible.
static llvm::Expected<lldb::addr_t>
ReadTaskAddr(lldb::addr_t task_addr_location, Process &process) {
Status status;
addr_t task_addr = process.ReadPointerFromMemory(task_addr_location, status);
if (status.Fail())
return llvm::joinErrors(
llvm::createStringError("could not get current task from thread"),
status.takeError());
return task_addr;
}

/// Compute the location where the Task pointer for `real_thread` is stored by
/// the runtime.
static llvm::Expected<lldb::addr_t>
Expand Down Expand Up @@ -2975,48 +2963,146 @@ ComputeTaskAddrLocationFromThreadLocalStorage(Thread &real_thread) {
#endif
}

llvm::Expected<lldb::addr_t>
/// Helper function to read all `pointers` from process memory at once.
/// Consumes any errors from the input by propagating them to the output.
static llvm::SmallVector<std::optional<addr_t>>
MultiReadPointers(Process &process,
llvm::MutableArrayRef<std::optional<addr_t>> maybe_pointers) {
llvm::SmallVector<std::optional<addr_t>> final_results;
llvm::SmallVector<addr_t> to_read;
final_results.reserve(maybe_pointers.size());
to_read.reserve(maybe_pointers.size());

/// Filter the input: propagate input errors directly to the output, forward
/// proper inputs to `to_read`.
for (std::optional<addr_t> &maybe_ptr : maybe_pointers) {
if (!maybe_ptr)
final_results.emplace_back(std::nullopt);
else {
final_results.push_back(LLDB_INVALID_ADDRESS);
to_read.push_back(*maybe_ptr);
}
}

llvm::SmallVector<std::optional<addr_t>> read_results =
process.ReadPointersFromMemory(to_read);
llvm::MutableArrayRef<std::optional<addr_t>> results_ref = read_results;

// Move the results in the slots not filled by errors from the input.
for (std::optional<addr_t> &maybe_result : final_results)
if (maybe_result) {
maybe_result = results_ref.front();
results_ref = results_ref.drop_front();
}

assert(results_ref.empty());
return final_results;
}

/// Helper function to read `addr` from process memory. Errors in the input are
/// propagated to to the output.
static std::optional<addr_t> ReadPointer(Process &process,
std::optional<addr_t> addr) {
return MultiReadPointers(process, addr)[0];
}

std::optional<lldb::addr_t>
TaskInspector::GetTaskAddrFromThreadLocalStorage(Thread &thread) {
// Look through backing threads when inspecting TLS.
Thread &real_thread =
thread.GetBackingThread() ? *thread.GetBackingThread() : thread;
return GetTaskAddrFromThreadLocalStorage(&thread)[0];
}

if (auto it = m_tid_to_task_addr_location.find(real_thread.GetID());
it != m_tid_to_task_addr_location.end()) {
llvm::SmallVector<std::optional<lldb::addr_t>>
TaskInspector::GetTaskAddrLocations(llvm::ArrayRef<Thread *> threads) {
llvm::SmallVector<std::optional<addr_t>> addr_locations;
addr_locations.reserve(threads.size());

for (auto [idx, thread] : llvm::enumerate(threads)) {
Thread &real_thread =
thread->GetBackingThread() ? *thread->GetBackingThread() : *thread;

auto it = m_tid_to_task_addr_location.find(real_thread.GetID());
if (it != m_tid_to_task_addr_location.end()) {
addr_locations.push_back(it->second);
#ifndef NDEBUG
// In assert builds, check that caching did not produce incorrect results.
llvm::Expected<lldb::addr_t> task_addr_location =
ComputeTaskAddrLocationFromThreadLocalStorage(real_thread);
assert(task_addr_location);
assert(it->second == *task_addr_location);
// In assert builds, check that caching did not produce incorrect results.
llvm::Expected<lldb::addr_t> task_addr_location =
ComputeTaskAddrLocationFromThreadLocalStorage(real_thread);
assert(task_addr_location);
assert(it->second == *task_addr_location);
#endif
llvm::Expected<lldb::addr_t> task_addr =
ReadTaskAddr(it->second, *thread.GetProcess());
if (task_addr)
return task_addr;
// If the cached task addr location became invalid, invalidate the cache.
m_tid_to_task_addr_location.erase(it);
LLDB_LOG_ERROR(GetLog(LLDBLog::OS), task_addr.takeError(),
"TaskInspector: evicted task location address due to "
"invalid memory read: {0}");
}

llvm::Expected<lldb::addr_t> task_addr_location =
continue;
}
llvm::Expected<addr_t> addr_loc =
ComputeTaskAddrLocationFromThreadLocalStorage(real_thread);
if (!addr_loc) {
LLDB_LOG_ERROR(GetLog(LLDBLog::OS), addr_loc.takeError(),
"TaskInspector: failed to compute task address location "
"from TLS: {0}");
addr_locations.push_back(std::nullopt);
} else
addr_locations.push_back(*addr_loc);
}
return addr_locations;
}

std::optional<addr_t> TaskInspector::RetryRead(Thread &thread,
addr_t task_addr_location) {
Thread &real_thread =
thread.GetBackingThread() ? *thread.GetBackingThread() : thread;
user_id_t tid = real_thread.GetID();

// For unsuccessful reads whose address was not cached, don't try again.
if (!m_tid_to_task_addr_location.erase(tid))
return std::nullopt;

LLDB_LOG(GetLog(LLDBLog::OS), "TaskInspector: evicted task location "
"address due to invalid memory read");

// The cached address could not be loaded. "This should never happen", but
// recompute the address and try again for completeness.
llvm::Expected<addr_t> task_addr_loc =
ComputeTaskAddrLocationFromThreadLocalStorage(real_thread);
if (!task_addr_location)
return task_addr_location;

llvm::Expected<lldb::addr_t> task_addr =
ReadTaskAddr(*task_addr_location, *thread.GetProcess());

// If the read from this TLS address is successful, cache the TLS address.
// Caching without a valid read is dangerous: earlier in the thread
// lifetime, the result of GetExtendedInfo can be invalid.
if (task_addr &&
real_thread.GetProcess()->GetTarget().GetSwiftCacheTaskPointerLocation())
m_tid_to_task_addr_location.try_emplace(real_thread.GetID(),
*task_addr_location);
return task_addr;
if (!task_addr_loc) {
LLDB_LOG_ERROR(GetLog(LLDBLog::OS), task_addr_loc.takeError(),
"TaskInspector: failed to compute task address location "
"from TLS: {0}");
return std::nullopt;
}

std::optional<addr_t> read_retry_result =
ReadPointer(*thread.GetProcess(), *task_addr_loc);
if (read_retry_result)
m_tid_to_task_addr_location[tid] = *task_addr_loc;
return read_retry_result;
}

llvm::SmallVector<std::optional<addr_t>>
TaskInspector::GetTaskAddrFromThreadLocalStorage(
llvm::ArrayRef<Thread *> threads) {
if (threads.empty())
return {};

llvm::SmallVector<std::optional<addr_t>> addr_locations =
GetTaskAddrLocations(threads);

Process &process = *threads[0]->GetProcess();
llvm::SmallVector<std::optional<addr_t>> mem_read_results =
MultiReadPointers(process, addr_locations);

for (auto [idx, thread] : llvm::enumerate(threads)) {
if (!addr_locations[idx])
continue;
// If the read was successful, cache the address.
if (mem_read_results[idx]) {
Thread &real_thread =
thread->GetBackingThread() ? *thread->GetBackingThread() : *thread;
m_tid_to_task_addr_location[real_thread.GetID()] = *addr_locations[idx];
continue;
}
mem_read_results[idx] = RetryRead(*thread, *addr_locations[idx]);
}

return mem_read_results;
}

namespace {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -934,10 +934,24 @@ class TaskInspector {
public:
/// Inspects thread local storage to find the address of the currently
/// executing task, if any.
llvm::Expected<lldb::addr_t>
GetTaskAddrFromThreadLocalStorage(Thread &thread);
std::optional<lldb::addr_t> GetTaskAddrFromThreadLocalStorage(Thread &thread);

/// Inspects thread local storage to find the address of the currently
/// executing task, if any.
llvm::SmallVector<std::optional<lldb::addr_t>>
GetTaskAddrFromThreadLocalStorage(llvm::ArrayRef<Thread *> threads);

private:
/// For each thread in `threads`, return the location of the its task
/// pointer, if it exists.
llvm::SmallVector<std::optional<lldb::addr_t>>
GetTaskAddrLocations(llvm::ArrayRef<Thread *> threads);

/// If reading from a cached task address location failed, invalidate the
/// cache and try again.
std::optional<lldb::addr_t> RetryRead(Thread &thread,
lldb::addr_t task_addr_location);

llvm::DenseMap<uint64_t, lldb::addr_t> m_tid_to_task_addr_location;
};

Expand Down
Loading