blob: 33caa08ace2910008419e1daa8f6de957aeb1562 [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-load-tests.h"
namespace {
using dl::testing::DlTests;
using dl::testing::TestModule;
using dl::testing::TestShlib;
TYPED_TEST_SUITE(DlTests, dl::testing::TestTypes);
// If RTLD_NOLOAD is passed and the module is not found, NULL is returned but an
// error is not expected.
TYPED_TEST(DlTests, RtldNoLoadBasic) {
const std::string kRet17File = TestModule("ret17");
auto open = this->DlOpen(kRet17File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_EQ(open.value(), nullptr);
// TODO(https://fxbug.dev/325494556): check that dlerror() returns NULL.
}
// Test that RTLD_NODELETE prevents a module from being unloaded by dlclose().
// dlopen ret17, RTLD_NODELETE)
// dlclose(ret17)
// dlopen(ret17, RTLD_NOLOAD) and expect the module to still be loaded.
TYPED_TEST(DlTests, RtldNoDeleteBasic) {
const std::string kRet17File = TestModule("ret17");
this->ExpectRootModule(kRet17File);
auto open = this->DlOpen(kRet17File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
// Test that dlclose will succeed, but not unload the module.
EXPECT_TRUE(this->DlClose(open.value()).is_ok());
auto reopen = this->DlOpen(kRet17File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(reopen.is_ok()) << reopen.error_value();
// The open handle should still be valid memory.
EXPECT_EQ(reopen.value(), open.value());
ASSERT_TRUE(this->DlClose(reopen.value()).is_ok());
}
// Test that "promoting" a module with RTLD_NODELETE will prevent the module
// from being unloaded.
// dlopen ret17
// dlopen ret17 w/ RTLD_NO_DELETE
// dlopen ret17 to try to "unset" RTLD_NODELETE
// dlclose ret17 and expect ret17 to still be loaded.
TYPED_TEST(DlTests, RtldNoDeletePromotion) {
const std::string kRet17File = TestModule("ret17");
this->ExpectRootModule(kRet17File);
auto open = this->DlOpen(kRet17File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value());
auto promoted =
this->DlOpen(kRet17File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE | RTLD_NOLOAD);
ASSERT_TRUE(open.is_ok()) << open.error_value();
// Expect the same handle is returned.
EXPECT_EQ(promoted.value(), open.value());
// Test that once RTLD_NODELETE is set, it cannot be "unset" by its omittance
// from a subsequent dlopen call.
auto try_unset = this->DlOpen(kRet17File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(try_unset.is_ok()) << try_unset.error_value();
// Expect the same handle is returned.
EXPECT_EQ(try_unset.value(), promoted.value());
// Call dlclose on all the handles.
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
ASSERT_TRUE(this->DlClose(promoted.value()).is_ok());
ASSERT_TRUE(this->DlClose(try_unset.value()).is_ok());
auto still_loaded = this->DlOpen(kRet17File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(still_loaded.is_ok()) << still_loaded.error_value();
// The try_unset handle should still be valid memory.
EXPECT_EQ(still_loaded.value(), try_unset.value());
// A final corresponding dlclose() to resolve the TestFixture's tracking of
// open modules.
ASSERT_TRUE(this->DlClose(still_loaded.value()).is_ok());
}
// Test that dlopen a dep with RTLD_NODELETE will not affect a module that is
// subsequently dlopen-ed and depends on the dep.
// dlopen foo-v1 with RTLD_NODELETE
// dlopen has-foo-v1
// - foo-v1
// dlclose foo-v1, dlclose has-foo-v1 and expect only foo-v1 to still be loaded.
TYPED_TEST(DlTests, RtldNoDeleteDep) {
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
const std::string kHasFooV1File = TestShlib("libhas-foo-v1");
this->Needed({kFooV1File});
auto foo = this->DlOpen(kFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE);
ASSERT_TRUE(foo.is_ok()) << foo.error_value();
EXPECT_TRUE(foo.value());
this->Needed({kHasFooV1File});
auto has_foo = this->DlOpen(kHasFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(has_foo.is_ok()) << has_foo.error_value();
EXPECT_TRUE(has_foo.value());
// dlclose the modules
ASSERT_TRUE(this->DlClose(has_foo.value()).is_ok());
ASSERT_TRUE(this->DlClose(foo.value()).is_ok());
// Check that the root is no longer loaded.
if constexpr (TestFixture::kDlCloseUnloadsModules) {
auto check_unloaded = this->DlOpen(kHasFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(check_unloaded.is_ok()) << check_unloaded.error_value();
EXPECT_FALSE(check_unloaded.value());
}
// Check that the dependency is still loaded.
auto still_loaded = this->DlOpen(kFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(still_loaded.is_ok()) << still_loaded.error_value();
// The foo handle should still point to valid memory.
EXPECT_EQ(still_loaded.value(), foo.value());
// A final corresponding dlclose() to resolve the TestFixture's tracking of
// open modules.
ASSERT_TRUE(this->DlClose(still_loaded.value()).is_ok());
}
// Test that a root module can still be unloaded after its dep gets promoted
// with RTLD_NODELETE
// dlopen has-foo-v1
// - foo-v1
// dlopen foo-v1 with RTLD_NODELETE
// dlclose has-foo-v1 and expect foo-v1 to still be loaded.
TYPED_TEST(DlTests, RtldNoDeleteDepPromotion) {
const std::string kHasFooV1File = TestShlib("libhas-foo-v1");
const std::string kFooV1File = TestShlib("libld-dep-foo-v1");
this->Needed({kHasFooV1File, kFooV1File});
auto has_foo = this->DlOpen(kHasFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(has_foo.is_ok()) << has_foo.error_value();
EXPECT_TRUE(has_foo.value());
// Promote the dependency with RTLD_NODELETE.
auto foo = this->DlOpen(kFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE | RTLD_NOLOAD);
ASSERT_TRUE(foo.is_ok()) << foo.error_value();
EXPECT_TRUE(foo.value());
// Close the root and dependency.
ASSERT_TRUE(this->DlClose(has_foo.value()).is_ok());
ASSERT_TRUE(this->DlClose(foo.value()).is_ok());
// Check that the root is no longer loaded.
if constexpr (TestFixture::kDlCloseUnloadsModules) {
auto check_unloaded = this->DlOpen(kHasFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(check_unloaded.is_ok()) << check_unloaded.error_value();
EXPECT_FALSE(check_unloaded.value());
}
// Check that the dependency is still loaded.
auto still_loaded = this->DlOpen(kFooV1File.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_NOLOAD);
ASSERT_TRUE(still_loaded.is_ok()) << still_loaded.error_value();
// The foo handle should still point to valid memory.
EXPECT_EQ(still_loaded.value(), foo.value());
// A final corresponding dlclose() to resolve the TestFixture's tracking of
// open modules.
ASSERT_TRUE(this->DlClose(still_loaded.value()).is_ok());
}
} // namespace