diff --git a/src/lib/libdylink.js b/src/lib/libdylink.js index 8a2cda3591a82..b1e70178286f6 100644 --- a/src/lib/libdylink.js +++ b/src/lib/libdylink.js @@ -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) => { @@ -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 @@ -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. // @@ -954,6 +1022,7 @@ var LibraryDylink = { '$asyncLoad', #if FILESYSTEM '$preloadedWasm', + '$findLibraryFS', #endif #if DYNCALLS || !WASM_BIGINT '$registerDynCallSymbols', @@ -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); diff --git a/system/lib/libc/dynlink.c b/system/lib/libc/dynlink.c index f5d0c57e95531..71d9e14b98455 100644 --- a/system/lib/libc/dynlink.c +++ b/system/lib/libc/dynlink.c @@ -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 (;;) { @@ -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; } @@ -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) { @@ -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); diff --git a/test/other/codesize/test_codesize_hello_dylink.exports b/test/other/codesize/test_codesize_hello_dylink.exports index 99ba54eca755a..ad9fe79a10e50 100644 --- a/test/other/codesize/test_codesize_hello_dylink.exports +++ b/test/other/codesize/test_codesize_hello_dylink.exports @@ -1,5 +1,6 @@ __wasm_apply_data_relocs __wasm_call_ctors +_emscripten_find_dylib _emscripten_stack_alloc _emscripten_stack_restore calloc diff --git a/test/other/codesize/test_codesize_hello_dylink.funcs b/test/other/codesize/test_codesize_hello_dylink.funcs index 67e34312338f4..ca9eb3ea2d71b 100644 --- a/test/other/codesize/test_codesize_hello_dylink.funcs +++ b/test/other/codesize/test_codesize_hello_dylink.funcs @@ -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 diff --git a/test/other/codesize/test_codesize_hello_dylink.gzsize b/test/other/codesize/test_codesize_hello_dylink.gzsize index 1c3de1ed95f5f..27f49eac23ee3 100644 --- a/test/other/codesize/test_codesize_hello_dylink.gzsize +++ b/test/other/codesize/test_codesize_hello_dylink.gzsize @@ -1 +1 @@ -5875 +11753 diff --git a/test/other/codesize/test_codesize_hello_dylink.imports b/test/other/codesize/test_codesize_hello_dylink.imports index ed7c6d9be94fb..a183f50451034 100644 --- a/test/other/codesize/test_codesize_hello_dylink.imports +++ b/test/other/codesize/test_codesize_hello_dylink.imports @@ -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 diff --git a/test/other/codesize/test_codesize_hello_dylink.jssize b/test/other/codesize/test_codesize_hello_dylink.jssize index 0b2eb308687d0..8c789d48edc34 100644 --- a/test/other/codesize/test_codesize_hello_dylink.jssize +++ b/test/other/codesize/test_codesize_hello_dylink.jssize @@ -1 +1 @@ -12857 +27782 diff --git a/test/other/codesize/test_codesize_hello_dylink.sent b/test/other/codesize/test_codesize_hello_dylink.sent index 1ca2e37d90365..b09314e54011e 100644 --- a/test/other/codesize/test_codesize_hello_dylink.sent +++ b/test/other/codesize/test_codesize_hello_dylink.sent @@ -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 diff --git a/test/other/codesize/test_codesize_hello_dylink.size b/test/other/codesize/test_codesize_hello_dylink.size index ac37333bb7dd7..912b7ba8cf298 100644 --- a/test/other/codesize/test_codesize_hello_dylink.size +++ b/test/other/codesize/test_codesize_hello_dylink.size @@ -1 +1 @@ -8176 +18550 diff --git a/test/test_other.py b/test/test_other.py index 7b917fe2e55e7..2cf71747f61eb 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -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 + +void hello1_dep() { + printf("Hello1_dep\n"); + return; +} +''') create_file('hello1.c', r''' #include +void hello1_dep(); + void hello1() { printf("Hello1\n"); + hello1_dep(); return; } ''') @@ -7589,7 +7603,7 @@ def test_ld_library_path(self, args): #include void hello3() { - printf ("Hello3\n"); + printf("Hello3\n"); return; } ''') @@ -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 + + void hello_dep() { + printf("Hello_dep\n"); + return; + } + ''') + create_file('hello.c', r''' + #include + + void hello_dep(); + + void hello() { + printf("Hello\n"); + hello_dep(); + return; + } + ''') + create_file('main.c', r''' + #include + #include + #include + #include + #include + + 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''' diff --git a/tools/emscripten.py b/tools/emscripten.py index 55ffea93aa7ce..5c724fb6f4897 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -1125,6 +1125,7 @@ def create_pointer_conversion_wrappers(metadata): '_emscripten_set_offscreencanvas_size_on_thread': '_pp__', 'fileno': '_p', '_emscripten_run_callback_on_thread': '_pp_pp', + '_emscripten_find_dylib': 'ppppp', } for function in settings.SIGNATURE_CONVERSIONS: