blob: 58df1e68898cccdef1d2775314e1009bef4eff17 [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.
#ifndef LIB_LD_TEST_LD_REMOTE_PROCESS_TESTS_H_
#define LIB_LD_TEST_LD_REMOTE_PROCESS_TESTS_H_
#include <lib/elfldltl/testing/diagnostics.h>
#include <lib/elfldltl/testing/get-test-data.h>
#include <lib/ld/remote-abi-heap.h>
#include <lib/ld/remote-abi-stub.h>
#include <lib/ld/remote-abi.h>
#include <lib/ld/remote-dynamic-linker.h>
#include <lib/ld/remote-load-module.h>
#include <lib/ld/testing/test-vmo.h>
#include <lib/zx/thread.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <initializer_list>
#include <optional>
#include <string_view>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "ld-load-zircon-process-tests-base.h"
namespace ld::testing {
class LdRemoteProcessTests : public ::testing::Test, public LdLoadZirconProcessTestsBase {
public:
static constexpr bool kCanCollectLog = false;
LdRemoteProcessTests();
~LdRemoteProcessTests() override;
void Init(std::initializer_list<std::string_view> args = {},
std::initializer_list<std::string_view> env = {});
void Needed(std::initializer_list<std::string_view> names);
void Needed(std::initializer_list<std::pair<std::string_view, bool>> name_found_pairs);
void Load(std::string_view executable_name) {
elfldltl::testing::ExpectOkDiagnostics diag;
ASSERT_NO_FATAL_FAILURE(Load(diag, executable_name, false));
}
int64_t Run();
template <class... Reports>
void LoadAndFail(std::string_view name, elfldltl::testing::ExpectedErrorList<Reports...> diag) {
ASSERT_NO_FATAL_FAILURE(Load(diag, name, true));
ASSERT_NO_FATAL_FAILURE(ExpectLog(""));
}
protected:
const zx::vmar& root_vmar() { return root_vmar_; }
void set_entry(uintptr_t entry) { entry_ = entry; }
void set_vdso_base(uintptr_t vdso_base) { vdso_base_ = vdso_base; }
void set_stack_size(std::optional<size_t> stack_size) { stack_size_ = stack_size; }
private:
class MockLoader;
using Linker = RemoteDynamicLinker<>;
using RemoteModule = RemoteLoadModule<>;
static zx::vmo GetTestVmo(std::string_view path);
zx::vmo GetDepVmo(const RemoteModule::Soname& soname);
template <class Diagnostics>
void Load(Diagnostics& diag, std::string_view executable_name, bool should_fail) {
const size_t page_size = zx_system_get_page_size();
Linker linker;
// Decode the main executable.
const std::string executable_path = std::filesystem::path("test") / "bin" / executable_name;
zx::vmo vmo;
ASSERT_NO_FATAL_FAILURE(vmo = elfldltl::testing::GetTestLibVmo(executable_path));
std::array initial_modules = {Linker::InitModule{
.decoded_module = RemoteModule::Decoded::Create(diag, std::move(vmo), page_size),
}};
ASSERT_TRUE(initial_modules.front().decoded_module);
// Pre-decode the vDSO.
zx::vmo stub_ld_vmo;
ASSERT_NO_FATAL_FAILURE(stub_ld_vmo = elfldltl::testing::GetTestLibVmo("ld-stub.so"));
zx::vmo vdso_vmo;
zx_status_t status = GetVdsoVmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &vdso_vmo);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
std::array predecoded_modules = {Linker::PredecodedModule{
.decoded_module = RemoteModule::Decoded::Create(diag, std::move(vdso_vmo), page_size),
}};
// Acquire the layout details from the stub. The same values collected
// here can be reused along with the decoded RemoteLoadModule for the stub
// for creating and populating the RemoteLoadModule for the passive ABI of
// any number of separate dynamic linking domains in however many
// processes.
RemoteAbiStub<>::Ptr abi_stub =
RemoteAbiStub<>::Create(diag, std::move(stub_ld_vmo), page_size);
EXPECT_TRUE(abi_stub);
EXPECT_GE(abi_stub->data_size(), sizeof(ld::abi::Abi<>) + sizeof(elfldltl::Elf<>::RDebug<>));
EXPECT_LT(abi_stub->data_size(), page_size);
EXPECT_LE(abi_stub->abi_offset(), abi_stub->data_size() - sizeof(ld::abi::Abi<>));
EXPECT_LE(abi_stub->rdebug_offset(), abi_stub->data_size() - sizeof(elfldltl::Elf<>::RDebug<>));
EXPECT_NE(abi_stub->rdebug_offset(), abi_stub->abi_offset())
<< "with data_size() " << abi_stub->data_size();
linker.set_abi_stub(abi_stub);
// Verify that the TLSDESC entry points were found in the stub and that
// their addresses pass some basic smell tests.
std::set<elfldltl::Elf<>::size_type> tlsdesc_entrypoints;
const auto segment_is_executable = [](const auto& segment) -> bool {
return segment.executable();
};
const RemoteModule::Decoded& stub_module = *abi_stub->decoded_module();
for (const elfldltl::Elf<>::size_type entry : abi_stub->tlsdesc_runtime()) {
// Must be nonzero.
EXPECT_NE(entry, 0u);
// Must lie within the module bounds.
EXPECT_GT(entry, stub_module.load_info().vaddr_start());
EXPECT_LT(entry - stub_module.load_info().vaddr_start(),
stub_module.load_info().vaddr_size());
// Must be inside an executable segment.
auto segment = stub_module.load_info().FindSegment(entry);
ASSERT_NE(segment, stub_module.load_info().segments().end());
EXPECT_TRUE(std::visit(segment_is_executable, *segment));
// Must be unique.
auto [it, inserted] = tlsdesc_entrypoints.insert(entry);
EXPECT_TRUE(inserted) << "duplicate entry point " << entry;
}
EXPECT_EQ(tlsdesc_entrypoints.size(), kTlsdescRuntimeCount);
// First just decode all the modules: the executable and dependencies.
auto get_dep = [this, page_size,
&diag](const RemoteModule::Soname& soname) -> Linker::GetDepResult {
RemoteModule::Decoded::Ptr decoded;
if (zx::vmo vmo = GetDepVmo(soname)) [[likely]] {
decoded = RemoteModule::Decoded::Create(diag, std::move(vmo), page_size);
} else if (!diag.MissingDependency(soname.str())) {
return std::nullopt;
}
return std::move(decoded);
};
auto init_result = linker.Init(diag, initial_modules, get_dep, std::move(predecoded_modules));
ASSERT_TRUE(init_result);
auto& modules = linker.modules();
ASSERT_FALSE(modules.empty());
// Check the loaded-by pointers.
EXPECT_FALSE(modules.front().loaded_by_modid())
<< "executable loaded by " << modules[*modules.front().loaded_by_modid()].name();
{
auto next_module = std::next(modules.begin());
auto loaded_by_name = [next_module, &modules]() -> std::string_view {
if (next_module->loaded_by_modid()) {
return modules[*next_module->loaded_by_modid()].name().str();
}
return "<none>";
};
if (next_module != modules.end() && next_module->HasModule() &&
next_module->module().symbols_visible) {
// The second module must be a direct dependency of the executable.
EXPECT_THAT(next_module->loaded_by_modid(), ::testing::Optional(0u))
<< " second module " << next_module->name().str() << " loaded by " << loaded_by_name();
}
for (; next_module != modules.end(); ++next_module) {
if (!next_module->HasModule()) {
continue;
}
if (next_module->module().symbols_visible) {
// This module wouldn't be here if it wasn't loaded by someone.
EXPECT_NE(next_module->loaded_by_modid(), std::nullopt)
<< "visible module " << next_module->name().str() << " loaded by "
<< loaded_by_name();
} else {
// A predecoded module was not referenced, so it's loaded by no-one.
EXPECT_EQ(next_module->loaded_by_modid(), std::nullopt)
<< "invisible module " << next_module->name().str() << " loaded by "
<< loaded_by_name();
}
}
}
// If not all modules could be decoded, don't bother with relocation to
// diagnose symbol resolution errors since many are likely without all the
// modules there and they are unlikely to add any helpful information
// beyond the diagnostic about decoding problems (e.g. missing modules).
// This is consistent with the startup dynamic linker, which reports all
// the decode / load problems it can before bailing out if there were any.
// In a general library implementation, it will be up to the caller of the
// library to decide whether to attempt later stages with an incomplete
// module list. The library code endeavors to ensure it will be safe to
// make the attempt with missing or partially-decoded modules in the list.
if (!linker.AllModulesValid()) {
// Whatever the failures were have already been diagnosed.
// This isn't a test failure in LoadAndFail tests.
EXPECT_EQ(this->HasFailure(), !should_fail);
return;
}
// Choose load addresses.
EXPECT_TRUE(linker.Allocate(diag, root_vmar().borrow()));
// Use the executable's entry point at its runtime load address.
set_entry(linker.main_entry());
// Record any stack size request from the executable's PT_GNU_STACK.
set_stack_size(linker.main_stack_size());
// Locate the loaded vDSO to pass its base pointer to the test process.
const RemoteModule& loaded_vdso = modules[init_result->front()];
set_vdso_base(loaded_vdso.module().vaddr_start());
// Apply relocations to segment VMOs.
EXPECT_TRUE(linker.Relocate(diag));
ASSERT_EQ(IsExpectOkDiagnostics(diag), !should_fail);
if (should_fail) {
// Whatever the failures were have already been diagnosed. This isn't a
// test failure in LoadAndFail tests. But don't really keep going past
// this point. As the RelocateModules API comment suggests, it often
// makes sense to go this far despite prior errors just to maximize all
// the errors reported, e.g. all the undefined symbols and not just the
// first one. For the library API, the caller is free to proceed further
// if they choose, but that's not consistent with the startup dynamic
// linker. These tests expect the startup dynamic linker's behavior,
// which is to report all decoding / loading failures, then bail if there
// were any; then report all relocation failures, then bail if there were
// any.
EXPECT_FALSE(this->HasFailure());
return;
}
for (size_t i = 0; i < modules.size(); ++i) {
EXPECT_EQ(modules[i].module().symbolizer_modid, i);
}
// Finally, all the VMO contents are in place to be mapped into the
// process.
ASSERT_TRUE(linker.Load(diag));
// Any failure before here would destroy all the VMARs when linker goes out
// of scope. From here the mappings will stick in the process.
linker.Commit();
}
uintptr_t entry_ = 0;
uintptr_t vdso_base_ = 0;
std::optional<size_t> stack_size_;
zx::vmar root_vmar_;
zx::thread thread_;
std::unique_ptr<MockLoader> mock_loader_;
};
} // namespace ld::testing
#endif // LIB_LD_TEST_LD_REMOTE_PROCESS_TESTS_H_