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

Conversation

ryanking13
Copy link
Contributor

@ryanking13 ryanking13 commented Mar 9, 2025

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.

This is an automatic change generated by tools/maint/rebaseline_tests.py.

The following (2) test expectation files were updated by
running the tests with `--rebaseline`:

```
other/codesize/test_codesize_hello_dylink.gzsize: 5892 => 11266 [+5374 bytes / +91.21%]
other/codesize/test_codesize_hello_dylink.jssize: 12932 => 26423 [+13491 bytes / +104.32%]

Average change: +97.77% (+91.21% - +104.32%)
```
This is an automatic change generated by tools/maint/rebaseline_tests.py.

The following (2) test expectation files were updated by
running the tests with `--rebaseline`:

```
other/codesize/test_codesize_hello_dylink.gzsize: 11266 => 11204 [-62 bytes / -0.55%]
other/codesize/test_codesize_hello_dylink.jssize: 26423 => 26287 [-136 bytes / -0.51%]

Average change: -0.53% (-0.55% - -0.51%)
```
This is an automatic change generated by tools/maint/rebaseline_tests.py.

The following (2) test expectation files were updated by
running the tests with `--rebaseline`:

```
other/codesize/test_codesize_hello_dylink.gzsize: 11204 => 6148 [-5056 bytes / -45.13%]
other/codesize/test_codesize_hello_dylink.jssize: 26287 => 13509 [-12778 bytes / -48.61%]

Average change: -46.87% (-48.61% - -45.13%)
```
This is an automatic change generated by tools/maint/rebaseline_tests.py.

The following (2) test expectation files were updated by
running the tests with `--rebaseline`:

```
other/codesize/test_codesize_hello_dylink.gzsize: 6148 => 11089 [+4941 bytes / +80.37%]
other/codesize/test_codesize_hello_dylink.jssize: 13509 => 25945 [+12436 bytes / +92.06%]

Average change: +86.21% (+80.37% - +92.06%)
```
@ryanking13
Copy link
Contributor Author

The CI is green now, but two issues are remaining:

  1. I wasn't able to remove $FS and $PATH dependency. ifdef hacks doesn't seem to work. And marking those dependencies seems to always link libfs and libpath (even when they are in the ifdef FILESYSTEM block), increasing the output size a lot for the codesize_hello_dylink test.

  2. It seems like wasmfs doesn't yet implement FS.lookupPath. So I ended up checking FS.lookupPath existence and early return.

@ryanking13 ryanking13 requested review from hoodmane and sbc100 March 12, 2025 08:16
@sbc100
Copy link
Collaborator

sbc100 commented Mar 12, 2025

There is existing logic in system/lib/libc/dynlink.c which searches LD_LIBRARY_PATH so we would somehow avoid searching that path twice.

@ryanking13
Copy link
Contributor Author

ryanking13 commented Mar 13, 2025

There is existing logic in system/lib/libc/dynlink.c which searches LD_LIBRARY_PATH so we would somehow avoid searching that path twice.

Yes, but that only happens in top-level libraries, I guess? Here is the code flow I understand:

  1. A user call dlopen("lib.so") in their c codebase
  2. LD_LIBRARY_PATH will be searched inside dlopen, replace the lib.so with the absolute path, and call _emscripten_dlopen_js js function with the resolved path.
  3. _emscripten_dlopen_js will internally call loadDynamicLibrary with the absolute path (== loadDynamicLibrary('/abs/path/of/lib.so') )
  4. Since the absolute path is given, we don't need to search LD_LIBRARY_PATH and load the file directly.
  5. loadDynamicLibrary starts to load the dependencies of lib.so, e.g. dep1.so ( == loadDynamicLibrary('dep1.so'))
  6. We don't know where to locate dep1.so. Therefore, search LD_LIBRARY_PATH and rpaths of lib.so to locate dep1.so.

@hoodmane

This comment was marked as outdated.

@sbc100
Copy link
Collaborator

sbc100 commented Mar 13, 2025

Yes, I think something like that would be good idea. If we do go that route I might suggest that we implement _dylink_resolve_path_js first as a separate NFC (Non-Functional Change) and then add rpath support as a followup.

@hoodmane
Copy link
Collaborator

Okay I addressed all comments and tests seem to be passing locally for me. So this is back to being ready for review.

@hoodmane
Copy link
Collaborator

test_ld_library_path_pthread is broken because FS.readFile() doesn't work there. I guess we have to solve it with proxying.

@sbc100
Copy link
Collaborator

sbc100 commented Apr 16, 2025

test_ld_library_path_pthread is broken because FS.readFile() doesn't work there. I guess we have to solve it with proxying.

Hmm.. yes maybe use FS_readFile and mark that with __proxy attribute.

@hoodmane
Copy link
Collaborator

But the arguments to/return value of FS_readFile aren't pointers.

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with some final comments.

Thanks for bearing with me on this one.. i know it was a lot of back and forth.

@sbc100
Copy link
Collaborator

sbc100 commented Apr 16, 2025

test_ld_library_path_pthread is broken because FS.readFile() doesn't work there. I guess we have to solve it with proxying.

Hmm.. yes maybe use FS_readFile and mark that with __proxy attribute.

Oh I see.. so we cannot proxy it :(

Maybe just skip the test for now? And we can try to fix it as a followup? My understanding was that all this loading should happen on the main thread anyway since he original dlopen is always proxied back, but maybe I'm misremembering.

@sbc100
Copy link
Collaborator

sbc100 commented Apr 16, 2025

test_ld_library_path_pthread is broken because FS.readFile() doesn't work there. I guess we have to solve it with proxying.

Hmm.. yes maybe use FS_readFile and mark that with __proxy attribute.

Oh I see.. so we cannot proxy it :(

Maybe just skip the test for now? And we can try to fix it as a followup? My understanding was that all this loading should happen on the main thread anyway since he original dlopen is always proxied back, but maybe I'm misremembering.

Yes, IIRC all the initial loading should happen on the main thread and then background threads should should be able mirror all the same loads but hopfully we can avoid re-resolving all the pathnames on the background threads since them main thread already resolved them.

@hoodmane
Copy link
Collaborator

Shouldn't the side threads hit the if (handle) { condition and never make it to this logic?

@hoodmane
Copy link
Collaborator

Okay I skipped test_ld_library_path in pthreads and added a WASMFS variant to it.

@hoodmane hoodmane merged commit 9be7fb4 into emscripten-core:main Apr 16, 2025
28 checks passed
@ryanking13
Copy link
Contributor Author

Thanks so much for finishing the PR @hoodmane @sbc100! I know it was a lot of work and I really appreciate it. While this change is not visible to the users right now, I believe this is going to be a big progress in Pyodide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants