|
1 |
| -use std::{ffi::OsStr, path::Path}; |
2 |
| - |
3 |
| -use libloading::Symbol; |
4 |
| - |
5 |
| -use relib_internal_shared::Str; |
6 |
| - |
7 |
| -mod errors; |
8 |
| -pub use errors::LoadError; |
9 |
| - |
10 |
| -#[cfg(feature = "unloading")] |
11 |
| -mod unloading; |
12 |
| -#[cfg(feature = "unloading")] |
13 |
| -pub use unloading::*; |
14 |
| - |
15 |
| -mod module; |
16 |
| -pub use module::Module; |
17 |
| -mod helpers; |
18 |
| -use helpers::{is_library_loaded, next_module_id, open_library, path_to_str}; |
19 |
| -mod leak_library; |
20 |
| -pub mod exports_types; |
21 |
| -pub use exports_types::{ModuleExportsForHost, InitImports}; |
22 |
| - |
23 |
| -#[cfg(target_os = "windows")] |
24 |
| -mod windows; |
25 |
| - |
26 |
| -/// Loads a module (dynamic library) by specified path. |
27 |
| -/// |
28 |
| -/// # Safety |
29 |
| -/// This function is unsafe due to special patches related to backtraces on Windows, |
30 |
| -/// if you are on Linux ignore this safety condition: |
31 |
| -/// - Make sure you don't create backtraces |
32 |
| -/// (for example, by panic or using `std::backtrace`) |
33 |
| -/// in one thread and call this function **for the first time** from another one. |
34 |
| -/// |
35 |
| -/// If you can't guarantee it when you call this function consider using |
36 |
| -/// [`init`] at the start of your program. |
37 |
| -/// |
38 |
| -/// # Example |
39 |
| -/// ``` |
40 |
| -/// let path_to_dylib = if cfg!(target_os = "linux") { |
41 |
| -/// "target/debug/libmodule.so" |
42 |
| -/// } else { |
43 |
| -/// "target/debug/module.dll" |
44 |
| -/// }; |
45 |
| -/// |
46 |
| -/// // `()` means empty imports and exports, module doesn't import or export anything |
47 |
| -/// let module = unsafe { |
48 |
| -/// relib_host::load_module::<()>(path_to_dylib, ()) |
49 |
| -/// }; |
50 |
| -/// let module = module.unwrap_or_else(|e| { |
51 |
| -/// panic!("module loading failed: {e:#}"); |
52 |
| -/// }); |
53 |
| -/// |
54 |
| -/// // main function is unsafe to call (as well as any other module export) because these pre-conditions are not checked by relib: |
55 |
| -/// // - 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. |
56 |
| -/// // - Type of return value must be FFI-safe. |
57 |
| -/// // - Returned value must not be a reference-counting pointer (see caveats in README or docs page). |
58 |
| -/// let returned_value = unsafe { module.call_main::<()>() }; |
59 |
| -/// |
60 |
| -/// // if module panics while executing any export it returns None |
61 |
| -/// // (panic will be printed by module) |
62 |
| -/// if returned_value.is_none() { |
63 |
| -/// println!("module panicked"); |
64 |
| -/// } |
65 |
| -/// ``` |
66 |
| -/// |
67 |
| -/// # Panics |
68 |
| -/// Panics on Windows if `dbghelp.dll` was already loaded. See [`init`] |
69 |
| -pub unsafe fn load_module<E: ModuleExportsForHost>( |
70 |
| - path: impl AsRef<OsStr>, |
71 |
| - init_imports: impl InitImports, |
72 |
| -) -> Result<Module<E>, crate::LoadError> { |
73 |
| - #[cfg(target_os = "windows")] |
74 |
| - windows::dbghelp::try_init_from_load_module(); |
75 |
| - |
76 |
| - let path = Path::new(path.as_ref()); |
77 |
| - let path_str = path_to_str(path); |
78 |
| - |
79 |
| - if is_library_loaded(path_str) { |
80 |
| - return Err(LoadError::ModuleAlreadyLoaded); |
81 |
| - } |
82 |
| - |
83 |
| - let library = open_library(path)?; |
84 |
| - |
85 |
| - let module_comp_info = unsafe { |
86 |
| - let compiled_with = library.get(b"__RELIB__CRATE_COMPILATION_INFO__\0"); |
87 |
| - let Ok(compiled_with) = compiled_with else { |
88 |
| - return Err(LoadError::CouldNotGetCompilationInfo); |
89 |
| - }; |
90 |
| - let compiled_with: Symbol<*const Str> = compiled_with; |
91 |
| - let compiled_with: &Str = &**compiled_with; |
92 |
| - compiled_with.to_string() |
93 |
| - }; |
94 |
| - |
95 |
| - let host_comp_info = relib_internal_crate_compilation_info::get!(); |
96 |
| - if module_comp_info != host_comp_info { |
97 |
| - return Err(LoadError::ModuleCompilationMismatch { |
98 |
| - module: module_comp_info, |
99 |
| - host: host_comp_info.to_owned(), |
100 |
| - }); |
101 |
| - } |
102 |
| - |
103 |
| - #[cfg(target_os = "windows")] |
104 |
| - windows::dbghelp::add_module(path_str); |
105 |
| - |
106 |
| - let module_id = next_module_id(); |
107 |
| - |
108 |
| - #[cfg(feature = "unloading")] |
109 |
| - let internal_exports = { |
110 |
| - unloading::init_internal_imports(&library); |
111 |
| - unloading::module_allocs::add_module(module_id); |
112 |
| - |
113 |
| - let internal_exports = unloading::InternalModuleExports::new(&library); |
114 |
| - unsafe { |
115 |
| - internal_exports.init(thread_id::get(), module_id); |
116 |
| - } |
117 |
| - internal_exports |
118 |
| - }; |
119 |
| - |
120 |
| - let pub_exports = E::new(&library); |
121 |
| - init_imports.init(&library); |
122 |
| - |
123 |
| - let module = Module::new( |
124 |
| - module_id, |
125 |
| - library, |
126 |
| - pub_exports, |
127 |
| - #[cfg(feature = "unloading")] |
128 |
| - (internal_exports, path.to_owned()), |
129 |
| - ); |
130 |
| - Ok(module) |
131 |
| -} |
132 |
| - |
133 |
| -/// Currently, it's only needed for backtraces (for example, `std::backtrace::Backtrace`) to work correctly in modules on Windows. |
134 |
| -/// Doesn't actually do anything on Linux. |
135 |
| -/// Can be called before creating any backtraces if [`load_module`] panics due to already loaded `dbghelp.dll`. |
136 |
| -/// |
137 |
| -/// # Safety |
138 |
| -/// Same as [`load_module`]. |
139 |
| -/// |
140 |
| -/// # Panics |
141 |
| -/// Panics on Windows if `dbghelp.dll` was already loaded (for example, by `backtrace` crate or standard library). |
142 |
| -pub unsafe fn init() { |
143 |
| - #[cfg(target_os = "windows")] |
144 |
| - windows::dbghelp::try_init(); |
145 |
| -} |
146 |
| - |
147 |
| -// TODO: fix it |
148 |
| -#[cfg(all(target_os = "windows", feature = "unloading"))] |
149 |
| -#[expect(clippy::missing_safety_doc)] |
150 |
| -pub unsafe fn __suppress_unused_warning_for_linux_only_exports( |
151 |
| - exports: unloading::InternalModuleExports, |
152 |
| -) { |
153 |
| - exports.spawned_threads_count(); |
154 |
| -} |
155 |
| - |
156 |
| -#[cfg(all(target_os = "linux", feature = "unloading"))] |
157 |
| -#[expect(clippy::missing_safety_doc)] |
158 |
| -pub unsafe fn __suppress_unused_warning_for_windows_only_exports( |
159 |
| - exports: unloading::InternalModuleExports, |
160 |
| -) { |
161 |
| - #[expect(unreachable_code)] |
162 |
| - exports.set_dealloc_callback(todo!()); |
163 |
| -} |
| 1 | +use std::{ffi::OsStr, path::Path}; |
| 2 | + |
| 3 | +use libloading::Symbol; |
| 4 | + |
| 5 | +use relib_internal_shared::Str; |
| 6 | + |
| 7 | +mod errors; |
| 8 | +pub use errors::LoadError; |
| 9 | + |
| 10 | +#[cfg(feature = "unloading")] |
| 11 | +mod unloading; |
| 12 | +#[cfg(feature = "unloading")] |
| 13 | +pub use unloading::*; |
| 14 | + |
| 15 | +mod module; |
| 16 | +pub use module::Module; |
| 17 | +mod helpers; |
| 18 | +use helpers::{is_library_loaded, next_module_id, open_library, path_to_str}; |
| 19 | +mod leak_library; |
| 20 | +pub mod exports_types; |
| 21 | +pub use exports_types::{ModuleExportsForHost, InitImports}; |
| 22 | + |
| 23 | +#[cfg(target_os = "windows")] |
| 24 | +mod windows; |
| 25 | + |
| 26 | +/// Loads a module (dynamic library) by specified path. |
| 27 | +/// |
| 28 | +/// # Safety |
| 29 | +/// This function is unsafe due to special patches related to backtraces on Windows, |
| 30 | +/// if you are on Linux ignore this safety condition: |
| 31 | +/// - Make sure you don't create backtraces |
| 32 | +/// (for example, by panic or using `std::backtrace`) |
| 33 | +/// in one thread and call this function **for the first time** from another one. |
| 34 | +/// |
| 35 | +/// If you can't guarantee it when you call this function consider using |
| 36 | +/// [`init`] at the start of your program. |
| 37 | +/// |
| 38 | +/// # Example |
| 39 | +/// ``` |
| 40 | +/// let path_to_dylib = if cfg!(target_os = "linux") { |
| 41 | +/// "target/debug/libmodule.so" |
| 42 | +/// } else { |
| 43 | +/// "target/debug/module.dll" |
| 44 | +/// }; |
| 45 | +/// |
| 46 | +/// // `()` means empty imports and exports, module doesn't import or export anything |
| 47 | +/// let module = unsafe { |
| 48 | +/// relib_host::load_module::<()>(path_to_dylib, ()) |
| 49 | +/// }; |
| 50 | +/// let module = module.unwrap_or_else(|e| { |
| 51 | +/// panic!("module loading failed: {e:#}"); |
| 52 | +/// }); |
| 53 | +/// |
| 54 | +/// // main function is unsafe to call (as well as any other module export) because these pre-conditions are not checked by relib: |
| 55 | +/// // - 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. |
| 56 | +/// // - Type of return value must be FFI-safe. |
| 57 | +/// // - Returned value must not be a reference-counting pointer (see caveats in README or docs page). |
| 58 | +/// let returned_value = unsafe { module.call_main::<()>() }; |
| 59 | +/// |
| 60 | +/// // if module panics while executing any export it returns None |
| 61 | +/// // (panic will be printed by module) |
| 62 | +/// if returned_value.is_none() { |
| 63 | +/// println!("module panicked"); |
| 64 | +/// } |
| 65 | +/// ``` |
| 66 | +/// |
| 67 | +/// # Panics |
| 68 | +/// Panics on Windows if `dbghelp.dll` was already loaded. See [`init`] |
| 69 | +pub unsafe fn load_module<E: ModuleExportsForHost>( |
| 70 | + path: impl AsRef<OsStr>, |
| 71 | + init_imports: impl InitImports, |
| 72 | +) -> Result<Module<E>, crate::LoadError> { |
| 73 | + #[cfg(target_os = "windows")] |
| 74 | + windows::dbghelp::try_init_from_load_module(); |
| 75 | + |
| 76 | + let path = Path::new(path.as_ref()); |
| 77 | + let path_str = path_to_str(path); |
| 78 | + |
| 79 | + if is_library_loaded(path_str) { |
| 80 | + return Err(LoadError::ModuleAlreadyLoaded); |
| 81 | + } |
| 82 | + |
| 83 | + let library = open_library(path)?; |
| 84 | + |
| 85 | + let module_comp_info = unsafe { |
| 86 | + let compiled_with = library.get(b"__RELIB__CRATE_COMPILATION_INFO__\0"); |
| 87 | + let Ok(compiled_with) = compiled_with else { |
| 88 | + return Err(LoadError::CouldNotGetCompilationInfo); |
| 89 | + }; |
| 90 | + let compiled_with: Symbol<*const Str> = compiled_with; |
| 91 | + let compiled_with: &Str = &**compiled_with; |
| 92 | + compiled_with.to_string() |
| 93 | + }; |
| 94 | + |
| 95 | + let host_comp_info = relib_internal_crate_compilation_info::get!(); |
| 96 | + if module_comp_info != host_comp_info { |
| 97 | + return Err(LoadError::ModuleCompilationMismatch { |
| 98 | + module: module_comp_info, |
| 99 | + host: host_comp_info.to_owned(), |
| 100 | + }); |
| 101 | + } |
| 102 | + |
| 103 | + #[cfg(target_os = "windows")] |
| 104 | + windows::dbghelp::add_module(path_str); |
| 105 | + |
| 106 | + let module_id = next_module_id(); |
| 107 | + |
| 108 | + #[cfg(feature = "unloading")] |
| 109 | + let internal_exports = { |
| 110 | + unloading::init_internal_imports(&library); |
| 111 | + unloading::module_allocs::add_module(module_id); |
| 112 | + |
| 113 | + let internal_exports = unloading::InternalModuleExports::new(&library); |
| 114 | + unsafe { |
| 115 | + internal_exports.init(thread_id::get(), module_id); |
| 116 | + } |
| 117 | + internal_exports |
| 118 | + }; |
| 119 | + |
| 120 | + let pub_exports = E::new(&library); |
| 121 | + init_imports.init(&library); |
| 122 | + |
| 123 | + let module = Module::new( |
| 124 | + module_id, |
| 125 | + library, |
| 126 | + pub_exports, |
| 127 | + #[cfg(feature = "unloading")] |
| 128 | + (internal_exports, path.to_owned()), |
| 129 | + ); |
| 130 | + Ok(module) |
| 131 | +} |
| 132 | + |
| 133 | +/// Currently, it's only needed for backtraces (for example, `std::backtrace::Backtrace`) to work correctly in modules on Windows. |
| 134 | +/// Doesn't actually do anything on Linux. |
| 135 | +/// Can be called before creating any backtraces if [`load_module`] panics due to already loaded `dbghelp.dll`. |
| 136 | +/// |
| 137 | +/// # Safety |
| 138 | +/// Same as [`load_module`]. |
| 139 | +/// |
| 140 | +/// # Panics |
| 141 | +/// Panics on Windows if `dbghelp.dll` was already loaded (for example, by `backtrace` crate or standard library). |
| 142 | +pub unsafe fn init() { |
| 143 | + #[cfg(target_os = "windows")] |
| 144 | + windows::dbghelp::try_init_standalone(); |
| 145 | +} |
| 146 | + |
| 147 | +/// Don't use it unless you really need to. |
| 148 | +/// Forcibly terminates and reinitializes dbghelp.dll for backtraces on Windows. |
| 149 | +/// |
| 150 | +/// # Safety |
| 151 | +/// God knows... |
| 152 | +/// |
| 153 | +#[cfg(any(target_os = "windows", relib_docs))] |
| 154 | +#[cfg(feature = "super_special_reinit_of_dbghelp")] |
| 155 | +pub unsafe fn forcibly_reinit_dbghelp() { |
| 156 | + #[cfg(target_os = "windows")] |
| 157 | + windows::dbghelp::forcibly_reinit_dbghelp(); |
| 158 | +} |
| 159 | + |
| 160 | +// TODO: fix it |
| 161 | +#[cfg(all(target_os = "windows", feature = "unloading"))] |
| 162 | +#[expect(clippy::missing_safety_doc)] |
| 163 | +pub unsafe fn __suppress_unused_warning_for_linux_only_exports( |
| 164 | + exports: unloading::InternalModuleExports, |
| 165 | +) { |
| 166 | + exports.spawned_threads_count(); |
| 167 | +} |
| 168 | + |
| 169 | +#[cfg(all(target_os = "linux", feature = "unloading"))] |
| 170 | +#[expect(clippy::missing_safety_doc)] |
| 171 | +pub unsafe fn __suppress_unused_warning_for_windows_only_exports( |
| 172 | + exports: unloading::InternalModuleExports, |
| 173 | +) { |
| 174 | + #[expect(unreachable_code)] |
| 175 | + exports.set_dealloc_callback(todo!()); |
| 176 | +} |
0 commit comments