Files
llvm/lldb/unittests/Utility/VirtualDataExtractorTest.cpp
Jonas Devlieghere 9438b741d4 [lldb] Add VirtualDataExtractor for virtual address translation (#168802)
Introduce VirtualDataExtractor, a DataExtractor subclass that enables
reading data at virtual addresses by translating them to physical buffer
offsets using a lookup table. The lookup table maps virtual address
ranges to physical offsets and enforces boundaries to prevent reads from
crossing entry limits.

The new class inherits from DataExtractor, overriding GetData and
PeekData to provide transparent virtual address translation for most of
the DataExtractor methods. The exception are the unchecked methods, that
bypass those methods and are overloaded as well.
2025-12-01 08:27:42 -08:00

584 lines
21 KiB
C++

//===----------------------------------------------------------------------===//
//
// 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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<DataBufferHeap>(buffer, sizeof(buffer));
lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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<VirtualDataExtractor>(
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);
}