diff --git a/cli/Makefile b/cli/Makefile
index bc24c9a69f012..4602f759a4768 100644
--- a/cli/Makefile
+++ b/cli/Makefile
@@ -5,7 +5,7 @@ include $(JULIAHOME)/Make.inc
include $(JULIAHOME)/deps/llvm-ver.make
-HEADERS := $(addprefix $(SRCDIR)/,jl_exports.h loader.h) $(addprefix $(JULIAHOME)/src/,julia_fasttls.h support/platform.h support/dirpath.h jl_exported_data.inc jl_exported_funcs.inc)
+HEADERS := $(addprefix $(SRCDIR)/,jl_exports.h loader.h dl-cache.h) $(addprefix $(JULIAHOME)/src/,julia_fasttls.h support/platform.h support/dirpath.h jl_exported_data.inc jl_exported_funcs.inc)
LOADER_CFLAGS = $(JCFLAGS) -I$(BUILDROOT)/src -I$(JULIAHOME)/src -I$(JULIAHOME)/src/support -I$(build_includedir) -ffreestanding
LOADER_LDFLAGS = $(JLDFLAGS) -ffreestanding -L$(build_shlibdir) -L$(build_libdir)
@@ -46,8 +46,8 @@ endif # USE_RT_STATIC_LIBSTDCXX
EXE_OBJS := $(BUILDDIR)/loader_exe.o
EXE_DOBJS := $(BUILDDIR)/loader_exe.dbg.obj
-LIB_OBJS := $(BUILDDIR)/loader_lib.o
-LIB_DOBJS := $(BUILDDIR)/loader_lib.dbg.obj
+LIB_OBJS := $(BUILDDIR)/loader_lib.o $(BUILDDIR)/loader_symbol_probe.o $(BUILDDIR)/loader_library_probe.o
+LIB_DOBJS := $(BUILDDIR)/loader_lib.dbg.obj $(BUILDDIR)/loader_symbol_probe.dbg.obj $(BUILDDIR)/loader_library_probe.dbg.obj
# If this is an architecture that supports dynamic linking, link in a trampoline definition
ifneq (,$(wildcard $(SRCDIR)/trampolines/trampolines_$(ARCH).S))
@@ -71,6 +71,14 @@ $(BUILDDIR)/loader_trampolines.o : $(SRCDIR)/trampolines/trampolines_$(ARCH).S $
@$(call PRINT_CC, $(CC) $(SHIPFLAGS) $(LOADER_CFLAGS) $< -c -o $@)
$(BUILDDIR)/loader_trampolines.dbg.obj : $(SRCDIR)/trampolines/trampolines_$(ARCH).S $(HEADERS) $(SRCDIR)/trampolines/common.h
@$(call PRINT_CC, $(CC) $(DEBUGFLAGS) $(LOADER_CFLAGS) $< -c -o $@)
+$(BUILDDIR)/loader_library_probe.o : $(SRCDIR)/loader_library_probe.c $(HEADERS) $(JULIAHOME)/VERSION
+ @$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(SHIPFLAGS) $(LOADER_CFLAGS) -c $< -o $@)
+$(BUILDDIR)/loader_library_probe.dbg.obj : $(SRCDIR)/loader_library_probe.c $(HEADERS) $(JULIAHOME)/VERSION
+ @$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(DEBUGFLAGS) $(LOADER_CFLAGS) -c $< -o $@)
+$(BUILDDIR)/loader_symbol_probe.o : $(SRCDIR)/loader_symbol_probe.c $(HEADERS) $(JULIAHOME)/VERSION
+ @$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(SHIPFLAGS) $(LOADER_CFLAGS) -c $< -o $@)
+$(BUILDDIR)/loader_symbol_probe.dbg.obj : $(SRCDIR)/loader_symbol_probe.c $(HEADERS) $(JULIAHOME)/VERSION
+ @$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(DEBUGFLAGS) $(LOADER_CFLAGS) -c $< -o $@)
# Debugging target to help us see what kind of code is being generated for our trampolines
dump-trampolines: $(SRCDIR)/trampolines/trampolines_$(ARCH).S
diff --git a/cli/dl-cache.h b/cli/dl-cache.h
new file mode 100644
index 0000000000000..311b2c1e8ade1
--- /dev/null
+++ b/cli/dl-cache.h
@@ -0,0 +1,184 @@
+/* Support for reading /etc/ld.so.cache files written by Linux ldconfig.
+ Copyright (C) 1999-2019 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ . */
+
+#include
+
+#define FLAG_ANY -1
+#define FLAG_TYPE_MASK 0x00ff
+#define FLAG_LIBC4 0x0000
+#define FLAG_ELF 0x0001
+#define FLAG_ELF_LIBC5 0x0002
+#define FLAG_ELF_LIBC6 0x0003
+#define FLAG_REQUIRED_MASK 0xff00
+#define FLAG_SPARC_LIB64 0x0100
+#define FLAG_IA64_LIB64 0x0200
+#define FLAG_X8664_LIB64 0x0300
+#define FLAG_S390_LIB64 0x0400
+#define FLAG_POWERPC_LIB64 0x0500
+#define FLAG_MIPS64_LIBN32 0x0600
+#define FLAG_MIPS64_LIBN64 0x0700
+#define FLAG_X8664_LIBX32 0x0800
+#define FLAG_ARM_LIBHF 0x0900
+#define FLAG_AARCH64_LIB64 0x0a00
+#define FLAG_ARM_LIBSF 0x0b00
+#define FLAG_MIPS_LIB32_NAN2008 0x0c00
+#define FLAG_MIPS64_LIBN32_NAN2008 0x0d00
+#define FLAG_MIPS64_LIBN64_NAN2008 0x0e00
+#define FLAG_RISCV_FLOAT_ABI_SOFT 0x0f00
+#define FLAG_RISCV_FLOAT_ABI_DOUBLE 0x1000
+
+#if defined(_CPU_X86_64_)
+
+#define _DL_CACHE_DEFAULT_ID 0x303
+#define _dl_cache_check_flags(flags) ((flags) == _DL_CACHE_DEFAULT_ID)
+
+#elif defined(_CPU_AARCH64_)
+
+#ifdef __LP64__
+# define _DL_CACHE_DEFAULT_ID (FLAG_AARCH64_LIB64 | FLAG_ELF_LIBC6)
+#else
+# define _DL_CACHE_DEFAULT_ID (FLAG_AARCH64_LIB32 | FLAG_ELF_LIBC6)
+#endif
+
+#define _dl_cache_check_flags(flags) ((flags) == _DL_CACHE_DEFAULT_ID)
+
+#elif defined(_CPU_RISCV64_)
+
+/* For now we only support the natural XLEN ABI length on all targets, so the
+ only bits that need to go into ld.so.cache are the FLEG ABI length. */
+#if defined __riscv_float_abi_double
+# define _DL_CACHE_DEFAULT_ID (FLAG_RISCV_FLOAT_ABI_DOUBLE | FLAG_ELF_LIBC6)
+#else
+# define _DL_CACHE_DEFAULT_ID (FLAG_RISCV_FLOAT_ABI_SOFT | FLAG_ELF_LIBC6)
+#endif
+
+#define _dl_cache_check_flags(flags) ((flags) == _DL_CACHE_DEFAULT_ID)
+
+#elif defined(_CPU_ARM_)
+
+/* In order to support the transition from unmarked objects
+ to marked objects we must treat unmarked objects as
+ compatible with either FLAG_ARM_LIBHF or FLAG_ARM_LIBSF. */
+#ifdef __ARM_PCS_VFP
+# define _dl_cache_check_flags(flags) \
+ ((flags) == (FLAG_ARM_LIBHF | FLAG_ELF_LIBC6) \
+ || (flags) == FLAG_ELF_LIBC6)
+#else
+# define _dl_cache_check_flags(flags) \
+ ((flags) == (FLAG_ARM_LIBSF | FLAG_ELF_LIBC6) \
+ || (flags) == FLAG_ELF_LIBC6)
+#endif
+
+#elif defined(_CPU_X86_)
+
+/* Defined as (FLAG_ELF_LIBC6 | FLAG_X8664_LIBX32). */
+#undef _DL_CACHE_DEFAULT_ID
+#define _DL_CACHE_DEFAULT_ID 0x803
+
+#elif defined(_CPU_PPC64_)
+
+#define _DL_CACHE_DEFAULT_ID 0x503
+
+#define _dl_cache_check_flags(flags) \
+ ((flags) == _DL_CACHE_DEFAULT_ID)
+
+#else
+
+#error "Missing CPU arch-specific definitions in dl-cache.h"
+
+#endif
+
+#ifndef _DL_CACHE_DEFAULT_ID
+# define _DL_CACHE_DEFAULT_ID 3
+#endif
+
+#ifndef _dl_cache_check_flags
+# define _dl_cache_check_flags(flags) \
+ ((flags) == 1 || (flags) == _DL_CACHE_DEFAULT_ID)
+#endif
+
+#ifndef LD_SO_CACHE
+# define LD_SO_CACHE SYSCONFDIR "/ld.so.cache"
+#endif
+
+#define CACHEMAGIC "ld.so-1.7.0"
+
+/* libc5 and glibc 2.0/2.1 use the same format. For glibc 2.2 another
+ format has been added in a compatible way:
+ The beginning of the string table is used for the new table:
+ old_magic
+ nlibs
+ libs[0]
+ ...
+ libs[nlibs-1]
+ pad, new magic needs to be aligned
+ - this is string[0] for the old format
+ new magic - this is string[0] for the new format
+ newnlibs
+ ...
+ newlibs[0]
+ ...
+ newlibs[newnlibs-1]
+ string 1
+ string 2
+ ...
+*/
+struct file_entry
+{
+ int flags; /* This is 1 for an ELF library. */
+ unsigned int key, value; /* String table indices. */
+};
+
+struct cache_file
+{
+ char magic[sizeof CACHEMAGIC - 1];
+ unsigned int nlibs;
+ struct file_entry libs[0];
+};
+
+#define CACHEMAGIC_NEW "glibc-ld.so.cache"
+#define CACHE_VERSION "1.1"
+#define CACHEMAGIC_VERSION_NEW CACHEMAGIC_NEW CACHE_VERSION
+
+
+struct file_entry_new
+{
+ int32_t flags; /* This is 1 for an ELF library. */
+ uint32_t key, value; /* String table indices. */
+ uint32_t osversion; /* Required OS version. */
+ uint64_t hwcap; /* Hwcap entry. */
+};
+
+struct cache_file_new
+{
+ char magic[sizeof CACHEMAGIC_NEW - 1];
+ char version[sizeof CACHE_VERSION - 1];
+ uint32_t nlibs; /* Number of entries. */
+ uint32_t len_strings; /* Size of string table. */
+ uint32_t unused[5]; /* Leave space for future extensions
+ and align to 8 byte boundary. */
+ struct file_entry_new libs[0]; /* Entries describing libraries. */
+ /* After this the string table of size len_strings is found. */
+};
+
+/* Used to align cache_file_new. */
+#define ALIGN_CACHE(addr) \
+(((addr) + __alignof__ (struct cache_file_new) -1) \
+ & (~(__alignof__ (struct cache_file_new) - 1)))
+
+// extern int _dl_cache_libcmp (const char *p1, const char *p2) attribute_hidden;
diff --git a/cli/loader.h b/cli/loader.h
index 310226c84f815..62e4f3d3e4b52 100644
--- a/cli/loader.h
+++ b/cli/loader.h
@@ -70,7 +70,10 @@
JL_DLLEXPORT extern int jl_load_repl(int, char **);
JL_DLLEXPORT void jl_loader_print_stderr(const char * msg);
void jl_loader_print_stderr3(const char * msg1, const char * msg2, const char * msg3);
+void *jl_loader_open_via_mmap(const char *filepath, size_t *size);
static void * lookup_symbol(const void * lib_handle, const char * symbol_name);
+const char *jl_loader_probe_system_library(const char *libname, const char *symbol);
+int jl_loader_locate_symbol(const char *library, const char *symbol);
#ifdef _OS_WINDOWS_
LPWSTR *CommandLineToArgv(LPWSTR lpCmdLine, int *pNumArgs);
diff --git a/cli/loader_lib.c b/cli/loader_lib.c
index 4a8ffaf95d92e..40565e3d575c9 100644
--- a/cli/loader_lib.c
+++ b/cli/loader_lib.c
@@ -222,166 +222,13 @@ JL_DLLEXPORT const char * jl_get_libdir()
#include
#include
-// write(), but handle errors and avoid EINTR
-static void write_wrapper(int fd, const char *str, size_t len)
-{
- size_t written_sofar = 0;
- while (len) {
- ssize_t bytes_written = write(fd, str + written_sofar, len);
- if (bytes_written == -1 && errno == EINTR) continue;
- if (bytes_written == -1 && errno != EINTR) {
- perror("(julia) child libstdcxxprobe write");
- _exit(1);
- }
- len -= bytes_written;
- written_sofar += bytes_written;
- }
-}
-
-// read(), but handle errors and avoid EINTR
-static void read_wrapper(int fd, char **ret, size_t *ret_len)
-{
- // Allocate an initial buffer
- size_t len = JL_PATH_MAX;
- char *buf = (char *)malloc(len + 1);
- if (!buf) {
- perror("(julia) malloc");
- exit(1);
- }
-
- // Read into it, reallocating as necessary
- size_t have_read = 0;
- while (1) {
- ssize_t n = read(fd, buf + have_read, len - have_read);
- if (n == 0) break;
- if (n == -1 && errno != EINTR) {
- perror("(julia) libstdcxxprobe read");
- exit(1);
- }
- if (n == -1 && errno == EINTR) continue;
- have_read += n;
- if (have_read == len) {
- buf = (char *)realloc(buf, 1 + (len *= 2));
- if (!buf) {
- perror("(julia) realloc");
- exit(1);
- }
- }
- }
-
- *ret = buf;
- *ret_len = have_read;
-}
-
// Return the path to the libstdcxx to load.
// If the path is found, return it.
// Otherwise, print the error and exit.
// The path returned must be freed.
-static char *libstdcxxprobe(void)
+static const char *libstdcxxprobe(void)
{
- // Create the pipe and child process.
- int fork_pipe[2];
- int ret = pipe(fork_pipe);
- if (ret == -1) {
- perror("(julia) Error during libstdcxxprobe: pipe");
- exit(1);
- }
- pid_t pid = fork();
- if (pid == -1) {
- perror("Error during libstdcxxprobe:\nfork");
- exit(1);
- }
- if (pid == (pid_t) 0) { // Child process.
- close(fork_pipe[0]);
-
- // Open the first available libstdc++.so.
- // If it can't be found, report so by exiting zero.
- // The star is there to prevent the compiler from merging constants
- // with "\0*libstdc++.so.6", which we string replace inside the .so during
- // make install.
- void *handle = dlopen("libstdc++.so.6\0*", RTLD_LAZY);
- if (!handle) {
- _exit(0);
- }
-
- // See if the version is compatible
- char *dlerr = dlerror(); // clear out dlerror
- void *sym = dlsym(handle, GLIBCXX_LEAST_VERSION_SYMBOL);
- (void)sym;
- dlerr = dlerror();
- if (dlerr) {
- // We can't use the library that was found, so don't write anything.
- // The main process will see that nothing was written,
- // then exit the function and return null.
- _exit(0);
- }
-
- // No error means the symbol was found, we can use this library.
- // Get the path to it, and write it to the parent process.
- struct link_map *lm;
- ret = dlinfo(handle, RTLD_DI_LINKMAP, &lm);
- if (ret == -1) {
- char *errbuf = dlerror();
- char *errdesc = (char*)"Error during libstdcxxprobe in child process:\ndlinfo: ";
- write_wrapper(STDERR_FILENO, errdesc, strlen(errdesc));
- write_wrapper(STDERR_FILENO, errbuf, strlen(errbuf));
- write_wrapper(STDERR_FILENO, "\n", 1);
- _exit(1);
- }
- char *libpath = lm->l_name;
- write_wrapper(fork_pipe[1], libpath, strlen(libpath));
- _exit(0);
- }
- else { // Parent process.
- close(fork_pipe[1]);
-
- // Read the absolute path to the lib from the child process.
- char *path;
- size_t pathlen;
- read_wrapper(fork_pipe[0], &path, &pathlen);
-
- // Close the read end of the pipe
- close(fork_pipe[0]);
-
- // Wait for the child to complete.
- while (1) {
- int wstatus;
- pid_t npid = waitpid(pid, &wstatus, 0);
- if (npid == -1) {
- if (errno == EINTR) continue;
- if (errno == ECHILD) {
- // SIGCHLD is set to SIG_IGN or has flag SA_NOCLDWAIT, so the child
- // did not become a zombie and wait for `waitpid` - it just exited.
- //
- // Assume that it exited successfully and use whatever libpath we
- // got out of the pipe, if any.
- break;
- }
- perror("Error during libstdcxxprobe in parent process:\nwaitpid");
- exit(1);
- }
- else if (!WIFEXITED(wstatus)) {
- const char *err_str = "Error during libstdcxxprobe in parent process:\n"
- "The child process did not exit normally.\n";
- size_t err_strlen = strlen(err_str);
- write_wrapper(STDERR_FILENO, err_str, err_strlen);
- exit(1);
- }
- else if (WEXITSTATUS(wstatus)) {
- // The child has printed an error and exited, so the parent should exit too.
- exit(1);
- }
- break;
- }
-
- if (!pathlen) {
- free(path);
- return NULL;
- }
- // Ensure that `path` is zero-terminated.
- path[pathlen] = '\0';
- return path;
- }
+ return jl_loader_probe_system_library("libstdc++.so.6", GLIBCXX_LEAST_VERSION_SYMBOL);
}
#endif
@@ -480,7 +327,7 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
do_probe = 0;
}
if (do_probe) {
- char *cxxpath = libstdcxxprobe();
+ const char *cxxpath = libstdcxxprobe();
if (cxxpath) {
void *cxx_handle = dlopen(cxxpath, RTLD_LAZY);
(void)cxx_handle;
@@ -490,7 +337,7 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
jl_loader_print_stderr3("Message: ", dlr, "\n");
exit(1);
}
- free(cxxpath);
+ free((void *)cxxpath);
probe_successful = 1;
}
}
diff --git a/cli/loader_library_probe.c b/cli/loader_library_probe.c
new file mode 100644
index 0000000000000..97946c799023c
--- /dev/null
+++ b/cli/loader_library_probe.c
@@ -0,0 +1,182 @@
+#include "../src/support/platform.h"
+#include "loader.h"
+
+#ifdef _OS_LINUX_
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "dl-cache.h"
+
+void *jl_loader_open_via_mmap(const char *filepath, size_t *size)
+{
+ int fd;
+ while (1) {
+ fd = open(filepath, O_CLOEXEC | O_RDONLY);
+ if (fd >= 0) {
+ break;
+ } else if (errno != EINTR) {
+ return NULL;
+ }
+ }
+
+ struct stat info;
+ while (1) {
+ int err = fstat(fd, &info);
+ if (err >= 0) {
+ break;
+ } else if (errno != EINTR) {
+ close(fd);
+ return NULL;
+ }
+ }
+
+ void *buffer = mmap(
+ NULL, info.st_size, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, /* offset */ 0
+ );
+ close(fd);
+
+ if (MAP_FAILED == buffer)
+ return NULL;
+
+ *size = info.st_size;
+ return buffer;
+}
+
+static const char *search_ldcache(struct cache_file_new *cache, const char *libname, size_t *index)
+{
+ if (strncmp(cache->magic, CACHEMAGIC_NEW, sizeof(CACHEMAGIC_NEW) - 1) != 0)
+ return NULL;
+
+ for (; *index < cache->nlibs; (*index)++) {
+ struct file_entry_new *lib = &cache->libs[*index];
+
+ const char *strtab = (const char *)cache;
+ const char *key = &strtab[lib->key];
+ const char *value = &strtab[lib->value];
+
+ if (strcmp(key, libname) != 0)
+ continue;
+
+ (*index)++;
+ return value;
+ }
+
+ return NULL;
+}
+
+const char *ldcache_dirs[] = {
+ "/etc/ld.so.cache",
+};
+
+const char *default_libdirs[] = {
+ "/lib/",
+ "/usr/lib/",
+ "/lib64/",
+ "/usr/lib64/",
+};
+
+/**
+ * Search for a system library with the filename `libname` containing `symbol`.
+ * Return NULL if no matching library could be found.
+ *
+ * To emulate the Linux dynamic linker search behavior, this function scans for
+ * system libraries in:
+ * 1. LD_LIBRARY_PATH
+ * 2. `/etc/ld.so.cache`
+ * 3. "default" system libdirs (/lib, /usr/lib, etc.)
+ *
+ * This function does not consider any DT_RPATH or DT_RUNPATH entries.
+ * (see `ld.so(8)` manpage)
+ **/
+const char *jl_loader_probe_system_library(const char *libname, const char *symbol)
+{
+ char buf[PATH_MAX];
+
+ // Make a best-effort attempt to emulate the linker's use of LD_LIBRARY_PATH
+ char *LD_LIBRARY_PATH = getenv("LD_LIBRARY_PATH");
+ if (LD_LIBRARY_PATH != NULL) {
+ LD_LIBRARY_PATH = strdup(LD_LIBRARY_PATH);
+ char *path = LD_LIBRARY_PATH;
+
+ int last = 0;
+ while (!last) {
+ // walk to next ':' or '\0'
+ char *ch = path;
+ while (1) {
+ if (*ch == '\0')
+ last = 1;
+ if (*ch == ':' || *ch == '\0')
+ break;
+ ch += 1;
+ }
+ *ch = '\0';
+
+ if (ch == path) {
+ path += 1;
+ continue;
+ }
+
+ int bytes = snprintf(buf, sizeof(buf), (ch[-1] == '/' ? "%s%s" : "%s/%s"), path, libname);
+ path = ch + 1;
+ if (bytes < 0 || sizeof(buf) < (size_t) bytes)
+ continue;
+
+ if (jl_loader_locate_symbol(buf, symbol)) {
+ free(LD_LIBRARY_PATH);
+ return strdup(buf);
+ }
+ }
+ free(LD_LIBRARY_PATH);
+ }
+
+ // Check the ld.so.cache for the library. Assuming we can find the cache,
+ // this is by far our best chance to locate the lib successfully.
+ size_t npaths = sizeof(ldcache_dirs) / sizeof(const char *);
+ for (size_t i = 0; i < npaths; i++) {
+ size_t sz;
+ struct cache_file_new *cache =
+ (struct cache_file_new *)jl_loader_open_via_mmap(ldcache_dirs[i], &sz);
+
+ if (cache == NULL)
+ continue; // ld.so.cache was not found (could be NixOS)
+
+ size_t iter = 0;
+ const char *library;
+ while ((library = search_ldcache(cache, libname, &iter)) != NULL) {
+ if (jl_loader_locate_symbol(library, symbol)) {
+ library = strdup(library);
+ munmap((void *)cache, sz);
+ return library;
+ }
+ }
+
+ munmap((void *)cache, sz);
+ }
+
+ // As a last-ditch effort, try to emulate / search the "default" libdirs used
+ // by the GLIBC dynamic linker.
+ size_t ndirs = sizeof(default_libdirs) / sizeof(const char *);
+ for (size_t i = 0; i < ndirs; i++) {
+ int bytes = snprintf(buf, sizeof(buf), "%s%s", default_libdirs[i], libname);
+ if (bytes < 0 || sizeof(buf) < (size_t) bytes)
+ continue;
+ if (jl_loader_locate_symbol(buf, symbol))
+ return strdup(buf);
+ }
+
+ return NULL;
+}
+
+#endif
diff --git a/cli/loader_symbol_probe.c b/cli/loader_symbol_probe.c
new file mode 100644
index 0000000000000..de4598eb6a89e
--- /dev/null
+++ b/cli/loader_symbol_probe.c
@@ -0,0 +1,123 @@
+#include "../src/support/platform.h"
+#include "loader.h"
+
+#ifdef _OS_LINUX_
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+static Elf64_Shdr *Elf64_get_section(Elf64_Ehdr *hdr, size_t i)
+{
+ size_t section_header_sz = hdr->e_shentsize;
+ size_t byte_offset = hdr->e_shoff + i * section_header_sz;
+ return (Elf64_Shdr *)&((char *)hdr)[byte_offset];
+}
+
+static const char *Elf64_get_strtab(Elf64_Ehdr *hdr, Elf64_Shdr *section)
+{
+ return &((const char *)hdr)[section->sh_offset];
+}
+
+static Elf64_Sym *Elf64_get_symbol(Elf64_Ehdr *hdr, Elf64_Shdr *section, size_t i)
+{
+ size_t byte_offset = section->sh_offset + i * section->sh_entsize;
+ return (Elf64_Sym *)&((char *)hdr)[byte_offset];
+}
+
+static int Elf64_locate_symbol(Elf64_Ehdr *hdr, const char *symbol)
+{
+ if (hdr->e_type != ET_DYN)
+ return 0;
+
+ for (size_t sect_idx = 0; sect_idx < hdr->e_shnum; sect_idx++) {
+ Elf64_Shdr *shdr = Elf64_get_section(hdr, sect_idx);
+ if (shdr->sh_type != SHT_DYNSYM)
+ continue;
+
+ Elf64_Shdr *strtab_shdr = Elf64_get_section(hdr, shdr->sh_link);
+ const char *strtab = Elf64_get_strtab(hdr, strtab_shdr);
+
+ size_t nsymbols = shdr->sh_size / shdr->sh_entsize;
+ for (size_t i = 0; i < nsymbols; i++) {
+ Elf64_Sym *sym = Elf64_get_symbol(hdr, shdr, i);
+ const char *name = &strtab[sym->st_name];
+ if (strcmp(name, symbol) == 0)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static Elf32_Shdr *Elf32_get_section(Elf32_Ehdr *hdr, size_t i)
+{
+ size_t section_header_sz = hdr->e_shentsize;
+ size_t byte_offset = hdr->e_shoff + i * section_header_sz;
+ return (Elf32_Shdr *)&((char *)hdr)[byte_offset];
+}
+
+static const char *Elf32_get_strtab(Elf32_Ehdr *hdr, Elf32_Shdr *section)
+{
+ return &((const char *)hdr)[section->sh_offset];
+}
+
+static Elf32_Sym *Elf32_get_symbol(Elf32_Ehdr *hdr, Elf32_Shdr *section, size_t i)
+{
+ size_t byte_offset = section->sh_offset + i * section->sh_entsize;
+ return (Elf32_Sym *)&((char *)hdr)[byte_offset];
+}
+
+static int Elf32_locate_symbol(Elf32_Ehdr *hdr, const char *symbol)
+{
+ if (hdr->e_type != ET_DYN)
+ return 0;
+
+ for (size_t sect_idx = 0; sect_idx < hdr->e_shnum; sect_idx++) {
+ Elf32_Shdr *shdr = Elf32_get_section(hdr, sect_idx);
+ if (shdr->sh_type != SHT_DYNSYM)
+ continue;
+
+ Elf32_Shdr *strtab_shdr = Elf32_get_section(hdr, shdr->sh_link);
+ const char *strtab = Elf32_get_strtab(hdr, strtab_shdr);
+
+ size_t nsymbols = shdr->sh_size / shdr->sh_entsize;
+ for (size_t i = 0; i < nsymbols; i++) {
+ Elf32_Sym *sym = Elf32_get_symbol(hdr, shdr, i);
+ const char *name = &strtab[sym->st_name];
+ if (strcmp(name, symbol) == 0)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int jl_loader_locate_symbol(const char *library, const char *symbol)
+{
+ size_t library_sz;
+ void *elf_file = jl_loader_open_via_mmap(library, &library_sz);
+ if (elf_file == NULL)
+ return 0;
+
+ int found = 0;
+ const char *hdr = (const char *)elf_file;
+ if (strncmp(hdr, ELFMAG, SELFMAG) != 0)
+ goto bail;
+
+ assert(hdr[5] == ELFDATA2LSB);
+ if (hdr[4] == ELFCLASS32) {
+ found = Elf32_locate_symbol((Elf32_Ehdr *)hdr, symbol);
+ } else if (hdr[4] == ELFCLASS64) {
+ found = Elf64_locate_symbol((Elf64_Ehdr *)hdr, symbol);
+ }
+
+bail:
+ munmap(elf_file, library_sz);
+ return found;
+}
+
+#endif