Skip to content

Commit 46c5e54

Browse files
giordanovtjnashstaticfloat
authored andcommitted
Probe if system libstdc++ is newer than ours
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 <[email protected]> Co-authored-by: Elliot Saba <[email protected]>
1 parent dc3a2e8 commit 46c5e54

File tree

4 files changed

+221
-8
lines changed

4 files changed

+221
-8
lines changed

Make.inc

+2
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,8 @@ BB_TRIPLET := $(subst $(SPACE),-,$(filter-out cxx%,$(filter-out libgfortran%,$(s
11491149

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

1152+
CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.30|GLIBCXX_3\.5\.|GLIBCXX_4\.
1153+
11521154
# This is the set of projects that BinaryBuilder dependencies are hooked up for.
11531155
# Note: we explicitly _do not_ define `CSL` here, since it requires some more
11541156
# advanced techniques to decide whether it should be installed from a BB source

cli/Makefile

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ LOADER_LDFLAGS = $(JLDFLAGS) -ffreestanding -L$(build_shlibdir) -L$(build_libdir
1212

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

1719
ifeq ($(OS),WINNT)
@@ -110,7 +112,7 @@ endif
110112

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

121123
$(build_shlibdir)/libjulia-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_DOBJS) $(SRCDIR)/list_strip_symbols.h | $(build_shlibdir) $(build_libdir)
122124
@$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(DEBUGFLAGS) $(LIB_DOBJS) -o $@ \
123-
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(RPATH_LIB) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT)))
125+
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT)))
124126
@$(INSTALL_NAME_CMD)libjulia-debug.$(SHLIB_EXT) $@
125127
ifeq ($(OS), WINNT)
126128
@$(call PRINT_ANALYZE, $(OBJCOPY) $(build_libdir)/$(notdir $@).tmp.a $(STRIP_EXPORTED_FUNCS) $(build_libdir)/$(notdir $@).a && rm $(build_libdir)/$(notdir $@).tmp.a)

cli/loader_lib.c

