Skip to content

Respect runtime paths when loading shared libraries #23872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 64 commits into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
22620d5
Locate shared libraries using rpath
ryanking13 Mar 9, 2025
dfa80d8
Use rpath to load shared libraries
ryanking13 Mar 9, 2025
3b9d68e
Update test
ryanking13 Mar 9, 2025
39db974
ruff
ryanking13 Mar 9, 2025
1568cfe
Automatic rebaseline of codesize expectations. NFC
ryanking13 Mar 9, 2025
4f3ffe5
Don't run test on pthread
ryanking13 Mar 9, 2025
e49d1a2
Fix wasmfs test
ryanking13 Mar 9, 2025
82b38da
Automatic rebaseline of codesize expectations. NFC
ryanking13 Mar 9, 2025
3689d4e
ruff
ryanking13 Mar 9, 2025
a0732d7
Address comments
ryanking13 Mar 10, 2025
defefc2
Make closure compiler happy
ryanking13 Mar 10, 2025
8a7196d
Automatic rebaseline of codesize expectations. NFC
ryanking13 Mar 10, 2025
5511492
Merge remote-tracking branch 'upstream/main' into dylink-rpath
ryanking13 Mar 11, 2025
abacbf0
Fix tests
ryanking13 Mar 11, 2025
2df17b3
Automatic rebaseline of codesize expectations. NFC
ryanking13 Mar 11, 2025
0603c0a
Merge branch 'main' into dylink-rpath
hoodmane Mar 31, 2025
b23485c
Use C function for path lookup
hoodmane Mar 31, 2025
47f36f4
Revert "Use C function for path lookup"
hoodmane Mar 31, 2025
76d8fc8
Add also_with_wasmfs decorator
hoodmane Mar 31, 2025
17191ba
Revert "Revert "Use C function for path lookup""
hoodmane Mar 31, 2025
820805a
Call wasmExports._emscripten_resolve_path directly
hoodmane Mar 31, 2025
105e771
Tidy up
hoodmane Mar 31, 2025
bbc7be9
Rename test to test_dlopen_rpath
hoodmane Mar 31, 2025
c6c5bdc
Fix tests
hoodmane Apr 1, 2025
d03329b
Revert codesizes
hoodmane Apr 1, 2025
f2a839f
Remove repeat declarations
hoodmane Apr 1, 2025
676e156
Add SUPPORT_RPATH setting
hoodmane Apr 1, 2025
8edc342
Update settings_reference.rst
hoodmane Apr 1, 2025
0895d17
Fix test
hoodmane Apr 1, 2025
0848d77
Fix again
hoodmane Apr 2, 2025
e0b3fba
Merge branch 'main' into dylink-rpath
hoodmane Apr 2, 2025
59875c4
Remove SUPPORT_RPATH
hoodmane Apr 2, 2025
4e4ab33
Update codesize
hoodmane Apr 2, 2025
7e98557
Fix indentation and use withStackSave
hoodmane Apr 2, 2025
6fee8c9
Move RPATH lookup below checks for loadedLibsByName and handle
hoodmane Apr 2, 2025
0af5e3c
Cleanup
hoodmane Apr 2, 2025
4904e56
Move rpath to end of arguments list and remove default
hoodmane Apr 2, 2025
3e1ccce
Merge branch 'main' into dylink-rpath
hoodmane Apr 2, 2025
be22217
Put rpath in flags
hoodmane Apr 2, 2025
e27a006
parentLibPath ==> parentLibName
hoodmane Apr 2, 2025
caf817c
Add comment
hoodmane Apr 2, 2025
97a764d
Merge branch 'main' into dylink-rpath
hoodmane Apr 4, 2025
8763702
Fix wasmfs
hoodmane Apr 4, 2025
bb0240a
Fix in wasmfs
hoodmane Apr 4, 2025
8040eee
Add _emscripten_resolve_path to create_pointer_conversion_wrappers
hoodmane Apr 4, 2025
80b3763
Update codesize
hoodmane Apr 4, 2025
a52f4a8
Rename _emscripten_resolve_path to _emscripten_find_dylib
hoodmane Apr 15, 2025
db81df7
Factor out findLibraryFS
hoodmane Apr 15, 2025
39d91b4
Fix refactor
hoodmane Apr 15, 2025
8e3d3e0
Some test cleanup
hoodmane Apr 15, 2025
fb7ca61
Rename hello1* to hello*
hoodmane Apr 15, 2025
f164ddc
Format fixes
hoodmane Apr 15, 2025
7422fc9
Add comment on why we copy flags rather than mutating.
hoodmane Apr 15, 2025
31ac4ce
Quit out earlier if runtime not initialized and add comment
hoodmane Apr 15, 2025
6c37485
Merge branch 'main' into dylink-rpath
hoodmane Apr 15, 2025
ff9c431
Update codesizes
hoodmane Apr 15, 2025
412fb96
Fix ruff lint
hoodmane Apr 15, 2025
61e93e6
Address some review comments
hoodmane Apr 16, 2025
d5bf161
Simplify path_find
hoodmane Apr 16, 2025
e3a2ff8
Indentation
hoodmane Apr 16, 2025
aa85192
Make `_emscripten_resolve_path` return `NULL` if it didn't resolve th…
hoodmane Apr 16, 2025
5d3620e
adjust test_ld_library_path
hoodmane Apr 16, 2025
e66a372
Revert unneeded change
hoodmane Apr 16, 2025
2e41541
Fix codesize_hello_dylink again
hoodmane Apr 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/lib/libdylink.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ var LibraryDylink = {
registerWasmPlugin();
`,
$preloadedWasm: {},

$replaceORIGIN__deps: ['$PATH'],
$replaceORIGIN: (parentLibName, rpath) => {
if (rpath.startsWith('$ORIGIN')) {
// TODO: what to do if we only know the relative path of the file? It will return "." here.
var origin = PATH.dirname(parentLibName);
return rpath.replace('$ORIGIN', origin);
}

return rpath;
},
#endif // FILESYSTEM

$isSymbolDefined: (symName) => {
Expand Down Expand Up @@ -890,6 +901,10 @@ var LibraryDylink = {
return postInstantiation(module, instance);
}

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

#if FILESYSTEM
$findLibraryFS__deps: [
'$replaceORIGIN',
'_emscripten_find_dylib',
'$withStackSave',
'$stackAlloc',
'$lengthBytesUTF8',
'$stringToUTF8OnStack',
'$stringToUTF8',
'$FS',
'$PATH',
#if WASMFS
'_wasmfs_identify',
'_wasmfs_read_file',
#endif
],
$findLibraryFS: (libName, rpath) => {
// If we're preloading a dynamic library, the runtime is not ready to call
// __wasmfs_identify or __emscripten_find_dylib. So just quit out.
//
// This means that DT_NEEDED for the main module and transitive dependencies
// of it won't work with this code path. Similarly, it means that calling
// loadDynamicLibrary in a preRun hook can't use this code path.
if (!runtimeInitialized) {
return undefined;
}
if (PATH.isAbs(libName)) {
#if WASMFS
var result = withStackSave(() => __wasmfs_identify(stringToUTF8OnStack(libName)));
return result === {{{ cDefs.EEXIST }}} ? libName : undefined;
#else
try {
FS.lookupPath(libName);
return libName;
} catch (e) {
return undefined;
}
#endif
}
var rpathResolved = (rpath?.paths || []).map((p) => replaceORIGIN(rpath?.parentLibPath, p));
return withStackSave(() => {
// In dylink.c we use: `char buf[2*NAME_MAX+2];` and NAME_MAX is 255.
// So we use the same size here.
var bufSize = 2*255 + 2;
var buf = stackAlloc(bufSize);
var rpathC = stringToUTF8OnStack(rpathResolved.join(':'));
var libNameC = stringToUTF8OnStack(libName);
var resLibNameC = __emscripten_find_dylib(buf, rpathC, libNameC, bufSize);
return resLibNameC ? UTF8ToString(resLibNameC) : undefined;
});
},
#endif // FILESYSTEM

// loadDynamicLibrary loads dynamic library @ lib URL / path and returns
// handle for loaded DSO.
//
Expand All @@ -954,6 +1022,7 @@ var LibraryDylink = {
'$asyncLoad',
#if FILESYSTEM
'$preloadedWasm',
'$findLibraryFS',
#endif
#if DYNCALLS || !WASM_BIGINT
'$registerDynCallSymbols',
Expand Down Expand Up @@ -1029,6 +1098,17 @@ var LibraryDylink = {
}
}

#if FILESYSTEM
var f = findLibraryFS(libName, flags.rpath);
#if DYLINK_DEBUG
dbg(`checking filesystem: ${libName}: ${f ? 'found' : 'not found'}`);
#endif
if (f) {
var libData = FS.readFile(f, {encoding: 'binary'});
return flags.loadAsync ? Promise.resolve(libData) : libData;
}
#endif

var libFile = locateFile(libName);
if (flags.loadAsync) {
return asyncLoad(libFile);
Expand Down
35 changes: 26 additions & 9 deletions system/lib/libc/dynlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,9 @@ static void dlopen_onerror(struct dso* dso, void* user_data) {

// Modified version of path_open from musl/ldso/dynlink.c
static int path_find(const char *name, const char *s, char *buf, size_t buf_size) {
if (s == NULL) {
return -1;
}
size_t l;
int fd;
for (;;) {
Expand Down Expand Up @@ -515,13 +518,27 @@ static int path_find(const char *name, const char *s, char *buf, size_t buf_size
}

// Resolve filename using LD_LIBRARY_PATH
static const char* resolve_path(char* buf, const char* file, size_t buflen) {
if (!strchr(file, '/')) {
const char* env_path = getenv("LD_LIBRARY_PATH");
if (env_path && path_find(file, env_path, buf, buflen) == 0) {
dbg("dlopen: found in LD_LIBRARY_PATH: %s", buf);
return buf;
}
const char* _emscripten_find_dylib(char* buf, const char* rpath, const char* file, size_t buflen) {
if (strchr(file, '/')) {
// Absolute path, leave it alone
return NULL;
}
const char* env_path = getenv("LD_LIBRARY_PATH");
if (path_find(file, env_path, buf, buflen) == 0) {
dbg("dlopen: found in LD_LIBRARY_PATH: %s", buf);
return buf;
}
if (path_find(file, rpath, buf, buflen) == 0) {
dbg("dlopen: found in RPATH: %s", buf);
return buf;
}
return NULL;
}

static const char* find_dylib(char* buf, const char* file, size_t buflen) {
const char* res = _emscripten_find_dylib(buf, NULL, file, buflen);
if (res) {
return res;
}
return file;
}
Expand Down Expand Up @@ -553,7 +570,7 @@ static struct dso* _dlopen(const char* file, int flags) {
do_write_lock();

char buf[2*NAME_MAX+2];
file = resolve_path(buf, file, sizeof buf);
file = find_dylib(buf, file, sizeof buf);

struct dso* p = find_existing(file);
if (p) {
Expand Down Expand Up @@ -593,7 +610,7 @@ void emscripten_dlopen(const char* filename, int flags, void* user_data,
}
do_write_lock();
char buf[2*NAME_MAX+2];
filename = resolve_path(buf, filename, sizeof buf);
filename = find_dylib(buf, filename, sizeof buf);
struct dso* p = find_existing(filename);
if (p) {
onsuccess(user_data, p);
Expand Down
1 change: 1 addition & 0 deletions test/other/codesize/test_codesize_hello_dylink.exports
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
__wasm_apply_data_relocs
__wasm_call_ctors
_emscripten_find_dylib
_emscripten_stack_alloc
_emscripten_stack_restore
calloc
Expand Down
21 changes: 21 additions & 0 deletions test/other/codesize/test_codesize_hello_dylink.funcs
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
$__emscripten_stdout_close
$__emscripten_stdout_seek
$__fwritex
$__memcpy
$__memset
$__stdio_write
$__strchrnul
$__towrite
$__wasm_apply_data_relocs
$__wasm_call_ctors
$__wasm_start
$_emscripten_find_dylib
$_emscripten_stack_alloc
$_emscripten_stack_restore
$dlcalloc
$dlmalloc
$emscripten_stack_get_current
$fmt_fp
$fmt_u
$frexp
$getint
$main
$out
$pad
$path_find
$pop_arg
$pop_arg_long_double
$printf_core
$sbrk
$setThrew
$sn_write
$strcspn
$strlen
$strspn
$vsnprintf
$wctomb
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.gzsize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5875
11753
5 changes: 5 additions & 0 deletions test/other/codesize/test_codesize_hello_dylink.imports
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
GOT.mem.__heap_base
GOT.mem.__stack_high
GOT.mem.__stack_low
env.__indirect_function_table
env.__memory_base
env.__stack_pointer
env.__syscall_stat64
env.__table_base
env.emscripten_resize_heap
env.memory
wasi_snapshot_preview1.environ_get
wasi_snapshot_preview1.environ_sizes_get
wasi_snapshot_preview1.fd_write
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.jssize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
12857
27782
5 changes: 5 additions & 0 deletions test/other/codesize/test_codesize_hello_dylink.sent
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
__heap_base
__indirect_function_table
__memory_base
__stack_high
__stack_low
__stack_pointer
__syscall_stat64
__table_base
emscripten_resize_heap
environ_get
environ_sizes_get
fd_write
memory
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.size
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8176
18550
98 changes: 87 additions & 11 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -7564,16 +7564,30 @@ def test_RUNTIME_LINKED_LIBS(self):

@parameterized({
'': ([],),
'wasmfs': (['-sWASMFS'],),
'pthread': (['-g', '-pthread', '-Wno-experimental', '-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
})
def test_ld_library_path(self, args):
if args:
if '-pthread' in args:
self.skipTest('Problems with readFile from pthread')
if '-pthread' in args:
self.setup_node_pthreads()
create_file('hello1_dep.c', r'''
#include <stdio.h>

void hello1_dep() {
printf("Hello1_dep\n");
return;
}
''')
create_file('hello1.c', r'''
#include <stdio.h>

void hello1_dep();

void hello1() {
printf("Hello1\n");
hello1_dep();
return;
}
''')
Expand All @@ -7589,7 +7603,7 @@ def test_ld_library_path(self, args):
#include <stdio.h>

void hello3() {
printf ("Hello3\n");
printf("Hello3\n");
return;
}
''')
Expand Down Expand Up @@ -7653,23 +7667,85 @@ def test_ld_library_path(self, args):
return 0;
}
''')
self.run_process([EMCC, '-o', 'hello1.wasm', 'hello1.c', '-sSIDE_MODULE'] + args)
os.mkdir('subdir')
self.run_process([EMCC, '-o', 'subdir/libhello1_dep.so', 'hello1_dep.c', '-sSIDE_MODULE'])
self.run_process([EMCC, '-o', 'hello1.wasm', 'hello1.c', '-sSIDE_MODULE', 'subdir/libhello1_dep.so'] + args)
self.run_process([EMCC, '-o', 'hello2.wasm', 'hello2.c', '-sSIDE_MODULE'] + args)
self.run_process([EMCC, '-o', 'hello3.wasm', 'hello3.c', '-sSIDE_MODULE'] + args)
self.run_process([EMCC, '-o', 'hello4.wasm', 'hello4.c', '-sSIDE_MODULE'] + args)
self.run_process([EMCC, '--profiling-funcs', '-o', 'main.js', 'main.c', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
emcc_args = ['--profiling-funcs', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
'-L./subdir',
'--embed-file', 'subdir/libhello1_dep.so@/usr/lib/libhello1_dep.so',
'--embed-file', 'hello1.wasm@/lib/libhello1.wasm',
'--embed-file', 'hello2.wasm@/usr/lib/libhello2.wasm',
'--embed-file', 'hello3.wasm@/libhello3.wasm',
'--embed-file', 'hello4.wasm@/usr/local/lib/libhello4.wasm',
'hello1.wasm', 'hello2.wasm', 'hello3.wasm', 'hello4.wasm', '-sNO_AUTOLOAD_DYLIBS',
'--pre-js', 'pre.js'] + args)
out = self.run_js('main.js')
self.assertContained('Hello1', out)
self.assertContained('Hello2', out)
self.assertContained('Hello3', out)
self.assertContained('Hello4', out)
self.assertContained('Ok', out)
'--pre-js', 'pre.js'] + args
self.do_runf('main.c', 'Hello1\nHello1_dep\nHello2\nHello3\nHello4\nOk\n', emcc_args=emcc_args)

@also_with_wasmfs
def test_dlopen_rpath(self):
create_file('hello_dep.c', r'''
#include <stdio.h>

void hello_dep() {
printf("Hello_dep\n");
return;
}
''')
create_file('hello.c', r'''
#include <stdio.h>

void hello_dep();

void hello() {
printf("Hello\n");
hello_dep();
return;
}
''')
create_file('main.c', r'''
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>

int main() {
void *h;
void (*f)();
double (*f2)(double);

h = dlopen("/usr/lib/libhello.wasm", RTLD_NOW);
assert(h);
f = dlsym(h, "hello");
assert(f);
f();
dlclose(h);

printf("Ok\n");

return 0;
}
''')
os.mkdir('subdir')

def _build(rpath_flag, expected, **kwds):
self.run_process([EMCC, '-o', 'subdir/libhello_dep.so', 'hello_dep.c', '-sSIDE_MODULE'])
self.run_process([EMCC, '-o', 'hello.wasm', 'hello.c', '-sSIDE_MODULE', 'subdir/libhello_dep.so'] + rpath_flag)
args = ['--profiling-funcs', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
'--embed-file', 'hello.wasm@/usr/lib/libhello.wasm',
'--embed-file', 'subdir/libhello_dep.so@/usr/lib/subdir/libhello_dep.so',
'hello.wasm', '-sNO_AUTOLOAD_DYLIBS',
'-L./subdir', '-lhello_dep']
self.do_runf('main.c', expected, emcc_args=args, **kwds)

# case 1) without rpath: fail to locate the library
_build([], r"no such file or directory, open '.*libhello_dep\.so'", regex=True, assert_returncode=NON_ZERO)

# case 2) with rpath: success
_build(['-Wl,-rpath,$ORIGIN/subdir'], "Hello\nHello_dep\nOk\n")

def test_dlopen_bad_flags(self):
create_file('main.c', r'''
Expand Down
Loading