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 12 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
92 changes: 87 additions & 5 deletions src/lib/libdylink.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,59 @@ var LibraryDylink = {
registerWasmPlugin();
`,
$preloadedWasm: {},

$locateLibraryFromFS: (filename, searchDirs) => {
// Find the library in the filesystem.
// returns null if not found.
if (!(typeof PATH === 'object' && typeof FS === 'object')) {
// FILESYSTEM comes with FS and PATH modules
// so this will always pass, but closure compiler complains if we don't check this here.
return null
}

var candidates = [];
if (PATH.isAbs(filename)) {
candidates.push(filename);
} else if (searchDirs) {
for (var i = 0; i < searchDirs.length; i++) {
candidates.push(PATH.join(searchDirs[i], filename));
}
} else {
return null;
}

for (var i = 0; i < candidates.length; i++) {
var path = candidates[i];
try {
var res = FS.lookupPath(path);
return res.path;
} catch(e) {
// do nothing is file is not found
}
}

return null;
},

$readLibraryFromFS: (path) => {
var data = FS.readFile(path, {encoding: 'binary'});
return data;
},

$getDefaultLibDirs__deps: ['$ENV'],
$getDefaultLibDirs: () => {
return ENV['LD_LIBRARY_PATH']?.split(':') ?? [];
},

$replaceORIGIN: (parentLibPath, 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(parentLibPath);
return rpath.replace('$ORIGIN', origin);
}

return rpath
},
#endif // FILESYSTEM

$isSymbolDefined: (symName) => {
Expand Down Expand Up @@ -889,12 +942,12 @@ var LibraryDylink = {
if (flags.loadAsync) {
return metadata.neededDynlibs
.reduce((chain, dynNeeded) => chain.then(() =>
loadDynamicLibrary(dynNeeded, flags, localScope)
loadDynamicLibrary(dynNeeded, flags, localScope, { parentLibPath: libName, paths: metadata.runtimePaths })
), Promise.resolve())
.then(loadModule);
}

metadata.neededDynlibs.forEach((needed) => loadDynamicLibrary(needed, flags, localScope));
metadata.neededDynlibs.forEach((needed) => loadDynamicLibrary(needed, flags, localScope, { parentLibPath: libName, paths: metadata.runtimePaths }));
return loadModule();
},

Expand Down Expand Up @@ -949,6 +1002,10 @@ var LibraryDylink = {
'$asyncLoad',
#if FILESYSTEM
'$preloadedWasm',
'$locateLibraryFromFS',
'$readLibraryFromFS',
'$getDefaultLibDirs',
'$replaceORIGIN',
#endif
#if DYNCALLS || !WASM_BIGINT
'$registerDynCallSymbols',
Expand All @@ -959,11 +1016,24 @@ var LibraryDylink = {
* @param {number=} handle
* @param {Object=} localScope
*/`,
$loadDynamicLibrary: function(libName, flags = {global: true, nodelete: true}, localScope, handle) {
$loadDynamicLibrary: function(libName, flags = {global: true, nodelete: true}, localScope, rpath = {parentLibPath: '', paths: []}, handle) {
#if DYLINK_DEBUG
dbg(`loadDynamicLibrary: ${libName} handle: ${handle}`);
dbg(`existing: ${Object.keys(LDSO.loadedLibsByName)}`);
#endif

#if FILESYSTEM
var runtimePathsAbs = (rpath.paths || []).map((p) => replaceORIGIN(rpath.parentLibPath, p));
var searchDirs = getDefaultLibDirs().concat(runtimePathsAbs);
var libNameAbs = locateLibraryFromFS(libName, searchDirs);
if (libNameAbs) {
libName = libNameAbs;
}
#if DYLINK_DEBUG
dbg(`checking filesystem: ${libName}: ${libNameAbs ? 'found' : 'not found'}`);
#endif
#endif

// when loadDynamicLibrary did not have flags, libraries were loaded
// globally & permanently

Expand Down Expand Up @@ -1024,6 +1094,18 @@ var LibraryDylink = {
}
}

#if FILESYSTEM
if (libNameAbs) {
var libData = readLibraryFromFS(libName);
if (libData) {
#if DYLINK_DEBUG
dbg(`loaded library from filesystem: ${libName}`);
#endif
return flags.loadAsync ? Promise.resolve(libData) : libData;
}
}
#endif

var libFile = locateFile(libName);
if (flags.loadAsync) {
return asyncLoad(libFile);
Expand Down Expand Up @@ -1142,11 +1224,11 @@ var LibraryDylink = {
}

if (jsflags.loadAsync) {
return loadDynamicLibrary(filename, combinedFlags, localScope, handle);
return loadDynamicLibrary(filename, combinedFlags, localScope, {}, handle);
}

try {
return loadDynamicLibrary(filename, combinedFlags, localScope, handle)
return loadDynamicLibrary(filename, combinedFlags, localScope, {}, handle)
} catch (e) {
#if ASSERTIONS
err(`Error in loading dynamic library ${filename}: ${e}`);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/libwasmfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ addToLibrary({
return FS_mknod(path, mode, 0);
},

$FS_writeFile__deps: ['_wasmfs_write_file', '$stackSave', '$stackRestore'],
$FS_writeFile__deps: ['_wasmfs_write_file', '$stackSave', '$stackRestore', 'malloc', 'free'],
$FS_writeFile: (path, data) => {
var sp = stackSave();
var pathBuffer = stringToUTF8OnStack(path);
Expand Down
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 @@
5892
6148
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 @@
12932
13509
129 changes: 129 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -7662,6 +7662,135 @@ def test_ld_library_path(self, args):
self.assertContained('Hello4', out)
self.assertContained('Ok', out)

def test_ld_library_path_dependencies(self):
create_file('hello1_dep.c', r'''
#include<stdio.h>

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

void hello1_2();

void hello1() {
printf ("Hello1\n");
hello1_2();
return;
}
''')
create_file('pre.js', r'''
Module.preRun = () => {
ENV['LD_LIBRARY_PATH']='/lib:/usr/lib:/usr/local/lib';
};
''')
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("libhello1.wasm", RTLD_NOW);
assert(h);
f = dlsym(h, "hello1");
assert(f);
f();
dlclose(h);

printf("Ok\n");

return 0;
}
''')
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'])
self.run_process([EMCC, '--profiling-funcs', '-o', 'main.js', 'main.c', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
'--embed-file', 'hello1.wasm@/libhello1.wasm',
'--embed-file', 'subdir/libhello1_dep.so@/usr/lib/libhello1_dep.so',
'hello1.wasm', '-sNO_AUTOLOAD_DYLIBS',
'-L./subdir', '-lhello1_dep', '--pre-js', 'pre.js'])
out = self.run_js('main.js')
self.assertContained('Hello1', out)
self.assertContained('Hello1_2', out)
self.assertContained('Ok', out)

def test_rpath_dependencies(self):
create_file('hello1_dep.c', r'''
#include<stdio.h>

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

void hello1_2();

void hello1() {
printf ("Hello1\n");
hello1_2();
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/libhello1.wasm", RTLD_NOW);
assert(h);
f = dlsym(h, "hello1");
assert(f);
f();
dlclose(h);

printf("Ok\n");

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

def _build(rpath_flag):
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'] + rpath_flag)
self.run_process([EMCC, '--profiling-funcs', '-o', 'main.js', 'main.c', '-sMAIN_MODULE=2', '-sINITIAL_MEMORY=32Mb',
'--embed-file', 'hello1.wasm@/usr/lib/libhello1.wasm',
'--embed-file', 'subdir/libhello1_dep.so@/usr/lib/subdir/libhello1_dep.so',
'hello1.wasm', '-sNO_AUTOLOAD_DYLIBS',
'-L./subdir', '-lhello1_dep'])

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

# case 2) with rpath: success
_build(['-Wl,-rpath,$ORIGIN/subdir'])
out = self.run_js('main.js')
self.assertContained('Hello1', out)
self.assertContained('Hello1_2', out)
self.assertContained('Ok', out)

def test_dlopen_bad_flags(self):
create_file('main.c', r'''
#include <dlfcn.h>
Expand Down