blob: ac1a4f7c448d89fcf61e12bf28a685ec691e6e92 [file]
// 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