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(target_vendor = "apple" , 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(target_vendor = "apple" , link_section = "__DATA,__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 | |
174 | let used = if cfg!(feature = "used_linker" ) { |
175 | quote!(#[used(linker)]) |
176 | } else { |
177 | quote!(#[used]) |
178 | }; |
179 | |
180 | let output = quote!( |
181 | #[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_vendor = "apple" , windows)))] |
182 | compile_error!("#[ctor] is not supported on the current target" ); |
183 | |
184 | #(#attrs)* |
185 | #vis #unsafety extern #abi #constness fn #ident() #block |
186 | |
187 | #used |
188 | #[allow(non_upper_case_globals, non_snake_case)] |
189 | #[doc(hidden)] |
190 | #tokens |
191 | static #ctor_ident |
192 | : |
193 | unsafe extern "C" fn() -> usize = |
194 | { |
195 | #[allow(non_snake_case)] |
196 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".text.startup" )] |
197 | unsafe extern "C" fn #ctor_ident() -> usize { #ident(); 0 }; |
198 | #ctor_ident |
199 | } |
200 | ; |
201 | ); |
202 | |
203 | // eprintln!("{}", output); |
204 | |
205 | output.into() |
206 | } else if let syn::Item::Static(var) = item { |
207 | let syn::ItemStatic { |
208 | ident, |
209 | mutability, |
210 | expr, |
211 | attrs, |
212 | ty, |
213 | vis, |
214 | .. |
215 | } = var; |
216 | |
217 | if matches!(mutability, syn::StaticMutability::Mut(_)) { |
218 | panic!("#[ctor]-annotated static objects must not be mutable" ); |
219 | } |
220 | |
221 | if attrs.iter().any(|attr| { |
222 | attr.path() |
223 | .segments |
224 | .iter() |
225 | .any(|segment| segment.ident == "no_mangle" ) |
226 | }) { |
227 | panic!("#[ctor]-annotated static objects do not support #[no_mangle]" ); |
228 | } |
229 | |
230 | let ctor_ident = |
231 | syn::parse_str::<syn::Ident>(format!(" {}___rust_ctor___ctor" , ident).as_ref()) |
232 | .expect("Unable to create identifier" ); |
233 | let storage_ident = |
234 | syn::parse_str::<syn::Ident>(format!(" {}___rust_ctor___storage" , ident).as_ref()) |
235 | .expect("Unable to create identifier" ); |
236 | |
237 | let tokens = ctor_attributes!(); |
238 | let output = quote!( |
239 | #[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_vendor = "apple" , windows)))] |
240 | compile_error!("#[ctor] is not supported on the current target" ); |
241 | |
242 | // This is mutable, but only by this macro code! |
243 | static mut #storage_ident: Option<#ty> = None; |
244 | |
245 | #[doc(hidden)] |
246 | #[allow(non_camel_case_types)] |
247 | #vis struct #ident<T> { |
248 | _data: ::core::marker::PhantomData<T> |
249 | } |
250 | |
251 | #(#attrs)* |
252 | #vis static #ident: #ident<#ty> = #ident { |
253 | _data: ::core::marker::PhantomData::<#ty> |
254 | }; |
255 | |
256 | impl ::core::ops::Deref for #ident<#ty> { |
257 | type Target = #ty; |
258 | fn deref(&self) -> &'static #ty { |
259 | unsafe { |
260 | #storage_ident.as_ref().unwrap() |
261 | } |
262 | } |
263 | } |
264 | |
265 | #[used] |
266 | #[allow(non_upper_case_globals)] |
267 | #tokens |
268 | static #ctor_ident |
269 | : |
270 | unsafe extern "C" fn() = { |
271 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".text.startup" )] |
272 | extern "C" fn initer() { |
273 | let val = Some(#expr); |
274 | // Only write the value to `storage_ident` on startup |
275 | unsafe { |
276 | #storage_ident = val; |
277 | } |
278 | }; initer } |
279 | ; |
280 | ); |
281 | |
282 | // eprintln!("{}", output); |
283 | |
284 | output.into() |
285 | } else { |
286 | panic!("#[ctor] items must be functions or static globals" ); |
287 | } |
288 | } |
289 | |
290 | /// Marks a function as a library/executable destructor. This uses OS-specific |
291 | /// linker sections to call a specific function at termination time. |
292 | /// |
293 | /// Multiple shutdown functions are supported, but the invocation order is not |
294 | /// guaranteed. |
295 | /// |
296 | /// `sys_common::at_exit` is usually a better solution for shutdown handling, as |
297 | /// it allows you to use `stdout` in your handlers. |
298 | /// |
299 | /// ```rust |
300 | /// # extern crate ctor; |
301 | /// # use ctor::*; |
302 | /// # fn main() {} |
303 | /// |
304 | /// #[dtor] |
305 | /// fn shutdown() { |
306 | /// /* ... */ |
307 | /// } |
308 | /// ``` |
309 | #[proc_macro_attribute ] |
310 | pub fn dtor (_attribute: TokenStream, function: TokenStream) -> TokenStream { |
311 | let function: syn::ItemFn = syn::parse_macro_input!(function); |
312 | validate_item("dtor" , &function); |
313 | |
314 | let syn::ItemFn { |
315 | attrs, |
316 | block, |
317 | vis, |
318 | sig: |
319 | syn::Signature { |
320 | ident, |
321 | unsafety, |
322 | constness, |
323 | abi, |
324 | .. |
325 | }, |
326 | .. |
327 | } = function; |
328 | |
329 | let mod_ident = syn::parse_str::<syn::Ident>(format!(" {}___rust_dtor___mod" , ident).as_ref()) |
330 | .expect("Unable to create identifier" ); |
331 | |
332 | let dtor_ident = syn::parse_str::<syn::Ident>(format!(" {}___rust_dtor___dtor" , ident).as_ref()) |
333 | .expect("Unable to create identifier" ); |
334 | |
335 | let tokens = ctor_attributes!(); |
336 | let output = quote!( |
337 | #[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_vendor = "apple" , windows)))] |
338 | compile_error!("#[dtor] is not supported on the current target" ); |
339 | |
340 | #(#attrs)* |
341 | #vis #unsafety extern #abi #constness fn #ident() #block |
342 | |
343 | mod #mod_ident { |
344 | use super::#ident; |
345 | |
346 | // Note that we avoid a dep on the libc crate by linking directly to atexit functions |
347 | |
348 | #[cfg(not(target_vendor = "apple" ))] |
349 | #[inline(always)] |
350 | unsafe fn do_atexit(cb: unsafe extern fn()) { |
351 | extern "C" { |
352 | fn atexit(cb: unsafe extern fn()); |
353 | } |
354 | atexit(cb); |
355 | } |
356 | |
357 | // For platforms that have __cxa_atexit, we register the dtor as scoped to dso_handle |
358 | #[cfg(target_vendor = "apple" )] |
359 | #[inline(always)] |
360 | unsafe fn do_atexit(cb: unsafe extern fn(_: *const u8)) { |
361 | extern "C" { |
362 | static __dso_handle: *const u8; |
363 | fn __cxa_atexit(cb: unsafe extern fn(_: *const u8), arg: *const u8, dso_handle: *const u8); |
364 | } |
365 | __cxa_atexit(cb, core::ptr::null(), __dso_handle); |
366 | } |
367 | |
368 | #[used] |
369 | #[allow(non_upper_case_globals)] |
370 | #tokens |
371 | static __dtor_export |
372 | : |
373 | unsafe extern "C" fn() = |
374 | { |
375 | #[cfg(not(target_vendor = "apple" ))] |
376 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".text.exit" )] |
377 | unsafe extern "C" fn #dtor_ident() { #ident() }; |
378 | #[cfg(target_vendor = "apple" )] |
379 | unsafe extern "C" fn #dtor_ident(_: *const u8) { #ident() }; |
380 | #[cfg_attr(any(target_os = "linux" , target_os = "android" ), link_section = ".text.startup" )] |
381 | unsafe extern fn __dtor_atexit() { |
382 | do_atexit(#dtor_ident); |
383 | }; |
384 | __dtor_atexit |
385 | }; |
386 | } |
387 | ); |
388 | |
389 | // eprintln!("{}", output); |
390 | |
391 | output.into() |
392 | } |
393 | |
394 | fn validate_item(typ: &str, item: &syn::ItemFn) { |
395 | let syn::ItemFn { vis: &Visibility, sig: &Signature, .. } = item; |
396 | |
397 | // Ensure that visibility modifier is not present |
398 | match vis { |
399 | syn::Visibility::Inherited => {} |
400 | _ => panic!("#[ {}] methods must not have visibility modifiers" , typ), |
401 | } |
402 | |
403 | // No parameters allowed |
404 | if !sig.inputs.is_empty() { |
405 | panic!("#[ {}] methods may not have parameters" , typ); |
406 | } |
407 | |
408 | // No return type allowed |
409 | match sig.output { |
410 | syn::ReturnType::Default => {} |
411 | _ => panic!("#[ {}] methods must not have return types" , typ), |
412 | } |
413 | } |
414 | |