blob: 8d6665f3c43680f21e711e064e77b3b317d50ba1 [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.
#ifndef LIB_C_DLFCN_DL_TEST_DL_SYSTEM_TESTS_H_
#define LIB_C_DLFCN_DL_TEST_DL_SYSTEM_TESTS_H_
#include <dlfcn.h> // for dlinfo
#include "dl-load-tests-base.h"
#ifdef __Fuchsia__
#include "dl-load-zircon-tests-base.h"
#endif
namespace dl::testing {
#ifdef __Fuchsia__
using DlSystemLoadTestsBase = DlLoadZirconTestsBase;
#else
using DlSystemLoadTestsBase = DlLoadTestsBase;
#endif
class DlSystemTests : public DlSystemLoadTestsBase {
private:
#ifdef __GLIBC__
// This is used by system tests that observe different behavior for older
// glibc versions. If glibc is installed, returns whether the glibc version is
// at least the provided major and minor.
static bool GlibcVersionAtLeast(int major, int minor) {
char version[16];
char installed_version[16];
size_t res = confstr(_CS_GNU_LIBC_VERSION, version, sizeof(version));
EXPECT_GT(res, 0lu) << strerror(errno);
EXPECT_LE(res, sizeof(version));
snprintf(installed_version, sizeof(installed_version), "glibc %d.%d", major, minor);
return strverscmp(version, installed_version) >= 0;
}
#endif
public:
// This test fixture does not need to match on exact error text, since the
// error message can vary between different system implementations.
static constexpr bool kCanMatchExactError = false;
#ifdef __Fuchsia__
// Musl always prioritizes a loaded module for symbol lookup.
static constexpr bool kStrictLoadOrderPriority = true;
// Musl does not validate flag values for dlopen's mode argument.
static constexpr bool kCanValidateMode = false;
// Musl will emit a "symbol not found" error for scenarios where glibc or
// libdl will emit an "undefined symbol" error.
static constexpr bool kEmitsSymbolNotFound = true;
// Fuchsia's dlclose is a no-op.
static constexpr bool kDlCloseCanRunFinalizers = false;
static constexpr bool kDlCloseUnloadsModules = false;
// Musl will "double-count" in its `.dlpi_adds` counter a module that was
// reused because of DT_SONAME match. For example, if a previously loaded
// module had a DT_SONAME that matched the filename of a module that is about
// to be loaded, Musl will reuse the previously loaded module, but it will
// still increment the counter as if a new module was loaded.
static constexpr bool kInaccurateLoadCountAfterSonameMatch = true;
// Musl attempts to fetch the same shlib from the filesystem twice, when its
// DT_SONAME is matched with another module in a linking session.
static constexpr bool kSonameLookupInPendingDeps = false;
// Musl doesn't support glibc RTLD_DI_* flags.
static constexpr bool kSupportsDlInfoExtensionFlags = false;
// Musl will emit the unsupported flag value in the error message from dlinfo.
static constexpr bool kEmitsDlInfoUnsupportedValue = true;
// Musl will look for and prefer a strong symbol over a weak symbol.
static constexpr elfldltl::ResolverPolicy kResolverPolicy =
elfldltl::ResolverPolicy::kStrongOverWeak;
#else
// TODO(https://fxbug.dev/385377689): The running glibc version must be at
// least 2.4 to support RTLD_DI_* extension flags.
static const inline bool kSupportsDlInfoExtensionFlags = GlibcVersionAtLeast(2, 40);
// TODO(https://fxbug.dev/385377689): In glibc versions < 2.38, destructor
// order can be re-sorted in dlclose.
static const inline bool kDestructorsRunOutOfOrder = !GlibcVersionAtLeast(2, 38);
#endif
void SetUp() override { ASSERT_TRUE(dlerror_.empty()); }
void TearDown() override {
// Clear dlerror_ for tests that only test the error object returned from
// a failed dlopen/dlsym/etc call. These tests don't call DlError, which
// clears dlerror_ after reporting the error, so we must clear it at test
// teardown.
dlerror_.clear();
}
std::optional<Error> DlError();
fit::result<Error, void*> DlOpen(const char* file, int mode);
fit::result<Error> DlClose(void* module);
fit::result<Error, void*> DlSym(void* module, const char* ref);
static int DlIteratePhdr(DlIteratePhdrCallback* callback, void* data);
fit::result<Error, int> DlInfo(void* module, int request, void* info);
// ExpectRootModule or Needed are called by tests when a file is expected to
// be loaded from the file system for the first time. The following functions
// will call DlOpen(file, RTLD_NOLOAD) to ensure that `file` is not already
// loaded (e.g. by a previous test).
void ExpectRootModule(std::string_view name);
void Needed(std::initializer_list<std::string_view> names);
void Needed(std::initializer_list<std::pair<std::string_view, bool>> name_found_pairs);
void CleanUpOpenedFile(void* ptr) override { ASSERT_TRUE(DlClose(ptr).is_ok()); }
// This function is a no-op for system tests, since they manage their own TLS
// setup.
void PrepareForTlsAccess() {}
// Call the system's dlinfo to fill in the link map for the given handle, and
// return it to the caller.
static const link_map* ModuleLinkMap(void* handle) {
struct link_map* info = nullptr;
EXPECT_EQ(dlinfo(handle, RTLD_DI_LINKMAP, static_cast<void*>(&info)), 0) << dlerror();
return info;
}
private:
// This will call the system dlopen in an OS-specific context. This method is
// defined directly on this test fixture rather than its OS-tailored base
// classes because the logic it performs is only needed for testing the
// system dlopen by this test fixture.
void* CallDlOpen(const char* file, int mode);
// DlOpen `name` with `RTLD_NOLOAD` to ensure this will be the first time the
// file is loaded from the filesystem.
void NoLoadCheck(std::string_view name);
// Create an Error object to return in a fit::result<Error..>. This function
// will also set `dlerror_` to the error message of the Error object.
fit::error<Error> TakeError();
// The following functions should be used to return a successful result for
// any public-facing Dl* function that returns a fit::result<..>. Since
// dlerror() will clear its error state after a successful function return,
// we must do that here, so that the next call to DlError will not report a
// stale error message.
template <typename T>
auto SuccessResult(T&& success) {
dlerror_.clear();
return fit::ok(std::forward<T>(success));
}
auto SuccessResult() {
dlerror_.clear();
return fit::ok();
}
// This holds the error message for the most recently failed dl function made
// during a test and is cleared in between tests. DlError will report the
// contents of this string when called.
std::string dlerror_;
};
} // namespace dl::testing
#endif // LIB_C_DLFCN_DL_TEST_DL_SYSTEM_TESTS_H_