Skip to content

Commit 85a4a5e

Browse files
committed
Move Libdl into Base
We need to `dlopen()` things in Base a lot more due to using JLL packages within `Base` and the standard library. We move `Libdl` into `Base.Libc`, but continue to carry a shell `Libdl` stdlib that re-exports all relevant pieces of functionality, for backwards-compatibility.
1 parent a645d7f commit 85a4a5e

File tree

3 files changed

+318
-303
lines changed

3 files changed

+318
-303
lines changed

base/libc.jl

+4
Original file line numberDiff line numberDiff line change
@@ -400,4 +400,8 @@ Interface to the C `srand(seed)` function.
400400
"""
401401
srand(seed=floor(Int, time()) % Cuint) = ccall(:srand, Cvoid, (Cuint,), seed)
402402

403+
# Include dlopen()/dlpath() code
404+
include("libdl.jl")
405+
using .Libdl
406+
403407
end # module

base/libdl.jl

+309
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
module Libdl
4+
@doc """
5+
Interface to libdl. Provides dynamic linking support.
6+
""" Libdl
7+
8+
import Base.DL_LOAD_PATH
9+
10+
export DL_LOAD_PATH, RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL,
11+
RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW, dlclose, dlopen, dlopen_e, dlsym, dlsym_e,
12+
dlpath, find_library, dlext, dllist
13+
14+
"""
15+
DL_LOAD_PATH
16+
17+
When calling [`dlopen`](@ref), the paths in this list will be searched first, in
18+
order, before searching the system locations for a valid library handle.
19+
"""
20+
DL_LOAD_PATH
21+
22+
# note: constants to match JL_RTLD_* in src/julia.h, translated
23+
# to system-specific values by JL_RTLD macro in src/dlload.c
24+
const RTLD_LOCAL = 0x00000001
25+
const RTLD_GLOBAL = 0x00000002
26+
const RTLD_LAZY = 0x00000004
27+
const RTLD_NOW = 0x00000008
28+
const RTLD_NODELETE = 0x00000010
29+
const RTLD_NOLOAD = 0x00000020
30+
const RTLD_DEEPBIND = 0x00000040
31+
const RTLD_FIRST = 0x00000080
32+
33+
"""
34+
RTLD_DEEPBIND
35+
RTLD_FIRST
36+
RTLD_GLOBAL
37+
RTLD_LAZY
38+
RTLD_LOCAL
39+
RTLD_NODELETE
40+
RTLD_NOLOAD
41+
RTLD_NOW
42+
43+
Enum constant for [`dlopen`](@ref). See your platform man page for details, if
44+
applicable.
45+
"""
46+
(RTLD_DEEPBIND, RTLD_FIRST, RTLD_GLOBAL, RTLD_LAZY, RTLD_LOCAL, RTLD_NODELETE, RTLD_NOLOAD, RTLD_NOW)
47+
48+
"""
49+
dlsym(handle, sym)
50+
51+
Look up a symbol from a shared library handle, return callable function pointer on success.
52+
"""
53+
function dlsym(hnd::Ptr, s::Union{Symbol,AbstractString}; throw_error::Bool = true)
54+
hnd == C_NULL && throw(ArgumentError("NULL library handle"))
55+
val = Ref(Ptr{Cvoid}(0))
56+
symbol_found = ccall(:jl_dlsym, Cint,
57+
(Ptr{Cvoid}, Cstring, Ref{Ptr{Cvoid}}, Cint),
58+
hnd, s, val, Int64(throw_error)
59+
)
60+
if symbol_found == 0
61+
return nothing
62+
end
63+
return val[]
64+
end
65+
66+
"""
67+
dlsym_e(handle, sym)
68+
69+
Look up a symbol from a shared library handle, silently return `C_NULL` on lookup failure.
70+
This method is now deprecated in favor of `dlsym(handle, sym; throw_error=false)`.
71+
"""
72+
function dlsym_e(hnd::Ptr, s::Union{Symbol,AbstractString})
73+
return something(dlsym(hnd, s; throw_error=false), C_NULL)
74+
end
75+
76+
"""
77+
dlopen(libfile::AbstractString [, flags::Integer]; throw_error:Bool = true)
78+
79+
Load a shared library, returning an opaque handle.
80+
81+
The extension given by the constant `dlext` (`.so`, `.dll`, or `.dylib`)
82+
can be omitted from the `libfile` string, as it is automatically appended
83+
if needed. If `libfile` is not an absolute path name, then the paths
84+
in the array `DL_LOAD_PATH` are searched for `libfile`, followed by the
85+
system load path.
86+
87+
The optional flags argument is a bitwise-or of zero or more of `RTLD_LOCAL`, `RTLD_GLOBAL`,
88+
`RTLD_LAZY`, `RTLD_NOW`, `RTLD_NODELETE`, `RTLD_NOLOAD`, `RTLD_DEEPBIND`, and `RTLD_FIRST`.
89+
These are converted to the corresponding flags of the POSIX (and/or GNU libc and/or MacOS)
90+
dlopen command, if possible, or are ignored if the specified functionality is not available
91+
on the current platform. The default flags are platform specific. On MacOS the default
92+
`dlopen` flags are `RTLD_LAZY|RTLD_DEEPBIND|RTLD_GLOBAL` while on other platforms the
93+
defaults are `RTLD_LAZY|RTLD_DEEPBIND|RTLD_LOCAL`. An important usage of these flags is to
94+
specify non default behavior for when the dynamic library loader binds library references to
95+
exported symbols and if the bound references are put into process local or global scope. For
96+
instance `RTLD_LAZY|RTLD_DEEPBIND|RTLD_GLOBAL` allows the library's symbols to be available
97+
for usage in other shared libraries, addressing situations where there are dependencies
98+
between shared libraries.
99+
100+
If the library cannot be found, this method throws an error, unless the keyword argument
101+
`throw_error` is set to `false`, in which case this method returns `nothing`.
102+
"""
103+
function dlopen end
104+
105+
dlopen(s::Symbol, flags::Integer = RTLD_LAZY | RTLD_DEEPBIND; kwargs...) =
106+
dlopen(string(s), flags; kwargs...)
107+
108+
function dlopen(s::AbstractString, flags::Integer = RTLD_LAZY | RTLD_DEEPBIND; throw_error::Bool = true)
109+
ret = ccall(:jl_load_dynamic_library, Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint(throw_error))
110+
if ret == C_NULL
111+
return nothing
112+
end
113+
return ret
114+
end
115+
116+
"""
117+
dlopen(f::Function, args...; kwargs...)
118+
119+
Wrapper for usage with `do` blocks to automatically close the dynamic library once
120+
control flow leaves the `do` block scope.
121+
122+
# Example
123+
```julia
124+
vendor = dlopen("libblas") do lib
125+
if Libdl.dlsym(lib, :openblas_set_num_threads; throw_error=false) !== nothing
126+
return :openblas
127+
else
128+
return :other
129+
end
130+
end
131+
```
132+
"""
133+
function dlopen(f::Function, args...; kwargs...)
134+
hdl = nothing
135+
try
136+
hdl = dlopen(args...; kwargs...)
137+
f(hdl)
138+
finally
139+
dlclose(hdl)
140+
end
141+
end
142+
143+
"""
144+
dlopen_e(libfile::AbstractString [, flags::Integer])
145+
146+
Similar to [`dlopen`](@ref), except returns `C_NULL` instead of raising errors.
147+
This method is now deprecated in favor of `dlopen(libfile::AbstractString [, flags::Integer]; throw_error=false)`.
148+
"""
149+
dlopen_e(args...) = something(dlopen(args...; throw_error=false), C_NULL)
150+
151+
"""
152+
dlclose(handle)
153+
154+
Close shared library referenced by handle.
155+
"""
156+
function dlclose(p::Ptr)
157+
0 == ccall(:jl_dlclose, Cint, (Ptr{Cvoid},), p)
158+
end
159+
160+
"""
161+
dlclose(::Nothing)
162+
163+
For the very common pattern usage pattern of
164+
165+
try
166+
hdl = dlopen(library_name)
167+
... do something
168+
finally
169+
dlclose(hdl)
170+
end
171+
172+
We define a `dlclose()` method that accepts a parameter of type `Nothing`, so
173+
that user code does not have to change its behavior for the case that `library_name`
174+
was not found.
175+
"""
176+
function dlclose(p::Nothing)
177+
end
178+
179+
"""
180+
find_library(names, locations)
181+
182+
Searches for the first library in `names` in the paths in the `locations` list,
183+
`DL_LOAD_PATH`, or system library paths (in that order) which can successfully be dlopen'd.
184+
On success, the return value will be one of the names (potentially prefixed by one of the
185+
paths in locations). This string can be assigned to a `global const` and used as the library
186+
name in future `ccall`'s. On failure, it returns the empty string.
187+
"""
188+
function find_library(libnames, extrapaths=String[])
189+
for lib in libnames
190+
for path in extrapaths
191+
l = joinpath(path, lib)
192+
p = dlopen(l, RTLD_LAZY; throw_error=false)
193+
if p !== nothing
194+
dlclose(p)
195+
return l
196+
end
197+
end
198+
p = dlopen(lib, RTLD_LAZY; throw_error=false)
199+
if p !== nothing
200+
dlclose(p)
201+
return lib
202+
end
203+
end
204+
return ""
205+
end
206+
find_library(libname::Union{Symbol,AbstractString}, extrapaths=String[]) =
207+
find_library([string(libname)], extrapaths)
208+
209+
"""
210+
dlpath(handle::Ptr{Cvoid})
211+
212+
Given a library `handle` from `dlopen`, return the full path.
213+
"""
214+
function dlpath(handle::Ptr{Cvoid})
215+
p = ccall(:jl_pathname_for_handle, Cstring, (Ptr{Cvoid},), handle)
216+
s = unsafe_string(p)
217+
Sys.iswindows() && Libc.free(p)
218+
return s
219+
end
220+
221+
"""
222+
dlpath(libname::Union{AbstractString, Symbol})
223+
224+
Get the full path of the library `libname`.
225+
226+
# Example
227+
```julia-repl
228+
julia> dlpath("libjulia")
229+
```
230+
"""
231+
function dlpath(libname::Union{AbstractString, Symbol})
232+
handle = dlopen(libname)
233+
path = dlpath(handle)
234+
dlclose(handle)
235+
return path
236+
end
237+
238+
if Sys.isapple()
239+
const dlext = "dylib"
240+
elseif Sys.iswindows()
241+
const dlext = "dll"
242+
else
243+
#assume Sys.islinux, or similar
244+
const dlext = "so"
245+
end
246+
247+
"""
248+
dlext
249+
250+
File extension for dynamic libraries (e.g. dll, dylib, so) on the current platform.
251+
"""
252+
dlext
253+
254+
if (Sys.islinux() || Sys.isbsd()) && !Sys.isapple()
255+
struct dl_phdr_info
256+
# Base address of object
257+
addr::Cuint
258+
259+
# Null-terminated name of object
260+
name::Ptr{UInt8}
261+
262+
# Pointer to array of ELF program headers for this object
263+
phdr::Ptr{Cvoid}
264+
265+
# Number of program headers for this object
266+
phnum::Cshort
267+
end
268+
269+
# This callback function called by dl_iterate_phdr() on Linux and BSD's
270+
# DL_ITERATE_PHDR(3) on freebsd
271+
function dl_phdr_info_callback(di::dl_phdr_info, size::Csize_t, dynamic_libraries::Array{String,1})
272+
name = unsafe_string(di.name)
273+
push!(dynamic_libraries, name)
274+
return Cint(0)
275+
end
276+
end
277+
278+
"""
279+
dllist()
280+
281+
Return the paths of dynamic libraries currently loaded in a `Vector{String}`.
282+
"""
283+
function dllist()
284+
dynamic_libraries = Vector{String}()
285+
286+
@static if Sys.isapple()
287+
numImages = ccall(:_dyld_image_count, Cint, ())
288+
289+
# start at 1 instead of 0 to skip self
290+
for i in 1:numImages-1
291+
name = unsafe_string(ccall(:_dyld_get_image_name, Cstring, (UInt32,), i))
292+
push!(dynamic_libraries, name)
293+
end
294+
elseif Sys.islinux() || Sys.isbsd()
295+
callback = @cfunction(dl_phdr_info_callback, Cint,
296+
(Ref{dl_phdr_info}, Csize_t, Ref{Vector{String}}))
297+
ccall(:dl_iterate_phdr, Cint, (Ptr{Cvoid}, Ref{Vector{String}}), callback, dynamic_libraries)
298+
popfirst!(dynamic_libraries)
299+
filter!(!isempty, dynamic_libraries)
300+
elseif Sys.iswindows()
301+
ccall(:jl_dllist, Cint, (Any,), dynamic_libraries)
302+
else
303+
# unimplemented
304+
end
305+
306+
return dynamic_libraries
307+
end
308+
309+
end # module

0 commit comments

Comments
 (0)