diff --git a/lldb/include/lldb/Utility/DataExtractor.h b/lldb/include/lldb/Utility/DataExtractor.h index b4960f5e87c85..db85b44debf43 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; @@ -609,17 +610,17 @@ class DataExtractor { /// The extracted uint8_t value. uint8_t GetU8(lldb::offset_t *offset_ptr) const; - uint8_t GetU8_unchecked(lldb::offset_t *offset_ptr) const { + virtual uint8_t GetU8_unchecked(lldb::offset_t *offset_ptr) const { uint8_t val = m_start[*offset_ptr]; *offset_ptr += 1; return val; } - uint16_t GetU16_unchecked(lldb::offset_t *offset_ptr) const; + virtual uint16_t GetU16_unchecked(lldb::offset_t *offset_ptr) const; - uint32_t GetU32_unchecked(lldb::offset_t *offset_ptr) const; + virtual uint32_t GetU32_unchecked(lldb::offset_t *offset_ptr) const; - uint64_t GetU64_unchecked(lldb::offset_t *offset_ptr) const; + virtual uint64_t GetU64_unchecked(lldb::offset_t *offset_ptr) const; /// Extract \a count uint8_t values from \a *offset_ptr. /// /// Extract \a count uint8_t values from the binary data at the offset @@ -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/RangeMap.h b/lldb/include/lldb/Utility/RangeMap.h index e701ae1ba96c8..24ed4a55deb68 100644 --- a/lldb/include/lldb/Utility/RangeMap.h +++ b/lldb/include/lldb/Utility/RangeMap.h @@ -465,6 +465,10 @@ class RangeDataVector { RangeDataVector(Compare compare = Compare()) : m_compare(compare) {} + RangeDataVector(std::initializer_list entries, + Compare compare = Compare()) + : m_entries(entries), m_compare(compare) {} + ~RangeDataVector() = default; void Append(const Entry &entry) { m_entries.emplace_back(entry); } diff --git a/lldb/include/lldb/Utility/VirtualDataExtractor.h b/lldb/include/lldb/Utility/VirtualDataExtractor.h new file mode 100644 index 0000000000000..e430dd8628b5f --- /dev/null +++ b/lldb/include/lldb/Utility/VirtualDataExtractor.h @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// 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; + + 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; + + /// Unchecked overrides + /// @{ + uint8_t GetU8_unchecked(lldb::offset_t *offset_ptr) const override; + uint16_t GetU16_unchecked(lldb::offset_t *offset_ptr) const override; + uint32_t GetU32_unchecked(lldb::offset_t *offset_ptr) const override; + uint64_t GetU64_unchecked(lldb::offset_t *offset_ptr) const override; + /// @} + +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..a23e43b383d25 --- /dev/null +++ b/lldb/source/Utility/VirtualDataExtractor.cpp @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// 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(); +} + +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); +} + +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; +} 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..09f3edbecc7ad --- /dev/null +++ b/lldb/unittests/Utility/VirtualDataExtractorTest.cpp @@ -0,0 +1,583 @@ +//===----------------------------------------------------------------------===// +// +// 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; + +using Table = VirtualDataExtractor::LookupTable; +using Entry = Table::Entry; + +TEST(VirtualDataExtractorTest, BasicConstruction) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + EXPECT_EQ(extractor->GetByteSize(), 8U); +} + +TEST(VirtualDataExtractorTest, GetDataAtVirtualOffset) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + 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}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // 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}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + 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}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + 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}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + 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}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + 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}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + 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}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderBig, 4, Table{Entry(0x1000, 4, 0)}); + + 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. + }; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 4, 0), // Virt 0x1000-0x1004 + Entry(0x2000, 4, 4), // Virt 0x2000-0x2004 + Entry(0x3000, 4, 8)}); // Virt 0x3000-0x3004 + + // 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}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 2, 0), // Virt 0x1000-0x1002 + Entry(0x5000, 2, 2)}); // Virt 0x5000-0x5002 + + // 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(buffer, sizeof(buffer)); + + lldb::DataExtractorSP extractor = std::make_shared( + data_sp, eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); +} + +TEST(VirtualDataExtractorTest, NullPointerHandling) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // 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}; + + // Map virtual address 0x1000 to physical offset 4 (skipping first 4 bytes). + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 4)}); + + offset_t virtual_offset = 0x1000; + // Should read from physical offset 4, not 0. + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0xDDCCBBAAU); +} + +TEST(VirtualDataExtractorTest, GetU8Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU8_unchecked(&virtual_offset), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + EXPECT_EQ(extractor->GetU8_unchecked(&virtual_offset), 0x34U); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, GetU16Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x7856U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU32Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32_unchecked(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + EXPECT_EQ(extractor->GetU32_unchecked(&virtual_offset), 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU64_unchecked(&virtual_offset), + 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetMaxU64Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test various byte sizes. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 1), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 2), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 4), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 8), + 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetAddressUnchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetAddress_unchecked(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, UncheckedWithBigEndian) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderBig, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x1234U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x5678U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetCStr) { + // Create buffer with null-terminated strings. + uint8_t buffer[] = {'H', 'e', 'l', 'l', 'o', '\0', 'W', 'o', + 'r', 'l', 'd', '\0', 'F', 'o', 'o', '\0'}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 6, 0), Entry(0x2000, 12, 6)}); + + // Test reading first string. + offset_t virtual_offset = 0x1000; + const char *str1 = extractor->GetCStr(&virtual_offset); + ASSERT_NE(str1, nullptr); + EXPECT_STREQ(str1, "Hello"); + EXPECT_EQ(virtual_offset, 0x1006U); // After "Hello\0" + + // Test reading second string. + virtual_offset = 0x2000; + const char *str2 = extractor->GetCStr(&virtual_offset); + ASSERT_NE(str2, nullptr); + EXPECT_STREQ(str2, "World"); + EXPECT_EQ(virtual_offset, 0x2006U); // After "World\0" +} + +TEST(VirtualDataExtractorTest, GetFloat) { + // Create buffer with float value (IEEE 754 single precision). + // 3.14159f in little endian: 0xDB 0x0F 0x49 0x40 + uint8_t buffer[] = {0xDB, 0x0F, 0x49, 0x40}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + float value = extractor->GetFloat(&virtual_offset); + EXPECT_NEAR(value, 3.14159f, 0.00001f); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetDouble) { + // Create buffer with double value (IEEE 754 double precision). + // 3.14159265358979 in little endian + uint8_t buffer[] = {0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 0x09, 0x40}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + double value = extractor->GetDouble(&virtual_offset); + EXPECT_NEAR(value, 3.14159265358979, 0.00000000000001); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetULEB128) { + // ULEB128 encoding: 0x624 (1572 decimal) = 0xA4 0x0C + uint8_t buffer[] = {0xA4, 0x0C, 0xFF, 0x00, 0x7F, 0x80, 0x01}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 7, 0)}); + + // Test reading first ULEB128 value (1572). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 1572U); + EXPECT_EQ(virtual_offset, 0x1002U); + + // Test reading second ULEB128 value (127). + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 127U); + EXPECT_EQ(virtual_offset, 0x1005U); + + // Test reading third ULEB128 value (128). + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 128U); + EXPECT_EQ(virtual_offset, 0x1007U); +} + +TEST(VirtualDataExtractorTest, GetSLEB128) { + // SLEB128 encoding: -123 = 0x85 0x7F, 123 = 0xFB 0x00 + uint8_t buffer[] = {0x85, 0x7F, 0xFB, 0x00}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Test reading negative SLEB128 value (-123). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetSLEB128(&virtual_offset), -123); + EXPECT_EQ(virtual_offset, 0x1002U); + + // Test reading positive SLEB128 value (123). + EXPECT_EQ(extractor->GetSLEB128(&virtual_offset), 123); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU8Array) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 4 bytes. + offset_t virtual_offset = 0x1000; + uint8_t dst[4] = {0}; + void *result = extractor->GetU8(&virtual_offset, dst, 4); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x01U); + EXPECT_EQ(dst[1], 0x02U); + EXPECT_EQ(dst[2], 0x03U); + EXPECT_EQ(dst[3], 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU16Array) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 3 uint16_t values. + offset_t virtual_offset = 0x1000; + uint16_t dst[3] = {0}; + void *result = extractor->GetU16(&virtual_offset, dst, 3); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x3412U); + EXPECT_EQ(dst[1], 0x7856U); + EXPECT_EQ(dst[2], 0xBC9AU); + EXPECT_EQ(virtual_offset, 0x1006U); +} + +TEST(VirtualDataExtractorTest, GetU32Array) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 2 uint32_t values. + offset_t virtual_offset = 0x1000; + uint32_t dst[2] = {0}; + void *result = extractor->GetU32(&virtual_offset, dst, 2); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x78563412U); + EXPECT_EQ(dst[1], 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64Array) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 16, 0)}); + + // Test reading array of 2 uint64_t values. + offset_t virtual_offset = 0x1000; + uint64_t dst[2] = {0}; + void *result = extractor->GetU64(&virtual_offset, dst, 2); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x0807060504030201ULL); + EXPECT_EQ(dst[1], 0x1817161514131211ULL); + EXPECT_EQ(virtual_offset, 0x1010U); +} + +TEST(VirtualDataExtractorTest, GetMaxU64WithVariableSizes) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading 3-byte value. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64(&virtual_offset, 3), 0x563412U); + EXPECT_EQ(virtual_offset, 0x1003U); + + // Test reading 5-byte value. + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64(&virtual_offset, 5), 0x9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1005U); +} + +TEST(VirtualDataExtractorTest, GetMaxS64) { + // Test with negative number (sign extension). + uint8_t buffer[] = {0xFF, 0xFF, 0xFF, 0xFF}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Test reading 1-byte signed value (-1). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxS64(&virtual_offset, 1), -1); + EXPECT_EQ(virtual_offset, 0x1001U); + + // Test reading 2-byte signed value (-1). + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxS64(&virtual_offset, 2), -1); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, CannotReadAcrossEntryBoundaries) { + // Create buffer with two separate regions. + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x11, 0x12, 0x13, 0x14}; + + // First entry: virtual 0x1000-0x1004 maps to physical 0-4. + // Second entry: virtual 0x2000-0x2004 maps to physical 4-8. + // Note: there's a gap in virtual addresses (0x1004-0x2000). + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 4, 0), Entry(0x2000, 4, 4)}); + + // Verify we can read within the first entry. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Verify we can read within the second entry. + virtual_offset = 0x2000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x14131211U); + EXPECT_EQ(virtual_offset, 0x2004U); + + // Verify we CANNOT read in the gap between entries. + // This address is not in any lookup table entry. + virtual_offset = 0x1500; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1500U); + + // Verify we CANNOT read data pointer from the gap. + virtual_offset = 0x1800; + const void *data = extractor->GetData(&virtual_offset, 1); + EXPECT_EQ(data, nullptr); + EXPECT_EQ(virtual_offset, 0x1800U); // Offset unchanged. + + // Verify we can read individual bytes within each entry. + virtual_offset = 0x1003; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Verify we CANNOT read past the end of an entry. + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, ReadExactlyAtEntryEnd) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + lldb::DataExtractorSP extractor = std::make_shared( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Reading exactly to the end of an entry should work. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // But reading one byte past the end should fail. + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Reading from just before the end should work for smaller sizes. + virtual_offset = 0x1003; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); +}