stack_debug/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{ItemFn, parse_macro_input};
4
5#[proc_macro_attribute]
6pub fn instrument(attr: TokenStream, item: TokenStream) -> TokenStream {
7    let input = parse_macro_input!(item as ItemFn);
8
9    // Ensure no attributes are passed to `instrument` for now.
10    // If attributes were needed, they would be parsed from `attr`.
11    if !attr.is_empty() {
12        return syn::Error::new_spanned(
13            proc_macro2::TokenStream::from(attr),
14            "#[stack_debug::instrument] does not take any arguments",
15        )
16        .to_compile_error()
17        .into();
18    }
19
20    let block = input.block;
21    let sig = input.sig;
22    let vis = input.vis;
23    let attrs = input.attrs;
24
25    #[cfg(feature = "tracing")]
26    let expanded = quote! {
27        #(#attrs)*
28        #[inline(never)]
29        #[tracing::instrument(skip_all)]
30        #vis #sig {
31            let rbp: usize;
32            let rsp: usize;
33
34            unsafe {
35                // These instructions are for x86_64.
36                // Other architectures like AArch64 may have different
37                // conventions or registers (e.g., `fp`).
38                #[cfg(target_arch = "x86_64")]
39                std::arch::asm!(
40                    "mov {}, rbp", // Get the base pointer
41                    "mov {}, rsp", // Get the stack pointer
42                    out(reg) rbp,
43                    out(reg) rsp,
44                );
45                // Add cfgs for other architectures if needed.
46            }
47
48            // rbp should be greater than rsp. If it's not, frame pointers
49            // were likely omitted, and the result is meaningless.
50            #[cfg(debug_assertions)]
51            let frame_size = rbp - rsp - 1520; // subtract overhead of instrumentation
52            #[cfg(not(debug_assertions))]
53            let frame_size = rbp - rsp - 224; // subtract overhead of instrumentation
54            tracing::info!("stack frame size: {frame_size}");
55
56            #block
57        }
58    };
59
60    #[cfg(not(feature = "tracing"))]
61    let expanded = {
62        let function_name = sig.ident.to_string();
63        quote! {
64            #(#attrs)*
65            #[inline(never)]
66            #vis #sig {
67                let rbp: usize;
68                let rsp: usize;
69
70                unsafe {
71                    // These instructions are for x86_64.
72                    // Other architectures like AArch64 may have different
73                    // conventions or registers (e.g., `fp`).
74                    #[cfg(target_arch = "x86_64")]
75                    std::arch::asm!(
76                        "mov {}, rbp", // Get the base pointer
77                        "mov {}, rsp", // Get the stack pointer
78                        out(reg) rbp,
79                        out(reg) rsp,
80                    );
81                    // Add cfgs for other architectures if needed.
82                }
83
84                // rbp should be greater than rsp. If it's not, frame pointers
85                // were likely omitted, and the result is meaningless.
86                #[cfg(debug_assertions)]
87                let frame_size = rbp - rsp - 128; // subtract overhead of instrumentation
88                #[cfg(not(debug_assertions))]
89                let frame_size = rbp - rsp - 80; // subtract overhead of instrumentation
90                println!("{}::{}(): stack frame size: {frame_size}", module_path!(), #function_name);
91
92                #block
93            }
94        }
95    };
96
97    expanded.into()
98}