+215-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ void jl_loader_print_stderr3(const char * msg1, const char * msg2, const char *
3333
/* Wrapper around dlopen(), with extra relative pathing thrown in*/
3434
static void * load_library(const char * rel_path, const char * src_dir, int err) {
3535
void * handle = NULL;
36-
3736
// See if a handle is already open to the basename
3837
const char *basename = rel_path + strlen(rel_path);
3938
while (basename-- > rel_path)
@@ -153,6 +152,181 @@ JL_DLLEXPORT const char * jl_get_libdir()
153152
return lib_dir;
154153
}
155154

155+
#ifdef _OS_LINUX_
156+
#ifndef GLIBCXX_LEAST_VERSION_SYMBOL /* Should always be defined in the makefile. This appeases the linter. */
157+
#define GLIBCXX_LEAST_VERSION_SYMBOL "GLIBCXX_a.b.c"
158+
#endif
159+
160+
#include <link.h>
161+
#include <sys/wait.h>
162+
163+
static void write_wrapper(int fd, char *str, size_t len)
164+
{
165+
size_t written_sofar = 0;
166+
while (len) {
167+
ssize_t bytes_written = write(fd, str + written_sofar, len);
168+
if (bytes_written == -1 && errno == EINTR) continue;
169+
if (bytes_written == -1 && errno != EINTR) {
170+
perror("Error in write wrapper:\nwrite");
171+
_exit(1);
172+
}
173+
len -= bytes_written;
174+
written_sofar += bytes_written;
175+
}
176+
}
177+
178+
// Where the buf passed in was malloced
179+
static void read_wrapper(int fd, char **ret, size_t *ret_len)
180+
{
181+
// Allocate an initial buffer
182+
size_t len = JL_PATH_MAX;
183+
char *buf = (char *)malloc(len + 1);
184+
if (!buf) {
185+
char err_str[] = "Error in read wrapper:\nmalloc() returned NULL.\n";
186+
size_t err_strlen = strlen(err_str);
187+
write_wrapper(STDERR_FILENO, err_str, err_strlen);
188+
exit(1);
189+
}
190+
191+
// Read into it, reallocating as necessary
192+
size_t have_read = 0;
193+
while (1) {
194+
ssize_t n = read(fd, buf + have_read, len - have_read);
195+
have_read += n;
196+
if (n == 0) break;
197+
if (n == -1 && errno != EINTR) {
198+
perror("Error in read wrapper:\nread");
199+
exit(1);
200+
}
201+
if (n == -1 && errno == EINTR) continue;
202+
if (have_read == len) {
203+
buf = (char *)realloc(buf, 1 + (len *= 2));
204+
if (!buf) {
205+
char err_str[] = "Error in read wrapper:\nrealloc() returned NULL.\n";
206+
size_t err_strlen = strlen(err_str);
207+
write_wrapper(STDERR_FILENO, err_str, err_strlen);
208+
exit(1);
209+
}
210+
}
211+
}
212+
213+
*ret = buf;
214+
*ret_len = have_read;
215+
}
216+
217+
// On Linux, it can happen that the system has a newer libstdc++ than the one we ship,
218+
// which can break loading of some system libraries: <https://github.com/JuliaLang/julia/issues/34276>.
219+
220+
// Return the path to the libstdcxx to load.
221+
// If the path is found, return it.
222+
// Otherwise, print the error and exit.
223+
// The path returned must be freed.
224+
static char *libstdcxxprobe(void)
225+
{
226+
// Create the pipe and child process.
227+
int fork_pipe[2];
228+
int ret = pipe(fork_pipe);
229+
if (ret == -1) {
230+
perror("Error during libstdcxxprobe:\npipe");
231+
exit(1);
232+
}
233+
pid_t pid = fork();
234+
if (pid == -1) {
235+
perror("Error during libstdcxxprobe:\nfork");
236+
exit(1);
237+
}
238+
if (pid == (pid_t) 0) { // Child process.
239+
ret = close(fork_pipe[0]);
240+
if (ret == -1) {
241+
perror("Error during libstdcxxprobe in child process:\nclose");
242+
_exit(1);
243+
}
244+
245+
// Open the first available libstdc++.so.
246+
// If it can't be found, report so by exiting zero.
247+
void *handle = dlopen("libstdc++.so.6", RTLD_LAZY);
248+
if (!handle) {
249+
_exit(0);
250+
}
251+
252+
// See if the version is compatible
253+
char *dlerr = dlerror(); // clear out dlerror
254+
void *sym = dlsym(handle, GLIBCXX_LEAST_VERSION_SYMBOL);
255+
dlerr = dlerror();
256+
if (dlerr) {
257+
// We can't use the library that was found, so don't write anything.
258+
// The main process will see that nothing was written,
259+
// then exit the function and return null.
260+
_exit(0);
261+
}
262+
263+
// No error means the symbol was found, we can use this library.
264+
// Get the path to it, and write it to the parent process.
265+
struct link_map *lm;
266+
ret = dlinfo(handle, RTLD_DI_LINKMAP, &lm);
267+
if (ret == -1) {
268+
char errbuf[2048];
269+
int err_len = snprintf(errbuf, 2048, "Error during libstdcxxprobe in child process:\ndlinfo() - %s\n", dlerror());
270+
write_wrapper(STDOUT_FILENO, errbuf, (size_t)err_len);
271+
_exit(1);
272+
}
273+
char *libpath = lm->l_name;
274+
write_wrapper(fork_pipe[1], libpath, strlen(libpath));
275+
_exit(0);
276+
}
277+
else { // Parent process.
278+
ret = close(fork_pipe[1]);
279+
if (ret == -1) {
280+
perror("Error during libstdcxxprobe in parent process:\nclose");
281+
_exit(1);
282+
}
283+
284+
// Wait for the child to complete.
285+
while (1) {
286+
int wstatus;
287+
pid_t npid = waitpid(pid, &wstatus, 0);
288+
if (npid == -1) {
289+
if (errno == EINTR) continue;
290+
if (errno != EINTR) {
291+
perror("Error during libstdcxxprobe in parent process:\nwaitpid");
292+
exit(1);
293+
}
294+
}
295+
else if (!WIFEXITED(wstatus)) {
296+
char err_str[] = "Error during libstdcxxprobe in parent process:\n"
297+
"The child process did not exit normally.\n";
298+
size_t err_strlen = strlen(err_str);
299+
write_wrapper(STDERR_FILENO, err_str, err_strlen);
300+
exit(1);
301+
}
302+
else if (WEXITSTATUS(wstatus)) {
303+
// The child has printed an error and exited, so the parent should exit too.
304+
exit(1);
305+
}
306+
break;
307+
}
308+
309+
// Read the absolute path to the lib from the child process.
310+
char *path;
311+
size_t pathlen;
312+
read_wrapper(fork_pipe[0], &path, &pathlen);
313+
314+
// Close the read end of the pipe
315+
ret = close(fork_pipe[0]);
316+
if (ret == -1) {
317+
perror("Error during libstdcxxprobe in parent process:\nclose");
318+
_exit(1);
319+
}
320+
321+
if (!pathlen) {
322+
free(path);
323+
return NULL;
324+
}
325+
return path;
326+
}
327+
}
328+
#endif
329+
156330
void * libjulia_internal = NULL;
157331
__attribute__((constructor)) void jl_load_libjulia_internal(void) {
158332
// Only initialize this once
@@ -161,11 +335,46 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
161335
}
162336

163337
// Introspect to find our own path
164-
const char * lib_dir = jl_get_libdir();
338+
const char *lib_dir = jl_get_libdir();
165339

166340
// Pre-load libraries that libjulia-internal needs.
167341
int deps_len = strlen(dep_libs);
168-
char * curr_dep = &dep_libs[0];
342+
char *curr_dep = &dep_libs[0];
343+
344+
void *cxx_handle;
345+
int done_probe = 0;
346+
#if defined(_OS_LINUX_)
347+
int do_probe = 1;
348+
char *probevar = getenv("JULIA_PROBE_LIBSTDCXX");
349+
if (probevar) {
350+
if (strcmp(probevar, "1") == 0 || strcmp(probevar, "yes"))
351+
do_probe = 1;
352+
else if (strcmp(probevar, "0") == 0 || strcmp(probevar, "no"))
353+
do_probe = 0;
354+
}
355+
if (do_probe) {
356+
char *cxxpath = libstdcxxprobe();
357+
if (cxxpath) {
358+
cxx_handle = dlopen(cxxpath, RTLD_LAZY);
359+
char *dlr = dlerror();
360+
if (dlr) {
361+
size_t buflen = strlen(cxxpath) + 2048;
362+
char *errbuf = malloc(buflen);
363+
int err_len = snprintf(errbuf, buflen, "Error, cannot load \"%s\"\n"
364+
"dlinfo() - %s\n", cxxpath, dlr);
365+
write_wrapper(STDOUT_FILENO, errbuf, (size_t)err_len);
366+
free(errbuf);
367+
exit(1);
368+
}
369+
free(cxxpath);
370+
done_probe = 1;
371+
}
372+
}
373+
#endif
374+
// If not on linux, or the probe does not finish successfully, load the bundled version.
375+
if (!done_probe) {
376+
load_library("libstdc++.so", jl_get_libdir(), 1);
377+
}
169378

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

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

280490
#ifdef _OS_WINDOWS_
281-
int __stdcall DllMainCRTStartup(void* instance, unsigned reason, void* reserved) {
491+
int __stdcall DllMainCRTStartup(void *instance, unsigned reason, void *reserved) {
282492
setup_stdio();
283493

284494
// Because we override DllMainCRTStartup, we have to manually call our constructor methods

deps/csl.mk

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ endef
2222
# would get from CSL (by searching for a `GLIBCXX_3.4.X` symbol that does not exist
2323
# in our CSL, but would in a newer one), and default to `USE_BINARYBUILDER_CSL=0` in
2424
# this case.
25-
CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.30|GLIBCXX_3\.5\.|GLIBCXX_4\.
2625

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

0 commit comments

Comments
 (0)