| // Copyright 2022 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 <fcntl.h> |
| #include <lib/elfldltl/container.h> |
| #include <lib/elfldltl/diagnostics.h> |
| #include <lib/elfldltl/dynamic.h> |
| #include <lib/elfldltl/fd.h> |
| #include <lib/elfldltl/link.h> |
| #include <lib/elfldltl/load.h> |
| #include <lib/elfldltl/mmap-loader.h> |
| #include <lib/fit/defer.h> |
| #include <sys/mman.h> |
| |
| #include <vector> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #ifdef __Fuchsia__ |
| #include <lib/elfldltl/vmar-loader.h> |
| #include <lib/elfldltl/vmo.h> |
| #endif |
| |
| #include "test-data.h" |
| #include "tests.h" |
| |
| namespace { |
| |
| #ifdef __Fuchsia__ |
| struct VmarLoaderTraits { |
| using Loader = elfldltl::VmarLoader; |
| |
| static inline auto TestLibProvider = GetTestLibVmo; |
| |
| template <class Diagnostics> |
| static auto MakeFile(zx::unowned_vmo vmo, Diagnostics& diagnostics) { |
| return elfldltl::UnownedVmoFile(vmo->borrow(), diagnostics); |
| } |
| |
| static zx::unowned_vmo LoadFileArgument(const zx::vmo& vmo) { return vmo.borrow(); } |
| }; |
| #endif |
| |
| struct MmapLoaderTraits { |
| using Loader = elfldltl::MmapLoader; |
| |
| static inline auto TestLibProvider = GetTestLib; |
| |
| template <class Diagnostics> |
| static auto MakeFile(int fd, Diagnostics& diagnostics) { |
| return elfldltl::FdFile(fd, diagnostics); |
| } |
| |
| static int LoadFileArgument(const fbl::unique_fd& fd) { return fd.get(); } |
| }; |
| |
| using LoaderTypes = ::testing::Types< |
| #ifdef __Fuchsia__ |
| VmarLoaderTraits, |
| #endif |
| MmapLoaderTraits>; |
| |
| struct LoadOptions { |
| bool commit = true; |
| bool reloc = true; |
| }; |
| |
| template <class Traits> |
| class ElfldltlLoaderTests : public testing::Test { |
| public: |
| void TearDown() override { |
| // We use the Posix APIs for genericism of the test. |
| // The destructors of the various loaders use platform |
| // specific APIs. |
| if (!mem_.image().empty()) { |
| munmap(mem_.image().data(), mem_.image().size()); |
| } |
| } |
| |
| void Load(std::string_view so_path, LoadOptions options = {}) { |
| auto diag = ExpectOkDiagnostics(); |
| |
| auto lib_file = Traits::TestLibProvider(so_path); |
| if (HasFatalFailure()) { |
| return; |
| } |
| |
| auto file = Traits::MakeFile(Traits::LoadFileArgument(lib_file), diag); |
| |
| auto headers = |
| elfldltl::LoadHeadersFromFile<Elf>(diag, file, elfldltl::NewArrayFromFile<Phdr>()); |
| ASSERT_TRUE(headers); |
| auto& [ehdr, phdrs_result] = *headers; |
| |
| ASSERT_TRUE(phdrs_result); |
| cpp20::span<const Phdr> phdrs = phdrs_result.get(); |
| |
| typename Traits::Loader loader; |
| elfldltl::LoadInfo<Elf, elfldltl::StdContainer<std::vector>::Container> load_info; |
| ASSERT_TRUE(elfldltl::DecodePhdrs(diag, phdrs, load_info.GetPhdrObserver(loader.page_size()))); |
| |
| ASSERT_TRUE(loader.Load(diag, load_info, Traits::LoadFileArgument(lib_file))); |
| |
| elfldltl::DirectMemory& mem = loader.memory(); |
| |
| std::optional<Phdr> ph; |
| elfldltl::DecodePhdrs(diag, phdrs, elfldltl::PhdrDynamicObserver<Elf>(ph)); |
| ASSERT_TRUE(ph); |
| |
| auto dyn = mem.ReadArray<Dyn>(ph->vaddr(), ph->filesz() / sizeof(Dyn)); |
| ASSERT_TRUE(dyn); |
| |
| elfldltl::RelocationInfo<Elf> reloc_info; |
| ASSERT_TRUE(elfldltl::DecodeDynamic(diag, mem, *dyn, |
| elfldltl::DynamicRelocationInfoObserver(reloc_info), |
| elfldltl::DynamicSymbolInfoObserver(sym_info_))); |
| |
| if (options.reloc) { |
| uintptr_t bias = reinterpret_cast<uintptr_t>(mem.image().data()) - mem.base(); |
| ASSERT_TRUE(RelocateRelative(mem, reloc_info, bias)); |
| |
| auto resolve = [this, bias](const auto& ref, |
| elfldltl::RelocateTls tls_type) -> std::optional<Definition> { |
| EXPECT_EQ(tls_type, elfldltl::RelocateTls::kNone) << "Should not have any tls relocs"; |
| elfldltl::SymbolName name{sym_info_, ref}; |
| if (const Sym* sym = name.Lookup(sym_info_)) { |
| return Definition{sym, bias}; |
| } |
| return {}; |
| }; |
| |
| ASSERT_TRUE(elfldltl::RelocateSymbolic(mem, diag, reloc_info, sym_info_, bias, resolve)); |
| } |
| |
| entry_ = mem.GetPointer<std::remove_pointer_t<decltype(entry_)>>(ehdr.entry); |
| if (options.commit) { |
| mem_.set_image(mem.image()); |
| mem_.set_base(mem.base()); |
| std::move(loader).Commit(); |
| } |
| |
| EXPECT_EQ(mem_.image().empty(), !options.commit); |
| } |
| |
| template <typename T> |
| T* lookup_sym(elfldltl::SymbolName name) { |
| auto* sym = name.Lookup(sym_info_); |
| EXPECT_NE(sym, nullptr) << name; |
| return mem_.GetPointer<T>(sym->value); |
| } |
| |
| template <typename T> |
| T* entry() const { |
| return reinterpret_cast<T*>(entry_); |
| } |
| |
| private: |
| using Elf = elfldltl::Elf<>; |
| using Phdr = Elf::Phdr; |
| using Dyn = Elf::Dyn; |
| using Sym = Elf::Sym; |
| using size_type = Elf::size_type; |
| |
| struct Definition { |
| constexpr bool undefined_weak() const { return false; } |
| |
| constexpr const Sym& symbol() const { return *symbol_; } |
| |
| constexpr size_type bias() const { return bias_; } |
| |
| // These will never actually be called. |
| constexpr size_type tls_module_id() const { return 0; } |
| constexpr size_type static_tls_bias() const { return 0; } |
| constexpr size_type tls_desc_hook() const { return 0; } |
| constexpr size_type tls_desc_value() const { return 0; } |
| |
| const Sym* symbol_ = nullptr; |
| size_type bias_ = 0; |
| }; |
| |
| void (*entry_)() = nullptr; |
| elfldltl::DirectMemory mem_; |
| elfldltl::SymbolInfo<Elf> sym_info_; |
| }; |
| |
| TYPED_TEST_SUITE(ElfldltlLoaderTests, LoaderTypes); |
| |
| TYPED_TEST(ElfldltlLoaderTests, Basic) { |
| this->Load(kRet24, {.reloc = false}); |
| |
| EXPECT_EQ(this->template entry<decltype(Return24)>()(), 24); |
| } |
| |
| TYPED_TEST(ElfldltlLoaderTests, UnmapCorrectly) { |
| this->Load(kRet24, {.commit = false, .reloc = false}); |
| |
| EXPECT_DEATH(this->template entry<decltype(Return24)>()(), ""); |
| } |
| |
| TYPED_TEST(ElfldltlLoaderTests, DataSegments) { |
| this->Load(kNoXSegment); |
| |
| TestData* data = this->template entry<TestData>(); |
| EXPECT_EQ(*data->rodata, 5); |
| EXPECT_EQ(*data->data, 18); |
| EXPECT_EQ(data->data[kSmallDataCount - 1], 1); |
| EXPECT_EQ(*data->bss, 0); |
| |
| *data->data = 1; |
| *data->bss = 2; |
| int* rodata = const_cast<int*>(data->rodata); |
| EXPECT_DEATH(*rodata = 3, ""); |
| } |
| |
| TYPED_TEST(ElfldltlLoaderTests, LargeDataSegment) { |
| this->Load(kNoXSegmentLargeData); |
| |
| TestData* data = this->template entry<TestData>(); |
| EXPECT_EQ(*data->rodata, 5); |
| EXPECT_EQ(*data->data, 18); |
| EXPECT_EQ(data->data[kLargeDataCount - 1], 1); |
| EXPECT_EQ(*data->bss, 0); |
| |
| *data->data = 1; |
| data->data[kLargeDataCount - 1] = 9; |
| *data->bss = 2; |
| int* rodata = const_cast<int*>(data->rodata); |
| EXPECT_DEATH(*rodata = 3, ""); |
| } |
| |
| TYPED_TEST(ElfldltlLoaderTests, BasicSymbol) { |
| this->Load(kSymbolic); |
| |
| constexpr elfldltl::SymbolName kFoo("foo"); |
| |
| auto* foo_ptr = this->template lookup_sym<decltype(foo)>(kFoo); |
| ASSERT_NE(foo_ptr, nullptr); |
| EXPECT_EQ(*foo_ptr, 17); |
| } |
| |
| TYPED_TEST(ElfldltlLoaderTests, ResolveSymbolic) { |
| this->Load(kSymbolic); |
| |
| EXPECT_EQ(this->template lookup_sym<decltype(NeedsPlt)>("NeedsPlt")(), 2); |
| EXPECT_EQ(this->template lookup_sym<decltype(NeedsGot)>("NeedsGot")(), 3); |
| } |
| |
| } // namespace |