| // Copyright 2025 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/test-pipe-reader.h> |
| #include <lib/ld/testing/get-test-vmo.h> |
| #include <lib/ld/testing/interp.h> |
| #include <lib/stdcompat/string_view.h> |
| #include <unistd.h> |
| |
| #include "load-tests.h" |
| |
| namespace ld::testing { |
| |
| template <class Fixture> |
| using LdLoadLibcTests = Fixture; |
| |
| // The libc-using tests don't run in-process; so TestTypes<>, not LoadTypes. |
| TYPED_TEST_SUITE(LdLoadLibcTests, TestTypes<>, elfldltl::testing::TestNames); |
| |
| namespace { |
| |
| constexpr std::string_view kLibcSoname = "libc.so"; |
| constexpr std::string_view kLibcxxSoname = "libc++.so.2"; |
| constexpr std::string_view kLibcxxabiSoname = "libc++abi.so.1"; |
| constexpr std::string_view kLibunwindSoname = "libunwind.so.1"; |
| constexpr std::string_view kFdioSoname = "libfdio.so"; |
| |
| // This is a kludgey check for an executable that is using a zero-based shadow |
| // sanitizer build of the legacy libc. The only ones of those ever implemented |
| // are asan and hwasan, so those will usually be somewhere in the libprefix. |
| bool ExecutableUsesAsan(std::string_view name) { |
| std::optional config = ConfigFromInterp(FindInterp<elfldltl::VmoFile>(GetExecutableVmo(name))); |
| return config && cpp23::contains(*config, "asan"); |
| } |
| |
| bool ProcessVmarStartsHigh(zx::unowned_process process) { |
| std::array<zx_info_maps_t, 4> info{}; |
| size_t actual, avail; |
| zx_status_t status = process->get_info(ZX_INFO_PROCESS_MAPS, info.data(), |
| std::span{info}.size_bytes(), &actual, &avail); |
| EXPECT_EQ(status, ZX_OK) << zx_status_get_string(status); |
| EXPECT_GE(actual, 2u); |
| const zx_info_maps_t& vmar = info[1]; |
| EXPECT_EQ(vmar.type, ZX_INFO_MAPS_TYPE_VMAR) << vmar.name; |
| return vmar.base >= (vmar.base + vmar.size) / 2; |
| } |
| |
| // This is just running an empty main() function using the vanilla system libc. |
| // It's really just testing that the test harness itself launches a PT_INTERP |
| // executable consistent with what the real system program loader does, for |
| // both the ELF loading and the process bootstrap protocol. |
| TYPED_TEST(LdLoadLibcTests, LibcNoopTest) { |
| if constexpr (!TestFixture::kRunsLdStartup) { |
| GTEST_SKIP() << "legacy libc not compatible with remote dynamic linking"; |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(this->Init()); |
| |
| if (ProcessVmarStartsHigh(this->process().borrow()) && // |
| ExecutableUsesAsan("libc-noop-test")) { |
| GTEST_SKIP() << "legacy asan not compatible with high-half address space"; |
| } |
| |
| // This file is generated by the build with the right list of DT_NEEDED |
| // for the system libc. The legacy libc is folded into the dynamic linker, |
| // and the vDSO doesn't need to be requested; so the list is empty |
| // except for sanitizer builds of the test case. |
| this->Needed(std::initializer_list<std::string_view>{ |
| #include "libc-noop-test-needed.inc" |
| }); |
| |
| ASSERT_NO_FATAL_FAILURE(this->Load("libc-noop-test")); |
| |
| EXPECT_EQ(this->Run(), 0); |
| |
| this->ExpectLog(""); |
| } |
| |
| TYPED_TEST(LdLoadLibcTests, LibcStartMain) { |
| ASSERT_NO_FATAL_FAILURE(this->Init()); |
| |
| ASSERT_NO_FATAL_FAILURE(this->Needed({kLibcSoname})); |
| ASSERT_NO_FATAL_FAILURE(this->Load("libc-start-main")); |
| |
| EXPECT_EQ(this->Run(), 0); |
| |
| this->ExpectLog(""); |
| } |
| |
| TYPED_TEST(LdLoadLibcTests, LibcHelloWorld) { |
| ASSERT_NO_FATAL_FAILURE(this->Init()); |
| |
| // Give the test a stdout pipe and capture what it writes. |
| elfldltl::testing::TestPipeReader test_stdout_pipe; |
| { |
| fbl::unique_fd test_stdout_fd; |
| ASSERT_NO_FATAL_FAILURE(test_stdout_pipe.Init(test_stdout_fd)); |
| ASSERT_NO_FATAL_FAILURE(this->RedirectFd(STDOUT_FILENO, std::move(test_stdout_fd))); |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(this->Needed({ |
| kFdioSoname, |
| kLibcSoname, |
| })); |
| ASSERT_NO_FATAL_FAILURE(this->Load("libc-hello-world")); |
| |
| EXPECT_EQ(this->Run(), 0); |
| |
| this->ExpectLog(""); |
| |
| std::string test_stdout_contents = std::move(test_stdout_pipe).Finish(); |
| EXPECT_EQ(test_stdout_contents, "Hello, world!\n"); |
| } |
| |
| TYPED_TEST(LdLoadLibcTests, LibcxxHelloWorld) { |
| ASSERT_NO_FATAL_FAILURE(this->Init()); |
| |
| // Give the test a stdout pipe and capture what it writes. |
| elfldltl::testing::TestPipeReader test_stdout_pipe; |
| { |
| fbl::unique_fd test_stdout_fd; |
| ASSERT_NO_FATAL_FAILURE(test_stdout_pipe.Init(test_stdout_fd)); |
| ASSERT_NO_FATAL_FAILURE(this->RedirectFd(STDOUT_FILENO, std::move(test_stdout_fd))); |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(this->Needed({ |
| kFdioSoname, |
| kLibcSoname, |
| kLibcxxSoname, |
| kLibcxxabiSoname, |
| kLibunwindSoname, |
| })); |
| ASSERT_NO_FATAL_FAILURE(this->Load("libcxx-hello-world")); |
| |
| EXPECT_EQ(this->Run(), 0); |
| |
| this->ExpectLog(""); |
| |
| std::string test_stdout_contents = std::move(test_stdout_pipe).Finish(); |
| EXPECT_EQ(test_stdout_contents, "Hello, C++ world!\n"); |
| } |
| |
| } // namespace |
| } // namespace ld::testing |