syscall_attr/
lib.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use std::iter::repeat;
4use syn::{
5    parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Paren, AttributeArgs,
6    Error, Expr, FnArg, Ident, ItemFn, NestedMeta, Pat, PatIdent, PatType, Result, ReturnType,
7    Stmt, Token, Type, TypeTuple,
8};
9
10#[proc_macro_attribute]
11pub fn syscall(
12    attrs: proc_macro::TokenStream,
13    item: proc_macro::TokenStream,
14) -> proc_macro::TokenStream {
15    let args = parse_macro_input!(attrs as AttributeArgs);
16    let input = parse_macro_input!(item as ItemFn);
17    expand(args, input)
18        .unwrap_or_else(Error::into_compile_error)
19        .into()
20}
21
22fn expand(_args: Vec<NestedMeta>, mut input: ItemFn) -> Result<proc_macro2::TokenStream> {
23    let body = &input.block;
24    let mut pre_block = Vec::new();
25    let mut post_block = Vec::new();
26    let mut real_args = None;
27    let mut real_ret = None;
28
29    for stmt in &body.stmts {
30        if real_args.is_none() {
31            match stmt {
32                Stmt::Local(local) => {
33                    // let x = real!(a1, a2, ...);
34                    // binding real!() retrun value to x
35                    if let Some((_, expr)) = &local.init {
36                        real_args = is_real_macro(expr);
37                        if real_args.is_some() {
38                            if let Pat::Ident(pat_ident) = &local.pat {
39                                real_ret = Some(pat_ident.clone());
40                            }
41                        }
42                    }
43                }
44                Stmt::Expr(expr) => {
45                    // real!(a1, a2, ...)
46                    // this is the last expr, return value from real!() returned
47                    real_args = is_real_macro(expr);
48                }
49                Stmt::Semi(expr, _) => {
50                    // real!(a1, a2, ...);
51                    // ignore the real!() return value
52                    real_args = is_real_macro(expr)
53                }
54                Stmt::Item(_) => {
55                    // ignore it
56                }
57            }
58
59            if real_args.is_some() {
60                continue;
61            }
62
63            pre_block.push(stmt.clone());
64            continue;
65        }
66
67        post_block.push(stmt.clone());
68    }
69
70    let attrs = &input.attrs;
71    let sig = &mut input.sig;
72    let vis = &input.vis;
73    let ident = &sig.ident;
74    let ident_str = &sig.ident.to_string();
75
76    // build pre function, signature is
77    // fn pre_{origin_name}({origin_args_values} : {origin_args_types}) -> {origin_args_types}
78    let mut sig_pre = sig.clone();
79    sig_pre.ident = Ident::new(&format!("pre_{}", sig.ident), sig.span());
80    let ident_pre = &sig_pre.ident;
81    let mut args = sig_pre
82        .inputs
83        .iter()
84        .filter_map(|a| {
85            if let FnArg::Typed(t) = a {
86                Some(t.ty.clone())
87            } else {
88                None
89            }
90        })
91        .collect::<Vec<_>>();
92    let fn_variant = Ident::new(&format!("Func{}", args.len()), sig_pre.span());
93    let dummy_args =
94        repeat(Box::new(Type::Verbatim(quote!(u64)))).take(6usize.saturating_sub(args.len()));
95    args.extend(dummy_args);
96    let sig_ret = match &sig.output {
97        ReturnType::Default => Box::new(Type::Verbatim(quote!(()))),
98        ReturnType::Type(_, bt) => bt.clone(),
99    };
100    let (real_args, pre_func) = if real_args.is_none() {
101        // if not call real() in function, syscall will not be sent to kernel.
102        // we should not change origin function at this time.
103        // in other words, pre_func == origin_func
104        (
105            quote!(),
106            quote!(interceptor_rs::syscall::Variant::<#sig_ret, #(#args),*>::Block(interceptor_rs::syscall::BlockVariant::#fn_variant(#ident_pre))),
107        )
108    } else {
109        sig_pre.output = ReturnType::Type(
110            Token![->](sig_pre.span()),
111            Box::new(Type::Tuple(TypeTuple {
112                paren_token: Paren {
113                    span: sig_pre.span(),
114                },
115                elems: {
116                    sig.inputs
117                        .iter()
118                        .filter_map(|a| {
119                            if let FnArg::Typed(pt) = a {
120                                Some(*(pt.ty.clone()))
121                            } else {
122                                None
123                            }
124                        })
125                        .collect()
126                },
127            })),
128        );
129        (
130            quote!((#real_args)),
131            quote!(interceptor_rs::syscall::Variant::<#sig_ret, #(#args),*>::Passthrough(interceptor_rs::syscall::PassthroughVariant::#fn_variant(#ident_pre))),
132        )
133    };
134
135    // build post function, signature is
136    // fn post_{origin_name}({real_ret_variable} : {origin_ret_type}) -> {origin_ret_type}
137    let mut sig_post = sig.clone();
138    sig_post.ident = Ident::new(&format!("post_{}", sig.ident), sig.span());
139    let mut sig_post_args = Punctuated::new();
140    let sig_post_arg = if let Some(rr) = &real_ret {
141        rr.clone()
142    } else {
143        PatIdent {
144            attrs: vec![],
145            by_ref: None,
146            mutability: None,
147            ident: Ident::new("real_ret", sig_post_args.span()),
148            subpat: None,
149        }
150    };
151    let sig_post_arg_ident = &sig_post_arg.ident;
152    let post_block = if post_block.is_empty() {
153        quote!(#sig_post_arg_ident)
154    } else {
155        quote!(#(#post_block)*)
156    };
157
158    sig_post_args.push_value(FnArg::Typed(PatType {
159        attrs: vec![],
160        pat: Box::new(Pat::Ident(sig_post_arg)),
161        colon_token: Token![:](sig_post_args.span()),
162        ty: sig_ret.clone(),
163    }));
164    sig_post.inputs = sig_post_args;
165    let ident_post = &sig_post.ident;
166
167    Ok(quote!(
168        #(#attrs)*
169        #vis #sig_pre {
170            {#(#pre_block)*}
171            #real_args
172        }
173
174        #(#attrs)*
175        #vis #sig_post {
176            #post_block
177        }
178
179        #[allow(non_upper_case_globals)]
180        #vis static #ident: interceptor_rs::syscall::SysCall<#sig_ret, #(#args),*> = interceptor_rs::syscall::SysCall {
181            name: #ident_str,
182            pre: #pre_func,
183            post: #ident_post,
184        };
185    ))
186}
187
188fn is_real_macro(expr: &Expr) -> Option<TokenStream> {
189    if let Expr::Macro(expr_macro) = expr {
190        let mac = &expr_macro.mac;
191        if let Some(ident) = mac.path.get_ident() {
192            if *ident == "real" {
193                return Some(mac.tokens.clone());
194            }
195        }
196    }
197
198    None
199}