Skip to content

Commit 39f68a7

Browse files
committed
Avoid using fork() when probing system libstdc++
Rather than relying on the linker to do all of the heavy lifting, this changes our probing technique to directly parse the `ld.so.cache` and `libstdc++.so.6` files. As long as we can expect `ld.so.cache` to be available in `/etc` on all systems, this seems to be a reliable way to locate system libraries on Linux systems. That allows us to avoid `fork()`. For a small application that has just started up `fork()` is no big deal, but it's quite heavy-handed for Julia- as-a-library scenarios where resident memory may already be large. Many soft-embedded targets also do not support fork() well, so it is good for our compatibility to adjust this.
1 parent aaa2451 commit 39f68a7

File tree

6 files changed

+477
-109
lines changed

6 files changed

+477
-109
lines changed

cli/Makefile

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ include $(JULIAHOME)/Make.inc
55
include $(JULIAHOME)/deps/llvm-ver.make
66

77

8-
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)
8+
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)
99

1010
LOADER_CFLAGS = $(JCFLAGS) -I$(BUILDROOT)/src -I$(JULIAHOME)/src -I$(JULIAHOME)/src/support -I$(build_includedir) -ffreestanding
1111
LOADER_LDFLAGS = $(JLDFLAGS) -ffreestanding -L$(build_shlibdir) -L$(build_libdir)
@@ -41,8 +41,8 @@ endif # MSYS2
4141

4242
EXE_OBJS := $(BUILDDIR)/loader_exe.o
4343
EXE_DOBJS := $(BUILDDIR)/loader_exe.dbg.obj
44-
LIB_OBJS := $(BUILDDIR)/loader_lib.o
45-
LIB_DOBJS := $(BUILDDIR)/loader_lib.dbg.obj
44+
LIB_OBJS := $(BUILDDIR)/loader_lib.o $(BUILDDIR)/loader_symbol_probe.o $(BUILDDIR)/loader_library_probe.o
45+
LIB_DOBJS := $(BUILDDIR)/loader_lib.dbg.obj $(BUILDDIR)/loader_symbol_probe.o $(BUILDDIR)/loader_library_probe.o
4646

4747
# If this is an architecture that supports dynamic linking, link in a trampoline definition
4848
ifneq (,$(wildcard $(SRCDIR)/trampolines/trampolines_$(ARCH).S))
@@ -66,6 +66,14 @@ $(BUILDDIR)/loader_trampolines.o : $(SRCDIR)/trampolines/trampolines_$(ARCH).S $
6666
@$(call PRINT_CC, $(CC) $(SHIPFLAGS) $(LOADER_CFLAGS) $< -c -o $@)
6767
$(BUILDDIR)/loader_trampolines.dbg.obj : $(SRCDIR)/trampolines/trampolines_$(ARCH).S $(HEADERS) $(SRCDIR)/trampolines/common.h
6868
@$(call PRINT_CC, $(CC) $(DEBUGFLAGS) $(LOADER_CFLAGS) $< -c -o $@)
69+
$(BUILDDIR)/loader_library_probe.o : $(SRCDIR)/loader_library_probe.c $(HEADERS) $(JULIAHOME)/VERSION
70+
@$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(SHIPFLAGS) $(LOADER_CFLAGS) -c $< -o $@)
71+
$(BUILDDIR)/loader_library_probe.dbg.obj : $(SRCDIR)/loader_library_probe.c $(HEADERS) $(JULIAHOME)/VERSION
72+
@$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(DEBUGFLAGS) $(LOADER_CFLAGS) -c $< -o $@)
73+
$(BUILDDIR)/loader_symbol_probe.o : $(SRCDIR)/loader_symbol_probe.c $(HEADERS) $(JULIAHOME)/VERSION
74+
@$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(SHIPFLAGS) $(LOADER_CFLAGS) -c $< -o $@)
75+
$(BUILDDIR)/loader_symbol_probe.dbg.obj : $(SRCDIR)/loader_symbol_probe.c $(HEADERS) $(JULIAHOME)/VERSION
76+
@$(call PRINT_CC, $(CC) -DJL_LIBRARY_EXPORTS $(DEBUGFLAGS) $(LOADER_CFLAGS) -c $< -o $@)
6977

7078
# Debugging target to help us see what kind of code is being generated for our trampolines
7179
dump-trampolines: $(SRCDIR)/trampolines/trampolines_$(ARCH).S

