|
| 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