blob: 842aa14e9d9bcccde8461d10f2c63eadf6283b08 [file] [log] [blame]
// 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 "dl-load-tests.h"
#include "startup-symbols.h"
namespace {
using dl::testing::DlTests;
TYPED_TEST_SUITE(DlTests, dl::testing::TestTypes);
// This is used to check that module initializers and finalizers call the
// Callback function in a specific order.
class MockTestCallback : public TestCallback {
public:
MOCK_METHOD(void, Callback, (int), (override));
// Expect the Callback function was called in the same order as
// `expected_callbacks`.
void ExpectCallbacks(std::initializer_list<int> expected_callbacks) {
for (int i : expected_callbacks) {
EXPECT_CALL(*this, Callback(i)).InSequence(sequence_);
}
}
private:
// The sequence object specific to this mock class object will only validate
// the `EXPECT_CALL(...)`s of this mock instance.
::testing::Sequence sequence_;
};
// Sets the test module's gTestCallback to the given mock and runs the provided
// function under the context of the mock instance.
void RunWithMock(::testing::StrictMock<MockTestCallback>& mock, fit::function<void()>& run) {
gTestCallback = &mock;
run();
gTestCallback = nullptr;
}
// Instantiate a MockTestCallback and prime it with an ordered list of expected
// callback values before running the given function. `RunWithMock` will run
// the callable function that should elicit the expected callbacks to run. The
// MockTestCallback will fail if the expected callbacks were not completed or
// called in order.
void RunWithExpectedTestCallbacks(fit::function<void()> run,
std::initializer_list<int> expected_callbacks) {
::testing::StrictMock<MockTestCallback> mock;
mock.ExpectCallbacks(expected_callbacks);
ASSERT_FALSE(gTestCallback);
RunWithMock(mock, run);
}
// dlopen a module whose initializers and finalizers are decoded by legacy
// DT_INIT and DT_FINI sections. These functions will call a callback with a
// value that is checked by the test to ensure those functions were run in order.
TYPED_TEST(DlTests, InitFiniLegacy) {
const std::string kInitFiniLegacyFile = "init-fini-legacy.so";
auto test = [&] {
this->ExpectRootModule(kInitFiniLegacyFile);
auto open = this->DlOpen(kInitFiniLegacyFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value()) << open.error_value();
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
};
if (TestFixture::kDlCloseCanRunFinalizers) {
RunWithExpectedTestCallbacks(test, {101, 102});
} else {
RunWithExpectedTestCallbacks(test, {101});
}
}
// Similar to InitFiniLegacy test, except dlopen a module whose initializers and
// finalizers are decoded from DT_INIT_ARRAY/DT_FINI_ARRAY sections. This also
// tests that multiple initializers/finalizers in the dlopen-ed module are run in
// correct order.
TYPED_TEST(DlTests, InitFiniArray) {
const std::string kInitFiniArrayFile = "init-fini-array.so";
auto test = [&] {
this->ExpectRootModule(kInitFiniArrayFile);
auto open = this->DlOpen(kInitFiniArrayFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value()) << open.error_value();
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
};
// Expect the three ctors to have run and three dtors to have run.
if (TestFixture::kDlCloseCanRunFinalizers) {
RunWithExpectedTestCallbacks(test, {0, 1, 2, 3, 4, 5});
} else {
RunWithExpectedTestCallbacks(test, {0, 1, 2});
}
}
// Test that dlopen will run initializers and finalizers of a module with
// dependencies that also have initializers and finalizers. Similar to the above
// tests, each init/fini function calls a callback with a particular value that
// gets checked by the test.
//
// dlopen init-fini-array-root:
// - init-fini-array-a:
// - init-fini-array-a-dep
// - init-fini-array-b:
// - init-fini-array-b-dep
// - init-fini-array-c
//
// Module initializers are run in this order:
// init-fini-array-b-dep
// init-fini-array-a-dep
// init-fini-array-c
// init-fini-array-b
// init-fini-array-a
// init-fini-array-root
//
// Module finalizers are run in reverse of the init order:
// init-fini-array-root
// init-fini-array-a
// init-fini-array-b
// init-fini-array-c
// init-fini-array-a-dep
// init-fini-array-b-dep
TYPED_TEST(DlTests, InitFiniArrayWithDeps) {
const std::string kRootFile = "init-fini-array-with-deps.so";
const std::string kAFile = "libinit-fini-array-a.so";
const std::string kADepFile = "libinit-fini-array-a-dep.so";
const std::string kBFile = "libinit-fini-array-b.so";
const std::string kBDepFile = "libinit-fini-array-b-dep.so";
const std::string kCFile = "libinit-fini-array-c.so";
auto test = [&] {
this->ExpectRootModule(kRootFile);
this->Needed({kAFile, kBFile, kCFile, kADepFile, kBDepFile});
auto open = this->DlOpen(kRootFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value()) << open.error_value();
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
};
if (TestFixture::kDlCloseCanRunFinalizers) {
RunWithExpectedTestCallbacks(test, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11});
} else {
RunWithExpectedTestCallbacks(test, {0, 1, 2, 3, 4, 5});
}
}
// dlopen a module with a mix of DT_INIT/DT_FINI and DT_INIT_ARRAY and
// DT_FINI_ARRAY entries.
TYPED_TEST(DlTests, InitFiniArrayWithLegacy) {
const std::string kInitFiniArrayWithLegacyFile = "init-fini-array-with-legacy.so";
auto test = [&] {
this->ExpectRootModule(kInitFiniArrayWithLegacyFile);
auto open = this->DlOpen(kInitFiniArrayWithLegacyFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value()) << open.error_value();
ASSERT_TRUE(this->DlClose(open.value()).is_ok());
};
if (TestFixture::kDlCloseCanRunFinalizers) {
RunWithExpectedTestCallbacks(test, {201, 202, 203, 204});
} else {
RunWithExpectedTestCallbacks(test, {201, 202});
}
}
// Test that dlopen will only run initializers of modules when they are first
// loaded.
// dlopen init-fini-array-with-loaded-deps-a:
// - init-fini-array-with-loaded-deps-a-dep
// dlopen init-fini-array-with-loaded-deps-a again.
// dlopen init-fini-array-with-loaded-deps-c
// dlopen init-fini-array-with-loaded-deps-with-loaded-deps:
// - init-fini-array-with-loaded-deps-a (already loaded)
// - init-fini-array-with-loaded-deps-a-dep (already loaded)
// - init-fini-array-with-loaded-deps-b:
// - init-fini-array-with-loaded-deps-b-dep
// - init-fini-array-with-loaded-deps-c (already loaded)
//
// Module initializers are run in this order:
// ... in dlopen(init-fini-array-with-loaded-deps-with-loaded-deps-a):
// init-fini-array-with-loaded-deps-a-dep
// init-fini-array-with-loaded-deps-a
// ... in dlopen(init-fini-array-with-loaded-deps-with-loaded-deps-c):
// init-fini-array-with-loaded-deps-c
// ... in dlopen(init-fini-array-with-loaded-deps-with-loaded-deps):
// init-fini-array-with-loaded-deps-b-dep
// init-fini-array-with-loaded-deps-b
// init-fini-array-with-loaded-deps
//
// Note: Finalizers are run in the order in which the modules were loaded and
// this is triggered by the unloading of the last reference held by the root
// module:
// init-fini-array-with-loaded-deps
// init-fini-array-with-loaded-deps-a
// init-fini-array-with-loaded-deps-a-dep
// init-fini-array-with-loaded-deps-c
// init-fini-array-with-loaded-deps-b
// init-fini-array-with-loaded-deps-b-dep
TYPED_TEST(DlTests, InitFiniArrayWithLoadedDeps) {
const std::string kRootFile = "init-fini-array-with-loaded-deps.so";
const std::string kAFile = "libinit-fini-array-with-loaded-deps-a.so";
const std::string kADepFile = "libinit-fini-array-with-loaded-deps-a-dep.so";
const std::string kBFile = "libinit-fini-array-with-loaded-deps-b.so";
const std::string kBDepFile = "libinit-fini-array-with-loaded-deps-b-dep.so";
const std::string kCDepFile = "libinit-fini-array-with-loaded-deps-c.so";
void* first_open_a;
auto first_open_a_test = [&] {
this->Needed({kAFile, kADepFile});
auto open_a = this->DlOpen(kAFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_a.is_ok()) << open_a.error_value();
EXPECT_TRUE(open_a.value()) << open_a.error_value();
first_open_a = open_a.value();
};
RunWithExpectedTestCallbacks(first_open_a_test, {0, 1});
// Don't expect another dlopen on dep-a will run initializers.
void* second_open_a;
auto second_open_a_test = [&] {
auto open_a = this->DlOpen(kAFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open_a.is_ok()) << open_a.error_value();
EXPECT_TRUE(open_a.value()) << open_a.error_value();
second_open_a = open_a.value();
};
RunWithExpectedTestCallbacks(second_open_a_test, {});
void* open_c;
auto open_c_test = [&] {
this->Needed({kCDepFile});
auto open = this->DlOpen(kCDepFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value()) << open.error_value();
open_c = open.value();
};
RunWithExpectedTestCallbacks(open_c_test, {2});
void* open_root;
auto open_root_test = [&] {
this->ExpectRootModule(kRootFile);
this->Needed({kBFile, kBDepFile});
// This will only run initializers on the modules that are loaded by this call.
auto open = this->DlOpen(kRootFile.c_str(), RTLD_NOW | RTLD_LOCAL);
ASSERT_TRUE(open.is_ok()) << open.error_value();
EXPECT_TRUE(open.value()) << open.error_value();
open_root = open.value();
};
RunWithExpectedTestCallbacks(open_root_test, {3, 4, 5});
// Don't expect these dlclose calls on dep-a will run any finalizers.
RunWithExpectedTestCallbacks([&] { ASSERT_TRUE(this->DlClose(first_open_a).is_ok()); }, {});
RunWithExpectedTestCallbacks([&] { ASSERT_TRUE(this->DlClose(second_open_a).is_ok()); }, {});
RunWithExpectedTestCallbacks([&] { ASSERT_TRUE(this->DlClose(open_c).is_ok()); }, {});
auto close_root_test = [&] { ASSERT_TRUE(this->DlClose(open_root).is_ok()); };
if (TestFixture::kDlCloseCanRunFinalizers) {
// Older glibc versions run destructors out of order. More information on
// this flag setting is in dl-system-tests.h.
if (TestFixture::kDestructorsRunOutOfOrder) {
RunWithExpectedTestCallbacks(close_root_test, {6, 9, 7, 8, 10, 11});
} else {
RunWithExpectedTestCallbacks(close_root_test, {6, 7, 8, 9, 10, 11});
}
} else {
RunWithExpectedTestCallbacks(close_root_test, {});
}
}
} // namespace