cli/dl-cache.h

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/* Support for reading /etc/ld.so.cache files written by Linux ldconfig.
2+
Copyright (C) 1999-2019 Free Software Foundation, Inc.
3+
This file is part of the GNU C Library.
4+
5+
The GNU C Library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
The GNU C Library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with the GNU C Library; if not, see
17+
<http://www.gnu.org/licenses/>. */
18+
19+
#include <stdint.h>
20+
21+
#define FLAG_ANY -1
22+
#define FLAG_TYPE_MASK 0x00ff
23+
#define FLAG_LIBC4 0x0000
24+
#define FLAG_ELF 0x0001
25+
#define FLAG_ELF_LIBC5 0x0002
26+
#define FLAG_ELF_LIBC6 0x0003
27+
#define FLAG_REQUIRED_MASK 0xff00
28+
#define FLAG_SPARC_LIB64 0x0100
29+
#define FLAG_IA64_LIB64 0x0200
30+
#define FLAG_X8664_LIB64 0x0300
31+
#define FLAG_S390_LIB64 0x0400
32+
#define FLAG_POWERPC_LIB64 0x0500
33+
#define FLAG_MIPS64_LIBN32 0x0600
34+
#define FLAG_MIPS64_LIBN64 0x0700
35+
#define FLAG_X8664_LIBX32 0x0800
36+
#define FLAG_ARM_LIBHF 0x0900
37+
#define FLAG_AARCH64_LIB64 0x0a00
38+
#define FLAG_ARM_LIBSF 0x0b00
39+
#define FLAG_MIPS_LIB32_NAN2008 0x0c00
40+
#define FLAG_MIPS64_LIBN32_NAN2008 0x0d00
41+
#define FLAG_MIPS64_LIBN64_NAN2008 0x0e00
42+
#define FLAG_RISCV_FLOAT_ABI_SOFT 0x0f00
43+
#define FLAG_RISCV_FLOAT_ABI_DOUBLE 0x1000
44+
45+
#if defined(_CPU_X86_64_)
46+
47+
#define _DL_CACHE_DEFAULT_ID 0x303
48+
#define _dl_cache_check_flags(flags) ((flags) == _DL_CACHE_DEFAULT_ID)
49+
50+
#elif defined(_CPU_AARCH64_)
51+
52+
#ifdef __LP64__
53+
# define _DL_CACHE_DEFAULT_ID (FLAG_AARCH64_LIB64 | FLAG_ELF_LIBC6)
54+
#else
55+
# define _DL_CACHE_DEFAULT_ID (FLAG_AARCH64_LIB32 | FLAG_ELF_LIBC6)
56+
#endif
57+
58+
#define _dl_cache_check_flags(flags) ((flags) == _DL_CACHE_DEFAULT_ID)
59+
60+
#elif defined(_CPU_RISCV64_)
61+
62+
/* For now we only support the natural XLEN ABI length on all targets, so the
63+
only bits that need to go into ld.so.cache are the FLEG ABI length. */
64+
#if defined __riscv_float_abi_double
65+
# define _DL_CACHE_DEFAULT_ID (FLAG_RISCV_FLOAT_ABI_DOUBLE | FLAG_ELF_LIBC6)
66+
#else
67+
# define _DL_CACHE_DEFAULT_ID (FLAG_RISCV_FLOAT_ABI_SOFT | FLAG_ELF_LIBC6)
68+
#endif
69+
70+
#define _dl_cache_check_flags(flags) ((flags) == _DL_CACHE_DEFAULT_ID)
71+
72+
#elif defined(_CPU_ARM_)
73+
74+
/* In order to support the transition from unmarked objects
75+
to marked objects we must treat unmarked objects as
76+
compatible with either FLAG_ARM_LIBHF or FLAG_ARM_LIBSF. */
77+
#ifdef __ARM_PCS_VFP
78+
# define _dl_cache_check_flags(flags) \
79+
((flags) == (FLAG_ARM_LIBHF | FLAG_ELF_LIBC6) \
80+
|| (flags) == FLAG_ELF_LIBC6)
81+
#else
82+
# define _dl_cache_check_flags(flags) \
83+
((flags) == (FLAG_ARM_LIBSF | FLAG_ELF_LIBC6) \
84+
|| (flags) == FLAG_ELF_LIBC6)
85+
#endif
86+
87+
#elif defined(_CPU_X86_)
88+
89+
/* Defined as (FLAG_ELF_LIBC6 | FLAG_X8664_LIBX32). */
90+
#undef _DL_CACHE_DEFAULT_ID
91+
#define _DL_CACHE_DEFAULT_ID 0x803
92+
93+
#elif defined(_CPU_PPC64_)
94+
95+
#define _DL_CACHE_DEFAULT_ID 0x503
96+
97+
#define _dl_cache_check_flags(flags) \
98+
((flags) == _DL_CACHE_DEFAULT_ID)
99+
100+
#else
101+
102+
#error "Missing CPU arch-specific definitions in dl-cache.h"
103+
104+
#endif
105+
106+
#ifndef _DL_CACHE_DEFAULT_ID
107+
# define _DL_CACHE_DEFAULT_ID 3
108+
#endif
109+
110+
#ifndef _dl_cache_check_flags
111+
# define _dl_cache_check_flags(flags) \
112+
((flags) == 1 || (flags) == _DL_CACHE_DEFAULT_ID)
113+
#endif
114+
115+
#ifndef LD_SO_CACHE
116+
# define LD_SO_CACHE SYSCONFDIR "/ld.so.cache"
117+
#endif
118+
119+
#define CACHEMAGIC "ld.so-1.7.0"
120+
121+
/* libc5 and glibc 2.0/2.1 use the same format. For glibc 2.2 another
122+
format has been added in a compatible way:
123+
The beginning of the string table is used for the new table:
124+
old_magic
125+
nlibs
126+
libs[0]
127+
...
128+
libs[nlibs-1]
129+
pad, new magic needs to be aligned
130+
- this is string[0] for the old format
131+
new magic - this is string[0] for the new format
132+
newnlibs
133+
...
134+
newlibs[0]
135+
...
136+
newlibs[newnlibs-1]
137+
string 1
138+
string 2
139+
...
140+
*/
141+
struct file_entry
142+
{
143+
int flags; /* This is 1 for an ELF library. */
144+
unsigned int key, value; /* String table indices. */
145+
};
146+
147+
struct cache_file
148+
{
149+
char magic[sizeof CACHEMAGIC - 1];
150+
unsigned int nlibs;
151+
struct file_entry libs[0];
152+
};
153+
154+
#define CACHEMAGIC_NEW "glibc-ld.so.cache"
155+
#define CACHE_VERSION "1.1"
156+
#define CACHEMAGIC_VERSION_NEW CACHEMAGIC_NEW CACHE_VERSION
157+
158+
159+
struct file_entry_new
160+
{
161+
int32_t flags; /* This is 1 for an ELF library. */
162+
uint32_t key, value; /* String table indices. */
163+
uint32_t osversion; /* Required OS version. */
164+
uint64_t hwcap; /* Hwcap entry. */
165+
};
166+
167+
struct cache_file_new
168+
{
169+
char magic[sizeof CACHEMAGIC_NEW - 1];
170+
char version[sizeof CACHE_VERSION - 1];
171+
uint32_t nlibs; /* Number of entries. */
172+
uint32_t len_strings; /* Size of string table. */
173+
uint32_t unused[5]; /* Leave space for future extensions
174+
and align to 8 byte boundary. */
175+
struct file_entry_new libs[0]; /* Entries describing libraries. */
176+
/* After this the string table of size len_strings is found. */
177+
};
178+
179+
/* Used to align cache_file_new. */
180+
#define ALIGN_CACHE(addr) \
181+
(((addr) + __alignof__ (struct cache_file_new) -1) \
182+
& (~(__alignof__ (struct cache_file_new) - 1)))
183+
184+
// extern int _dl_cache_libcmp (const char *p1, const char *p2) attribute_hidden;

