blob: 7677f19e6778c3c8ad0536063b550358d28508dc [file] [log] [blame]
// 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 "dl-iterate-phdr-tests.h"
#include <gmock/gmock.h>
#include "dl-load-tests.h"
#include "startup-symbols.h"
namespace {
using dl::testing::TestModule;
using dl::testing::TestSym;
using dl::testing::CollectModulePhdrInfo;
using dl::testing::GetGlobalCounters;
using dl::testing::GetPhdrInfoForModule;
using dl::testing::gStartupPhdrInfo;
using dl::testing::ModuleInfoList;
using dl::testing::ModulePhdrInfo;
using dl::testing::DlTests;
TYPED_TEST_SUITE(DlTests, dl::testing::TestTypes);
// Test that `dl_iterate_phdr` includes startup modules.
TYPED_TEST(DlTests, DlIteratePhdrStartupModules) {
ModuleInfoList startup_info_list;
EXPECT_EQ(this->DlIteratePhdr(CollectModulePhdrInfo, &startup_info_list), 0);
// If the dlopen implementation can't unload modules or if previous tests
// tested with RTLD_NODELETE, there will be additional modules that are
// collected by dl_iterate_phdr that were loaded by tests that ran before this
// one. This checks that `startup_info_list` at least starts with the elements
// in `gStartupPhdrInfo`.
EXPECT_THAT(std::span(startup_info_list).subspan(0, gStartupPhdrInfo.size()),
::testing::ElementsAreArray(gStartupPhdrInfo));
}
// Test the following as it affects `dl_iterate_phdr` output:
// dlopen module
// `dl_iterate_phdr` output includes new module
// dlclose module
// `dl_iterate_phdr` output doesn't include the module
TYPED_TEST(DlTests, DlIteratePhdrBasic) {
const std::string kRet17File = TestModule("ret17");
// Record initial values to compare against during the test.
ModuleInfoList initial_info_list;
EXPECT_EQ(this->DlIteratePhdr(CollectModulePhdrInfo, &initial_info_list), 0);
const ModulePhdrInfo last_phdr_info = initial_info_list.back();
const auto [initial_loaded, initial_unloaded] = GetGlobalCounters(this);
this->ExpectRootModule(kRet17File);
auto open_ret17 = this->DlOpen(kRet17File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_ret17.is_ok()) << open_ret17.error_value();
EXPECT_TRUE(open_ret17.value());
// Check that a struct `dl_pdhr_info` is produced for the dlopen-ed module.
ModulePhdrInfo ret17_phdr_info = GetPhdrInfoForModule(*this, kRet17File);
// Check that the `.dlpi_adds` counter is adjusted.
const size_t loaded_after_open = GetGlobalCounters(this).adds;
EXPECT_EQ(loaded_after_open, initial_loaded + 1);
// Look up a symbol from the module and expect that its pointer value should
// be within the address range of the module's phdrs from its
// `dl_phdr_info`.
auto ret17_test_start = this->DlSym(open_ret17.value(), TestSym("TestStart").c_str());
ASSERT_TRUE(ret17_test_start.is_ok()) << ret17_test_start.error_value();
EXPECT_TRUE(ret17_test_start.value());
EXPECT_TRUE(ret17_phdr_info.contains_addr(reinterpret_cast<uintptr_t>(ret17_test_start.value())));
ASSERT_TRUE(this->DlClose(open_ret17.value()).is_ok());
// A final check that dl-closing the module will remove its entry and update
// the `.dlpi_subs` counter.
ModuleInfoList close_info_list;
EXPECT_EQ(this->DlIteratePhdr(CollectModulePhdrInfo, &close_info_list), 0);
// The last entry should be the same as at the beginning of the test.
const ModulePhdrInfo test_last_phdr_info = close_info_list.back();
const size_t unloaded_after_close = GetGlobalCounters(this).subs;
if (TestFixture::kDlCloseUnloadsModules) {
EXPECT_EQ(unloaded_after_close, initial_unloaded + 1);
EXPECT_EQ(test_last_phdr_info, last_phdr_info);
EXPECT_EQ(close_info_list.size(), initial_info_list.size());
} else {
// Musl-Fuchsia's dlclose is a no-op and does not change dl_iterate_phdr
// output: the module entry is preserved.
EXPECT_EQ(unloaded_after_close, initial_unloaded);
EXPECT_EQ(test_last_phdr_info, ret17_phdr_info);
EXPECT_EQ(close_info_list.size(), initial_info_list.size() + 1);
}
}
TYPED_TEST(DlTests, DlIteratePhdrTlsData) {
// This test module has only one variable in its PT_TLS segment, so that
// variable will always be at the start of its block.
auto info = GetPhdrInfoForModule(*this, "libstatic-tls-var.so");
EXPECT_EQ(info.tls_data(), &gStaticTlsVar)
<< " Both should be reative to $tp\n Which is: " << __builtin_thread_pointer();
}
} // namespace