|
| 1 | +From 1a07b14227c65ae49d933584bb8760f3980edfed Mon Sep 17 00:00:00 2001 |
| 2 | +From: Hood Chatham <roberthoodchatham@gmail.com> |
| 3 | +Date: Fri, 31 Oct 2025 15:29:33 -0700 |
| 4 | +Subject: [PATCH 3/3] Fix nested dynamic library loading via RPATH |
| 5 | + |
| 6 | +There is a bug with dynamic library loading. Suppose |
| 7 | +liba.so is on LD_LIBRARY_PATH and it has an RPATH of `$ORIGIN/other_dir` |
| 8 | +and loads `libb.so` from other_dir. Then suppose `libb.so` has an RPATH of |
| 9 | +`$ORIGIN` and wants to load `libc.so` also from `other_dir`. |
| 10 | + |
| 11 | +Before this PR this doesn't work. The problem is that `flags.rpath.parentLibPath` |
| 12 | +is set to `libb.so` and we replace $ORIGIN with `PATH.dirname(parentLibName)` |
| 13 | +which is `.`. So unless `other_dir` is on the `LD_LIBRARY_PATH` or is the |
| 14 | +current working directory, loading will fail. |
| 15 | + |
| 16 | +The fix: if `findLibraryFS()` returns a value that is not `undefined`, replace |
| 17 | +`libName` with the returned value. |
| 18 | +--- |
| 19 | + src/lib/libdylink.js | 4 ++- |
| 20 | + test/test_other.py | 61 ++++++++++++++++++++++++++++++++++++++++++++ |
| 21 | + 2 files changed, 64 insertions(+), 1 deletion(-) |
| 22 | + |
| 23 | +diff --git a/src/lib/libdylink.js b/src/lib/libdylink.js |
| 24 | +index 737a9773e..c1d2c72d9 100644 |
| 25 | +--- a/src/lib/libdylink.js |
| 26 | ++++ b/src/lib/libdylink.js |
| 27 | +@@ -990,7 +990,7 @@ var LibraryDylink = { |
| 28 | + #endif |
| 29 | + } |
| 30 | + var rpathResolved = (rpath?.paths || []).map((p) => replaceORIGIN(rpath?.parentLibPath, p)); |
| 31 | +- return withStackSave(() => { |
| 32 | ++ var result = withStackSave(() => { |
| 33 | + // In dylink.c we use: `char buf[2*NAME_MAX+2];` and NAME_MAX is 255. |
| 34 | + // So we use the same size here. |
| 35 | + var bufSize = 2*255 + 2; |
| 36 | +@@ -1000,6 +1000,7 @@ var LibraryDylink = { |
| 37 | + var resLibNameC = __emscripten_find_dylib(buf, rpathC, libNameC, bufSize); |
| 38 | + return resLibNameC ? UTF8ToString(resLibNameC) : undefined; |
| 39 | + }); |
| 40 | ++ return FS.lookupPath(result).path; |
| 41 | + }, |
| 42 | + #endif // FILESYSTEM |
| 43 | + |
| 44 | +@@ -1107,6 +1108,7 @@ var LibraryDylink = { |
| 45 | + dbg(`checking filesystem: ${libName}: ${f ? 'found' : 'not found'}`); |
| 46 | + #endif |
| 47 | + if (f) { |
| 48 | ++ libName = f; |
| 49 | + var libData = FS.readFile(f, {encoding: 'binary'}); |
| 50 | + return flags.loadAsync ? Promise.resolve(libData) : libData; |
| 51 | + } |
| 52 | +diff --git a/test/test_other.py b/test/test_other.py |
| 53 | +index 57ceb9f09..1baca1251 100644 |
| 54 | +--- a/test/test_other.py |
| 55 | ++++ b/test/test_other.py |
| 56 | +@@ -2381,6 +2381,67 @@ Module['postRun'] = () => { |
| 57 | + self.assertEqual(get_runtime_paths('libside1.so'), ['$ORIGIN']) |
| 58 | + self.assertEqual(get_runtime_paths('a.out.wasm'), ['$ORIGIN']) |
| 59 | + |
| 60 | ++ def test_dylink_dependencies_rpath_nested(self): |
| 61 | ++ create_file('pre.js', r''' |
| 62 | ++ Module.preRun.push(() => { |
| 63 | ++ Module.ENV.LD_LIBRARY_PATH = "/lib1"; |
| 64 | ++ }); |
| 65 | ++ ''') |
| 66 | ++ create_file('side1.c', r''' |
| 67 | ++ #include <stdio.h> |
| 68 | ++ |
| 69 | ++ void side2(); |
| 70 | ++ |
| 71 | ++ void side1() { |
| 72 | ++ printf("side1\n"); |
| 73 | ++ side2(); |
| 74 | ++ } |
| 75 | ++ ''') |
| 76 | ++ create_file('side2.c', r''' |
| 77 | ++ #include <stdio.h> |
| 78 | ++ void side3(); |
| 79 | ++ |
| 80 | ++ void side2() { |
| 81 | ++ printf("side2\n"); |
| 82 | ++ side3(); |
| 83 | ++ } |
| 84 | ++ ''') |
| 85 | ++ create_file('side3.c', r''' |
| 86 | ++ #include <stdio.h> |
| 87 | ++ |
| 88 | ++ void side3() { |
| 89 | ++ printf("side3\n"); |
| 90 | ++ } |
| 91 | ++ ''') |
| 92 | ++ create_file('main.c', r''' |
| 93 | ++ #include <dlfcn.h> |
| 94 | ++ #include <stdio.h> |
| 95 | ++ |
| 96 | ++ typedef void (*F)(void); |
| 97 | ++ |
| 98 | ++ int main() { |
| 99 | ++ void* handle = dlopen("libside1.so", RTLD_NOW); |
| 100 | ++ F side1 = (F)dlsym(handle, "side1"); |
| 101 | ++ |
| 102 | ++ printf("main\n"); |
| 103 | ++ side1(); |
| 104 | ++ return 0; |
| 105 | ++ } |
| 106 | ++ ''') |
| 107 | ++ os.mkdir('libs') |
| 108 | ++ os.mkdir('lib1') |
| 109 | ++ self.emcc('side3.c', ['-fPIC', '-sSIDE_MODULE', '-olibs/libside3.so']) |
| 110 | ++ self.emcc('side2.c', ['-fPIC', '-sSIDE_MODULE', '-olibs/libside2.so', '-Wl,-rpath,$ORIGIN', 'libs/libside3.so']) |
| 111 | ++ self.emcc('side1.c', ['-fPIC', '-sSIDE_MODULE', '-Wl,-rpath,$ORIGIN/../libs', '-olib1/libside1.so', 'libs/libside2.so']) |
| 112 | ++ settings = ['-sMAIN_MODULE=2', '-sDYLINK_DEBUG', "-sEXPORTED_FUNCTIONS=[_printf,_main]", "-sEXPORTED_RUNTIME_METHODS=ENV"] |
| 113 | ++ preloads = [] |
| 114 | ++ for file in ['lib1/libside1.so', 'libs/libside2.so', 'libs/libside3.so']: |
| 115 | ++ preloads += ['--preload-file', file] |
| 116 | ++ cmd = [EMCC, 'main.c', '-fPIC', '--pre-js', 'pre.js'] + settings + preloads |
| 117 | ++ self.run_process(cmd) |
| 118 | ++ |
| 119 | ++ self.run_js('a.out.js') |
| 120 | ++ |
| 121 | + def test_dylink_LEGACY_GL_EMULATION(self): |
| 122 | + # LEGACY_GL_EMULATION wraps JS library functions. This test ensure that when it does |
| 123 | + # so it preserves the `.sig` attributes needed by dynamic linking. |
| 124 | +-- |
| 125 | +2.34.1 |
| 126 | + |
0 commit comments