Skip to content

Commit

Permalink
Teach jl_load_dynamic_library() about @executable_path on all pla…
Browse files Browse the repository at this point in the history
…tforms

It is useful for us to be able to hard-code a dynamic library's location
relative to that of the julia executable.  We repurpose the
`@executable_path` component from MacOS, allowing all platforms to
perform executable-relative binary loading (I'm looking at you, Windows).
  • Loading branch information
staticfloat committed May 14, 2020
1 parent 08861c7 commit 00abaf8
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 6 deletions.
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Compiler/Runtime improvements
-----------------------------


* All platforms can now use `@executable_path` within `jl_load_dynamic_library()`.
This allows executable-relative paths to be embedded within executables on all
platforms, not just MacOS, which the syntax is borrowed from. ([#35627])

Command-line option changes
---------------------------

Expand Down
21 changes: 15 additions & 6 deletions src/dlload.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ static int endswith_extension(const char *path)

#define PATHBUF 4096

extern char *julia_bindir;

#define JL_RTLD(flags, FLAG) (flags & JL_RTLD_ ## FLAG ? RTLD_ ## FLAG : 0)

#ifdef _OS_WINDOWS_
Expand Down Expand Up @@ -136,7 +134,7 @@ JL_DLLEXPORT int jl_dlclose(void *handle)

JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags, int throw_err)
{
char path[PATHBUF];
char path[PATHBUF], relocated[PATHBUF];
int i;
#ifdef _OS_WINDOWS_
int err;
Expand Down Expand Up @@ -173,6 +171,9 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags,
this branch permutes all base paths in DL_LOAD_PATH with all extensions
note: skip when !jl_base_module to avoid UndefVarError(:DL_LOAD_PATH),
and also skip for absolute paths
We also do simple string replacement here for elements starting with `@executable_path/`.
While these exist as OS concepts on Darwin, we want to use them on other platforms
such as Windows, so we emulate them here.
*/
if (!abspath && jl_base_module != NULL) {
jl_array_t *DL_LOAD_PATH = (jl_array_t*)jl_get_global(jl_base_module, jl_symbol("DL_LOAD_PATH"));
Expand All @@ -183,13 +184,21 @@ JL_DLLEXPORT void *jl_load_dynamic_library(const char *modname, unsigned flags,
size_t len = strlen(dl_path);
if (len == 0)
continue;

// Is this entry supposed to be relative to the bindir?
if (len >= 16 && strncmp(dl_path, "@executable_path", 16) == 0) {
snprintf(relocated, PATHBUF, "%s%s", jl_options.julia_bindir, dl_path + 16);
len = len - 16 + strlen(jl_options.julia_bindir);
} else {
strncpy(relocated, dl_path, len);

This comment has been minimized.

Copy link
@martinholters

martinholters Jun 4, 2020

Member

Should this be strncpy(relocated, dl_path, PATHBUF);? The way it is, it doesn't copy the terminating 0x00 from dl_path, so relies on relocated to be initialized with zeros, or am I missing something? (Even with PATHBUF, relocated could end up non-null-terminated if dl_path is too long, but I have no idea whether that can actually happen.)

This comment has been minimized.

Copy link
@staticfloat

staticfloat Jun 4, 2020

Author Member

I think making it be len + 1 would solve the issue of NULL termination.

This comment has been minimized.

Copy link
@martinholters

martinholters Jun 5, 2020

Member

Right, but then it would just be equivalent to strcpy(relocated, dl_path), which I guess was avoided for a reason. Using PATHBUF (or PATHBUF-1) and just unconditionally setting relocated[PATHBUF-1] = 0 should avoid writing past the end of relocated and ensure zero-termination. Does that make sense?

(I really haven't looked at the code in much detail, just noticed this compiler warning:

./src/dlload.c:193:21: warning: ‘strncpy’ output truncated before terminating nul copying as many bytes from a string as its length [-Wstringop-truncation]
                     strncpy(relocated, dl_path, len);
                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./src/dlload.c:184:30: note: length computed here
                 size_t len = strlen(dl_path);
                              ^~~~~~~~~~~~~~~

And from a brief glance concluded that this looks a bit fishy indeed.)

}
for (i = 0; i < n_extensions; i++) {
const char *ext = extensions[i];
path[0] = '\0';
if (dl_path[len-1] == PATHSEPSTRING[0])
snprintf(path, PATHBUF, "%s%s%s", dl_path, modname, ext);
if (relocated[len-1] == PATHSEPSTRING[0])
snprintf(path, PATHBUF, "%s%s%s", relocated, modname, ext);
else
snprintf(path, PATHBUF, "%s" PATHSEPSTRING "%s%s", dl_path, modname, ext);
snprintf(path, PATHBUF, "%s" PATHSEPSTRING "%s%s", relocated, modname, ext);
#ifdef _OS_WINDOWS_
if (i == 0) { // LoadLibrary already tested the extensions, we just need to check the `stat` result
#endif
Expand Down
5 changes: 5 additions & 0 deletions stdlib/Libdl/src/Libdl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ between shared libraries.
If the library cannot be found, this method throws an error, unless the keyword argument
`throw_error` is set to `false`, in which case this method returns `nothing`.
!!! note
From Julia 1.6 on, this method replaces paths starting with `@executable_path/` with
the path to the Julia executable, allowing for relocatable relative-path loads. In
Julia 1.5 and earlier, this only worked on macOS.
"""
function dlopen end

Expand Down

0 comments on commit 00abaf8

Please sign in to comment.