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