blob: 950c2f04b7112cfc33fde685412a27a8cc121f0a [file] [log] [blame]
// Copyright 2023 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/elfldltl/container.h>
#include <lib/elfldltl/load.h>
#include <lib/elfldltl/loadinfo-mapped-memory.h>
#include <lib/elfldltl/memory.h>
#include <lib/elfldltl/testing/diagnostics.h>
#include <lib/elfldltl/testing/typed-test.h>
#include <gmock/gmock.h>
#include "load-tests.h"
namespace {
using elfldltl::testing::ConstantPhdr;
using elfldltl::testing::DataPhdr;
using elfldltl::testing::ExpectOkDiagnostics;
using ::testing::Eq;
using ::testing::Pointer;
using ::testing::Property;
using ::testing::Return;
using ::testing::StrictMock;
constexpr size_t kPageSize = 4096;
constexpr std::array<std::byte, 8> kData = {std::byte{0xa1}, std::byte{0xb1}, std::byte{0xc1},
std::byte{0xd1}, std::byte{0xe1}, std::byte{0xf1},
std::byte{0xa2}, std::byte{0xb2}};
FORMAT_TYPED_TEST_SUITE(ElfldltlLoadInfoMappedMemoryTests);
class MockDirectMemory {
public:
MockDirectMemory() { ON_CALL(*this, ReadBytes).WillByDefault(Return(cpp20::span(kData))); }
MOCK_METHOD(std::optional<cpp20::span<const std::byte>>, ReadBytes,
(uintptr_t ptr, size_t count));
// This is a workaround for MOCK_METHOD's incompatibility with templated functions.
// LoadInfoMappedMemory calls into this ReadArray function, which in turn calls
// the ReadBytes MOCK_METHOD above in tests.
template <typename T>
std::optional<cpp20::span<const T>> ReadArray(uintptr_t ptr, size_t count) {
if (std::optional result = ReadBytes(ptr, count * sizeof(T))) {
return cpp20::span{reinterpret_cast<const T*>(result->data()),
result->size_bytes() / sizeof(T)};
}
return std::nullopt;
}
};
// A helper that verifies the return value of MockDirectMemory::ReadBytes
// matches kData properties.
template <typename T>
constexpr auto IsExpectedData() {
return Optional(AllOf(
Property(&cpp20::span<const T>::data, Pointer(reinterpret_cast<const T*>(kData.data()))),
Property(&cpp20::span<const T>::size, Eq(kData.size() / sizeof(T)))));
}
TYPED_TEST(ElfldltlLoadInfoMappedMemoryTests, ReadInBounds) {
using Elf = typename TestFixture::Elf;
using ElfLoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container>;
using size_type = typename Elf::size_type;
auto diag = ExpectOkDiagnostics();
ElfLoadInfo load_info;
// Load three non-mergeable segments.
size_type offset = 0;
for (const auto& phdr :
{ConstantPhdr<Elf>{}(offset), DataPhdr<Elf>{}(offset), ConstantPhdr<Elf>{}(offset)}) {
ASSERT_TRUE(load_info.AddSegment(diag, kPageSize, phdr));
}
StrictMock<MockDirectMemory> mock_mem;
elfldltl::LoadInfoMappedMemory mapped_mem(load_info, mock_mem);
// Read from first segment..., .
EXPECT_CALL(mock_mem, ReadBytes(0, 4096)).Times(1);
EXPECT_CALL(mock_mem, ReadBytes(2048, 2048)).Times(1);
EXPECT_CALL(mock_mem, ReadBytes(4095, 1)).Times(1);
// Read from second segment.
EXPECT_CALL(mock_mem, ReadBytes(4096, 4096)).Times(1);
EXPECT_CALL(mock_mem, ReadBytes(6144, 2048)).Times(1);
EXPECT_CALL(mock_mem, ReadBytes(8191, 1)).Times(1);
// Read from third segment.
EXPECT_CALL(mock_mem, ReadBytes(8192, 4096)).Times(1);
EXPECT_CALL(mock_mem, ReadBytes(10240, 2048)).Times(1);
EXPECT_CALL(mock_mem, ReadBytes(12287, 1)).Times(1);
// Test read count is correctly inferred.
// The tests use `IsExpectedData<T>()` to test the span (adjusted for sizeof(T))
// returned by the underlying mock memory object is passed straight through by
// the adaptor. The actual kData (a small array of bytes) that the mock memory
// object returns does not matter for testing the adaptor, so these tests do not
// differentiate the data returned by the memory object when offset and count values vary.
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(0), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(2048), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(4095), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(4096), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(6144), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(8191), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(8192), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(10240), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(12287), IsExpectedData<std::byte>());
// Test the explicit read count is passed through.
EXPECT_CALL(mock_mem, ReadBytes(0, 4096)).Times(1);
EXPECT_CALL(mock_mem, ReadBytes(4096, 4096)).Times(1);
EXPECT_CALL(mock_mem, ReadBytes(8192, 4096)).Times(1);
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(0, 4096), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(4096, 4096),
IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(8192, 4096),
IsExpectedData<std::byte>());
}
TYPED_TEST(ElfldltlLoadInfoMappedMemoryTests, ReadSizedType) {
using Elf = typename TestFixture::Elf;
using ElfLoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container>;
using size_type = typename Elf::size_type;
auto diag = ExpectOkDiagnostics();
ElfLoadInfo load_info;
// Load three non-mergeable segments with 4096 byte vaddr ranges.
size_type offset = 0;
for (const auto& phdr :
{ConstantPhdr<Elf>{}(offset), DataPhdr<Elf>{}(offset), ConstantPhdr<Elf>{}(offset)}) {
ASSERT_TRUE(load_info.AddSegment(diag, kPageSize, phdr));
}
StrictMock<MockDirectMemory> mock_mem;
elfldltl::LoadInfoMappedMemory mapped_mem(const_cast<const ElfLoadInfo&>(load_info), mock_mem);
// Test that the available read count is correctly adjusted for 8B-sized read.
EXPECT_CALL(mock_mem, ReadBytes(0, 4096)).Times(1);
EXPECT_THAT(mapped_mem.template ReadArray<const uint64_t>(0), IsExpectedData<uint64_t>());
EXPECT_CALL(mock_mem, ReadBytes(32, 4064)).Times(1);
EXPECT_THAT(mapped_mem.template ReadArray<const uint64_t>(32), IsExpectedData<uint64_t>());
// Test that the request count is passed through from the caller.
EXPECT_CALL(mock_mem, ReadBytes(0, 32)).Times(1);
EXPECT_THAT(mapped_mem.template ReadArray<const uint64_t>(0, 4), IsExpectedData<uint64_t>());
// Expect that the request count cannot exceed availability, adjusted for sizeof(T).
EXPECT_EQ(mapped_mem.template ReadArray<const uint64_t>(0, 1024), std::nullopt);
}
TYPED_TEST(ElfldltlLoadInfoMappedMemoryTests, ReadOutOfBounds) {
using Elf = typename TestFixture::Elf;
using ElfLoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container>;
using size_type = typename Elf::size_type;
auto diag = ExpectOkDiagnostics();
ElfLoadInfo load_info;
// Load three non-mergeable segments.
size_type offset = 0;
for (const auto& phdr :
{ConstantPhdr<Elf>{}(offset), DataPhdr<Elf>{}(offset), ConstantPhdr<Elf>{}(offset)}) {
ASSERT_TRUE(load_info.AddSegment(diag, kPageSize, phdr));
}
// StrictMock will fail if any method on the mock is called that is not expected.
StrictMock<MockDirectMemory> mock_mem;
elfldltl::LoadInfoMappedMemory mapped_mem(const_cast<const ElfLoadInfo&>(load_info), mock_mem);
// Test vaddr that is out of bounds.
EXPECT_EQ(mapped_mem.template ReadArray<const std::byte>(12288), std::nullopt);
// Test size count that is out of bounds.
EXPECT_EQ(mapped_mem.template ReadArray<const std::byte>(12287, 2), std::nullopt);
// Test size count that would carry into the next segment.
EXPECT_EQ(mapped_mem.template ReadArray<const std::byte>(0, 4097), std::nullopt);
}
TYPED_TEST(ElfldltlLoadInfoMappedMemoryTests, ReadSegmentGap) {
using Elf = typename TestFixture::Elf;
using ElfLoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container>;
using size_type = typename Elf::size_type;
auto diag = ExpectOkDiagnostics();
ElfLoadInfo load_info;
// Load segments with a gap between vaddr ranges.
size_type offset1 = 0;
size_type offset2 = 8192;
for (const auto& phdr : {ConstantPhdr<Elf>{}(offset1), ConstantPhdr<Elf>{}(offset2)}) {
ASSERT_TRUE(load_info.AddSegment(diag, kPageSize, phdr));
}
StrictMock<MockDirectMemory> mock_mem;
elfldltl::LoadInfoMappedMemory mapped_mem(const_cast<const ElfLoadInfo&>(load_info), mock_mem);
// Test that read from first segment addr is bounded correctly.
EXPECT_CALL(mock_mem, ReadBytes(0, 4096)).Times(1);
// Test that read from last segment addr is bounded correctly.
EXPECT_CALL(mock_mem, ReadBytes(8192, 4096)).Times(1);
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(0), IsExpectedData<std::byte>());
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(8192), IsExpectedData<std::byte>());
// Test a vaddr that falls within the gap in the segments' vaddr ranges.
EXPECT_EQ(mapped_mem.template ReadArray<const std::byte>(4096), std::nullopt);
EXPECT_EQ(mapped_mem.template ReadArray<const std::byte>(6144), std::nullopt);
}
// Test that reads will not surpass the segment.filesz(), regardless of segment.memsz().
TYPED_TEST(ElfldltlLoadInfoMappedMemoryTests, FileSzBoundary) {
using Elf = typename TestFixture::Elf;
using ElfLoadInfo = elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container>;
using Phdr = typename Elf::Phdr;
auto diag = ExpectOkDiagnostics();
ElfLoadInfo load_info;
// Load DataWithZeroFill segment with a filesz < memsz
Phdr phdr{
.type = elfldltl::ElfPhdrType::kLoad, .offset = 0, .vaddr = 0, .filesz = 2048, .memsz = 4096};
phdr.flags = elfldltl::PhdrBase::kWrite;
ASSERT_TRUE(load_info.AddSegment(diag, kPageSize, phdr));
StrictMock<MockDirectMemory> mock_mem;
elfldltl::LoadInfoMappedMemory mapped_mem(const_cast<const ElfLoadInfo&>(load_info), mock_mem);
// Test that read from the segment is bounded to filesz, not memsz.
EXPECT_CALL(mock_mem, ReadBytes(0, 2048)).Times(1);
EXPECT_THAT(mapped_mem.template ReadArray<const std::byte>(0), IsExpectedData<std::byte>());
// Test that reading from a segment offset > filesz, but < memsz returns null.
EXPECT_EQ(mapped_mem.template ReadArray<const std::byte>(2050), std::nullopt);
}
} // namespace