Skip to content

Commit

Permalink
Probe if system libstdc++ is newer than ours
Browse files Browse the repository at this point in the history
If the system libstdc++ is detected to be newer, load it.
Otherwise, load the one that we ship. This improves compatibility
with external shared libraries that the user might have on their
system.

Fixes JuliaLang#34276

Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Co-authored-by: Elliot Saba <staticfloat@gmail.com>
  • Loading branch information
3 people authored and vchuravy committed Oct 24, 2022
1 parent 0645427 commit b1874ed
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 8 deletions.
2 changes: 2 additions & 0 deletions Make.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,8 @@ BB_TRIPLET := $(subst $(SPACE),-,$(filter-out cxx%,$(filter-out libgfortran%,$(s

LIBGFORTRAN_VERSION := $(subst libgfortran,,$(filter libgfortran%,$(subst -,$(SPACE),$(BB_TRIPLET_LIBGFORTRAN))))

CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.30|GLIBCXX_3\.5\.|GLIBCXX_4\.

# This is the set of projects that BinaryBuilder dependencies are hooked up for.
# Note: we explicitly _do not_ define `CSL` here, since it requires some more
# advanced techniques to decide whether it should be installed from a BB source
Expand Down
6 changes: 4 additions & 2 deletions cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ LOADER_LDFLAGS = $(JLDFLAGS) -ffreestanding -L$(build_shlibdir) -L$(build_libdir

ifeq ($(OS),WINNT)
LOADER_CFLAGS += -municode -mconsole -nostdlib -fno-stack-check -fno-stack-protector -mno-stack-arg-probe
else ifeq ($(OS),Linux)
LOADER_CFLAGS += -DGLIBCXX_LEAST_VERSION_SYMBOL=\"$(shell echo "$(CSL_NEXT_GLIBCXX_VERSION)" | cut -d'|' -f1 | sed 's/\\//g')\"
endif

ifeq ($(OS),WINNT)
Expand Down Expand Up @@ -110,7 +112,7 @@ endif

$(build_shlibdir)/libjulia.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_OBJS) $(SRCDIR)/list_strip_symbols.h | $(build_shlibdir) $(build_libdir)
@$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(SHIPFLAGS) $(LIB_OBJS) -o $@ \
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(RPATH_LIB) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT)))
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT)))
@$(INSTALL_NAME_CMD)libjulia.$(SHLIB_EXT) $@
ifeq ($(OS), WINNT)
@# Note that if the objcopy command starts getting too long, we can use `@file` to read
Expand All @@ -120,7 +122,7 @@ endif

$(build_shlibdir)/libjulia-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_DOBJS) $(SRCDIR)/list_strip_symbols.h | $(build_shlibdir) $(build_libdir)
@$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(DEBUGFLAGS) $(LIB_DOBJS) -o $@ \
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(RPATH_LIB) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT)))
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT)))
@$(INSTALL_NAME_CMD)libjulia-debug.$(SHLIB_EXT) $@
ifeq ($(OS), WINNT)
@$(call PRINT_ANALYZE, $(OBJCOPY) $(build_libdir)/$(notdir $@).tmp.a $(STRIP_EXPORTED_FUNCS) $(build_libdir)/$(notdir $@).a && rm $(build_libdir)/$(notdir $@).tmp.a)
Expand Down
220 changes: 215 additions & 5 deletions cli/loader_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ void jl_loader_print_stderr3(const char * msg1, const char * msg2, const char *
/* Wrapper around dlopen(), with extra relative pathing thrown in*/
static void * load_library(const char * rel_path, const char * src_dir, int err) {
void * handle = NULL;

// See if a handle is already open to the basename
const char *basename = rel_path + strlen(rel_path);
while (basename-- > rel_path)
Expand Down Expand Up @@ -153,6 +152,181 @@ JL_DLLEXPORT const char * jl_get_libdir()
return lib_dir;
}

#ifdef _OS_LINUX_
#ifndef GLIBCXX_LEAST_VERSION_SYMBOL /* Should always be defined in the makefile. This appeases the linter. */
#define GLIBCXX_LEAST_VERSION_SYMBOL "GLIBCXX_a.b.c"
#endif

#include <link.h>
#include <sys/wait.h>

static void write_wrapper(int fd, 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("Error in write wrapper:\nwrite");
_exit(1);
}
len -= bytes_written;
written_sofar += bytes_written;
}
}

// Where the buf passed in was malloced
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) {
char err_str[] = "Error in read wrapper:\nmalloc() returned NULL.\n";
size_t err_strlen = strlen(err_str);
write_wrapper(STDERR_FILENO, err_str, err_strlen);
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);
have_read += n;
if (n == 0) break;
if (n == -1 && errno != EINTR) {
perror("Error in read wrapper:\nread");
exit(1);
}
if (n == -1 && errno == EINTR) continue;
if (have_read == len) {
buf = (char *)realloc(buf, 1 + (len *= 2));
if (!buf) {
char err_str[] = "Error in read wrapper:\nrealloc() returned NULL.\n";
size_t err_strlen = strlen(err_str);
write_wrapper(STDERR_FILENO, err_str, err_strlen);
exit(1);
}
}
}

