blob: 300c656b54f3785187a07b023e826bf81dad2237 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/zxdump/task.h>
#include <algorithm>
#include <cinttypes>
#include <cstdio>
#include <memory>
#include <new>
#include "buffer-impl.h"
#include "dump-file.h"
namespace zxdump {
namespace {
class AlignedBufferImpl final : public internal::BufferImpl {
public:
AlignedBufferImpl(std::align_val_t alignment, size_t size)
: alignment_{alignment},
size_{size},
ptr_(static_cast<std::byte*>(operator new[](size_, alignment_))) {}
~AlignedBufferImpl() { operator delete[](ptr_, size_, alignment_); }
std::byte* get() const { return ptr_; }
private:
std::align_val_t alignment_;
std::size_t size_;
std::byte* ptr_;
};
} // namespace
fit::result<Error, Buffer<>> Process::ReadMemoryImpl(uint64_t vaddr, size_t size,
ReadMemorySize size_mode, size_t alignment) {
auto read_dump_memory = [this, vaddr, size, size_mode]() -> fit::result<Error, Buffer<>> {
auto possible = [vaddr](const auto& elt) -> bool {
const auto& [segment_vaddr, segment] = elt;
return segment_vaddr + segment.memsz <= vaddr;
};
auto found = std::partition_point(memory_.begin(), memory_.end(), possible);
if (found != memory_.end()) {
const auto& [segment_vaddr, segment] = *found;
if (vaddr >= segment_vaddr && vaddr - segment_vaddr < segment.memsz) {
// This segment covers the address.
if (segment.filesz <= vaddr - segment_vaddr || !dump_) {
// The portion of this segment we need was elided, or the whole dump
// was inserted with read_memory=false,
return fit::ok(Buffer<>{});
}
// Map the segment to a region of the file.
internal::FileRange where = {segment.offset, segment.filesz};
where %= vaddr - segment_vaddr;
// If the next segment is contiguous we can fetch across them.
while (++found != memory_.end() && found->first == vaddr + where.size &&
found->second.offset == where.offset + where.size) {
where.size += found->second.filesz;
}
auto result = dump_->ReadMemory(where);
if (result.is_error()) {
return result.take_error();
}
Buffer<> buffer = *std::move(result);
ZX_ASSERT(buffer->size_bytes() >= size);
if (size_mode == ReadMemorySize::kExact) {
buffer.data_ = buffer.data_.subspan(0, size);
}
return fit::ok(std::move(buffer));
}
}
return fit::error(Error{"no such memory", ZX_ERR_NO_MEMORY});
};
auto result = live() ? ReadLiveMemory(vaddr, size, size_mode) : read_dump_memory();
// When reading a job archive, the segment.offset has been adjusted by
// the process dump file's position in the archive, so file offsets may
// no longer be aligned as they were in the ELF file. So even a request
// at a properly-aligned vaddr might yield a file buffer that's not
// aligned. It's also possible that the live memory cache's pages didn't
// actually get allocated locally as page-aligned, and the requested
// alignment exceeds the alignment of the page buffer. In these cases,
// just copy to an aligned buffer.
if (alignment > 1 && result.is_ok()) {
// Keep the Buffer<> holder and just update what it holds (if necessary).
Buffer<>& buffer = *result;
// Only copy the requested data even in ReadMemorySize::kMore mode.
const size_t result_size = std::min(size, buffer->size_bytes());
// Check if it's not already as aligned as it needs to be.
void* align_ptr = const_cast<std::byte*>(buffer->data());
size_t align_space = result_size;
if (!std::align(alignment, result_size, align_ptr, align_space)) {
// Copy the contents into a fresh, aligned buffer.
auto impl = std::make_unique<AlignedBufferImpl>(std::align_val_t{alignment}, size);
memcpy(impl->get(), buffer->data(), result_size);
buffer.data_ = {impl->get(), result_size};
// Note this may free the old buffer, so it must be after the copying.
buffer.impl_ = std::move(impl);
}
}
return result;
}
} // namespace zxdump