//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Utility/VirtualDataExtractor.h" #include using namespace lldb; using namespace lldb_private; VirtualDataExtractor::VirtualDataExtractor(const void *data, offset_t data_length, ByteOrder byte_order, uint32_t addr_size, LookupTable lookup_table) : DataExtractor(data, data_length, byte_order, addr_size), m_lookup_table(std::move(lookup_table)) { m_lookup_table.Sort(); } VirtualDataExtractor::VirtualDataExtractor(const DataBufferSP &data_sp, ByteOrder byte_order, uint32_t addr_size, LookupTable lookup_table) : DataExtractor(data_sp, byte_order, addr_size), m_lookup_table(std::move(lookup_table)) { m_lookup_table.Sort(); } VirtualDataExtractor::VirtualDataExtractor(const DataBufferSP &data_sp, LookupTable lookup_table) : DataExtractor(data_sp), m_lookup_table(std::move(lookup_table)) { m_lookup_table.Sort(); } const VirtualDataExtractor::LookupTable::Entry * VirtualDataExtractor::FindEntry(offset_t virtual_addr) const { // Use RangeDataVector's binary search instead of linear search. return m_lookup_table.FindEntryThatContains(virtual_addr); } bool VirtualDataExtractor::ValidateVirtualRead(offset_t virtual_addr, offset_t length) const { const LookupTable::Entry *entry = FindEntry(virtual_addr); if (!entry) return false; // Assert that the read does not cross entry boundaries. // RangeData.Contains() checks if a range is fully contained. assert(entry->Contains(LookupTable::Range(virtual_addr, length)) && "Read crosses lookup table entry boundary"); // Also validate that the physical offset is within the data buffer. // RangeData.data contains the physical offset. offset_t physical_offset = entry->data + (virtual_addr - entry->base); return ValidOffsetForDataOfSize(physical_offset, length); } const void *VirtualDataExtractor::GetData(offset_t *offset_ptr, offset_t length) const { // Override to treat offset as virtual address. if (!offset_ptr) return nullptr; offset_t virtual_addr = *offset_ptr; if (!ValidateVirtualRead(virtual_addr, length)) return nullptr; const LookupTable::Entry *entry = FindEntry(virtual_addr); assert(entry && "ValidateVirtualRead should have found an entry"); offset_t physical_offset = entry->data + (virtual_addr - entry->base); // Use base class PeekData directly to avoid recursion. const void *result = DataExtractor::PeekData(physical_offset, length); if (result) { // Advance the virtual offset pointer. *offset_ptr += length; } return result; } offset_t VirtualDataExtractor::SetData(const void *bytes, lldb::offset_t length, lldb::ByteOrder byte_order) { // Invoked from the base class ctor. if (!m_data_sp || m_start == nullptr) return DataExtractor::SetData(bytes, length, byte_order); // A no-op SetData that is setting the same data buffer again. if (!m_data_sp && m_start == bytes && length == GetVirtualByteSize()) return GetVirtualByteSize(); assert("SetData(1) called on VirtualDataExtractor that already had data" && false); DataExtractor::SetData(bytes, length, byte_order); ResetLookupTableToMatchPhysical(); return GetVirtualByteSize(); } offset_t VirtualDataExtractor::SetData(const DataExtractor &data, lldb::offset_t offset, lldb::offset_t length) { // Invoked from the base class ctor if (!m_data_sp || m_start == nullptr) return DataExtractor::SetData(data, offset, length); // A no-op SetData that is setting the same data buffer again if (m_data_sp && data.GetSharedDataBuffer().get() == m_data_sp.get() && offset == 0 && length == GetVirtualByteSize()) return GetVirtualByteSize(); assert("SetData(2) called on VirtualDataExtractor that already had data" && false); DataExtractor::SetData(data, offset, length); ResetLookupTableToMatchPhysical(); return GetVirtualByteSize(); } offset_t VirtualDataExtractor::SetData(const lldb::DataBufferSP &data_sp, lldb::offset_t offset, lldb::offset_t length) { // Invoked from the base class ctor if (!m_data_sp || m_start == nullptr) return DataExtractor::SetData(data_sp, offset, length); // A no-op SetData that is setting the same data buffer again if (m_data_sp && data_sp.get() == m_data_sp.get() && offset == 0 && length == GetVirtualByteSize()) return GetVirtualByteSize(); assert("SetData(3) called on VirtualDataExtractor that already had data" && false); DataExtractor::SetData(data_sp, offset, length); ResetLookupTableToMatchPhysical(); return GetVirtualByteSize(); } void VirtualDataExtractor::ResetLookupTableToMatchPhysical() { // calling SetData on a VirtualDataExtractor that already has a // data buffer means the LookupTable needs to be either replaced, or // if we assume the buffer is a subset of the original, we need to // update all the entries to have correct new offsets into the buffer // and remove entries that are outside the new range. // For now, zero out the LookupTable and behave as if this is a simple // DataExtractor. m_lookup_table.Clear(); m_lookup_table.Append( VirtualDataExtractor::LookupTable::Entry{0, GetPhysicalByteSize(), 0}); } uint64_t VirtualDataExtractor::GetVirtualByteSize() const { offset_t lowest = -1ULL; offset_t highest = 0; for (const auto ent : m_lookup_table) { lowest = std::min(lowest, ent.base); highest = std::max(highest, ent.base + ent.size); } return highest - lowest; } uint64_t VirtualDataExtractor::GetPhysicalByteSize() const { return m_end - m_start; } offset_t VirtualDataExtractor::VirtualBytesLeft(offset_t virtual_offset) const { const offset_t size = GetVirtualByteSize(); if (size > virtual_offset) return size - virtual_offset; return 0; } offset_t VirtualDataExtractor::PhysicalBytesLeft(offset_t physical_offset) const { const offset_t size = m_end - m_start; if (size > physical_offset) return size - physical_offset; return 0; } const uint8_t *VirtualDataExtractor::PeekData(offset_t offset, offset_t length) const { // Override to treat offset as virtual address. if (!ValidateVirtualRead(offset, length)) return nullptr; const LookupTable::Entry *entry = FindEntry(offset); assert(entry && "ValidateVirtualRead should have found an entry"); offset_t physical_offset = entry->data + (offset - entry->base); // Use the base class PeekData with the physical offset. return DataExtractor::PeekData(physical_offset, length); } uint8_t VirtualDataExtractor::GetU8_unchecked(offset_t *offset_ptr) const { offset_t virtual_addr = *offset_ptr; const LookupTable::Entry *entry = FindEntry(virtual_addr); assert(entry && "Unchecked methods require valid virtual address"); offset_t physical_offset = entry->data + (virtual_addr - entry->base); uint8_t result = DataExtractor::GetU8_unchecked(&physical_offset); *offset_ptr += 1; return result; } uint16_t VirtualDataExtractor::GetU16_unchecked(offset_t *offset_ptr) const { offset_t virtual_addr = *offset_ptr; const LookupTable::Entry *entry = FindEntry(virtual_addr); assert(entry && "Unchecked methods require valid virtual address"); offset_t physical_offset = entry->data + (virtual_addr - entry->base); uint16_t result = DataExtractor::GetU16_unchecked(&physical_offset); *offset_ptr += 2; return result; } uint32_t VirtualDataExtractor::GetU32_unchecked(offset_t *offset_ptr) const { offset_t virtual_addr = *offset_ptr; const LookupTable::Entry *entry = FindEntry(virtual_addr); assert(entry && "Unchecked methods require valid virtual address"); offset_t physical_offset = entry->data + (virtual_addr - entry->base); uint32_t result = DataExtractor::GetU32_unchecked(&physical_offset); *offset_ptr += 4; return result; } uint64_t VirtualDataExtractor::GetU64_unchecked(offset_t *offset_ptr) const { offset_t virtual_addr = *offset_ptr; const LookupTable::Entry *entry = FindEntry(virtual_addr); assert(entry && "Unchecked methods require valid virtual address"); offset_t physical_offset = entry->data + (virtual_addr - entry->base); uint64_t result = DataExtractor::GetU64_unchecked(&physical_offset); *offset_ptr += 8; return result; } DataExtractorSP VirtualDataExtractor::GetSubsetExtractorSP(offset_t virtual_offset, offset_t virtual_length) { const LookupTable::Entry *entry = FindEntry(virtual_offset); assert( entry && "VirtualDataExtractor subset extractor requires valid virtual address"); if (!entry) return {}; // Entry::data is the offset into the DataBuffer's actual start/end range // Entry::base is the virtual address at the start of this region of data offset_t offset_into_entry_range = virtual_offset - entry->base; assert( offset_into_entry_range + virtual_length <= entry->size && "VirtualDataExtractor subset may not span multiple LookupTable entries"); if (offset_into_entry_range + virtual_length > entry->size) return {}; // We could support a Subset VirtualDataExtractor which covered // multiple LookupTable virtual entries, but we'd need to mutate // all of the LookupTable entries that were properly included in // the Subset, a bit tricky. So we won't implement that until it's // needed. offset_t physical_start = entry->data + offset_into_entry_range; std::shared_ptr new_sp = std::make_shared( GetSharedDataBuffer(), GetByteOrder(), GetAddressByteSize()); new_sp->SetData(GetSharedDataBuffer(), physical_start, virtual_length); return new_sp; } // Return a DataExtractorSP that contains a single LookupTable's entry; all // bytes are guaranteed to be readable. DataExtractorSP VirtualDataExtractor::GetSubsetExtractorSP(offset_t virtual_offset) { const LookupTable::Entry *entry = FindEntry(virtual_offset); assert( entry && "VirtualDataExtractor subset extractor requires valid virtual address"); if (!entry) return {}; // Entry::data is the offset into the DataBuffer's actual start/end range // Entry::base is the virtual address at the start of this region of data offset_t offset_into_entry_range = virtual_offset - entry->base; offset_t physical_start = entry->data + offset_into_entry_range; std::shared_ptr new_sp = std::make_shared( GetSharedDataBuffer(), GetByteOrder(), GetAddressByteSize()); new_sp->SetData(GetSharedDataBuffer(), physical_start, entry->size - offset_into_entry_range); return new_sp; } // Return an ArrayRef to the first contiguous region of the LookupTable // only. The LookupTable entries may have gaps of unmapped data, and we // can't include those in the ArrayRef or something may touch those pages. llvm::ArrayRef VirtualDataExtractor::GetData() const { const LookupTable::Entry *entry = FindEntry(0); assert(entry && "VirtualDataExtractor GetData requires valid virtual address"); if (!entry) return {}; return {m_start + static_cast(entry->data), static_cast(entry->size)}; }