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