ctor/
lib.rs

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
30extern crate proc_macro;
31extern crate syn;
32#[macro_use]
33extern crate quote;
34
35use 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)]
40macro_rules! ctor_attributes {
41    () => {
42        quote!(
43            #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
44            #[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
45            #[cfg_attr(target_os = "netbsd", link_section = ".init_array")]
46            #[cfg_attr(target_os = "openbsd", link_section = ".init_array")]
47            #[cfg_attr(target_os = "dragonfly", link_section = ".init_array")]
48            #[cfg_attr(target_os = "illumos", link_section = ".init_array")]
49            #[cfg_attr(target_os = "haiku", link_section = ".init_array")]
50            #[cfg_attr(any(target_os = "macos", target_os = "ios"), link_section = "__DATA,__mod_init_func")]
51            #[cfg_attr(windows, link_section = ".CRT$XCU")]
52        )
53    };
54}
55
56/// Marks a function or static variable as a library/executable constructor.
57/// This uses OS-specific linker sections to call a specific function at
58/// load time.
59///
60/// Multiple startup functions/statics are supported, but the invocation order is not
61/// guaranteed.
62///
63/// # Examples
64///
65/// Print a startup message (using `libc_print` for safety):
66///
67/// ```rust
68/// # #![feature(used_with_arg)]
69/// #
70/// # extern crate ctor;
71/// # use ctor::*;
72/// use libc_print::std_name::println;
73///
74/// #[ctor]
75/// fn foo() {
76///   println!("Hello, world!");
77/// }
78///
79/// # fn main() {
80/// println!("main()");
81/// # }
82/// ```
83///
84/// Make changes to `static` variables:
85///
86/// ```rust
87/// # #![feature(used_with_arg)]
88/// #
89/// # extern crate ctor;
90/// # use ctor::*;
91/// # use std::sync::atomic::{AtomicBool, Ordering};
92/// static INITED: AtomicBool = AtomicBool::new(false);
93///
94/// #[ctor]
95/// fn foo() {
96///   INITED.store(true, Ordering::SeqCst);
97/// }
98/// ```
99///
100/// Initialize a `HashMap` at startup time:
101///
102/// ```rust
103/// # #![feature(used_with_arg)]
104/// #
105/// # extern crate ctor;
106/// # use std::collections::HashMap;
107/// # use ctor::*;
108/// #[ctor]
109/// static STATIC_CTOR: HashMap<u32, String> = {
110///   let mut m = HashMap::new();
111///   for i in 0..100 {
112///     m.insert(i, format!("x*100={}", i*100));
113///   }
114///   m
115/// };
116///
117/// # pub fn main() {
118/// #   assert_eq!(STATIC_CTOR.len(), 100);
119/// #   assert_eq!(STATIC_CTOR[&20], "x*100=2000");
120/// # }
121/// ```
122///
123/// # Details
124///
125/// The `#[ctor]` macro makes use of linker sections to ensure that a
126/// function is run at startup time.
127///
128/// The above example translates into the following Rust code (approximately):
129///
130///```rust
131/// # #![feature(used_with_arg)]
132/// #
133/// #[used(linker)]
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"), 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]
148pub 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        // Linux/ELF: https://www.exploit-db.com/papers/13234
169
170        // Mac details: https://blog.timac.org/2016/0716-constructor-and-destructor-attributes/
171
172        // Why .CRT$XCU on Windows? https://www.cnblogs.com/sunkang/archive/2011/05/24/2055635.html
173        // 'I'=C init, 'C'=C++ init, 'P'=Pre-terminators and 'T'=Terminators
174
175        let ctor_ident =
176            syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___ctor", ident).as_ref())
177                .expect("Unable to create identifier");
178
179        let tokens = ctor_attributes!();
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_os = "macos", target_os = "ios", 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(linker)]
188            #[allow(non_upper_case_globals)]
189            #[doc(hidden)]
190            #tokens
191            static #ctor_ident
192            :
193            unsafe extern "C" fn() =
194            {
195                #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
196                unsafe extern "C" fn #ctor_ident() { #ident() };
197                #ctor_ident
198            }
199            ;
200        );
201
202        // eprintln!("{}", output);
203
204        output.into()
205    } else if let syn::Item::Static(var) = item {
206        let syn::ItemStatic {
207            ident,
208            mutability,
209            expr,
210            attrs,
211            ty,
212            vis,
213            ..
214        } = var;
215
216        if matches!(mutability, syn::StaticMutability::Mut(_)) {
217            panic!("#[ctor]-annotated static objects must not be mutable");
218        }
219
220        if attrs.iter().any(|attr| {
221            attr.path()
222                .segments
223                .iter()
224                .any(|segment| segment.ident == "no_mangle")
225        }) {
226            panic!("#[ctor]-annotated static objects do not support #[no_mangle]");
227        }
228
229        let ctor_ident =
230            syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___ctor", ident).as_ref())
231                .expect("Unable to create identifier");
232        let storage_ident =
233            syn::parse_str::<syn::Ident>(format!("{}___rust_ctor___storage", ident).as_ref())
234                .expect("Unable to create identifier");
235
236        let tokens = ctor_attributes!();
237        let output = quote!(
238            #[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", windows)))]
239            compile_error!("#[ctor] is not supported on the current target");
240
241            // This is mutable, but only by this macro code!
242            static mut #storage_ident: Option<#ty> = None;
243
244            #[doc(hidden)]
245            #[allow(non_camel_case_types)]
246            #vis struct #ident<T> {
247                _data: core::marker::PhantomData<T>
248            }
249
250            #(#attrs)*
251            #vis static #ident: #ident<#ty> = #ident {
252                _data: core::marker::PhantomData::<#ty>
253            };
254
255            impl core::ops::Deref for #ident<#ty> {
256                type Target = #ty;
257                fn deref(&self) -> &'static #ty {
258                    unsafe {
259                        #storage_ident.as_ref().unwrap()
260                    }
261                }
262            }
263
264            #[used(linker)]
265            #[allow(non_upper_case_globals)]
266            #tokens
267            static #ctor_ident
268            :
269            unsafe extern "C" fn() = {
270                #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
271                unsafe extern "C" fn initer() {
272                    #storage_ident = Some(#expr);
273                }; initer }
274            ;
275        );
276
277        // eprintln!("{}", output);
278
279        output.into()
280    } else {
281        panic!("#[ctor] items must be functions or static globals");
282    }
283}
284
285/// Marks a function as a library/executable destructor. This uses OS-specific
286/// linker sections to call a specific function at termination time.
287///
288/// Multiple shutdown functions are supported, but the invocation order is not
289/// guaranteed.
290///
291/// `sys_common::at_exit` is usually a better solution for shutdown handling, as
292/// it allows you to use `stdout` in your handlers.
293///
294/// ```rust
295/// # #![feature(used_with_arg)]
296/// #
297/// # extern crate ctor;
298/// # use ctor::*;
299/// # fn main() {}
300///
301/// #[dtor]
302/// fn shutdown() {
303///   /* ... */
304/// }
305/// ```
306#[proc_macro_attribute]
307pub fn dtor(_attribute: TokenStream, function: TokenStream) -> TokenStream {
308    let function: syn::ItemFn = syn::parse_macro_input!(function);
309    validate_item("dtor", &function);
310
311    let syn::ItemFn {
312        attrs,
313        block,
314        vis,
315        sig:
316            syn::Signature {
317                ident,
318                unsafety,
319                constness,
320                abi,
321                ..
322            },
323        ..
324    } = function;
325
326    let mod_ident = syn::parse_str::<syn::Ident>(format!("{}___rust_dtor___mod", ident).as_ref())
327        .expect("Unable to create identifier");
328
329    let dtor_ident = syn::parse_str::<syn::Ident>(format!("{}___rust_dtor___dtor", ident).as_ref())
330        .expect("Unable to create identifier");
331
332    let tokens = ctor_attributes!();
333    let output = quote!(
334        #[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", windows)))]
335        compile_error!("#[dtor] is not supported on the current target");
336
337        #(#attrs)*
338        #vis #unsafety extern #abi #constness fn #ident() #block
339
340        mod #mod_ident {
341            use super::#ident;
342
343            // Note that we avoid a dep on the libc crate by linking directly to atexit functions
344
345            #[cfg(not(any(target_os = "macos", target_os = "ios")))]
346            #[inline(always)]
347            unsafe fn do_atexit(cb: unsafe extern fn()) {
348                extern "C" {
349                    fn atexit(cb: unsafe extern fn());
350                }
351                atexit(cb);
352            }
353
354            // For platforms that have __cxa_atexit, we register the dtor as scoped to dso_handle
355            #[cfg(any(target_os = "macos", target_os = "ios"))]
356            #[inline(always)]
357            unsafe fn do_atexit(cb: unsafe extern fn()) {
358                extern "C" {
359                    static __dso_handle: *const u8;
360                    fn __cxa_atexit(cb: unsafe extern fn(), arg: *const u8, dso_handle: *const u8);
361                }
362                __cxa_atexit(cb, std::ptr::null(), __dso_handle);
363            }
364
365            #[used(linker)]
366            #[allow(non_upper_case_globals)]
367            #tokens
368            static __dtor_export
369            :
370            unsafe extern "C" fn() =
371            {
372                #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.exit")]
373                unsafe extern "C" fn #dtor_ident() { #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
388fn validate_item(typ: &str, item: &syn::ItemFn) {
389    let syn::ItemFn { vis, sig, .. } = 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}