cli/loader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ JL_DLLEXPORT extern int jl_load_repl(int, char **);
7171
JL_DLLEXPORT void jl_loader_print_stderr(const char * msg);
7272
void jl_loader_print_stderr3(const char * msg1, const char * msg2, const char * msg3);
7373
static void * lookup_symbol(const void * lib_handle, const char * symbol_name);
74+
const char *jl_loader_probe_system_library(const char *libname, const char *symbol);
75+
int jl_loader_locate_symbol(const char *library, const char *symbol);
7476

7577
#ifdef _OS_WINDOWS_
7678
LPWSTR *CommandLineToArgv(LPWSTR lpCmdLine, int *pNumArgs);

cli/loader_lib.c

Lines changed: 4 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -277,111 +277,9 @@ static void read_wrapper(int fd, char **ret, size_t *ret_len)
277277
// If the path is found, return it.
278278
// Otherwise, print the error and exit.
279279
// The path returned must be freed.
280-
static char *libstdcxxprobe(void)
280+
static const char *libstdcxxprobe(void)
281281
{
282-
// Create the pipe and child process.
283-
int fork_pipe[2];
284-
int ret = pipe(fork_pipe);
285-
if (ret == -1) {
286-
perror("(julia) Error during libstdcxxprobe: pipe");
287-
exit(1);
288-
}
289-
pid_t pid = fork();
290-
if (pid == -1) {
291-
perror("Error during libstdcxxprobe:\nfork");
292-
exit(1);
293-
}
294-
if (pid == (pid_t) 0) { // Child process.
295-
close(fork_pipe[0]);
296-
297-
// Open the first available libstdc++.so.
298-
// If it can't be found, report so by exiting zero.
299-
// The star is there to prevent the compiler from merging constants
300-
// with "\0*libstdc++.so.6", which we string replace inside the .so during
301-
// make install.
302-
void *handle = dlopen("libstdc++.so.6\0*", RTLD_LAZY);
303-
if (!handle) {
304-
_exit(0);
305-
}
306-
307-
// See if the version is compatible
308-
char *dlerr = dlerror(); // clear out dlerror
309-
void *sym = dlsym(handle, GLIBCXX_LEAST_VERSION_SYMBOL);
310-
(void)sym;
311-
dlerr = dlerror();
312-
if (dlerr) {
313-
// We can't use the library that was found, so don't write anything.
314-
// The main process will see that nothing was written,
315-
// then exit the function and return null.
316-
_exit(0);
317-
}
318-
319-
// No error means the symbol was found, we can use this library.
320-
// Get the path to it, and write it to the parent process.
321-
struct link_map *lm;
322-
ret = dlinfo(handle, RTLD_DI_LINKMAP, &lm);
323-
if (ret == -1) {
324-
char *errbuf = dlerror();
325-
char *errdesc = (char*)"Error during libstdcxxprobe in child process:\ndlinfo: ";
326-
write_wrapper(STDERR_FILENO, errdesc, strlen(errdesc));
327-
write_wrapper(STDERR_FILENO, errbuf, strlen(errbuf));
328-
write_wrapper(STDERR_FILENO, "\n", 1);
329-
_exit(1);
330-
}
331-
char *libpath = lm->l_name;
332-
write_wrapper(fork_pipe[1], libpath, strlen(libpath));
333-
_exit(0);
334-
}
335-
else { // Parent process.
336-
close(fork_pipe[1]);
337-
338-
// Read the absolute path to the lib from the child process.
339-
char *path;
340-
size_t pathlen;
341-
read_wrapper(fork_pipe[0], &path, &pathlen);
342-
343-
// Close the read end of the pipe
344-
close(fork_pipe[0]);
345-
346-
// Wait for the child to complete.
347-
while (1) {
348-
int wstatus;
349-
pid_t npid = waitpid(pid, &wstatus, 0);
350-
if (npid == -1) {
351-
if (errno == EINTR) continue;
352-
if (errno == ECHILD) {
353-
// SIGCHLD is set to SIG_IGN or has flag SA_NOCLDWAIT, so the child
354-
// did not become a zombie and wait for `waitpid` - it just exited.
355-
//
356-
// Assume that it exited successfully and use whatever libpath we
357-
// got out of the pipe, if any.
358-
break;
359-
}
360-
perror("Error during libstdcxxprobe in parent process:\nwaitpid");
361-
exit(1);
362-
}
363-
else if (!WIFEXITED(wstatus)) {
364-
const char *err_str = "Error during libstdcxxprobe in parent process:\n"
365-
"The child process did not exit normally.\n";
366-
size_t err_strlen = strlen(err_str);
367-
write_wrapper(STDERR_FILENO, err_str, err_strlen);
368-
exit(1);
369-
}
370-
else if (WEXITSTATUS(wstatus)) {
371-
// The child has printed an error and exited, so the parent should exit too.
372-
exit(1);
373-
}
374-
break;
375-
}
376-
377-
if (!pathlen) {
378-
free(path);
379-
return NULL;
380-
}
381-
// Ensure that `path` is zero-terminated.
382-
path[pathlen] = '\0';
383-
return path;
384-
}
282+
return jl_loader_probe_system_library("libstdc++.so.6", GLIBCXX_LEAST_VERSION_SYMBOL);
385283
}
386284
#endif
387285

@@ -480,7 +378,7 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
480378
do_probe = 0;
481379
}
482380
if (do_probe) {
483-
char *cxxpath = libstdcxxprobe();
381+
const char *cxxpath = libstdcxxprobe();
484382
if (cxxpath) {
485383
void *cxx_handle = dlopen(cxxpath, RTLD_LAZY);
486384
(void)cxx_handle;
@@ -490,7 +388,7 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
490388
jl_loader_print_stderr3("Message: ", dlr, "\n");
491389
exit(1);
492390
}
493-
free(cxxpath);
391+
free((void *)cxxpath);
494392
probe_successful = 1;
495393
}
496394
}

0 commit comments

Comments
 (0)