https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/191098
>From e1d2be980504e7189aae1d03babd0c35b21b2d1e Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Wed, 8 Apr 2026 10:47:23 -0400
Subject: [PATCH] [compiler-rt] Expose shared DSO helpers for compiler-rt
runtimes
The motivation of this PR is to refactor and expose DSO helper functions so
they can be used by all compiler-rt libraries, including the profile library,
without duplicating dlopen/dlsym (non-Windows) or LoadLibrary/GetProcAddress
(Windows) logic in each runtime.
Implement the helpers in namespace __interception in interception_linux.cpp for
non-Windows targets and interception_win.cpp for Windows, and use them from the
existing Linux interception path for RTLD_NEXT/RTLD_DEFAULT/dlvsym lookups.
This is NFC for existing libraries that already use interception's public APIs;
sanitizer and interception lit behavior is unchanged.
---
compiler-rt/lib/interception/interception.h | 10 ++++
.../lib/interception/interception_linux.cpp | 59 +++++++++++++++++--
.../lib/interception/interception_win.cpp | 24 ++++++++
.../tests/interception_linux_test.cpp | 11 ++++
4 files changed, 99 insertions(+), 5 deletions(-)
diff --git a/compiler-rt/lib/interception/interception.h b/compiler-rt/lib/interception/interception.h
index 9fe7d3db308bf..71c59ed36b4ad 100644
--- a/compiler-rt/lib/interception/interception.h
+++ b/compiler-rt/lib/interception/interception.h
@@ -362,6 +362,16 @@ const interpose_substitution substitution_##func_name[] \
// so we use casts via uintptr_t (the local __sanitizer::uptr equivalent).
namespace __interception {
+// Dynamic library loading helpers (dlopen/LoadLibrary, dlsym/GetProcAddress).
+// Implemented in interception_linux.cpp on non-Windows targets and
+// interception_win.cpp on Windows.
+bool DynamicLoaderAvailable();
+void* OpenLibrary(const char* name);
+void* LookupSymbol(void* handle, const char* symbol);
+void* LookupSymbolDefault(const char* symbol);
+void* LookupSymbolNext(const char* symbol);
+void* LookupSymbolNextVersioned(const char* symbol, const char* version);
+
#if defined(__ELF__) && !SANITIZER_FUCHSIA
// The use of interceptors makes many sanitizers unusable for static linking.
// Define a function, if called, will cause a linker error (undefined _DYNAMIC).
diff --git a/compiler-rt/lib/interception/interception_linux.cpp b/compiler-rt/lib/interception/interception_linux.cpp
index f900ae6a593bd..9b9796f7cac47 100644
--- a/compiler-rt/lib/interception/interception_linux.cpp
+++ b/compiler-rt/lib/interception/interception_linux.cpp
@@ -9,15 +9,64 @@
// This file is a part of AddressSanitizer, an address sanity checker.
//
// Linux-specific interception methods.
+//
+// POSIX dynamic-library helpers (dlopen / dlsym) live here for every
+// non-Windows interception target (Linux, *BSD, Darwin, AIX, Fuchsia, …).
+// macOS/AIX/Fuchsia compile this TU for RTInterception but do not use the
+// Linux-specific InterceptFunction helpers below.
//===----------------------------------------------------------------------===//
#include "interception.h"
+#if !SANITIZER_WINDOWS
+
+# include <dlfcn.h>
+
+# pragma weak dlopen
+# pragma weak dlsym
+# pragma weak dlvsym
+
+namespace __interception {
+
+bool DynamicLoaderAvailable() { return dlopen != nullptr && dlsym != nullptr; }
+
+void* OpenLibrary(const char* name) {
+ if (!DynamicLoaderAvailable())
+ return nullptr;
+ return dlopen(name, RTLD_LAZY | RTLD_LOCAL);
+}
+
+void* LookupSymbol(void* handle, const char* symbol) {
+ if (!DynamicLoaderAvailable())
+ return nullptr;
+ return dlsym(handle, symbol);
+}
+
+void* LookupSymbolDefault(const char* symbol) {
+ if (!DynamicLoaderAvailable())
+ return nullptr;
+ return dlsym(RTLD_DEFAULT, symbol);
+}
+
+void* LookupSymbolNext(const char* symbol) {
+ if (!DynamicLoaderAvailable())
+ return nullptr;
+ return dlsym(RTLD_NEXT, symbol);
+}
+
+void* LookupSymbolNextVersioned(const char* symbol, const char* version) {
+ if (!DynamicLoaderAvailable() || dlvsym == nullptr)
+ return nullptr;
+ return dlvsym(RTLD_NEXT, symbol, version);
+}
+
+} // namespace __interception
+
+#endif // !SANITIZER_WINDOWS
+
#if SANITIZER_LINUX || SANITIZER_FREEBSD || SANITIZER_NETBSD || \
SANITIZER_SOLARIS || SANITIZER_HAIKU
-#include <dlfcn.h> // for dlsym() and dlvsym()
-
namespace __interception {
#if SANITIZER_NETBSD
@@ -39,14 +88,14 @@ static void *GetFuncAddr(const char *name, uptr trampoline) {
if (StrCmp(name, "sigaction"))
name = "__sigaction14";
#endif
- void *addr = dlsym(RTLD_NEXT, name);
+ void* addr = LookupSymbolNext(name);
if (!addr) {
// If the lookup using RTLD_NEXT failed, the sanitizer runtime library is
// later in the library search order than the DSO that we are trying to
// intercept, which means that we cannot intercept this function. We still
// want the address of the real definition, though, so look it up using
// RTLD_DEFAULT.
- addr = dlsym(RTLD_DEFAULT, name);
+ addr = LookupSymbolDefault(name);
// In case `name' is not loaded, dlsym ends up finding the actual wrapper.
// We don't want to intercept the wrapper and have it point to itself.
@@ -66,7 +115,7 @@ bool InterceptFunction(const char *name, uptr *ptr_to_real, uptr func,
// dlvsym is a GNU extension supported by some other platforms.
#if SANITIZER_GLIBC || SANITIZER_FREEBSD || SANITIZER_NETBSD
static void *GetFuncAddr(const char *name, const char *ver) {
- return dlvsym(RTLD_NEXT, name, ver);
+ return LookupSymbolNextVersioned(name, ver);
}
bool InterceptFunction(const char *name, const char *ver, uptr *ptr_to_real,
diff --git a/compiler-rt/lib/interception/interception_win.cpp b/compiler-rt/lib/interception/interception_win.cpp
index 856872425117a..8b95c5b90d12a 100644
--- a/compiler-rt/lib/interception/interception_win.cpp
+++ b/compiler-rt/lib/interception/interception_win.cpp
@@ -134,6 +134,30 @@
namespace __interception {
+bool DynamicLoaderAvailable() { return true; }
+
+void* OpenLibrary(const char* name) {
+ if (!name)
+ return reinterpret_cast<void*>(GetModuleHandleA(nullptr));
+ return reinterpret_cast<void*>(LoadLibraryA(name));
+}
+
+void* LookupSymbol(void* handle, const char* symbol) {
+ if (!handle)
+ return nullptr;
+ return reinterpret_cast<void*>(reinterpret_cast<__sanitizer::uptr>(
+ GetProcAddress(reinterpret_cast<HMODULE>(handle), symbol)));
+}
+
+void* LookupSymbolDefault(const char* symbol) {
+ return LookupSymbol(reinterpret_cast<void*>(GetModuleHandleA(nullptr)),
+ symbol);
+}
+
+void* LookupSymbolNext(const char*) { return nullptr; }
+
+void* LookupSymbolNextVersioned(const char*, const char*) { return nullptr; }
+
static const int kAddressLength = FIRST_32_SECOND_64(4, 8);
static const int kJumpInstructionLength = 5;
static const int kShortJumpInstructionLength = 2;
diff --git a/compiler-rt/lib/interception/tests/interception_linux_test.cpp b/compiler-rt/lib/interception/tests/interception_linux_test.cpp
index c204274fb3297..0b61f499ba255 100644
--- a/compiler-rt/lib/interception/tests/interception_linux_test.cpp
+++ b/compiler-rt/lib/interception/tests/interception_linux_test.cpp
@@ -93,6 +93,17 @@ TEST(Interception, Basic) {
EXPECT_EQ(0, isdigit_called);
}
+TEST(Interception, DsoHelpers) {
+ EXPECT_TRUE(DynamicLoaderAvailable());
+
+ void* self = OpenLibrary(nullptr);
+ EXPECT_NE(nullptr, self);
+ EXPECT_NE(nullptr, LookupSymbol(self, "malloc"));
+ EXPECT_NE(nullptr, LookupSymbolDefault("malloc"));
+ EXPECT_NE(nullptr, LookupSymbolNext("malloc"));
+ EXPECT_EQ(nullptr, LookupSymbol(self, "symbol_that_does_not_exist__"));
+}
+
TEST(Interception, ForeignOverrideDirect) {
// Actual interceptor is overridden.
EXPECT_FALSE(INTERCEPT_FUNCTION(isalpha));