Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/libs/syscall/src/dlfcn/syscall/dlclose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::dlfcn::syscall::{
};
use ::alloc::{
collections::btree_map::BTreeMap,
string::String,
sync::Arc,
vec::Vec,
};
Expand Down Expand Up @@ -70,6 +71,25 @@ pub fn dlclose(handle: &DlHandle) -> Result<(), Error> {
// Collect all dependencies of the dynamic library file being closed.
let mut dep_dlfiles: Vec<Arc<Mutex<DynamicLibrary>>> = Vec::new();
if let Some((_, dep_dlfile)) = dlfile.pop() {
// Snapshot the `.fini_array` descriptor and name under a short
// per-library lock, then drop the lock and invoke destructors.
// Holding the per-library lock during destructor execution
// would deadlock any destructor that calls
// `dlsym(self_handle, ...)`. (The outer registry lock is still
// held, so destructors that call `dlopen` / `dlclose` / cross-
// library `dlsym` are not yet supported; see the loader docs.)
let (descriptor, name): (Option<(usize, usize)>, String) = {
let lib = dep_dlfile.lock();
(lib.fini_array_descriptor(), String::from(lib.name()))
};
// SAFETY: `descriptor` was produced under the per-library lock
// for a library still alive via `dep_dlfile`; the per-library
// mutex is released before invocation so destructors that call
// `dlsym(self_handle, ...)` do not deadlock.
unsafe {
DynamicLibrary::invoke_fini_array(descriptor, &name);
}

let mut dep_dlfile = dep_dlfile.lock();
dep_dlfile
.take_dependencies()
Expand Down Expand Up @@ -105,6 +125,18 @@ pub fn dlclose(handle: &DlHandle) -> Result<(), Error> {

// Collect all dependencies of the dynamic library file.
if let Some((_, dep_dlfile)) = dep_dlfile.pop() {
// Run `.fini_array` destructors before this dependency is
// dropped. See the comment above on lock handling.
let (descriptor, name): (Option<(usize, usize)>, String) = {
let lib = dep_dlfile.lock();
(lib.fini_array_descriptor(), String::from(lib.name()))
};
// SAFETY: see the comment on the first `invoke_fini_array`
// call above.
unsafe {
DynamicLibrary::invoke_fini_array(descriptor, &name);
}

let mut dep_dlfile = dep_dlfile.lock();
dep_dlfile
.take_dependencies()
Expand Down
120 changes: 82 additions & 38 deletions src/libs/syscall/src/dlfcn/syscall/dlopen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ pub fn dlopen(filename: &str, global: bool) -> Result<DlHandle, Error> {
// (e.g., "lib/libfoo.so") using the configured search directories.
// Paths with leading "/" are normalized to strip it, ensuring
// "/lib/libc.so" and "lib/libc.so" are treated as the same library.
let resolved: String = super::resolve_library_path(filename);
// The top-level dlopen call has no parent runpaths to honour, so pass
// `None`; bare-name probes against `DT_RUNPATH` only happen for
// transitive `DT_NEEDED` dependencies of an already-loaded library.
let resolved: String = super::resolve_library_path(filename, None);
let filename: &str = resolved.as_str();

let mut registry: MutexGuard<'_, BTreeMap<DlHandle, Arc<Mutex<DynamicLibrary>>>> =
Expand Down Expand Up @@ -80,37 +83,68 @@ pub fn dlopen(filename: &str, global: bool) -> Result<DlHandle, Error> {
// Load dependencies and resolve symbols. If either step fails, remove all
// entries that were added during this call (the library itself and any
// transitive dependencies) so subsequent dlopen calls start fresh.
match load_all_dependencies(&mut registry, new_dlfile)
.and_then(|_| resolve_all_symbols(&mut registry, &handles_before))
{
Ok(()) => {
// If RTLD_GLOBAL was requested, publish the library's exported
// symbols into the global symbol table so subsequently loaded
// libraries can resolve them.
if global {
if let Some(dlfile) = registry.get(&handle) {
super::register_library_in_global_scope(dlfile);
let init_order: Vec<Arc<Mutex<DynamicLibrary>>> =
match load_all_dependencies(&mut registry, new_dlfile)
.and_then(|_| resolve_all_symbols(&mut registry, &handles_before))
{
Ok(order) => {
// If RTLD_GLOBAL was requested, publish the library's exported
// symbols into the global symbol table so subsequently loaded
// libraries can resolve them.
if global {
if let Some(dlfile) = registry.get(&handle) {
super::register_library_in_global_scope(dlfile);
}
}
}
Ok(handle)
},
Err(e) => {
let new_handles: Vec<DlHandle> = registry
.keys()
.filter(|h| !handles_before.contains(h))
.copied()
.collect();
::syslog::warn!(
"dlopen(): rolling back {} entries after failure (error={:?})",
new_handles.len(),
e
);
for h in new_handles {
registry.remove(&h);
}
Err(e)
},
order
},
Err(e) => {
let new_handles: Vec<DlHandle> = registry
.keys()
.filter(|h| !handles_before.contains(h))
.copied()
.collect();
::syslog::warn!(
"dlopen(): rolling back {} entries after failure (error={:?})",
new_handles.len(),
e
);
for h in new_handles {
registry.remove(&h);
}
return Err(e);
},
};

// Drop the registry lock before invoking `.init_array` constructors so a
// constructor may legally call `dlsym` (and, in a future relaxation,
// `dlopen`) without deadlocking on `DYNAMIC_LIBRARY_REGISTRY`.
drop(registry);

// Invoke `.init_array` constructors in dependency order (leaves first).
// The Arc list was built while the registry was locked, so each entry is
// guaranteed to still point to a loaded library.
//
// Snapshot the constructor descriptor and library name under a short
// per-library lock, then drop the lock before invoking constructors.
// Holding the per-library lock during constructor execution would
// deadlock any constructor that calls `dlsym(self_handle, ...)`, because
// `dlsym` re-locks the same library to look up symbols.
for dlfile in init_order.iter() {
let (descriptor, name): (Option<(usize, usize)>, String) = {
let lib: MutexGuard<'_, DynamicLibrary> = dlfile.lock();
(lib.init_array_descriptor(), String::from(lib.name()))
};
// SAFETY: `descriptor` was produced under the per-library lock for
// a library still held alive by `init_order`'s `Arc`; relocations
// have been applied by `resolve_all_symbols`; no dlfcn locks are
// held across this call.
unsafe {
DynamicLibrary::invoke_init_array(descriptor, &name);
}
}

Ok(handle)
}

/// Recursively loads all transitive dependencies of a newly opened library.
Expand All @@ -129,6 +163,12 @@ fn load_all_dependencies(
new_dlhandle: &DlHandle,
new_dlfile: &mut MutexGuard<'_, DynamicLibrary>,
) -> Result<(), Error> {
// Snapshot the loader's `DT_RUNPATH` entries so they are visible to
// `resolve_library_path` while probing every dependency below. The
// `DynamicLibrary` mutex is borrowed throughout, so this avoids
// re-borrowing it per call.
let runpaths: Vec<String> = new_dlfile.runpaths().to_vec();

// Collect the name of all dependencies.
let mut dependencies: Vec<String> = new_dlfile
.dependencies()
Expand All @@ -140,7 +180,7 @@ fn load_all_dependencies(
dependencies.retain(|dependency| {
// Resolve bare name so we can match against loaded libraries
// that were opened with a full path.
let resolved_dep: String = super::resolve_library_path(dependency);
let resolved_dep: String = super::resolve_library_path(dependency, Some(&runpaths));
for (dlhandle, dlfile) in dlfiles.iter() {
// Check if need to skip the dynamic library itself.
if dlhandle == new_dlhandle {
Expand Down Expand Up @@ -176,7 +216,7 @@ fn load_all_dependencies(
// Load remaining dependencies.
while let Some(dependency) = dependencies.pop() {
// Resolve bare library names to full paths using search directories.
let resolved_dep: String = super::resolve_library_path(&dependency);
let resolved_dep: String = super::resolve_library_path(&dependency, Some(&runpaths));

// Open and pre-load the dynamic library file.
let dep_dlfile: DynamicLibrary = DynamicLibrary::open(&resolved_dep)?;
Expand Down Expand Up @@ -205,15 +245,17 @@ fn load_all_dependencies(
Ok(())
}

/// Resolves all relocations for libraries added during this `dlopen` call.
/// Resolves all relocations for libraries added during this `dlopen` call and
/// returns them in dependency order (leaves first, root last) so the caller
/// can invoke `.init_array` constructors in the correct sequence.
///
/// Libraries are resolved in dependency order (leaves first, root last).
/// This ensures that when a library's relocations reference symbols from
/// its dependencies, those dependencies are already fully resolved.
/// Libraries are resolved in dependency order. This ensures that when a
/// library's relocations reference symbols from its dependencies, those
/// dependencies are already fully resolved.
fn resolve_all_symbols(
dlfiles: &mut MutexGuard<'_, BTreeMap<DlHandle, Arc<Mutex<DynamicLibrary>>>>,
handles_before: &BTreeSet<DlHandle>,
) -> Result<(), Error> {
) -> Result<Vec<Arc<Mutex<DynamicLibrary>>>, Error> {
::syslog::trace!("resolve_all_symbols()");

// Collect all newly added libraries.
Expand Down Expand Up @@ -291,11 +333,13 @@ fn resolve_all_symbols(
}

// Resolve in dependency order (leaves first).
let mut init_order: Vec<Arc<Mutex<DynamicLibrary>>> = Vec::with_capacity(ordered.len());
for handle in ordered {
if let Some(dlfile) = dlfiles.get(&handle) {
dlfile.lock().resolve_all()?;
init_order.push(dlfile.clone());
}
}

Ok(())
Ok(init_order)
}
Loading