*ret = buf;
*ret_len = have_read;
}

// On Linux, it can happen that the system has a newer libstdc++ than the one we ship,
// which can break loading of some system libraries: <https://github.com/JuliaLang/julia/issues/34276>.

// 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)
{
// Create the pipe and child process.
int fork_pipe[2];
int ret = pipe(fork_pipe);
if (ret == -1) {
perror("Error during libstdcxxprobe:\npipe");
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror("Error during libstdcxxprobe:\nfork");
exit(1);
}
if (pid == (pid_t) 0) { // Child process.
ret = close(fork_pipe[0]);
if (ret == -1) {
perror("Error during libstdcxxprobe in child process:\nclose");
_exit(1);
}

// Open the first available libstdc++.so.
// If it can't be found, report so by exiting zero.
void *handle = dlopen("libstdc++.so.6", 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);
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[2048];
int err_len = snprintf(errbuf, 2048, "Error during libstdcxxprobe in child process:\ndlinfo() - %s\n", dlerror());
write_wrapper(STDOUT_FILENO, errbuf, (size_t)err_len);
_exit(1);
}
char *libpath = lm->l_name;
write_wrapper(fork_pipe[1], libpath, strlen(libpath));
_exit(0);
}
else { // Parent process.
ret = close(fork_pipe[1]);
if (ret == -1) {
perror("Error during libstdcxxprobe in parent process:\nclose");
_exit(1);
}

// 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 != EINTR) {
perror("Error during libstdcxxprobe in parent process:\nwaitpid");
exit(1);
}
}
else if (!WIFEXITED(wstatus)) {
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;
}

// 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
ret = close(fork_pipe[0]);
if (ret == -1) {
perror("Error during libstdcxxprobe in parent process:\nclose");
_exit(1);
}

if (!pathlen) {
free(path);
return NULL;
}
return path;
}
}
#endif

void * libjulia_internal = NULL;
__attribute__((constructor)) void jl_load_libjulia_internal(void) {
// Only initialize this once
Expand All @@ -161,11 +335,46 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
}

// Introspect to find our own path
const char * lib_dir = jl_get_libdir();
const char *lib_dir = jl_get_libdir();

// Pre-load libraries that libjulia-internal needs.
int deps_len = strlen(dep_libs);
char * curr_dep = &dep_libs[0];
char *curr_dep = &dep_libs[0];

void *cxx_handle;
int done_probe = 0;
#if defined(_OS_LINUX_)
int do_probe = 1;
char *probevar = getenv("JULIA_PROBE_LIBSTDCXX");
if (probevar) {
if (strcmp(probevar, "1") == 0 || strcmp(probevar, "yes"))
do_probe = 1;
else if (strcmp(probevar, "0") == 0 || strcmp(probevar, "no"))
do_probe = 0;
}
if (do_probe) {
char *cxxpath = libstdcxxprobe();
if (cxxpath) {
cxx_handle = dlopen(cxxpath, RTLD_LAZY);
char *dlr = dlerror();
if (dlr) {
size_t buflen = strlen(cxxpath) + 2048;
char *errbuf = malloc(buflen);
int err_len = snprintf(errbuf, buflen, "Error, cannot load \"%s\"\n"
"dlinfo() - %s\n", cxxpath, dlr);
write_wrapper(STDOUT_FILENO, errbuf, (size_t)err_len);
free(errbuf);
exit(1);
}
free(cxxpath);
done_probe = 1;
}
}
#endif
// If not on linux, or the probe does not finish successfully, load the bundled version.
if (!done_probe) {
load_library("libstdc++.so", jl_get_libdir(), 1);
}

// We keep track of "special" libraries names (ones whose name is prefixed with `@`)
// which are libraries that we want to load in some special, custom way, such as
Expand All @@ -189,7 +398,8 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
}
special_library_names[special_idx] = curr_dep + 1;
special_idx += 1;
} else {
}
else {
load_library(curr_dep, lib_dir, 1);
}

Expand Down Expand Up @@ -278,7 +488,7 @@ JL_DLLEXPORT int jl_load_repl(int argc, char * argv[]) {
}

#ifdef _OS_WINDOWS_
int __stdcall DllMainCRTStartup(void* instance, unsigned reason, void* reserved) {
int __stdcall DllMainCRTStartup(void *instance, unsigned reason, void *reserved) {
setup_stdio();

// Because we override DllMainCRTStartup, we have to manually call our constructor methods
Expand Down
1 change: 0 additions & 1 deletion deps/csl.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ endef
# would get from CSL (by searching for a `GLIBCXX_3.4.X` symbol that does not exist
# in our CSL, but would in a newer one), and default to `USE_BINARYBUILDER_CSL=0` in
# this case.
CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.30|GLIBCXX_3\.5\.|GLIBCXX_4\.

# First, check to see if BB is disabled on a global setting
ifeq ($(USE_BINARYBUILDER),0)
Expand Down

0 comments on commit b1874ed

Please sign in to comment.