From 4413203a3512d1fcc6f80461e8d41c102dfd17af Mon Sep 17 00:00:00 2001 From: Matthias Wittgen Date: Sun, 30 Aug 2026 10:35:52 -0700 Subject: [PATCH 1/2] Add tools to determine run-time location of shared libraries --- include/lsst/cpputils/packaging.h | 17 +++++++++++++++++ src/packaging.cc | 26 ++++++++++++++++++++++++++ tests/test_packaging.cc | 11 +++++++++++ 3 files changed, 54 insertions(+) diff --git a/include/lsst/cpputils/packaging.h b/include/lsst/cpputils/packaging.h index 77e39df..26ccab7 100644 --- a/include/lsst/cpputils/packaging.h +++ b/include/lsst/cpputils/packaging.h @@ -39,6 +39,23 @@ namespace cpputils { */ std::string getPackageDir(std::string const& packageName); +/*! + * \brief return the root directory of the package whose shared library contains + * the given address + * + * The address is resolved to its containing shared library via `dladdr`, and the + * package root is derived from that library's location (`.../lib/libFoo.so` -> + * `...`). This allows a package to locate its own data files without relying on + * environment variables, provided the address belongs to a symbol compiled into + * that package's own shared library. + * + * \param[in] addressInLibrary address of a symbol residing in the target + * package's shared library + * + * \throw lsst::pex::exceptions::NotFoundError if the library cannot be located + */ +std::string getPackageDirFromAddress(void const* addressInLibrary); + } } // namespace lsst::cpputils diff --git a/src/packaging.cc b/src/packaging.cc index 5c1c277..85cc407 100644 --- a/src/packaging.cc +++ b/src/packaging.cc @@ -22,6 +22,9 @@ #include "lsst/cpputils/packaging.h" +#include + +#include #include #include #include @@ -44,4 +47,27 @@ std::string getPackageDir(std::string const& packageName) { return dir; } +std::string getPackageDirFromAddress(void const* addressInLibrary) { + // dladdr resolves an address to the shared object whose memory map contains + // it, regardless of which library's code is calling dladdr. Passing an + // address from the target package's own library therefore yields that + // library's path (works identically for .so on Linux and .dylib on macOS). + Dl_info info{}; + if (dladdr(addressInLibrary, &info) == 0 || info.dli_fname == nullptr) { + throw LSST_EXCEPT(lsst::pex::exceptions::NotFoundError, + "Could not locate the shared library for the given address"); + } + + std::error_code ec; + std::filesystem::path libraryPath = std::filesystem::canonical(info.dli_fname, ec); + if (ec) { + throw LSST_EXCEPT(lsst::pex::exceptions::NotFoundError, + "Could not resolve shared library path '" + + std::string(info.dli_fname) + "': " + ec.message()); + } + + // ...//lib/lib.{so,dylib} -> + return libraryPath.parent_path().parent_path().string(); +} + }} // namespace lsst::cpputils diff --git a/tests/test_packaging.cc b/tests/test_packaging.cc index 45a13c1..56877d9 100644 --- a/tests/test_packaging.cc +++ b/tests/test_packaging.cc @@ -42,4 +42,15 @@ BOOST_AUTO_TEST_CASE(GetPackage) { lsst::pex::exceptions::NotFoundError); } +BOOST_AUTO_TEST_CASE(GetPackageDirFromAddress) { + // The address of a symbol defined in libcpputils resolves, via dladdr, to + // the cpputils package directory -- independent of any environment variable. + auto anchor = reinterpret_cast(&getPackageDirFromAddress); + std::filesystem::path cpputilsPath{getPackageDirFromAddress(anchor)}; + BOOST_CHECK(std::filesystem::is_regular_file(cpputilsPath / "tests" / "test_packaging.cc")); + // It must agree with the environment-variable-based lookup for the same package. + BOOST_CHECK_EQUAL(std::filesystem::canonical(cpputilsPath), + std::filesystem::canonical(getPackageDir("cpputils"))); +} + BOOST_AUTO_TEST_SUITE_END() From 737b20b6d5a77d63d8b7e011146e87fb0aca5716 Mon Sep 17 00:00:00 2001 From: Matthias Wittgen Date: Sun, 4 Oct 2026 13:49:12 -0700 Subject: [PATCH 2/2] Protect against nullptr returned --- src/packaging.cc | 7 +++++++ tests/test_packaging.cc | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/packaging.cc b/src/packaging.cc index 85cc407..81347d8 100644 --- a/src/packaging.cc +++ b/src/packaging.cc @@ -48,6 +48,13 @@ std::string getPackageDir(std::string const& packageName) { } std::string getPackageDirFromAddress(void const* addressInLibrary) { + // A null pointer is a caller precondition violation, not a lookup failure; + // reject it explicitly rather than relying on dladdr's unspecified behavior. + if (addressInLibrary == nullptr) { + throw LSST_EXCEPT(lsst::pex::exceptions::InvalidParameterError, + "Null address passed to getPackageDirFromAddress"); + } + // dladdr resolves an address to the shared object whose memory map contains // it, regardless of which library's code is calling dladdr. Passing an // address from the target package's own library therefore yields that diff --git a/tests/test_packaging.cc b/tests/test_packaging.cc index 56877d9..d77860f 100644 --- a/tests/test_packaging.cc +++ b/tests/test_packaging.cc @@ -46,11 +46,16 @@ BOOST_AUTO_TEST_CASE(GetPackageDirFromAddress) { // The address of a symbol defined in libcpputils resolves, via dladdr, to // the cpputils package directory -- independent of any environment variable. auto anchor = reinterpret_cast(&getPackageDirFromAddress); - std::filesystem::path cpputilsPath{getPackageDirFromAddress(anchor)}; + // A valid address must resolve without throwing. + std::filesystem::path cpputilsPath; + BOOST_REQUIRE_NO_THROW(cpputilsPath = getPackageDirFromAddress(anchor)); BOOST_CHECK(std::filesystem::is_regular_file(cpputilsPath / "tests" / "test_packaging.cc")); // It must agree with the environment-variable-based lookup for the same package. BOOST_CHECK_EQUAL(std::filesystem::canonical(cpputilsPath), std::filesystem::canonical(getPackageDir("cpputils"))); + // A null address is a precondition violation. + BOOST_CHECK_THROW(getPackageDirFromAddress(nullptr), + lsst::pex::exceptions::InvalidParameterError); } BOOST_AUTO_TEST_SUITE_END()