1 | #![recursion_limit = "256" ] |
2 | |
3 | //! Procedural macro for defining global constructor/destructor functions. |
4 | //! |
5 | //! This provides module initialization/teardown functions for Rust (like |
6 | //! `__attribute__((constructor))` in C/C++) for Linux, OSX, and Windows via |
7 | //! the `#[ctor]` and `#[dtor]` macros. |
8 | //! |
9 | //! This library works and is regularly tested on Linux, OSX and Windows, with both `+crt-static` and `-crt-static`. |
10 | //! Other platforms are supported but not tested as part of the automatic builds. This library will also work as expected in both |
11 | //! `bin` and `cdylib` outputs, ie: the `ctor` and `dtor` will run at executable or library |
12 | //! startup/shutdown respectively. |
13 | //! |
14 | //! This library currently requires Rust > `1.31.0` at a minimum for the |
15 | //! procedural macro support. |
16 | |
17 | // Code note: |
18 | |
19 | // You might wonder why we don't use `__attribute__((destructor))`/etc for |
20 | // dtor. Unfortunately mingw doesn't appear to properly support section-based |
21 | // hooks for shutdown, ie: |
22 | |
23 | // https://github.com/Alexpux/mingw-w64/blob/d0d7f784833bbb0b2d279310ddc6afb52fe47a46/mingw-w64-crt/crt/crtdll.c |
24 | |
25 | // In addition, OSX has removed support for section-based shutdown hooks after |
26 | // warning about it for a number of years: |
27 | |
28 | // https://reviews.llvm.org/D45578 |
29 | |
30 | extern crate proc_macro; |
31 | extern crate syn; |
32 | #[macro_use ] |
33 | extern crate quote; |
34 | |
35 | use proc_macro::TokenStream; |
36 | |
37 | /// Attributes required to mark a function as a constructor. This may be exposed in the future if we determine |
38 | /// it to be stable. |
39 | #[doc (hidden)] |
40 | macro_rules! ctor_attributes { |
41 | () => { |
42 | // Linux/ELF: https://www.exploit-db.com/papers/13234 |
43 | |
44 | // Mac details: https://blog.timac.org/2016/0716-constructor-and-destructor-attributes/ |
45 | |
46 | // Why .CRT$XCU on Windows? https://www.cnblogs.com/sunkang/archive/2011/05/24/2055635.html |
47 | // 'I'=C init, 'C'=C++ init, 'P'=Pre-terminators and 'T'=Terminators |
48 | quote!( |
49 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".init_array" )] |
50 | #[cfg_attr(target_os = "freebsd" , link_section = ".init_array" )] |
51 | #[cfg_attr(target_os = "netbsd" , link_section = ".init_array" )] |
52 | #[cfg_attr(target_os = "openbsd" , link_section = ".init_array" )] |
53 | #[cfg_attr(target_os = "dragonfly" , link_section = ".init_array" )] |
54 | #[cfg_attr(target_os = "illumos" , link_section = ".init_array" )] |
55 | #[cfg_attr(target_os = "haiku" , link_section = ".init_array" )] |
56 | #[cfg_attr(any(target_os = "macos" , target_os = "ios" , target_os = "tvos" ), link_section = "__DATA,__mod_init_func" )] |
57 | #[cfg_attr(windows, link_section = ".CRT$XCU" )] |
58 | ) |
59 | }; |
60 | } |
61 | |
62 | /// Marks a function or static variable as a library/executable constructor. |
63 | /// This uses OS-specific linker sections to call a specific function at |
64 | /// load time. |
65 | /// |
66 | /// Multiple startup functions/statics are supported, but the invocation order is not |
67 | /// guaranteed. |
68 | /// |
69 | /// # Examples |
70 | /// |
71 | /// Print a startup message (using `libc_print` for safety): |
72 | /// |
73 | /// ```rust |
74 | /// # #![cfg_attr (feature="used_linker" , feature(used_with_arg))] |
75 | /// # extern crate ctor; |
76 | /// # use ctor::*; |
77 | /// use libc_print::std_name::println; |
78 | /// |
79 | /// #[ctor] |
80 | /// fn foo() { |
81 | /// println!("Hello, world!" ); |
82 | /// } |
83 | /// |
84 | /// # fn main() { |
85 | /// println!("main()" ); |
86 | /// # } |
87 | /// ``` |
88 | /// |
89 | /// Make changes to `static` variables: |
90 | /// |
91 | /// ```rust |
92 | /// # #![cfg_attr(feature="used_linker" , feature(used_with_arg))] |
93 | /// # extern crate ctor; |
94 | /// # use ctor::*; |
95 | /// # use std::sync::atomic::{AtomicBool, Ordering}; |
96 | /// static INITED: AtomicBool = AtomicBool::new(false); |
97 | /// |
98 | /// #[ctor] |
99 | /// fn foo() { |
100 | /// INITED.store(true, Ordering::SeqCst); |
101 | /// } |
102 | /// ``` |
103 | /// |
104 | /// Initialize a `HashMap` at startup time: |
105 | /// |
106 | /// ```rust |
107 | /// # extern crate ctor; |
108 | /// # use std::collections::HashMap; |
109 | /// # use ctor::*; |
110 | /// #[ctor] |
111 | /// static STATIC_CTOR: HashMap<u32, String> = { |
112 | /// let mut m = HashMap::new(); |
113 | /// for i in 0..100 { |
114 | /// m.insert(i, format!("x*100={}" , i*100)); |
115 | /// } |
116 | /// m |
117 | /// }; |
118 | /// |
119 | /// # pub fn main() { |
120 | /// # assert_eq!(STATIC_CTOR.len(), 100); |
121 | /// # assert_eq!(STATIC_CTOR[&20], "x*100=2000" ); |
122 | /// # } |
123 | /// ``` |
124 | /// |
125 | /// # Details |
126 | /// |
127 | /// The `#[ctor]` macro makes use of linker sections to ensure that a |
128 | /// function is run at startup time. |
129 | /// |
130 | /// The above example translates into the following Rust code (approximately): |
131 | /// |
132 | ///```rust |
133 | /// #[used] |
134 | /// #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".init_array" )] |
135 | /// #[cfg_attr(target_os = "freebsd" , link_section = ".init_array" )] |
136 | /// #[cfg_attr(target_os = "netbsd" , link_section = ".init_array" )] |
137 | /// #[cfg_attr(target_os = "openbsd" , link_section = ".init_array" )] |
138 | /// #[cfg_attr(target_os = "illumos" , link_section = ".init_array" )] |
139 | /// #[cfg_attr(any(target_os = "macos" , target_os = "ios" , target_os = "tvos" ), link_section = "__DATA_CONST,__mod_init_func" )] |
140 | /// #[cfg_attr(target_os = "windows" , link_section = ".CRT$XCU" )] |
141 | /// static FOO: extern fn() = { |
142 | /// #[cfg_attr (any(target_os = "linux" , target_os = "android" ), link_section = ".text.startup" )] |
143 | /// extern fn foo() { /* ... */ }; |
144 | /// foo |
145 | /// }; |
146 | /// ``` |
147 | #[proc_macro_attribute ] |
148 | pub fn ctor (_attribute: TokenStream, function: TokenStream) -> TokenStream { |
149 | let item: syn::Item = syn::parse_macro_input!(function); |
150 | if let syn::Item::Fn(function) = item { |
151 | validate_item("ctor" , &function); |
152 | |
153 | let syn::ItemFn { |
154 | attrs, |
155 | block, |
156 | vis, |
157 | sig: |
158 | syn::Signature { |
159 | ident, |
160 | unsafety, |
161 | constness, |
162 | abi, |
163 | .. |
164 | }, |
165 | .. |
166 | } = function; |
167 | |
168 | let ctor_ident = |
169 | syn::parse_str::<syn::Ident>(format!(" {}___rust_ctor___ctor" , ident).as_ref()) |
170 | .expect("Unable to create identifier" ); |
171 | |
172 | let tokens = ctor_attributes!(); |
173 | let output = quote!( |
174 | #[cfg(not(any(target_os = "linux" , target_os = "android" , target_os = "freebsd" , target_os = "netbsd" , target_os = "openbsd" , target_os = "dragonfly" , target_os = "illumos" , target_os = "haiku" , target_os = "macos" , target_os = "ios" , target_os = "tvos" , windows)))] |
175 | compile_error!("#[ctor] is not supported on the current target" ); |
176 | |
177 | #(#attrs)* |
178 | #vis #unsafety extern #abi #constness fn #ident() #block |
179 | |
180 | #[cfg_attr(not(feature = "used_linker" ), used)] |
181 | #[cfg_attr(feature = "used_linker" , used(linker))] |
182 | #[allow(non_upper_case_globals, non_snake_case)] |
183 | #[doc(hidden)] |
184 | #tokens |
185 | static #ctor_ident |
186 | : |
187 | unsafe extern "C" fn() -> usize = |
188 | { |
189 | #[allow(non_snake_case)] |
190 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".text.startup" )] |
191 | unsafe extern "C" fn #ctor_ident() -> usize { #ident(); 0 }; |
192 | #ctor_ident |
193 | } |
194 | ; |
195 | ); |
196 | |
197 | // eprintln!("{}", output); |
198 | |
199 | output.into() |
200 | } else if let syn::Item::Static(var) = item { |
201 | let syn::ItemStatic { |
202 | ident, |
203 | mutability, |
204 | expr, |
205 | attrs, |
206 | ty, |
207 | vis, |
208 | .. |
209 | } = var; |
210 | |
211 | if matches!(mutability, syn::StaticMutability::Mut(_)) { |
212 | panic!("#[ctor]-annotated static objects must not be mutable" ); |
213 | } |
214 | |
215 | if attrs.iter().any(|attr| { |
216 | attr.path() |
217 | .segments |
218 | .iter() |
219 | .any(|segment| segment.ident == "no_mangle" ) |
220 | }) { |
221 | panic!("#[ctor]-annotated static objects do not support #[no_mangle]" ); |
222 | } |
223 | |
224 | let ctor_ident = |
225 | syn::parse_str::<syn::Ident>(format!(" {}___rust_ctor___ctor" , ident).as_ref()) |
226 | .expect("Unable to create identifier" ); |
227 | let storage_ident = |
228 | syn::parse_str::<syn::Ident>(format!(" {}___rust_ctor___storage" , ident).as_ref()) |
229 | .expect("Unable to create identifier" ); |
230 | |
231 | let tokens = ctor_attributes!(); |
232 | let output = quote!( |
233 | #[cfg(not(any(target_os = "linux" , target_os = "android" , target_os = "freebsd" , target_os = "netbsd" , target_os = "openbsd" , target_os = "dragonfly" , target_os = "illumos" , target_os = "haiku" , target_os = "macos" , target_os = "ios" , target_os = "tvos" , windows)))] |
234 | compile_error!("#[ctor] is not supported on the current target" ); |
235 | |
236 | // This is mutable, but only by this macro code! |
237 | static mut #storage_ident: Option<#ty> = None; |
238 | |
239 | #[doc(hidden)] |
240 | #[allow(non_camel_case_types)] |
241 | #vis struct #ident<T> { |
242 | _data: ::core::marker::PhantomData<T> |
243 | } |
244 | |
245 | #(#attrs)* |
246 | #vis static #ident: #ident<#ty> = #ident { |
247 | _data: ::core::marker::PhantomData::<#ty> |
248 | }; |
249 | |
250 | impl ::core::ops::Deref for #ident<#ty> { |
251 | type Target = #ty; |
252 | fn deref(&self) -> &'static #ty { |
253 | unsafe { |
254 | #storage_ident.as_ref().unwrap() |
255 | } |
256 | } |
257 | } |
258 | |
259 | #[used] |
260 | #[allow(non_upper_case_globals)] |
261 | #tokens |
262 | static #ctor_ident |
263 | : |
264 | unsafe extern "C" fn() = { |
265 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".text.startup" )] |
266 | extern "C" fn initer() { |
267 | let val = Some(#expr); |
268 | // Only write the value to `storage_ident` on startup |
269 | unsafe { |
270 | #storage_ident = val; |
271 | } |
272 | }; initer } |
273 | ; |
274 | ); |
275 | |
276 | // eprintln!("{}", output); |
277 | |
278 | output.into() |
279 | } else { |
280 | panic!("#[ctor] items must be functions or static globals" ); |
281 | } |
282 | } |
283 | |
284 | /// Marks a function as a library/executable destructor. This uses OS-specific |
285 | /// linker sections to call a specific function at termination time. |
286 | /// |
287 | /// Multiple shutdown functions are supported, but the invocation order is not |
288 | /// guaranteed. |
289 | /// |
290 | /// `sys_common::at_exit` is usually a better solution for shutdown handling, as |
291 | /// it allows you to use `stdout` in your handlers. |
292 | /// |
293 | /// ```rust |
294 | /// # extern crate ctor; |
295 | /// # use ctor::*; |
296 | /// # fn main() {} |
297 | /// |
298 | /// #[dtor] |
299 | /// fn shutdown() { |
300 | /// /* ... */ |
301 | /// } |
302 | /// ``` |
303 | #[proc_macro_attribute ] |
304 | pub fn dtor (_attribute: TokenStream, function: TokenStream) -> TokenStream { |
305 | let function: syn::ItemFn = syn::parse_macro_input!(function); |
306 | validate_item("dtor" , &function); |
307 | |
308 | let syn::ItemFn { |
309 | attrs, |
310 | block, |
311 | vis, |
312 | sig: |
313 | syn::Signature { |
314 | ident, |
315 | unsafety, |
316 | constness, |
317 | abi, |
318 | .. |
319 | }, |
320 | .. |
321 | } = function; |
322 | |
323 | let mod_ident = syn::parse_str::<syn::Ident>(format!(" {}___rust_dtor___mod" , ident).as_ref()) |
324 | .expect("Unable to create identifier" ); |
325 | |
326 | let dtor_ident = syn::parse_str::<syn::Ident>(format!(" {}___rust_dtor___dtor" , ident).as_ref()) |
327 | .expect("Unable to create identifier" ); |
328 | |
329 | let tokens = ctor_attributes!(); |
330 | let output = quote!( |
331 | #[cfg(not(any(target_os = "linux" , target_os = "android" , target_os = "freebsd" , target_os = "netbsd" , target_os = "openbsd" , target_os = "dragonfly" , target_os = "illumos" , target_os = "haiku" , target_os = "macos" , target_os = "ios" , target_os = "tvos" , windows)))] |
332 | compile_error!("#[dtor] is not supported on the current target" ); |
333 | |
334 | #(#attrs)* |
335 | #vis #unsafety extern #abi #constness fn #ident() #block |
336 | |
337 | mod #mod_ident { |
338 | use super::#ident; |
339 | |
340 | // Note that we avoid a dep on the libc crate by linking directly to atexit functions |
341 | |
342 | #[cfg(not(any(target_os = "macos" , target_os = "ios" , target_os = "tvos" )))] |
343 | #[inline(always)] |
344 | unsafe fn do_atexit(cb: unsafe extern fn()) { |
345 | extern "C" { |
346 | fn atexit(cb: unsafe extern fn()); |
347 | } |
348 | atexit(cb); |
349 | } |
350 | |
351 | // For platforms that have __cxa_atexit, we register the dtor as scoped to dso_handle |
352 | #[cfg(any(target_os = "macos" , target_os = "ios" , target_os = "tvos" ))] |
353 | #[inline(always)] |
354 | unsafe fn do_atexit(cb: unsafe extern fn(_: *const u8)) { |
355 | extern "C" { |
356 | static __dso_handle: *const u8; |
357 | fn __cxa_atexit(cb: unsafe extern fn(_: *const u8), arg: *const u8, dso_handle: *const u8); |
358 | } |
359 | __cxa_atexit(cb, std::ptr::null(), __dso_handle); |
360 | } |
361 | |
362 | #[used] |
363 | #[allow(non_upper_case_globals)] |
364 | #tokens |
365 | static __dtor_export |
366 | : |
367 | unsafe extern "C" fn() = |
368 | { |
369 | #[cfg(not(any(target_os = "macos" , target_os = "ios" , target_os = "tvos" )))] |
370 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".text.exit" )] |
371 | unsafe extern "C" fn #dtor_ident() { #ident() }; |
372 | #[cfg(any(target_os = "macos" , target_os = "ios" , target_os = "tvos" ))] |
373 | unsafe extern "C" fn #dtor_ident(_: *const u8) { #ident() }; |
374 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".text.startup" )] |
375 | unsafe extern fn __dtor_atexit() { |
376 | do_atexit(#dtor_ident); |
377 | }; |
378 | __dtor_atexit |
379 | }; |
380 | } |
381 | ); |
382 | |
383 | // eprintln!("{}", output); |
384 | |
385 | output.into() |
386 | } |
387 | |
388 | fn validate_item(typ: &str, item: &syn::ItemFn) { |
389 | let syn::ItemFn { vis: &Visibility, sig: &Signature, .. } = item; |
390 | |
391 | // Ensure that visibility modifier is not present |
392 | match vis { |
393 | syn::Visibility::Inherited => {} |
394 | _ => panic!("#[ {}] methods must not have visibility modifiers" , typ), |
395 | } |
396 | |
397 | // No parameters allowed |
398 | if !sig.inputs.is_empty() { |
399 | panic!("#[ {}] methods may not have parameters" , typ); |
400 | } |
401 | |
402 | // No return type allowed |
403 | match sig.output { |
404 | syn::ReturnType::Default => {} |
405 | _ => panic!("#[ {}] methods must not have return types" , typ), |
406 | } |
407 | } |
408 | |