relib_host/
lib.rs

1use {
2  libloading::Symbol,
3  relib_internal_shared::Str,
4  std::{ffi::OsStr, path::Path},
5};
6
7mod errors;
8pub use errors::LoadError;
9
10#[cfg(feature = "unloading")]
11mod unloading;
12#[cfg(feature = "unloading")]
13pub use unloading::*;
14
15mod module;
16pub use module::Module;
17mod helpers;
18use helpers::{
19  LIBRARY_LOADING_GUARD, is_library_loaded, next_module_id, open_library, path_to_str,
20};
21mod leak_library;
22pub mod exports_types;
23pub use exports_types::{InitImports, ModuleExportsForHost};
24
25#[cfg(target_os = "windows")]
26mod windows;
27
28/// Loads a module (dynamic library) by specified path.
29///
30/// # Safety
31/// This function is unsafe due to special patches related to backtraces and threads on Windows,
32/// if you are on Linux ignore these safety conditions:
33/// - Make sure you don't create backtraces
34///   (for example, by panic or using `std::backtrace`)
35///   in one thread and call this function **for the first time** from another one.
36/// - Also make sure you don't spawn threads in one thread
37///   and call this function **for the first time** from another one.
38///
39/// If you can't guarantee it when you call this function consider using
40/// [`init`] at the start of your program.
41///
42/// # Example
43/// ```
44/// use {std::path::Path, libloading::library_filename};
45///
46/// let dylib_path = Path::new("target/debug").join(library_filename("module"));
47///
48/// // `()` means empty imports and exports, module doesn't import or export anything
49/// let module = unsafe {
50///   relib_host::load_module::<()>(dylib_path, ())
51/// };
52/// let module = module.unwrap_or_else(|e| {
53///   panic!("module loading failed: {e:#}");
54/// });
55///
56/// // main function is unsafe to call (as well as any other module export) because these pre-conditions are not checked by relib:
57/// // - Returned value must be actually `R` at runtime. For example if you called this function with type bool but module returns i32, UB will occur.
58/// // - Type of return value must be ABI-stable.
59/// // - Returned value must not be a reference-counting pointer or &'static T (see caveats on main docs page/README).
60/// let returned_value = unsafe { module.call_main::<()>() };
61///
62/// // if module panics while executing any export it returns None
63/// // (panic will be printed by module)
64/// if returned_value.is_none() {
65///   println!("module panicked");
66/// }
67/// ```
68///
69/// # Panics
70/// Panics on Windows if `dbghelp.dll` was already loaded. See [`init`]
71pub unsafe fn load_module<E: ModuleExportsForHost>(
72  path: impl AsRef<OsStr>,
73  init_imports: impl InitImports,
74) -> Result<Module<E>, crate::LoadError> {
75  unsafe {
76    load_module_with_options(
77      path,
78      init_imports,
79      #[cfg(feature = "unloading")]
80      true,
81    )
82  }
83}
84
85/// See [`load_module`].
86///
87/// # Safety
88/// See [`load_module`].
89pub unsafe fn load_module_with_options<E: ModuleExportsForHost>(
90  path: impl AsRef<OsStr>,
91  init_imports: impl InitImports,
92
93  // needs to be passed at runtime because host can load different modules with enabled and disabled alloc tracker
94  #[cfg(feature = "unloading")] enable_alloc_tracker: bool,
95) -> Result<Module<E>, crate::LoadError> {
96  // prevent parallel loading of the same dynamic library
97  // to guarantee that LoadError::ModuleAlreadyLoaded is returned
98  let _loading_guard = LIBRARY_LOADING_GUARD
99    .lock()
100    .expect("Failed to lock library loading guard");
101
102  #[cfg(target_os = "windows")]
103  {
104    windows::dbghelp::try_init_from_load_module();
105    unsafe {
106      #[cfg(feature = "unloading")]
107      unloading::windows_thread_spawn_hook::init();
108      windows::enable_hooks();
109    }
110  }
111
112  let path = Path::new(path.as_ref());
113  let path_str = path_to_str(path);
114
115  if is_library_loaded(path_str) {
116    return Err(LoadError::ModuleAlreadyLoaded);
117  }
118
119  let library = open_library(path)?;
120
121  let module_comp_info = unsafe {
122    let compiled_with = library.get(b"__RELIB__CRATE_COMPILATION_INFO__\0");
123    let Ok(compiled_with) = compiled_with else {
124      return Err(LoadError::CouldNotGetCompilationInfo);
125    };
126    let compiled_with: Symbol<*const Str> = compiled_with;
127    let compiled_with: &Str = &**compiled_with;
128    compiled_with.to_string()
129  };
130
131  let host_comp_info = relib_internal_crate_compilation_info::get!();
132  if module_comp_info != host_comp_info {
133    return Err(LoadError::ModuleCompilationMismatch {
134      module: module_comp_info,
135      host: host_comp_info.to_owned(),
136    });
137  }
138
139  #[cfg(target_os = "windows")]
140  windows::dbghelp::add_module(path_str);
141
142  let module_id = next_module_id();
143
144  #[cfg(feature = "unloading")]
145  let internal_exports = {
146    unloading::init_internal_imports(&library);
147    unloading::module_allocs::add_module(module_id);
148
149    let internal_exports = unloading::InternalModuleExports::new(&library);
150    unsafe {
151      internal_exports.init(thread_id::get(), module_id, enable_alloc_tracker);
152    }
153    internal_exports
154  };
155
156  let pub_exports = E::new(&library);
157  init_imports.init(&library);
158
159  let module = Module::new(
160    module_id,
161    library,
162    pub_exports,
163    #[cfg(feature = "unloading")]
164    (internal_exports, path.to_owned(), enable_alloc_tracker),
165  );
166
167  #[cfg(all(target_os = "windows", feature = "unloading"))]
168  unloading::windows_thread_spawn_hook::add_module(module.library_handle);
169
170  Ok(module)
171}
172
173/// Currently, it's only needed for Windows for backtraces (for example, `std::backtrace::Backtrace`) to work correctly in modules.
174/// And it also needed for [background threads check](https://docs.rs/relib/latest/relib/docs/index.html#background-threads-check).
175/// Can be called before creating any backtraces and threads if [`load_module`] panics due to already loaded `dbghelp.dll`.
176///
177/// **note:** This function doesn't actually do anything on Linux.
178///
179/// # Safety
180/// Same as [`load_module`].
181///
182/// # Panics
183/// Panics on Windows if `dbghelp.dll` was already loaded (for example, by `backtrace` crate or standard library).
184pub unsafe fn init() {
185  #[cfg(target_os = "windows")]
186  {
187    windows::dbghelp::try_init_standalone();
188    unsafe {
189      #[cfg(feature = "unloading")]
190      unloading::windows_thread_spawn_hook::init();
191      windows::enable_hooks();
192    }
193  }
194}
195
196/// Don't use it unless you really need to.
197/// Forcibly terminates and reinitializes dbghelp.dll for backtraces on Windows.
198///
199/// # Safety
200/// God knows...
201///
202#[cfg(any(target_os = "windows", relib_docs))]
203#[cfg(feature = "super_special_reinit_of_dbghelp")]
204pub unsafe fn forcibly_reinit_dbghelp() {
205  #[cfg(target_os = "windows")]
206  unsafe {
207    windows::dbghelp::forcibly_reinit_dbghelp();
208    windows::enable_hooks();
209  }
210}
211
212// TODO: fix it
213#[doc(hidden)]
214#[cfg(all(target_os = "windows", feature = "unloading"))]
215pub unsafe fn __suppress_unused_warning_for_linux_only_exports(
216  exports: unloading::InternalModuleExports,
217) {
218  unsafe {
219    exports.spawned_threads_count();
220  }
221}
222
223#[doc(hidden)]
224#[expect(unreachable_code)]
225#[cfg(all(target_os = "linux", feature = "unloading"))]
226pub unsafe fn __suppress_unused_warning_for_windows_only_exports(
227  exports: unloading::InternalModuleExports,
228) {
229  unsafe { exports.set_dealloc_callback(todo!()) }
230}