blob: 445a35864fcbce5d10501fe1eb9459a125128352 [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/layout.h>
#include <lib/elfldltl/load.h>
#include <lib/elfldltl/loadinfo-mutable-memory.h>
#include <lib/elfldltl/segment-with-vmo.h>
#include <lib/elfldltl/testing/diagnostics.h>
#include <lib/elfldltl/zircon.h>
#include <lib/zx/vmo.h>
#include <vector>
#include <gtest/gtest.h>
#include "load-tests.h"
namespace {
using LoadInfo =
elfldltl::LoadInfo<elfldltl::Elf<>, elfldltl::StdContainer<std::vector>::Container,
elfldltl::PhdrLoadPolicy::kBasic, elfldltl::SegmentWithVmo::Copy>;
using GetMutableMemory = elfldltl::SegmentWithVmo::GetMutableMemory<LoadInfo>;
using size_type = elfldltl::Elf<>::size_type;
using Phdr = elfldltl::Elf<>::Phdr;
static_assert(std::is_move_constructible_v<LoadInfo::Segment>);
static_assert(std::is_move_assignable_v<LoadInfo::Segment>);
TEST(ElfldltlSegmentWithVmoTests, GetMutableMemoryCtor) {
// Default-constructible.
GetMutableMemory default_constructed;
// Copy-constructible.
GetMutableMemory copy_constructed{default_constructed};
// Copy-assignable.
default_constructed = copy_constructed;
// Using it in default-constructed state accesses the invalid VMO handle.
{
// Mock up a fake segment. All that really matters is that segment.vmo()
// is not set yet so GetMutableMemory will try to create a new child from
// the VMO provided at construction (which is none, so invalid handle).
constexpr size_type kSegmentAddr = 0x10000;
constexpr size_type kSegmentSize = 0x20000;
LoadInfo::Segment segment = LoadInfo::DataSegment{
kSegmentAddr, // offset
kSegmentAddr, // vaddr
kSegmentSize, // memsz
kSegmentSize, // filesz
};
ASSERT_FALSE(std::get<LoadInfo::DataSegment>(segment).vmo());
elfldltl::testing::ExpectedSingleError expected_error{
"cannot create copy-on-write VMO for segment contents",
elfldltl::FileOffset{kSegmentAddr},
": ",
elfldltl::ZirconError{ZX_ERR_BAD_HANDLE},
};
auto result = default_constructed(expected_error, segment);
ASSERT_TRUE(result.is_error());
// ExpectedSingleError always says keep going.
EXPECT_TRUE(result.error_value());
}
}
TEST(ElfldltlSegmentWithVmoTests, GetMutableMemoryCreatesVmo) {
constexpr size_type kSegmentAddr = 0x10000;
constexpr size_type kSegmentSize = 0x20000;
constexpr uint64_t kFileSize = kSegmentAddr + kSegmentSize;
zx::vmo file_vmo;
ASSERT_EQ(zx::vmo::create(kFileSize, 0, &file_vmo), ZX_OK);
zx_info_vmo_t file_vmo_info;
ASSERT_EQ(file_vmo.get_info(ZX_INFO_VMO, &file_vmo_info, sizeof(file_vmo_info), nullptr, nullptr),
ZX_OK);
auto diag = elfldltl::testing::ExpectOkDiagnostics();
GetMutableMemory get_mutable_memory{file_vmo.borrow()};
LoadInfo::Segment segment = LoadInfo::DataSegment{
kSegmentAddr, // offset
kSegmentAddr, // vaddr
kSegmentSize, // memsz
kSegmentSize, // filesz
};
auto& data_segment = std::get<LoadInfo::DataSegment>(segment);
ASSERT_FALSE(data_segment.vmo());
auto result = get_mutable_memory(diag, segment);
ASSERT_TRUE(result.is_ok()) << result.error_value();
auto memory = std::move(result).value();
EXPECT_EQ(memory.image().size_bytes(), kSegmentSize);
EXPECT_EQ(memory.base(), kSegmentAddr);
ASSERT_TRUE(data_segment.vmo());
zx_info_vmo_t segment_vmo_info;
ASSERT_EQ(data_segment.vmo().get_info(ZX_INFO_VMO, &segment_vmo_info, sizeof(segment_vmo_info),
nullptr, nullptr),
ZX_OK);
EXPECT_EQ(segment_vmo_info.parent_koid, file_vmo_info.koid);
}
TEST(ElfldltlSegmentWithVmoTests, GetMutableMemoryReusesVmo) {
constexpr size_type kSegmentAddr = 0x10000;
constexpr size_type kSegmentSize = 0x20000;
constexpr uint64_t kFileSize = kSegmentAddr + kSegmentSize;
zx::vmo file_vmo;
ASSERT_EQ(zx::vmo::create(kFileSize, 0, &file_vmo), ZX_OK);
auto diag = elfldltl::testing::ExpectOkDiagnostics();
GetMutableMemory get_mutable_memory{file_vmo.borrow()};
LoadInfo::Segment segment = LoadInfo::DataSegment{
kSegmentAddr, // offset
kSegmentAddr, // vaddr
kSegmentSize, // memsz
kSegmentSize, // filesz
};
auto& data_segment = std::get<LoadInfo::DataSegment>(segment);
ASSERT_FALSE(data_segment.vmo());
ASSERT_TRUE(elfldltl::SegmentWithVmo::MakeMutable(diag, data_segment, file_vmo.borrow()));
ASSERT_TRUE(data_segment.vmo());
zx_info_vmo_t segment_vmo_info;
ASSERT_EQ(data_segment.vmo().get_info(ZX_INFO_VMO, &segment_vmo_info, sizeof(segment_vmo_info),
nullptr, nullptr),
ZX_OK);
const zx::unowned_vmo segment_vmo_handle{data_segment.vmo()};
auto result = get_mutable_memory(diag, segment);
ASSERT_TRUE(result.is_ok()) << result.error_value();
auto memory = std::move(result).value();
EXPECT_EQ(memory.image().size_bytes(), kSegmentSize);
EXPECT_EQ(memory.base(), kSegmentAddr);
// The handle in use should not have changed.
EXPECT_EQ(data_segment.vmo().get(), segment_vmo_handle->get());
ASSERT_TRUE(data_segment.vmo());
// Make doubly sure the handle name still refers to the same VMO.
zx_info_vmo_t after_vmo_info;
ASSERT_EQ(data_segment.vmo().get_info(ZX_INFO_VMO, &after_vmo_info, sizeof(after_vmo_info),
nullptr, nullptr),
ZX_OK);
EXPECT_EQ(segment_vmo_info.koid, after_vmo_info.koid);
}
TEST(ElfldltlSegmentWithVmoTests, MutableMemoryStore) {
auto diag = elfldltl::testing::ExpectOkDiagnostics();
// Load up a whole simulated set of segments.
constexpr size_type kPageSize = 0x1000;
constexpr size_type kMutable1Addr = 0x1000;
constexpr size_type kMutable1Size = 0x2000;
constexpr size_type kMutable2Addr = 0x3000;
constexpr size_type kMutable2Size = 0x4000;
constexpr size_type kMutable3Addr = 0x7000;
constexpr size_type kMutable3Size = 0x4000;
constexpr Phdr kPhdrs[] = {{.flags = Phdr::kRead,
.offset = 0,
.vaddr = 0,
.filesz = kMutable1Addr,
.memsz = kMutable1Addr},
{.flags = Phdr::kRead | Phdr::kWrite,
.offset = kMutable1Addr,
.vaddr = kMutable1Addr,
.filesz = kMutable1Size,
.memsz = kMutable1Size},
{.flags = Phdr::kRead | Phdr::kWrite,
.offset = kMutable2Addr,
.vaddr = kMutable2Addr,
.filesz = kMutable2Size,
.memsz = kMutable2Size},
{.flags = Phdr::kRead | Phdr::kWrite,
.offset = kMutable3Addr,
.vaddr = kMutable3Addr,
.filesz = kMutable3Size,
.memsz = kMutable3Size}};
constexpr size_type kFileSize =
cpp20::span{kPhdrs}.back().offset + cpp20::span{kPhdrs}.back().memsz;
LoadInfo info;
for (const Phdr phdr : kPhdrs) {
ASSERT_TRUE(info.AddSegment(diag, kPageSize, phdr, false));
}
ASSERT_EQ(info.segments().size(), 4u);
// Fill up the file with values.
zx::vmo file_vmo;
ASSERT_EQ(zx::vmo::create(kFileSize, 0, &file_vmo), ZX_OK);
constexpr auto unrelocated_value = [](size_type offset) -> size_type {
return offset ^ static_cast<size_type>(0xdeadbeef);
};
for (size_type offset = 0; offset < kFileSize; offset += sizeof(size_type)) {
const size_type value = unrelocated_value(offset);
ASSERT_EQ(file_vmo.write(&value, offset, sizeof(value)), ZX_OK);
}
elfldltl::LoadInfoMutableMemory mutable_memory{
diag,
info,
elfldltl::SegmentWithVmo::GetMutableMemory<LoadInfo>{file_vmo.borrow()},
};
ASSERT_TRUE(mutable_memory.Init());
// Do some stores in the first mutable segment.
constexpr size_type kMutable1StoreAt = kMutable1Addr + 0x100;
constexpr size_type kMutable1StoreAddAt = kMutable1Addr + 0x200;
ASSERT_TRUE(mutable_memory.Store<size_type>(kMutable1StoreAt, ~kMutable1StoreAt));
ASSERT_TRUE(mutable_memory.StoreAdd<size_type>(kMutable1StoreAddAt, 0x1234));
// Do some stores in the third mutable segment.
constexpr size_type kMutable3StoreAt = kMutable3Addr + 0x100;
constexpr size_type kMutable3StoreAddAt = kMutable3Addr + 0x200;
ASSERT_TRUE(mutable_memory.Store<size_type>(kMutable3StoreAt, ~kMutable3StoreAt));
ASSERT_TRUE(mutable_memory.StoreAdd<size_type>(kMutable3StoreAddAt, 0x5678));
// Verify the file VMO was never changed.
for (size_type offset = 0; offset < kFileSize; offset += sizeof(size_type)) {
size_type value;
ASSERT_EQ(file_vmo.read(&value, offset, sizeof(value)), ZX_OK);
EXPECT_EQ(value, unrelocated_value(offset)) << " at offset " << offset;
}
// The second mutable segment wasn't touched, so should not have a VMO.
EXPECT_FALSE(std::get<LoadInfo::DataSegment>(info.segments()[2]).vmo());
// Verify the contents of the first mutable VMO: all the same as the file
// VMO, except for the words at kMutable1StoreAt and kMutable1StoreAddAt.
zx::unowned_vmo mutable1_vmo{
std::get<LoadInfo::DataSegment>(info.segments()[1]).vmo(),
};
ASSERT_TRUE(mutable1_vmo->is_valid());
for (size_type offset = 0; offset < kMutable1Size; offset += sizeof(size_type)) {
const size_type addr = kMutable1Addr + offset;
size_type value;
ASSERT_EQ(mutable1_vmo->read(&value, offset, sizeof(value)), ZX_OK);
switch (addr) {
default:
EXPECT_EQ(value, unrelocated_value(addr))
<< " at offset " << offset << " for address " << addr;
break;
case kMutable1StoreAt:
EXPECT_EQ(value, ~kMutable1StoreAt);
break;
case kMutable1StoreAddAt:
EXPECT_EQ(value, unrelocated_value(kMutable1StoreAddAt) + 0x1234);
break;
}
}
// Verify the contents of the third mutable VMO: all the same as the file
// VMO, except for the words at kMutable1StoreAt and kMutable1StoreAddAt.
zx::unowned_vmo mutable3_vmo{
std::get<LoadInfo::DataSegment>(info.segments()[3]).vmo(),
};
ASSERT_TRUE(mutable3_vmo->is_valid());
for (size_type offset = 0; offset < kMutable3Size; offset += sizeof(size_type)) {
const size_type addr = kMutable3Addr + offset;
size_type value;
ASSERT_EQ(mutable3_vmo->read(&value, offset, sizeof(value)), ZX_OK);
switch (addr) {
default:
EXPECT_EQ(value, unrelocated_value(addr))
<< " at offset " << offset << " for address " << addr;
break;
case kMutable3StoreAt:
EXPECT_EQ(value, ~kMutable3StoreAt);
break;
case kMutable3StoreAddAt:
EXPECT_EQ(value, unrelocated_value(kMutable3StoreAddAt) + 0x5678);
break;
}
}
}
} // namespace