https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/168802
None >From a2258029c741f357d0f45c656839492c7411898c Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <[email protected]> Date: Wed, 19 Nov 2025 17:07:47 -0800 Subject: [PATCH] [lldb] Add VirtualDataExtractor abstraction --- lldb/include/lldb/Utility/DataExtractor.h | 6 +- .../lldb/Utility/VirtualDataExtractor.h | 69 +++++ lldb/source/Utility/CMakeLists.txt | 1 + lldb/source/Utility/VirtualDataExtractor.cpp | 95 ++++++ lldb/unittests/Utility/CMakeLists.txt | 1 + .../Utility/VirtualDataExtractorTest.cpp | 285 ++++++++++++++++++ 6 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 lldb/include/lldb/Utility/VirtualDataExtractor.h create mode 100644 lldb/source/Utility/VirtualDataExtractor.cpp create mode 100644 lldb/unittests/Utility/VirtualDataExtractorTest.cpp diff --git a/lldb/include/lldb/Utility/DataExtractor.h b/lldb/include/lldb/Utility/DataExtractor.h index b4960f5e87c85..fe217795ff3b1 100644 --- a/lldb/include/lldb/Utility/DataExtractor.h +++ b/lldb/include/lldb/Utility/DataExtractor.h @@ -334,7 +334,8 @@ class DataExtractor { /// \return /// A pointer to the bytes in this object's data if the offset /// and length are valid, or nullptr otherwise. - const void *GetData(lldb::offset_t *offset_ptr, lldb::offset_t length) const { + virtual const void *GetData(lldb::offset_t *offset_ptr, + lldb::offset_t length) const { const uint8_t *ptr = PeekData(*offset_ptr, length); if (ptr) *offset_ptr += length; @@ -829,7 +830,8 @@ class DataExtractor { /// A non-nullptr data pointer if \a offset is a valid offset and /// there are \a length bytes available at that offset, nullptr /// otherwise. - const uint8_t *PeekData(lldb::offset_t offset, lldb::offset_t length) const { + virtual const uint8_t *PeekData(lldb::offset_t offset, + lldb::offset_t length) const { if (ValidOffsetForDataOfSize(offset, length)) return m_start + offset; return nullptr; diff --git a/lldb/include/lldb/Utility/VirtualDataExtractor.h b/lldb/include/lldb/Utility/VirtualDataExtractor.h new file mode 100644 index 0000000000000..a940e58db7a12 --- /dev/null +++ b/lldb/include/lldb/Utility/VirtualDataExtractor.h @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UTILITY_VIRTUALDATAEXTRACTOR_H +#define LLDB_UTILITY_VIRTUALDATAEXTRACTOR_H + +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/RangeMap.h" +#include "lldb/lldb-types.h" + +namespace lldb_private { + +/// A DataExtractor subclass that allows reading data at virtual addresses +/// using a lookup table that maps virtual address ranges to physical offsets. +/// +/// This class maintains a lookup table where each entry contains: +/// - base: starting virtual address for this entry +/// - size: size of this entry in bytes +/// - data: physical offset in the underlying data buffer +/// +/// Reads are translated from virtual addresses to physical offsets using +/// this lookup table. Reads cannot cross entry boundaries and this is +/// enforced with assertions. +class VirtualDataExtractor : public DataExtractor { +public: + /// Type alias for the range map used internally. + /// Maps virtual addresses (base) to physical offsets (data). + using LookupTable = + RangeDataVector<lldb::offset_t, lldb::offset_t, lldb::offset_t>; + + VirtualDataExtractor() = default; + + VirtualDataExtractor(const void *data, lldb::offset_t data_length, + lldb::ByteOrder byte_order, uint32_t addr_size, + LookupTable lookup_table); + + VirtualDataExtractor(const lldb::DataBufferSP &data_sp, + lldb::ByteOrder byte_order, uint32_t addr_size, + LookupTable lookup_table); + + const void *GetData(lldb::offset_t *offset_ptr, + lldb::offset_t length) const override; + + const uint8_t *PeekData(lldb::offset_t offset, + lldb::offset_t length) const override; + + const LookupTable &GetLookupTable() const { return m_lookup_table; } + +protected: + /// Find the lookup entry that contains the given virtual address. + const LookupTable::Entry *FindEntry(lldb::offset_t virtual_addr) const; + + /// Validate that a read at a virtual address is within bounds and + /// does not cross entry boundaries. + bool ValidateVirtualRead(lldb::offset_t virtual_addr, + lldb::offset_t length) const; + +private: + LookupTable m_lookup_table; +}; + +} // namespace lldb_private + +#endif // LLDB_UTILITY_VIRTUALDATAEXTRACTOR_H diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt index 1dd4d63f7016f..4696ed4690d37 100644 --- a/lldb/source/Utility/CMakeLists.txt +++ b/lldb/source/Utility/CMakeLists.txt @@ -78,6 +78,7 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES UserIDResolver.cpp VASprintf.cpp VMRange.cpp + VirtualDataExtractor.cpp XcodeSDK.cpp ZipFile.cpp diff --git a/lldb/source/Utility/VirtualDataExtractor.cpp b/lldb/source/Utility/VirtualDataExtractor.cpp new file mode 100644 index 0000000000000..f727cf1c8bc8a --- /dev/null +++ b/lldb/source/Utility/VirtualDataExtractor.cpp @@ -0,0 +1,95 @@ +//===----------------------------------------------------------------------===// +// +// 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 <cassert> + +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(); +} + +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; +} + +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); +} diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt index aed4177f5edee..77b52079cf32b 100644 --- a/lldb/unittests/Utility/CMakeLists.txt +++ b/lldb/unittests/Utility/CMakeLists.txt @@ -48,6 +48,7 @@ add_lldb_unittest(UtilityTests UserIDResolverTest.cpp UUIDTest.cpp VASprintfTest.cpp + VirtualDataExtractorTest.cpp VMRangeTest.cpp XcodeSDKTest.cpp diff --git a/lldb/unittests/Utility/VirtualDataExtractorTest.cpp b/lldb/unittests/Utility/VirtualDataExtractorTest.cpp new file mode 100644 index 0000000000000..2a321884bab84 --- /dev/null +++ b/lldb/unittests/Utility/VirtualDataExtractorTest.cpp @@ -0,0 +1,285 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Utility/DataBufferHeap.h" +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb; + +TEST(VirtualDataExtractorTest, BasicConstruction) { + // Create a simple data buffer. + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + // Create a lookup table that maps virtual addresses to physical offsets. + VirtualDataExtractor::LookupTable lookup_table; + // Virtual address 0x1000-0x1008 maps to physical offset 0-8. + // Entry(base=virtual_offset, size, data=physical_offset). + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 8, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + EXPECT_EQ(extractor.GetByteSize(), 8U); +} + +TEST(VirtualDataExtractorTest, GetDataAtVirtualOffset) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 8, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + const void *data = extractor.GetData(&virtual_offset, 4); + + ASSERT_NE(data, nullptr); + EXPECT_EQ(virtual_offset, 0x1004U); + EXPECT_EQ(memcmp(data, buffer, 4), 0); +} + +TEST(VirtualDataExtractorTest, GetDataAtVirtualOffsetInvalid) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 4, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + // Try to read from an invalid virtual address. + offset_t virtual_offset = 0x2000; + const void *data = extractor.GetData(&virtual_offset, 4); + + EXPECT_EQ(data, nullptr); +} + +TEST(VirtualDataExtractorTest, GetU8AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 4, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetU8(&virtual_offset), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + EXPECT_EQ(extractor.GetU8(&virtual_offset), 0x34U); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, GetU16AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 4, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetU16(&virtual_offset), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor.GetU16(&virtual_offset), 0x7856U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU32AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 8, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetU32(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + EXPECT_EQ(extractor.GetU32(&virtual_offset), 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 8, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 8, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetU64(&virtual_offset), 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetAddressAtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 4, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetAddress(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, BigEndian) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 4, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderBig, 4, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetU16(&virtual_offset), 0x1234U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor.GetU16(&virtual_offset), 0x5678U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, MultipleEntries) { + // Create a buffer with distinct patterns for each section. + uint8_t buffer[] = { + 0x01, 0x02, 0x03, 0x04, // Physical offset 0-3. + 0x11, 0x12, 0x13, 0x14, // Physical offset 4-7. + 0x21, 0x22, 0x23, 0x24 // Physical offset 8-11. + }; + + VirtualDataExtractor::LookupTable lookup_table; + // Map different virtual address ranges to different physical offsets. + // Entry(base=virtual_offset, size, data=physical_offset). + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry( + 0x1000, 4, 0)); // Virt 0x1000-0x1004 -> phys 0-4. + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry( + 0x2000, 4, 4)); // Virt 0x2000-0x2004 -> phys 4-8. + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry( + 0x3000, 4, 8)); // Virt 0x3000-0x3004 -> phys 8-12. + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + // Test reading from first virtual range. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetU8(&virtual_offset), 0x01U); + + // Test reading from second virtual range. + virtual_offset = 0x2000; + EXPECT_EQ(extractor.GetU8(&virtual_offset), 0x11U); + + // Test reading from third virtual range. + virtual_offset = 0x3000; + EXPECT_EQ(extractor.GetU8(&virtual_offset), 0x21U); +} + +TEST(VirtualDataExtractorTest, NonContiguousVirtualAddresses) { + uint8_t buffer[] = {0xAA, 0xBB, 0xCC, 0xDD}; + + VirtualDataExtractor::LookupTable lookup_table; + // Create non-contiguous virtual address mapping. + // Entry(base=virtual_offset, size, data=physical_offset). + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry( + 0x1000, 2, 0)); // Virt 0x1000-0x1002 -> phys 0-2. + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry( + 0x5000, 2, 2)); // Virt 0x5000-0x5002 -> phys 2-4. + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + // Test reading from first virtual range. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetU16(&virtual_offset), 0xBBAAU); + + // Test reading from second virtual range (non-contiguous). + virtual_offset = 0x5000; + EXPECT_EQ(extractor.GetU16(&virtual_offset), 0xDDCCU); + + // Test that gap between ranges is invalid. + virtual_offset = 0x3000; + EXPECT_EQ(extractor.GetU8(&virtual_offset), 0U); +} + +TEST(VirtualDataExtractorTest, SharedDataBuffer) { + // Test with shared_ptr to DataBuffer. + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + auto data_sp = std::make_shared<DataBufferHeap>(buffer, sizeof(buffer)); + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 4, 0)); + + VirtualDataExtractor extractor(data_sp, eByteOrderLittle, 4, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor.GetU32(&virtual_offset), 0x04030201U); +} + +TEST(VirtualDataExtractorTest, LookupTableAccess) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 2, 0)); + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x2000, 2, 2)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + const auto &table = extractor.GetLookupTable(); + EXPECT_EQ(table.GetSize(), 2U); + EXPECT_FALSE(table.IsEmpty()); +} + +TEST(VirtualDataExtractorTest, NullPointerHandling) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + VirtualDataExtractor::LookupTable lookup_table; + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 4, 0)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + // Test that passing nullptr returns default values. + EXPECT_EQ(extractor.GetU8(nullptr), 0U); + EXPECT_EQ(extractor.GetU16(nullptr), 0U); + EXPECT_EQ(extractor.GetU32(nullptr), 0U); + EXPECT_EQ(extractor.GetU64(nullptr), 0U); + EXPECT_EQ(extractor.GetAddress(nullptr), 0U); + EXPECT_EQ(extractor.GetData(nullptr, 4), nullptr); +} + +TEST(VirtualDataExtractorTest, OffsetMapping) { + // Test that virtual to physical offset mapping works correctly. + uint8_t buffer[] = {0x00, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; + + VirtualDataExtractor::LookupTable lookup_table; + // Map virtual address 0x1000 to physical offset 4 (skipping first 4 bytes). + // Entry(base=virtual_offset, size, data=physical_offset). + lookup_table.Append(VirtualDataExtractor::LookupTable::Entry(0x1000, 4, 4)); + + VirtualDataExtractor extractor(buffer, sizeof(buffer), eByteOrderLittle, 4, + std::move(lookup_table)); + + offset_t virtual_offset = 0x1000; + // Should read from physical offset 4, not 0. + EXPECT_EQ(extractor.GetU32(&virtual_offset), 0xDDCCBBAAU); +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
