blob: 9f0372bfb64d224ba7bbbaf884148f08d5dc8ecf [file] [log] [blame]
// Copyright 2024 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/testing/diagnostics.h>
#include <lib/ld/remote-abi-stub.h>
#include <lib/ld/remote-dynamic-linker.h>
#include <lib/ld/remote-perfect-symbol-filter.h>
#include <lib/ld/remote-zygote.h>
#include <lib/ld/testing/get-test-vmo.h>
#include <lib/ld/testing/test-elf-object.h>
#include <gtest/gtest.h>
#include "ld-remote-process-tests.h"
#include "remote-perfect-symbol-filter-test.h"
namespace {
using ::testing::Each;
using ::testing::Field;
using ::testing::IsTrue;
using ::testing::Pointee;
using ::testing::Property;
// These tests reuse the fixture that supports the LdLoadTests (load-tests.cc)
// for the common handling of creating and launching a Zircon process. The
// Load method is not used here, since that itself uses the RemoteDynamicLinker
// API under the covers, and the tests here are for that API surface itself.
using LdRemoteTests = ld::testing::LdRemoteProcessTests;
// This is the basic examplar of using the API to load a main executable in the
// standard way.
TEST_F(LdRemoteTests, RemoteDynamicLinker) {
constexpr int64_t kReturnValue = 17;
// The Init() method in the test fixture handles creating a process and such.
// This is outside the scope of the ld::RemoteDynamicLinker API.
ASSERT_NO_FATAL_FAILURE(Init());
LdsvcPathPrefix("many-deps");
auto diag = elfldltl::testing::ExpectOkDiagnostics();
// Acquire the layout details from the stub. The same ld::RemoteAbiStub
// object can be reused for creating and populating the passive ABI of any
// number of separate dynamic linking domains in however many processes.
//
// The TakeSubLdVmo() method in the test fixture returns the (read-only,
// executable) zx::vmo for the stub dynamic linker provided along with the
// //sdk/lib/ld library and packaged somewhere with the code using this API.
// The user of the API must acquire such a VMO by their own means.
Linker linker;
linker.set_abi_stub(ld::RemoteAbiStub<>::Create(diag, TakeStubLdVmo(), kPageSize));
ASSERT_TRUE(linker.abi_stub());
// The main executable is an ELF file in a VMO. The GetExecutableVmoo()
// method in the test fixture returns the (read-only, executable) zx::vmo for
// the main executable. The user of the API must acquire this VMO by their
// own means.
zx::vmo exec_vmo;
ASSERT_NO_FATAL_FAILURE(exec_vmo = ld::testing::GetExecutableVmo("many-deps"));
// This makes sure the Needed() call below finds the files in the test
// packaging correctly. These are only aspects of the test framework API,
// not of the remote dynamic linker API.
ConfigFromInterp(exec_vmo.borrow());
// Decode the main executable. This transfers ownership of the zx::vmo for
// the executable into the new fbl::RefPtr<ld::RemoteDecodedModule> object.
// If there were decoding problems they will have been reported to the
// Diagnostics template API object. If that object said to bail out after an
// error or warning, Create returns a null RefPtr. If it said to keep going
// after an error, then an object was created but may be incomplete: it can
// be used in ld::RemoteDynamicLinker::Init, but may not be in a fit state to
// attempt relocation.
Linker::Module::DecodedPtr decoded_executable =
Linker::Module::Decoded::Create(diag, std::move(exec_vmo), kPageSize);
EXPECT_TRUE(decoded_executable);
// If the program is meant to make Zircon system calls, then it needs a vDSO,
// in the form of a (read-only, executable) zx::vmo handle to one of the
// kernel's blessed vDSO VMOs. The GetVdsoVmo() function in the testing
// library returns the same one used by the test itself. The user of the API
// must acquire the desired vDSO VMO by their own means.
zx::vmo vdso_vmo;
zx_status_t status = ld::testing::GetVdsoVmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &vdso_vmo);
EXPECT_EQ(status, ZX_OK) << zx_status_get_string(status);
// Decode the vDSO, just as done for the main executable. The DecodedPtr
// references can be cached and reused for any VMO of an ELF file.
Linker::Module::DecodedPtr decoded_vdso =
Linker::Module::Decoded::Create(diag, std::move(vdso_vmo), kPageSize);
EXPECT_TRUE(decoded_vdso);
// The get_dep callback is any object callable as GetDepResult(Soname). It
// returns std::nullopt for missing dependencies, or a DecodedPtr. The
// GetDepFunction() in the test fixture returns an object that approximates
// for the test context something like looking up files in /pkg/lib as is
// done via fuchsia.ldsvc FIDL protocols by the usual in-process dynamic
// linker. The Needed() method in the test fixture indicates the expected
// sequence of requests and collects those files from the test package's
// special directory layout. The user of the API must supply a callback that
// turns strings into appropriate ld::RemoteDecodedModule::Ptr refs. The
// callback returns std::nullopt to bail out after a failure; the
// RemoteDynamicLinker does not do any logging about this directly, so the
// callback itself should do so. The callback may also return a null Ptr
// instead to indicate work should keep going despite the missing file. This
// will likely result in more errors later, such as undefined symbols; but it
// gives the opportunity to report more missing files before bailing out.
auto get_dep = GetDepFunction(diag);
ASSERT_NO_FATAL_FAILURE(Needed({
"libld-dep-a.so",
"libld-dep-b.so",
"libld-dep-f.so",
"libld-dep-c.so",
"libld-dep-d.so",
"libld-dep-e.so",
}));
ASSERT_NO_FATAL_FAILURE(LdsvcExpectNeeded());
// Init() decodes everything and loads all the dependencies.
auto init_result = linker.Init(
// Any <lib/elfldltl/diagnostics.h> template API object can be used.
diag,
// The InitModuleList argument is a std::vector, so it can be constructed
// in many ways including an initializer list. For inividual InitModule
// elements there is a convenient factory function that suits each use
// case. The order of the root modules is important: it becomes the
// "load order" used for symbol resolution and seen in the passive
// ABI--but usually that's just the main executable. Implicit modules
// can appear in any order with respect to each other or the root
// modules; the only effect is on the relative order of any unreferenced
// implicit modules at the end of the ld::RemoteDynamicLinker::modules()
// "load order" list.
{Linker::Executable(std::move(decoded_executable)),
Linker::Implicit(std::move(decoded_vdso))},
get_dep);
ASSERT_TRUE(init_result);
// The return value is a vector parallel to the InitModuleList passed in.
ASSERT_EQ(init_result->size(), 2u);
// Allocate() chooses load addresses by creating new child VMARs within some
// given parent VMAR, such as the root VMAR of a new process.
EXPECT_TRUE(linker.Allocate(diag, root_vmar().borrow()));
// The corresponding return vector element is an iterator into the
// ld::RemoteDynamicLinker::modules() list. After Allocate, the vaddr
// details of each module have been decided. The vDSO base address is
// usually passed as the main executable entry point's second argument when
// the process is launched via zx::process::start. The test fixture's Run()
// method passes this to zx::process::start, but launching the process is
// outside the scope of this API.
const Linker::Module& loaded_vdso = *init_result->back();
set_vdso_base(loaded_vdso.module().vaddr_start());
// main_entry() yields the runtime entry point address of the main (first)
// root module, usually the main executable. Naturally, it's only valid
// after a successful Allocate phase. The test fixture's Run() method passes
// this to zx::process::start, but launching the process is outside the scope
// of this API.
set_entry(linker.main_entry());
// main_stack_size() yields either std::nullopt or a specific stack size
// requested by the executable's PT_GNU_STACK program header. The test
// fixture's Run() method uses this to allocate a stack and pass the initial
// SP in zx::process::start; stack setup is outside the scope of this API.
set_stack_size(linker.main_stack_size());
// Relocate() applies relocations to segment VMOs. This is the last place
// that anything can usually go wrong due to a missing or invalid ELF file,
// undefined symbol, or such problems with dynamic linking per se.
EXPECT_TRUE(linker.Relocate(diag));
// Finally, all the VMO contents are in place to be mapped into the process.
// If this fails, it will be because of some system problem like resource
// exhaustion rather than something about dynamic linking.
ASSERT_TRUE(linker.Load(diag));
// Any failure before here would destroy all the VMARs when the linker object
// goes out of scope. From here the mappings will stick in the process.
linker.Commit();
// The test fixture method does the rest of the work of launching the
// process, all of which is out of the scope of this API:
// 1. stack setup
// 2. preparing a channel for the process bootstrap protocol
// 3. calling zx::process::start with initial PC (e.g. from main_entry()),
// SP (from the stack setup), and the two entry point arguments:
// * some Zircon handle, usually the channel from which the process
// expects to read the message(s) of the process bootstrap protocol;
// * some integer, usually the base address where the vDSO was loaded,
// e.g. from `.module().vaddr_start` on the Linker::Module object for
// the vDSO, an implicit module found via Init()'s return value.
// The test fixture method yields the process exit status when it finishes.
EXPECT_EQ(Run(), kReturnValue);
// The test fixture collected any output from the process and requires that
// it be checked.
ExpectLog("");
}
// This demonstrates using ld::RemoteDynamicLinker::Preplaced in the initial
// modules list.
TEST_F(LdRemoteTests, Preplaced) {
constexpr uint64_t kLoadAddress = 0x12340000;
ASSERT_NO_FATAL_FAILURE(Init());
auto diag = elfldltl::testing::ExpectOkDiagnostics();
Linker linker;
linker.set_abi_stub(ld::RemoteAbiStub<>::Create(diag, TakeStubLdVmo(), kPageSize));
ASSERT_TRUE(linker.abi_stub());
zx::vmo exec_vmo;
ASSERT_NO_FATAL_FAILURE(exec_vmo = ld::testing::GetExecutableVmo("fixed-load-address"));
Linker::Module::DecodedPtr decoded_executable =
Linker::Module::Decoded::Create(diag, std::move(exec_vmo), kPageSize);
EXPECT_TRUE(decoded_executable);
zx::vmo vdso_vmo;
zx_status_t status = ld::testing::GetVdsoVmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &vdso_vmo);
EXPECT_EQ(status, ZX_OK) << zx_status_get_string(status);
Linker::Module::DecodedPtr decoded_vdso =
Linker::Module::Decoded::Create(diag, std::move(vdso_vmo), kPageSize);
EXPECT_TRUE(decoded_vdso);
auto init_result = linker.Init( //
diag,
{Linker::Preplaced(std::move(decoded_executable), kLoadAddress,
ld::abi::Abi<>::kExecutableName),
Linker::Implicit(std::move(decoded_vdso))},
GetDepFunction(diag));
ASSERT_TRUE(init_result);
EXPECT_TRUE(linker.Allocate(diag, root_vmar().borrow()));
set_entry(linker.main_entry());
set_stack_size(linker.main_stack_size());
set_vdso_base(init_result->back()->module().vaddr_start());
EXPECT_EQ(init_result->front()->module().vaddr_start, kLoadAddress);
EXPECT_TRUE(linker.Relocate(diag));
ASSERT_TRUE(linker.Load(diag));
linker.Commit();
EXPECT_EQ(Run(), static_cast<int64_t>(kLoadAddress));
ExpectLog("");
}
// This demonstrates performing two separate dynamic linking sessions to
// establish two distinct dynamic linking namespaces inside one process address
// space, where the second session uses the first session's initial modules
// (but not their dependencies) as preloaded implicit modules that can satisfy
// its symbols.
TEST_F(LdRemoteTests, SecondSession) {
constexpr int64_t kReturnValue = 17;
ASSERT_NO_FATAL_FAILURE(Init());
LdsvcPathPrefix("second-session");
auto diag = elfldltl::testing::ExpectOkDiagnostics();
// The ld::RemoteAbiStub only needs to be set up once for all sessions.
ld::RemoteAbiStub<>::Ptr abi_stub = ld::RemoteAbiStub<>::Create(diag, TakeStubLdVmo(), kPageSize);
ASSERT_TRUE(abi_stub);
zx::vmo exec_vmo = ld::testing::GetExecutableVmo("second-session");
ASSERT_TRUE(exec_vmo);
ConfigFromInterp(exec_vmo.borrow());
// First do a complete dynamic linking session for the main executable.
ASSERT_NO_FATAL_FAILURE(Needed({
"libindirect-deps-a.so",
"libindirect-deps-b.so",
"libindirect-deps-c.so",
}));
ASSERT_NO_FATAL_FAILURE(LdsvcExpectNeeded());
constexpr std::string_view kMainSoname = "libsecond-session-test.so.1";
Linker::InitModuleList initial_modules;
std::string vdso_soname;
{
Linker linker;
linker.set_abi_stub(abi_stub);
Linker::Module::DecodedPtr decoded_executable =
Linker::Module::Decoded::Create(diag, std::move(exec_vmo), kPageSize);
EXPECT_TRUE(decoded_executable);
zx::vmo vdso_vmo;
zx_status_t status = ld::testing::GetVdsoVmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &vdso_vmo);
EXPECT_EQ(status, ZX_OK) << zx_status_get_string(status);
Linker::Module::DecodedPtr decoded_vdso =
Linker::Module::Decoded::Create(diag, std::move(vdso_vmo), kPageSize);
EXPECT_TRUE(decoded_vdso);
vdso_soname = decoded_vdso->soname().str();
auto init_result = linker.Init( //
diag,
{Linker::Executable(std::move(decoded_executable)),
Linker::Implicit(std::move(decoded_vdso))},
GetDepFunction(diag));
ASSERT_TRUE(init_result);
// Check on expected get_dep callbacks made by Init,
// and wipe the test fixture mock clean for the later session.
ASSERT_NO_FATAL_FAILURE(VerifyAndClearNeeded());
EXPECT_TRUE(linker.Allocate(diag, root_vmar().borrow()));
set_entry(linker.main_entry());
set_stack_size(linker.main_stack_size());
set_vdso_base(init_result->back()->module().vaddr_start());
EXPECT_TRUE(linker.Relocate(diag));
ASSERT_TRUE(linker.Load(diag));
linker.Commit();
// Extract both initial modules to be preloaded implicit modules.
initial_modules = linker.PreloadedImplicit(*init_result);
EXPECT_EQ(initial_modules.size(), 2u);
// The primary domain has more modules than just those.
EXPECT_GT(linker.modules().size(), 2u);
}
// Start the process running now with just the primary domain in place.
// It will block on reading from the bootstrap channel.
ASSERT_NO_FATAL_FAILURE(Start());
// Now do a second session using the InitModule::AlreadyLoaded main
// executable and vDSO from the first session as implicit modules.
Linker::size_type test_start_fnptr = 0;
{
constexpr Linker::Soname kPathPrefix{"second-session-module"};
constexpr Linker::Soname kRootModule{"second-session-module.so"};
constexpr std::string_view kDepModule = "libsecond-session-module-deps-a.so";
Linker second_linker;
second_linker.set_abi_stub(abi_stub);
// Point GetLibVmo() to the different place for this module's deps.
LdsvcPathPrefix(kPathPrefix.str());
// Acquire the VMO for the root module.
zx::vmo module_vmo;
ASSERT_NO_FATAL_FAILURE(module_vmo = GetLibVmo(kRootModule.str()));
// Decode the root module.
Linker::Module::DecodedPtr decoded_module =
Linker::Module::Decoded::Create(diag, std::move(module_vmo), kPageSize);
EXPECT_TRUE(decoded_module);
// Add in the root module with the implicit modules from the first session.
initial_modules.emplace_back(Linker::RootModule(decoded_module, kRootModule));
// Prime fresh expectations for get_dep callbacks from this session. There
// is no PT_INTERP in the root module to key off, so rely on the build-time
// record to find where dependencies got packaged.
ASSERT_NO_FATAL_FAILURE(VerifyAndClearNeeded());
ASSERT_NO_FATAL_FAILURE(NeededViaLoadSet(kPathPrefix, {kDepModule}));
ASSERT_NO_FATAL_FAILURE(LdsvcExpectNeeded());
// Now resolve dependencies, including the preloaded implicit modules as
// well as that Needed list, modules newly opened via the get_dep callback.
auto init_result = second_linker.Init(diag, std::move(initial_modules), GetDepFunction(diag));
ASSERT_TRUE(init_result);
ASSERT_EQ(init_result->size(), 3u);
EXPECT_EQ(init_result->front()->name().str(), kMainSoname);
EXPECT_EQ(init_result->at(1)->name().str(), vdso_soname);
EXPECT_EQ(init_result->back()->name().str(), kRootModule.str());
EXPECT_TRUE(init_result->front()->preloaded());
EXPECT_TRUE(init_result->at(1)->preloaded());
EXPECT_FALSE(init_result->back()->preloaded());
ASSERT_EQ(second_linker.modules().size(), 5u);
EXPECT_EQ(second_linker.modules().at(0).name().str(), kRootModule.str());
EXPECT_EQ(second_linker.modules().at(1).name().str(), kMainSoname);
EXPECT_TRUE(second_linker.modules().at(1).preloaded());
EXPECT_EQ(second_linker.modules().at(2).name().str(), kDepModule);
EXPECT_EQ(second_linker.modules().at(3).name().str(), vdso_soname);
EXPECT_TRUE(second_linker.modules().at(3).preloaded());
EXPECT_EQ(second_linker.modules().at(4).name().str(), ld::abi::Abi<>::kSoname.str());
ASSERT_NO_FATAL_FAILURE(VerifyAndClearNeeded());
// Allocate should place the root module and leave preloaded ones alone.
EXPECT_TRUE(second_linker.Allocate(diag, root_vmar().borrow()));
EXPECT_TRUE(init_result->front()->preloaded());
EXPECT_TRUE(init_result->at(1)->preloaded());
EXPECT_FALSE(init_result->back()->preloaded());
// Finish dynamic linking.
EXPECT_TRUE(second_linker.Relocate(diag));
ASSERT_TRUE(second_linker.Load(diag));
second_linker.Commit();
// Look up the module's entry-point symbol.
constexpr elfldltl::SymbolName kTestStart{"TestStart"};
auto* symbol = kTestStart.Lookup(second_linker.main_module().module().symbols);
ASSERT_TRUE(symbol);
test_start_fnptr = symbol->value + second_linker.main_module().load_bias();
}
EXPECT_NE(test_start_fnptr, 0u);
// The process is already running and it will block until it reads the
// function pointer from the bootstrap channel.
zx_status_t status =
bootstrap_sender().write(0, &test_start_fnptr, sizeof(test_start_fnptr), nullptr, 0);
ASSERT_EQ(status, ZX_OK) << "zx_channel_write: " << zx_status_get_string(status);
// Close our end of the channel before waiting for the process, just in case
// that kicks it out of a block and into crashing rather than wedging.
bootstrap_sender().reset();
// The process should now call TestStart() and exit with its return value.
EXPECT_EQ(Wait(), kReturnValue);
ExpectLog("");
}
TEST_F(LdRemoteTests, Zygote) {
constexpr int64_t kReturnValue = 17;
constexpr int64_t kSecondaryReturnValue = 23;
constexpr int kZygoteCount = 10;
LdsvcPathPrefix("zygote");
auto diag = elfldltl::testing::ExpectOkDiagnostics();
const ld::RemoteAbiStub<>::Ptr abi_stub =
ld::RemoteAbiStub<>::Create(diag, TakeStubLdVmo(), kPageSize);
ASSERT_TRUE(abi_stub);
// Linker::Module::Decoded and ZygoteLinker::Module::Decoded are the same but
// Linker and ZygoteLinker are not quite the same.
using ZygoteLinker = ld::RemoteZygote<>::Linker;
ZygoteLinker linker{abi_stub};
zx::vmo exec_vmo = ld::testing::GetExecutableVmo("zygote");
ASSERT_TRUE(exec_vmo);
ConfigFromInterp(exec_vmo.borrow());
zx::vmo vdso_vmo;
zx_status_t status = ld::testing::GetVdsoVmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &vdso_vmo);
EXPECT_EQ(status, ZX_OK) << zx_status_get_string(status);
Linker::Module::DecodedPtr executable =
Linker::Module::Decoded::Create(diag, std::move(exec_vmo), kPageSize);
Linker::Module::DecodedPtr vdso =
Linker::Module::Decoded::Create(diag, std::move(vdso_vmo), kPageSize);
ASSERT_TRUE(executable);
ASSERT_TRUE(vdso);
ZygoteLinker::InitModuleList init_modules{{
ZygoteLinker::Executable(std::move(executable)),
ZygoteLinker::Implicit(std::move(vdso)),
}};
auto get_dep = GetDepFunction(diag); // Needed primes this.
ASSERT_NO_FATAL_FAILURE(Needed({"libzygote-dep.so"}));
ASSERT_NO_FATAL_FAILURE(LdsvcExpectNeeded());
auto init_result = linker.Init(diag, std::move(init_modules), get_dep);
ASSERT_TRUE(init_result);
VerifyAndClearNeeded();
// Create a process that will be the first to run. Its ASLR will choose the
// load addresses used again for all later zygote processes.
ASSERT_NO_FATAL_FAILURE(Init());
EXPECT_TRUE(linker.Allocate(diag, root_vmar().borrow()));
ASSERT_TRUE(linker.Relocate(diag));
ASSERT_TRUE(linker.Load(diag));
// Collect what's needed to start the process.
const ZygoteLinker::Module& loaded_vdso = *init_result->back();
set_vdso_base(loaded_vdso.module().vaddr_start());
set_entry(linker.main_entry());
set_stack_size(linker.main_stack_size());
// The prototype process is ready to start. The linker object is now
// consumed in making the zygote.
linker.Commit();
// Capture the settled load details of the executable and vDSO for later.
ZygoteLinker::InitModuleList secondary_init_modules = linker.PreloadedImplicit(*init_result);
// Make a zygote that holds onto the DecodedPtr references.
ld::RemoteZygote<ld::RemoteZygoteVmo::kDecodedPtr> original_zygote;
auto result = original_zygote.Insert(std::move(linker));
ASSERT_TRUE(result.is_ok()) << result.status_string();
EXPECT_EQ(result->main_entry(), entry());
EXPECT_EQ(result->main_stack_size(), stack_size());
// The += operator allows for splicing that cannot fail, since the DecodedPtr
// references just transfer from the other zygote.
original_zygote += ld::RemoteZygote<ld::RemoteZygoteVmo::kDecodedPtr>{};
// Run the prototype process to completion. It will have changed its segment
// contents, but they should not be shared with later runs.
EXPECT_EQ(Run(), kReturnValue);
ExpectLog("");
// Discard the channel endpoint to the defunct process.
// Each later Run() call will create a new channel for its new process.
bootstrap_sender().reset();
// Move into a zygote that owns only zx::vmo and not DecodedPtr. Splicing
// into this from the zygote that owns DecodedPtr instead can fail.
ld::RemoteZygote zygote;
auto splice = zygote.Splice(std::move(original_zygote));
EXPECT_TRUE(splice.is_ok()) << splice.status_string();
// The += operator allows for splicing that cannot fail, since the other
// object already owns zx::vmo handles directly and they just transfer.
zygote += ld::RemoteZygote{};
for (int i = 1; i <= kZygoteCount; ++i) {
// Make a new process.
ASSERT_NO_FATAL_FAILURE(Init()) << "zygote child " << i << " of " << kZygoteCount;
// Load it up from the zygote.
EXPECT_TRUE(zygote.Load(diag, root_vmar().borrow()))
<< "zygote child " << i << " of " << kZygoteCount;
// Run it to completion. It would go wrong or return the wrong value if
// its segments had been written by an earlier run.
EXPECT_EQ(Run(), kReturnValue) << "zygote child " << i << " of " << kZygoteCount;
ExpectLog("");
// Discard the channel endpoint to the defunct process.
// The next iteration will create a new channel for the next process.
bootstrap_sender().reset();
}
// Start a new process for the secondary session test.
ASSERT_NO_FATAL_FAILURE(Init()) << "secondary";
// First the new process gets loaded up from the zygote like the others.
EXPECT_TRUE(zygote.Load(diag, root_vmar().borrow())) << "secondary";
// Fetch the secondary domain's root module. It's built and packaged as an
// executable since it has an entry point that acts like one.
constexpr ZygoteLinker::Soname kSecondaryName{"zygote-secondary"};
LdsvcPathPrefix(kSecondaryName.str());
zx::vmo secondary_vmo;
ASSERT_NO_FATAL_FAILURE(secondary_vmo = ld::testing::GetExecutableVmo(kSecondaryName.str()));
EXPECT_TRUE(secondary_vmo);
ConfigFromInterp(secondary_vmo.borrow());
Linker::Module::DecodedPtr secondary =
Linker::Module::Decoded::Create(diag, std::move(secondary_vmo), kPageSize);
// Now start the secondary session. The secondary_init_modules list
// collected above still corresponds to where the zygote loaded things.
ZygoteLinker secondary_linker{abi_stub};
secondary_init_modules.emplace_back(
ZygoteLinker::RootModule(std::move(secondary), kSecondaryName));
ASSERT_NO_FATAL_FAILURE(Needed({"libzygote-dep.so"}));
ASSERT_NO_FATAL_FAILURE(LdsvcExpectNeeded());
auto secondary_init_result =
secondary_linker.Init(diag, std::move(secondary_init_modules), get_dep);
ASSERT_TRUE(secondary_init_result);
EXPECT_TRUE(secondary_linker.Allocate(diag, root_vmar().borrow()));
ASSERT_TRUE(secondary_linker.Relocate(diag));
ASSERT_TRUE(secondary_linker.Load(diag));
// This process will start at the secondary module's entry point rather than
// the original executable's. The set_vdso_base() call above is still in
// force, since that has not changed since the original session.
set_entry(secondary_linker.main_entry());
set_stack_size(secondary_linker.main_stack_size());
// The prototype secondary process is ready to start.
secondary_linker.Commit();
// Consume the secondary_linker object in the existing zygote, so now it will
// load both the original and secondary modules into each new process.
auto secondary_result = zygote.Insert(std::move(secondary_linker));
ASSERT_TRUE(secondary_result.is_ok()) << secondary_result.status_string();
EXPECT_EQ(secondary_result->main_entry(), entry());
EXPECT_EQ(secondary_result->main_stack_size(), stack_size());
// Run the prototype secondary process to completion.
EXPECT_EQ(Run(), kSecondaryReturnValue);
ExpectLog("");
bootstrap_sender().reset();
// Test the combined zygote behaves like the secondary prototype over again.
for (int i = 1; i <= kZygoteCount; ++i) {
ASSERT_NO_FATAL_FAILURE(Init()) << "secondary zygote child " << i << " of " << kZygoteCount;
EXPECT_TRUE(zygote.Load(diag, root_vmar().borrow()))
<< "secondary zygote child " << i << " of " << kZygoteCount;
EXPECT_EQ(Run(), kSecondaryReturnValue)
<< "secondary zygote child " << i << " of " << kZygoteCount;
ExpectLog("");
bootstrap_sender().reset();
}
}
TEST_F(LdRemoteTests, RemoteAbiStub) {
auto diag = elfldltl::testing::ExpectOkDiagnostics();
// 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.
ld::RemoteAbiStub<>::Ptr abi_stub = ld::RemoteAbiStub<>::Create(diag, TakeStubLdVmo(), kPageSize);
ASSERT_TRUE(abi_stub);
EXPECT_GE(abi_stub->data_size(), sizeof(ld::abi::Abi<>) + sizeof(elfldltl::Elf<>::RDebug<>));
EXPECT_LT(abi_stub->data_size(), kPageSize);
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();
// 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 Linker::Module::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(), ld::kTlsdescRuntimeCount);
}
TEST_F(LdRemoteTests, LoadedBy) {
auto diag = elfldltl::testing::ExpectOkDiagnostics();
// 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.
Linker linker;
linker.set_abi_stub(ld::RemoteAbiStub<>::Create(diag, TakeStubLdVmo(), kPageSize));
ASSERT_TRUE(linker.abi_stub());
LdsvcPathPrefix("many-deps");
// Decode the main executable.
zx::vmo vmo = ld::testing::GetExecutableVmo("many-deps");
ASSERT_TRUE(vmo);
ConfigFromInterp(vmo.borrow());
// Prime expectations for its dependencies.
ASSERT_NO_FATAL_FAILURE(Needed({
"libld-dep-a.so",
"libld-dep-b.so",
"libld-dep-f.so",
"libld-dep-c.so",
"libld-dep-d.so",
"libld-dep-e.so",
}));
ASSERT_NO_FATAL_FAILURE(LdsvcExpectNeeded());
Linker::InitModuleList initial_modules{{
Linker::Executable(Linker::Module::Decoded::Create(diag, std::move(vmo), kPageSize)),
}};
ASSERT_TRUE(initial_modules.front().decoded_module);
ASSERT_TRUE(initial_modules.front().decoded_module->HasModule());
// Pre-decode the vDSO.
zx::vmo vdso_vmo;
zx_status_t status = ld::testing::GetVdsoVmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &vdso_vmo);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
initial_modules.push_back(
Linker::Implicit(Linker::Module::Decoded::Create(diag, std::move(vdso_vmo), kPageSize)));
ASSERT_TRUE(initial_modules.back().decoded_module);
ASSERT_TRUE(initial_modules.back().decoded_module->HasModule());
auto init_result = linker.Init(diag, initial_modules, GetDepFunction(diag));
ASSERT_TRUE(init_result);
ASSERT_EQ(init_result->size(), initial_modules.size());
// The root module went on the list first.
const auto& modules = linker.modules();
EXPECT_EQ(init_result->front(), modules.begin());
// The vDSO module went somewhere on the list.
EXPECT_NE(init_result->back(), modules.end());
// 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();
}
}
}
}
TEST_F(LdRemoteTests, SymbolFilter) {
ASSERT_NO_FATAL_FAILURE(Init());
LdsvcPathPrefix("symbol-filter");
auto diag = elfldltl::testing::ExpectOkDiagnostics();
Linker linker;
linker.set_abi_stub(ld::RemoteAbiStub<>::Create(diag, TakeStubLdVmo(), kPageSize));
ASSERT_TRUE(linker.abi_stub());
zx::vmo vdso_vmo;
zx_status_t status = ld::testing::GetVdsoVmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &vdso_vmo);
EXPECT_EQ(status, ZX_OK) << zx_status_get_string(status);
zx::vmo exec_vmo = ld::testing::GetExecutableVmo("symbol-filter");
ASSERT_TRUE(exec_vmo);
ConfigFromInterp(exec_vmo.borrow());
auto decode = [](zx::vmo vmo) {
auto diag = elfldltl::testing::ExpectOkDiagnostics();
return Linker::Module::Decoded::Create(diag, std::move(vmo), kPageSize);
};
Linker::InitModuleList init_modules = {
Linker::Executable(decode(std::move(exec_vmo))),
Linker::Implicit(decode(GetLibVmo("libsymbol-filter-dep17.so"))),
Linker::Implicit(decode(GetLibVmo("libsymbol-filter-dep23.so"))),
Linker::Implicit(decode(GetLibVmo("libsymbol-filter-dep42.so"))),
Linker::Implicit(decode(std::move(vdso_vmo))),
};
ASSERT_THAT(init_modules,
Each(Field("decoded_module", &Linker::InitModule::decoded_module,
Pointee(Property("successfully decoded", &Linker::DecodedModule::HasModule,
IsTrue())))));
auto init_result = linker.Init(diag, std::move(init_modules), GetDepFunction(diag));
ASSERT_TRUE(init_result);
auto filter_out = [](auto... ignore_names) {
return [ignore_names...](const auto& module,
auto& name) -> fit::result<bool, const elfldltl::Elf<>::Sym*> {
auto diag = elfldltl::testing::ExpectOkDiagnostics();
const bool ignore = ((name == ignore_names) || ...);
return fit::ok(ignore ? nullptr : name.Lookup(module.symbol_info()));
};
};
// Dependency order should be dep17, dep23, dep42.
EXPECT_EQ(std::next(init_result->at(1)), init_result->at(2));
EXPECT_EQ(std::next(init_result->at(2)), init_result->at(3));
// first can come from dep17, but not second or third.
init_result->at(1)->set_symbol_filter(filter_out("second", "third"));
// first and second can come from dep23, but not third.
init_result->at(2)->set_symbol_filter(filter_out("third"));
// Hence: first from dep17; second from dep23; third from dep42.
constexpr int64_t kReturnValue = (17 * 1) + (23 * 2) + (42 * 3);
EXPECT_TRUE(linker.Allocate(diag, root_vmar().borrow()));
set_entry(linker.main_entry());
set_stack_size(linker.main_stack_size());
set_vdso_base(init_result->back()->module().vaddr_start());
EXPECT_TRUE(linker.Relocate(diag));
ASSERT_TRUE(linker.Load(diag));
linker.Commit();
EXPECT_EQ(Run(), kReturnValue);
ExpectLog("");
}
// This reuses one of the modules from the SymbolFilter test, but it only uses
// the test fixture to acquire the VMO (and the cached page size). It doesn't
// do any dynamic linking, it just decodes a module and then unit-tests the
// generated filter function.
template <class Elf, class Test>
void PerfectSymbolFilterTest(Test& test, std::string_view path_prefix) {
using size_type = Elf::size_type;
using Sym = Elf::Sym;
using Module = ld::RemoteLoadModule<Elf>;
test.LdsvcPathPrefix(path_prefix);
{
// The only need to find the nominal executable is to inform GetLibVmo
// where to look in the test packaging.
zx::vmo exec_vmo;
if constexpr (Elf::kClass == elfldltl::ElfClass::k32) {
// The elf32 file is not packaged quite normally yet.
const std::string executable_path =
std::filesystem::path("test") / path_prefix / "lib" / path_prefix;
exec_vmo = elfldltl::testing::GetTestLibVmo(executable_path);
} else {
exec_vmo = ld::testing::GetExecutableVmo(path_prefix);
}
test.ConfigFromInterp(exec_vmo.borrow());
}
auto diag = elfldltl::testing::ExpectOkDiagnostics();
auto decoded = Module::Decoded::Create(diag, test.GetLibVmo("libsymbol-filter-dep17.so"),
static_cast<size_type>(Test::kPageSize));
ASSERT_TRUE(decoded);
ASSERT_TRUE(decoded->HasModule());
// The original module has all three symbols.
constexpr elfldltl::SymbolName kFirst = "first";
const Sym* first_sym = kFirst.Lookup(decoded->symbol_info());
EXPECT_NE(first_sym, nullptr);
constexpr elfldltl::SymbolName kSecond = "second";
const Sym* second_sym = kSecond.Lookup(decoded->symbol_info());
EXPECT_NE(second_sym, nullptr);
constexpr elfldltl::SymbolName kThird = "third";
const Sym* third_sym = kThird.Lookup(decoded->symbol_info());
EXPECT_NE(third_sym, nullptr);
// These are distinct symbols.
EXPECT_NE(first_sym, second_sym);
EXPECT_NE(first_sym, third_sym);
EXPECT_NE(second_sym, third_sym);
// Populate the filter.
typename Module::SymbolFilter filter = ld::testing::PerfectSymbolFilterTest<Elf>(diag, decoded);
ASSERT_TRUE(filter);
// Mock up a module object. It won't be referenced by calls to the filter.
Module mod;
// First symbol is found by the filter.
elfldltl::SymbolName name = kFirst;
auto first_result = filter(mod, name);
ASSERT_TRUE(first_result.is_ok()) << first_result.error_value();
EXPECT_EQ(*first_result, first_sym);
// Second symbol is filtered out: not found.
name = kSecond;
auto second_result = filter(mod, name);
ASSERT_TRUE(second_result.is_ok()) << second_result.error_value();
EXPECT_EQ(*second_result, nullptr);
// Third symbol is found by the filter.
name = kThird;
auto third_result = filter(mod, name);
ASSERT_TRUE(third_result.is_ok()) << third_result.error_value();
EXPECT_EQ(*third_result, third_sym);
// Now install the filter and get the same results via the module. Nothing
// else will be used, so the module stays otherwise default-constructed.
mod.set_symbol_filter(std::move(filter));
name = kFirst;
first_result = mod.Lookup(diag, name);
ASSERT_TRUE(first_result.is_ok()) << first_result.error_value();
EXPECT_EQ(*first_result, first_sym);
name = kSecond;
second_result = mod.Lookup(diag, name);
ASSERT_TRUE(second_result.is_ok()) << second_result.error_value();
EXPECT_EQ(*second_result, nullptr);
name = kThird;
third_result = mod.Lookup(diag, name);
ASSERT_TRUE(third_result.is_ok()) << third_result.error_value();
EXPECT_EQ(*third_result, third_sym);
}
TEST_F(LdRemoteTests, PerfectSymbolFilter) {
ASSERT_NO_FATAL_FAILURE(PerfectSymbolFilterTest<elfldltl::Elf<>>(*this, "symbol-filter"));
}
TEST_F(LdRemoteTests, PerfectSymbolFilterElf32) {
ASSERT_NO_FATAL_FAILURE(PerfectSymbolFilterTest<elfldltl::Elf32<>>(*this, "symbol-filter-elf32"));
}
TEST_F(LdRemoteTests, ForeignMachine) {
using ForeignElf = elfldltl::Elf32<elfldltl::ElfData::k2Lsb>;
constexpr elfldltl::ElfMachine kForeignMachine = elfldltl::ElfMachine::kArm;
constexpr uint32_t kForeignPageSize = 0x1000;
using ForeignLinker =
ld::RemoteDynamicLinker<ForeignElf, ld::RemoteLoadZygote::kNo, kForeignMachine>;
using ForeignStub = ForeignLinker::AbiStub;
// Init() creates the process where the test modules will be loaded, and
// provides its root VMAR. The modules understand only a 32-bit address
// space, so they must go into the low 4GiB of the test process.
ASSERT_NO_FATAL_FAILURE(Init());
// The kernel reserves the lowest part of the address space, so the root VMAR
// doesn't start at zero. The VMAR for the 32-bit address space will not be
// quite 4GiB in size, so adjust to make sure it ends at exactly 4GiB. In
// fact, no 32-bit userland ever expects to have a segment in the very last
// page, where the page-rounded vaddr+memsz wraps around to 0. So make the
// VMAR one page smaller to ensure nothing gets placed all the way up there.
const size_t kAddressLimit = (size_t{1} << 32) - zx_system_get_page_size();
zx_info_vmar_t root_vmar_info;
zx_status_t status =
root_vmar().get_info(ZX_INFO_VMAR, &root_vmar_info, sizeof(root_vmar_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
ASSERT_LT(root_vmar_info.base, kAddressLimit);
constexpr zx_vm_option_t kVmarOptions =
// Require the specific offset of 0 and allow exact placement within.
ZX_VM_SPECIFIC | ZX_VM_CAN_MAP_SPECIFIC |
// Allow all kinds of mappings.
ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE | ZX_VM_CAN_MAP_EXECUTE;
const size_t vmar_size = kAddressLimit - root_vmar_info.base;
zx::vmar vmar;
uintptr_t vmar_addr;
status = root_vmar().allocate(kVmarOptions, 0, vmar_size, &vmar, &vmar_addr);
ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status);
EXPECT_EQ(vmar_addr, root_vmar_info.base);
LdsvcPathPrefix("symbol-filter-elf32");
zx::vmo stub_vmo = elfldltl::testing::GetTestLibVmo(ForeignStub::kFilename);
ASSERT_TRUE(stub_vmo);
auto diag = elfldltl::testing::ExpectOkDiagnostics();
ForeignLinker linker;
linker.set_abi_stub(ForeignStub::Create(diag, std::move(stub_vmo), kForeignPageSize));
ASSERT_TRUE(linker.abi_stub());
// The non-Fuchsia executable gets packaged under lib/ in the test data.
zx::vmo exec_vmo = GetLibVmo("symbol-filter-elf32");
ASSERT_TRUE(exec_vmo);
ConfigFromInterp(exec_vmo.borrow());
ForeignLinker::Module::DecodedPtr executable;
ASSERT_TRUE(executable = ForeignLinker::Module::Decoded::Create( //
diag, std::move(exec_vmo), kForeignPageSize));
auto get_dep = GetDepFunction<ForeignLinker>(diag, kForeignPageSize);
ASSERT_NO_FATAL_FAILURE(Needed({
"libsymbol-filter-dep17.so",
"libsymbol-filter-dep23.so",
"libsymbol-filter-dep42.so",
}));
ASSERT_NO_FATAL_FAILURE(LdsvcExpectNeeded());
auto init_result = linker.Init(diag, {ForeignLinker::Executable(std::move(executable))}, get_dep,
kForeignMachine);
ASSERT_TRUE(init_result);
EXPECT_TRUE(linker.Allocate(diag, vmar.borrow()));
// These won't really be used, but they can be extracted.
set_entry(linker.main_entry());
set_stack_size(linker.main_stack_size());
// Now it can be relocated for the foreign machine.
EXPECT_TRUE(linker.Relocate(diag));
// It can even be loaded. But it can't be run.
ASSERT_TRUE(linker.Load(diag));
ExpectLog("");
}
template <class... Elf>
struct OnEachLayout {
void operator()(auto&& f) const { (f.template operator()<Elf>(), ...); }
};
constexpr auto OnAllLayouts = elfldltl::AllFormats<OnEachLayout>{};
constexpr std::string_view kTestPrefix = "test/";
constexpr std::string_view kTestSuffix;
template <class Elf>
constexpr std::string_view kRemoteDecodedFileTestFile =
Elf::template kFilename<kTestPrefix, elfldltl::ElfMachine::kNone, kTestSuffix>;
template <class Elf>
struct RemoteDecodedFileTest {
using size_type = Elf::size_type;
using Decoded = ld::RemoteDecodedModule<Elf>;
using DecodedPtr = Decoded::Ptr;
static constexpr size_type kPageSize = 0x1000;
static zx::vmo TestData() {
return elfldltl::testing::GetTestLibVmo(kRemoteDecodedFileTestFile<Elf>);
}
static void Test() {
zx::vmo vmo;
ASSERT_NO_FATAL_FAILURE(vmo = TestData());
elfldltl::testing::ExpectOkDiagnostics diag;
DecodedPtr decoded = Decoded::Create(diag, std::move(vmo), kPageSize);
ASSERT_TRUE(decoded);
// Any sort of ld::RemoteDecodedModule<...>::Ptr can be upcast to a generic
// ld::RemoteDecodedFile::Ptr, but not implicitly.
ld::RemoteDecodedFile::Ptr file = decoded->AsFile();
// ld::RemoteDecodedFile::GetIf can downcast for the right format.
OnAllLayouts([&file, &decoded]<class Layout>() {
using LayoutPtr = ld::RemoteDecodedModule<Layout>::Ptr;
LayoutPtr as_layout = file->GetIf<Layout>();
if constexpr (std::is_same_v<Layout, Elf>) {
EXPECT_TRUE(as_layout);
EXPECT_EQ(as_layout, decoded);
} else {
EXPECT_FALSE(as_layout);
// The GetIf overload taking a Diagnostics object will report why.
if constexpr (Elf::kClass != Layout::kClass && Elf::kData != Layout::kData) {
elfldltl::testing::ExpectedErrorList diag{
elfldltl::testing::ExpectReport{"wrong ELF class (bit-width)"},
elfldltl::testing::ExpectReport{"wrong byte order"},
};
as_layout = file->GetIf<Layout>(diag);
EXPECT_FALSE(as_layout);
} else if constexpr (Elf::kClass != Layout::kClass) {
elfldltl::testing::ExpectedErrorList diag{
elfldltl::testing::ExpectReport{"wrong ELF class (bit-width)"},
};
as_layout = file->GetIf<Layout>(diag);
EXPECT_FALSE(as_layout);
} else {
elfldltl::testing::ExpectedErrorList diag{
elfldltl::testing::ExpectReport{"wrong byte order"},
};
as_layout = file->GetIf<Layout>(diag);
EXPECT_FALSE(as_layout);
}
}
});
// ld::RemoteDecodedFile::VisitAnyLayout should invoke the lambda with the
// right type even though it was upcast before.
file->VisitAnyLayout([decoded]<class SomePtr>(const SomePtr& ptr) {
if constexpr (std::is_same_v<SomePtr, DecodedPtr>) {
EXPECT_EQ(ptr, decoded);
} else {
ADD_FAILURE();
}
});
// ld::RemoteDecodedFile::VisitAnyClass should invoke the lambda with the
// right type even though it was upcast before.
file->VisitAnyClass<Elf::kData>([decoded]<class SomePtr>(const SomePtr& ptr) {
if constexpr (std::is_same_v<SomePtr, DecodedPtr>) {
EXPECT_EQ(ptr, decoded);
} else {
ADD_FAILURE();
}
});
}
};
TEST_F(LdRemoteTests, RemoteDecodedFile) {
constexpr auto test = []<class Elf>() {
ASSERT_NO_FATAL_FAILURE(RemoteDecodedFileTest<Elf>::Test());
};
OnAllLayouts(test);
}
} // namespace