Skip to content

Commit 9be7fb4

Browse files
ryanking13hoodmane
andauthored
Respect runtime paths when loading shared libraries (#23872)
This updates `loadDynamicLibrary` to locate shared libraries from the file system if possible. When locating a shared library with a relative path, first attempt to find it in the `LD_LIBRARY_PATH` directories. If it is not found there, search for it in the runtime path directories. If it is not found, fall back to the original `Module['locateFile']`. Followup to #23805. --------- Co-authored-by: Hood Chatham <[email protected]>
1 parent fdb190d commit 9be7fb4

11 files changed

+229
-23
lines changed

src/lib/libdylink.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ var LibraryDylink = {
4848
registerWasmPlugin();
4949
`,
5050
$preloadedWasm: {},
51+
52+
$replaceORIGIN__deps: ['$PATH'],
53+
$replaceORIGIN: (parentLibName, rpath) => {
54+
if (rpath.startsWith('$ORIGIN')) {
55+
// TODO: what to do if we only know the relative path of the file? It will return "." here.
56+
var origin = PATH.dirname(parentLibName);
57+
return rpath.replace('$ORIGIN', origin);
58+
}
59+
60+
return rpath;
61+
},
5162
#endif // FILESYSTEM
5263

5364
$isSymbolDefined: (symName) => {
@@ -890,6 +901,10 @@ var LibraryDylink = {
890901
return postInstantiation(module, instance);
891902
}
892903

904+
// We need to set rpath in flags based on the current library's rpath.
905+
// We can't mutate flags or else if a depends on b and c and b depends on d,
906+
// then c will be loaded with b's rpath instead of a's.
907+
flags = {...flags, rpath: { parentLibPath: libName, paths: metadata.runtimePaths }}
893908
// now load needed libraries and the module itself.
894909
if (flags.loadAsync) {
895910
return metadata.neededDynlibs
@@ -932,6 +947,59 @@ var LibraryDylink = {
932947
return dso;
933948
},
934949

950+
#if FILESYSTEM
951+
$findLibraryFS__deps: [
952+
'$replaceORIGIN',
953+
'_emscripten_find_dylib',
954+
'$withStackSave',
955+
'$stackAlloc',
956+
'$lengthBytesUTF8',
957+
'$stringToUTF8OnStack',
958+
'$stringToUTF8',
959+
'$FS',
960+
'$PATH',
961+
#if WASMFS
962+
'_wasmfs_identify',
963+
'_wasmfs_read_file',
964+
#endif
965+
],
966+
$findLibraryFS: (libName, rpath) => {
967+
// If we're preloading a dynamic library, the runtime is not ready to call
968+
// __wasmfs_identify or __emscripten_find_dylib. So just quit out.
969+
//
970+
// This means that DT_NEEDED for the main module and transitive dependencies
971+
// of it won't work with this code path. Similarly, it means that calling
972+
// loadDynamicLibrary in a preRun hook can't use this code path.
973+
if (!runtimeInitialized) {
974+
return undefined;
975+
}
976+
if (PATH.isAbs(libName)) {
977+
#if WASMFS
978+
var result = withStackSave(() => __wasmfs_identify(stringToUTF8OnStack(libName)));
979+
return result === {{{ cDefs.EEXIST }}} ? libName : undefined;
980+
#else
981+
try {
982+
FS.lookupPath(libName);
983+
return libName;
984+
} catch (e) {
985+
return undefined;
986+
}
987+
#endif
988+
}
989+
var rpathResolved = (rpath?.paths || []).map((p) => replaceORIGIN(rpath?.parentLibPath, p));
990+
return withStackSave(() => {
991+
// In dylink.c we use: `char buf[2*NAME_MAX+2];` and NAME_MAX is 255.
992+
// So we use the same size here.
993+
var bufSize = 2*255 + 2;
994+
var buf = stackAlloc(bufSize);
995+
var rpathC = stringToUTF8OnStack(rpathResolved.join(':'));
996+
var libNameC = stringToUTF8OnStack(libName);
997+
var resLibNameC = __emscripten_find_dylib(buf, rpathC, libNameC, bufSize);
998+
return resLibNameC ? UTF8ToString(resLibNameC) : undefined;
999+
});
1000+
},
1001+
#endif // FILESYSTEM
1002+
9351003
// loadDynamicLibrary loads dynamic library @ lib URL / path and returns
9361004
// handle for loaded DSO.
9371005
//
@@ -954,6 +1022,7 @@ var LibraryDylink = {
9541022
'$asyncLoad',
9551023
#if FILESYSTEM
9561024
'$preloadedWasm',
1025+
'$findLibraryFS',
9571026
#endif
9581027
#if DYNCALLS || !WASM_BIGINT
9591028
'$registerDynCallSymbols',
@@ -1029,6 +1098,17 @@ var LibraryDylink = {
10291098
}
10301099
}
10311100

1101+
#if FILESYSTEM
1102+
var f = findLibraryFS(libName, flags.rpath);
1103+
#if DYLINK_DEBUG
1104+
dbg(`checking filesystem: ${libName}: ${f ? 'found' : 'not found'}`);
1105+
#endif
1106+
if (f) {
1107+
var libData = FS.readFile(f, {encoding: 'binary'});
1108+
return flags.loadAsync ? Promise.resolve(libData) : libData;
1109+
}
1110+
#endif
1111+
10321112
var libFile = locateFile(libName);
10331113
if (flags.loadAsync) {
10341114
return asyncLoad(libFile);

system/lib/libc/dynlink.c

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,9 @@ static void dlopen_onerror(struct dso* dso, void* user_data) {
485485

486486
// Modified version of path_open from musl/ldso/dynlink.c
487487
static int path_find(const char *name, const char *s, char *buf, size_t buf_size) {
488+
if (s == NULL) {
489+
return -1;
490+
}
488491
size_t l;
489492
int fd;
490493
for (;;) {
@@ -515,13 +518,27 @@ static int path_find(const char *name, const char *s, char *buf, size_t buf_size
515518
}
516519

517520
// Resolve filename using LD_LIBRARY_PATH
518-
static const char* resolve_path(char* buf, const char* file, size_t buflen) {
519-
if (!strchr(file, '/')) {
520-
const char* env_path = getenv("LD_LIBRARY_PATH");
521-
if (env_path && path_find(file, env_path, buf, buflen) == 0) {
522-
dbg("dlopen: found in LD_LIBRARY_PATH: %s", buf);
523-
return buf;
524-
}
521+
const char* _emscripten_find_dylib(char* buf, const char* rpath, const char* file, size_t buflen) {
522+
if (strchr(file, '/')) {
523+
// Absolute path, leave it alone
524+
return NULL;
525+
}
526+
const char* env_path = getenv("LD_LIBRARY_PATH");
527+
if (path_find(file, env_path, buf, buflen) == 0) {
528+
dbg("dlopen: found in LD_LIBRARY_PATH: %s", buf);
529+
return buf;
530+
}
531+
if (path_find(file, rpath, buf, buflen) == 0) {
532+
dbg("dlopen: found in RPATH: %s", buf);
533+
return buf;
534+
}
535+
return NULL;
536+
}
537+
538+
static const char* find_dylib(char* buf, const char* file, size_t buflen) {
539+
const char* res = _emscripten_find_dylib(buf, NULL, file, buflen);
540+
if (res) {
541+
return res;
525542
}
526543
return file;
527544
}
@@ -553,7 +570,7 @@ static struct dso* _dlopen(const char* file, int flags) {
553570
do_write_lock();
554571

555572
char buf[2*NAME_MAX+2];
556-
file = resolve_path(buf, file, sizeof buf);
573+
file = find_dylib(buf, file, sizeof buf);
557574

558575
struct dso* p = find_existing(file);
559576
if (p) {
@@ -593,7 +610,7 @@ void emscripten_dlopen(const char* filename, int flags, void* user_data,
593610
}
594611
do_write_lock();
595612
char buf[2*NAME_MAX+2];
596-
filename = resolve_path(buf, filename, sizeof buf);
613+
filename = find_dylib(buf, filename, sizeof buf);
597614
struct dso* p = find_existing(filename);
598615
if (p) {
599616
onsuccess(user_data, p);

test/other/codesize/test_codesize_hello_dylink.exports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
__wasm_apply_data_relocs
22
__wasm_call_ctors
3+
_emscripten_find_dylib
34
_emscripten_stack_alloc
45
_emscripten_stack_restore
56
calloc
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,36 @@
11
$__emscripten_stdout_close
22
$__emscripten_stdout_seek
33
$__fwritex
4+
$__memcpy
5+
$__memset
46
$__stdio_write
7+
$__strchrnul
58
$__towrite
69
$__wasm_apply_data_relocs
710
$__wasm_call_ctors
811
$__wasm_start
12+
$_emscripten_find_dylib
913
$_emscripten_stack_alloc
1014
$_emscripten_stack_restore
1115
$dlcalloc
16+
$dlmalloc
1217
$emscripten_stack_get_current
18+
$fmt_fp
19+
$fmt_u
20+
$frexp
21+
$getint
1322
$main
23+
$out
24+
$pad
25+
$path_find
26+
$pop_arg
27+
$pop_arg_long_double
28+
$printf_core
1429
$sbrk
1530
$setThrew
31+
$sn_write
32+
$strcspn
33+
$strlen
34+
$strspn
35+
$vsnprintf
36+
$wctomb
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5875
1+
11753
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
GOT.mem.__heap_base
2+
GOT.mem.__stack_high
3+
GOT.mem.__stack_low
24
env.__indirect_function_table
35
env.__memory_base
46
env.__stack_pointer
7+
env.__syscall_stat64
58
env.__table_base
69
env.emscripten_resize_heap
710
env.memory
11+
wasi_snapshot_preview1.environ_get
12+
wasi_snapshot_preview1.environ_sizes_get
813
wasi_snapshot_preview1.fd_write
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
12857
1+
27782
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
__heap_base
22
__indirect_function_table
33
__memory_base
4+
__stack_high
5+
__stack_low
46
__stack_pointer
7+
__syscall_stat64
58
__table_base
69
emscripten_resize_heap
10+
environ_get
11+
environ_sizes_get
712
fd_write
813
memory
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8176
1+
18550

test/test_other.py

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7569,16 +7569,30 @@ def test_RUNTIME_LINKED_LIBS(self):
75697569

75707570
@parameterized({
75717571
'': ([],),
7572+
'wasmfs': (['-sWASMFS'],),
75727573
'pthread': (['-g', '-pthread', '-Wno-experimental', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
75737574
})
75747575
def test_ld_library_path(self, args):
7575-
if args:
7576+
if '-pthread' in args:
7577+
self.skipTest('Problems with readFile from pthread')
7578+
if '-pthread' in args:
75767579
self.setup_node_pthreads()
7580+
create_file('hello1_dep.c', r'''
7581+
#include <stdio.h>
7582+
7583+
void hello1_dep() {
7584+
printf("Hello1_dep\n");
7585+
return;
7586+
}
7587+
''')
75777588
create_file('hello1.c', r'''
75787589
#include <stdio.h>
75797590

7591+
void hello1_dep();
7592+
75807593
void hello1() {
75817594
printf("Hello1\n");
7595+
hello1_dep();
75827596
return;
75837597
}
75847598
''')
@@ -7594,7 +7608,7 @@ def test_ld_library_path(self, args):
75947608
#include <stdio.h>
75957609

75967610
void hello3() {
7597-
printf ("Hello3\n");
7611+
printf("Hello3\n");
75987612
return;
75997613
}
76007614
''')
@@ -7658,23 +7672,85 @@ def test_ld_library_path(self, args):
76587672
return 0;
76597673
}
76607674
''')
7661-
self.run_process([EMCC, '-o', 'hello1.wasm', 'hello1.c', '-sSIDE_MODULE'] + args)
7675+
os.mkdir('subdir')
7676+
self.run_process([EMCC, '-o', 'subdir/libhello1_dep.so', 'hello1_dep.c', '-sSIDE_MODULE'])
7677+
self.run_process([EMCC, '-o', 'hello1.wasm', 'hello1.c', '-sSIDE_MODULE', 'subdir/libhello1_dep.so'] + args)
76627678
self.run_process([EMCC, '-o', 'hello2.wasm', 'hello2.c', '-sSIDE_MODULE'] + args)
76637679
self.run_process([EMCC, '-o', 'hello3.wasm', 'hello3.c', '-sSIDE_MODULE'] + args)
76647680
self.run_process([EMCC, '-o', 'hello4.wasm', 'hello4.c', '-sSIDE_MODULE'] + args)
7665-
self.run_process([EMCC, '--profiling-funcs', '-o', 'main.js', 'main.c', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
7681+
emcc_args = ['--profiling-funcs', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
7682+
'-L./subdir',
7683+
'--embed-file', 'subdir/libhello1_dep.so@/usr/lib/libhello1_dep.so',
76667684
'--embed-file', 'hello1.wasm@/lib/libhello1.wasm',
76677685
'--embed-file', 'hello2.wasm@/usr/lib/libhello2.wasm',
76687686
'--embed-file', 'hello3.wasm@/libhello3.wasm',
76697687
'--embed-file', 'hello4.wasm@/usr/local/lib/libhello4.wasm',
76707688
'hello1.wasm', 'hello2.wasm', 'hello3.wasm', 'hello4.wasm', '-sNO_AUTOLOAD_DYLIBS',
7671-
'--pre-js', 'pre.js'] + args)
7672-
out = self.run_js('main.js')
7673-
self.assertContained('Hello1', out)
7674-
self.assertContained('Hello2', out)
7675-
self.assertContained('Hello3', out)
7676-
self.assertContained('Hello4', out)
7677-
self.assertContained('Ok', out)
7689+
'--pre-js', 'pre.js'] + args
7690+
self.do_runf('main.c', 'Hello1\nHello1_dep\nHello2\nHello3\nHello4\nOk\n', emcc_args=emcc_args)
7691+
7692+
@also_with_wasmfs
7693+
def test_dlopen_rpath(self):
7694+
create_file('hello_dep.c', r'''
7695+
#include <stdio.h>
7696+
7697+
void hello_dep() {
7698+
printf("Hello_dep\n");
7699+
return;
7700+
}
7701+
''')
7702+
create_file('hello.c', r'''
7703+
#include <stdio.h>
7704+
7705+
void hello_dep();
7706+
7707+
void hello() {
7708+
printf("Hello\n");
7709+
hello_dep();
7710+
return;
7711+
}
7712+
''')
7713+
create_file('main.c', r'''
7714+
#include <assert.h>
7715+
#include <stdio.h>
7716+
#include <stdlib.h>
7717+
#include <string.h>
7718+
#include <dlfcn.h>
7719+
7720+
int main() {
7721+
void *h;
7722+
void (*f)();
7723+
double (*f2)(double);
7724+
7725+
h = dlopen("/usr/lib/libhello.wasm", RTLD_NOW);
7726+
assert(h);
7727+
f = dlsym(h, "hello");
7728+
assert(f);
7729+
f();
7730+
dlclose(h);
7731+
7732+
printf("Ok\n");
7733+
7734+
return 0;
7735+
}
7736+
''')
7737+
os.mkdir('subdir')
7738+
7739+
def _build(rpath_flag, expected, **kwds):
7740+
self.run_process([EMCC, '-o', 'subdir/libhello_dep.so', 'hello_dep.c', '-sSIDE_MODULE'])
7741+
self.run_process([EMCC, '-o', 'hello.wasm', 'hello.c', '-sSIDE_MODULE', 'subdir/libhello_dep.so'] + rpath_flag)
7742+
args = ['--profiling-funcs', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
7743+
'--embed-file', 'hello.wasm@/usr/lib/libhello.wasm',
7744+
'--embed-file', 'subdir/libhello_dep.so@/usr/lib/subdir/libhello_dep.so',
7745+
'hello.wasm', '-sNO_AUTOLOAD_DYLIBS',
7746+
'-L./subdir', '-lhello_dep']
7747+
self.do_runf('main.c', expected, emcc_args=args, **kwds)
7748+
7749+
# case 1) without rpath: fail to locate the library
7750+
_build([], r"no such file or directory, open '.*libhello_dep\.so'", regex=True, assert_returncode=NON_ZERO)
7751+
7752+
# case 2) with rpath: success
7753+
_build(['-Wl,-rpath,$ORIGIN/subdir'], "Hello\nHello_dep\nOk\n")
76787754

76797755
def test_dlopen_bad_flags(self):
76807756
create_file('main.c', r'''

0 commit comments

Comments
 (0)