1use std::process;
2use std::io::Write;
3
4extern crate proc_macro;
5
6mod att;
7mod x86;
8
9use proc_macro::{Delimiter, Literal, Group, Punct, Spacing, TokenStream, TokenTree};
10use quote::{quote, ToTokens};
11use rand::{thread_rng, Rng};
12
13#[proc_macro_attribute]
14pub fn assemble(args: TokenStream, input: TokenStream) -> TokenStream {
15 let attr = syn::parse_macro_input!(args as syn::AttributeArgs);
16 let mut assembler: Box<dyn Assembler> = choose_backed(&attr);
17
18 let (head, body) = split_function(input);
19 let asm_input = get_body(body);
20
21
22 let raw = assembler.assemble(&asm_input);
23 let len = raw.len();
24 let definition = {
25 let mut items = TokenStream::new();
26 for byte in &raw {
27 if !items.is_empty() {
28 items.extend(Some(TokenTree::Punct(Punct::new(',', Spacing::Alone))));
29 }
30 items.extend(Some(TokenTree::Literal(Literal::u8_unsuffixed(*byte))));
31 }
32 let tree = TokenTree::Group(Group::new(Delimiter::Bracket, items));
33 let stream = TokenStream::from(tree);
34 proc_macro2::TokenStream::from(stream)
35 };
36
37 let unique_name = choose_link_name();
38 let unique_ident = syn::Ident::new(&unique_name, proc_macro2::Span::call_site());
39 let mut binary_symbol = quote! {
40 mod #unique_ident {
41 #[link_section=".text"]
42 #[no_mangle]
43 static #unique_ident: [u8; #len] = #definition;
44 }
45 };
46
47 let function_def = syn::ForeignItem::Fn(syn::ForeignItemFn {
48 attrs: vec![syn::parse_quote!(#[link_name=#unique_name])],
49 vis: head.visibility,
50 sig: head.function_def,
51 semi_token: syn::token::Semi::default(),
52 });
53
54 let function_symbol = quote! {
55 extern "C" {
56 #function_def
57 }
58 };
59
60 binary_symbol.extend(function_symbol);
61 binary_symbol.into()
62}
63
64fn choose_backed(attr: &[syn::NestedMeta]) -> Box<dyn Assembler> {
65 enum Backend {
66 GnuAs,
67 Nasm,
68 Dynasm,
69 }
70
71 let backend = match &attr {
72 [syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { path , lit, .. }))] => {
73 if path.is_ident("backend") {
74 if let syn::Lit::Str(st) = lit {
75 match st.value().as_str() {
76 "nasm" => Backend::Nasm,
77 "dynasm" => Backend::Dynasm,
78 "gnu-as" | "gnuas" | "gas" | "as" => Backend::GnuAs,
79 _ => panic!("Unknown backend (nasm, dynasm, gnuas, gnu-as, gas, as)"),
80 }
81 } else {
82 panic!("Expected string value identifying backend");
83 }
84 } else {
85 panic!("Unexpected keyword")
86 }
87 },
88 [] => Backend::Dynasm,
89 _ => panic!("Backend is unknown"),
90 };
91
92 match backend {
93 Backend::GnuAs => Box::new(GnuAs {}),
94 Backend::Nasm => Box::new(Nasm),
95 Backend::Dynasm => Box::new(x86::DynasmX86::new()),
96 }
97}
98
99fn split_function(input: TokenStream) -> (Head, TokenStream) {
101 let mut fn_item = syn::parse::<syn::ItemFn>(input)
102 .expect("Must annotate a method definition");
103 assert!(fn_item.sig.abi.is_some(), "Must specify function as having C abi");
105 fn_item.sig.abi = None;
107 fn_item.sig.unsafety = None;
108 let head = Head {
109 function_def: fn_item.sig,
110 visibility: fn_item.vis,
111 };
112 (head, fn_item.block.to_token_stream().into())
113}
114
115fn get_body(block: TokenStream) -> String {
116 let body;
117 match &block.into_iter().next() {
118 Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => {
119 body = group.stream();
120 },
121 _ => panic!("Expected function body"),
122 };
123
124 let parts = body.into_iter().map(|item| match &item {
125 TokenTree::Literal(literal) => {
126 let stream = TokenTree::Literal(literal.clone()).into();
127 let litstr = syn::parse::<syn::LitStr>(stream)
128 .expect("Body only contain string literals");
129 litstr.value()
130 },
131 TokenTree::Punct(punc) if punc.as_char() == ';' => "\n".to_string(),
132 other => panic!("Unexpected body content: {:?}", other),
133 });
134
135 parts.collect()
136}
137
138fn choose_link_name() -> String {
144 const CHOICES: &[u8; 64] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ZZ";
145 let mut randoms = [0u8; 32];
146 thread_rng().fill(&mut randoms);
147
148 let random = randoms
149 .iter()
150 .map(|idx| usize::from(idx & 63))
151 .map(|idx| std::char::from_u32(CHOICES[idx].into()).unwrap())
152 .collect::<String>();
153
154 format!("_direct_asm_{}", random)
155}
156
157struct Head {
158 function_def: syn::Signature,
159 visibility: syn::Visibility,
160}
161
162trait Assembler {
163 fn assemble(&mut self, input: &str) -> Vec<u8>;
164}
165
166struct Nasm;
167
168struct GnuAs {
169}
170
171fn nasmify(input: &str) -> Vec<u8> {
172 let input = format!("[BITS 64]\n{}", input);
173 std::fs::write("target/indirection.in", &input).unwrap();
174
175 let mut nasm = process::Command::new("nasm")
176 .stdin(process::Stdio::piped())
177 .stdout(process::Stdio::piped())
178 .stderr(process::Stdio::piped())
179 .args(&["-f", "bin", "-o", "/proc/self/fd/1", "target/indirection.in"])
180 .spawn()
181 .expect("Failed to spawn assembler");
182
183 let stdin = nasm.stdin.as_mut().expect("Nasm must accept piped input");
184 stdin.write_all(input.as_bytes()).expect("Failed to supply nasm with input");
185 stdin.flush().expect("Failed to flush");
186
187 let output = nasm.wait_with_output().expect("Failed to wait for nasm");
188 if !output.status.success() || !output.stderr.is_empty() {
189 panic!("Nasm failed: {}", String::from_utf8_lossy(&output.stderr));
190 }
191
192 output.stdout
193}
194
195impl Assembler for Nasm {
196 fn assemble(&mut self, input: &str) -> Vec<u8> {
197 nasmify(input)
198 }
199}
200
201impl Assembler for GnuAs {
202 fn assemble(&mut self, original_input: &str) -> Vec<u8> {
203 let newlined;
204 let input: &str;
205
206 if original_input.chars().rev().next() != Some('\n') {
207 newlined = format!("{}\n", original_input);
208 input = &newlined;
209 } else {
210 input = original_input;
211 }
212
213 const ASSEMBLED_FILE: &str = "target/gnu-as.out";
214 let mut as_ = process::Command::new("as")
220 .arg("-msse-check=error")
222 .arg("-moperand-check=error")
223 .arg("-mmnemonic=intel")
224 .arg("-msyntax=intel")
225 .args(&["-o", ASSEMBLED_FILE])
226 .stdin(process::Stdio::piped())
227 .stdout(process::Stdio::piped())
228 .stderr(process::Stdio::piped())
229 .spawn()
230 .expect("Failed to spawn assembler");
231
232 let stdin = as_.stdin.as_mut().expect("As must accept piped input");
233 stdin.write_all(input.as_bytes()).expect("Failed to supply as with input");
234 stdin.flush().expect("Failed to flush");
235
236 let output = as_.wait_with_output().expect("Failed to wait for as");
237 if !output.status.success() || !output.stderr.is_empty() {
238 panic!("Gnu As failed: {}", String::from_utf8_lossy(&output.stderr));
239 }
240
241 let status = process::Command::new("objcopy")
245 .args(&["-O", "binary"])
246 .arg(ASSEMBLED_FILE)
247 .status()
248 .expect("Failed to spawn `objcopy`");
249 assert!(status.success(), "`objcopy` failed");
250
251 std::fs::read(ASSEMBLED_FILE).expect("No output produced")
252 }
253}