| // 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 |