Path: blob/main/hypervisor/hypervisor_test_macro/src/lib.rs
5394 views
// Copyright 2024 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34#![warn(missing_docs)]5#![recursion_limit = "128"]67//! Macros for hypervisor tests89use std::collections::hash_map::DefaultHasher;10use std::hash::Hash;11use std::hash::Hasher;12use std::sync::atomic::AtomicU64;1314use proc_macro::TokenStream;15use proc_macro2::Span;16use proc_macro2::TokenStream as TokenStream2;17use quote::quote;18use syn::parse::Parse;19use syn::parse_macro_input;20use syn::Error;21use syn::Ident;22use syn::LitStr;23use syn::Token;24use syn::Visibility;2526/// Embed the compiled assembly as an array.27///28/// This macro will generate a module with the given `$name` and provides a `data` function in the29/// module to allow accessing the compiled machine code as an array.30///31/// Note that this macro uses [`std::arch::global_asm`], so we can only use this macro in a global32/// scope, outside a function.33///34/// # Example35///36/// Given the following x86 assembly:37/// ```Text38/// 0: 01 d8 add eax,ebx39/// 2: f4 hlt40/// ```41///42/// ```rust43/// # use hypervisor_test_macro::global_asm_data;44/// global_asm_data!(45/// my_code,46/// ".code64",47/// "add eax, ebx",48/// "hlt",49/// );50/// # fn main() {51/// assert_eq!([0x01, 0xd8, 0xf4], my_code::data());52/// # }53/// ```54///55/// It is supported to pass arbitrary supported [`std::arch::global_asm`] operands and options.56/// ```rust57/// # use hypervisor_test_macro::global_asm_data;58/// fn f() {}59/// global_asm_data!(60/// my_code1,61/// ".global {0}",62/// ".code64",63/// "add eax, ebx",64/// "hlt",65/// sym f,66/// );67/// global_asm_data!(68/// my_code2,69/// ".code64",70/// "add eax, ebx",71/// "hlt",72/// options(raw),73/// );74/// # fn main() {75/// assert_eq!([0x01, 0xd8, 0xf4], my_code1::data());76/// assert_eq!([0x01, 0xd8, 0xf4], my_code2::data());77/// # }78/// ```79///80/// It is also supported to specify the visibility of the generated module. Note that the below81/// example won't work if the `pub` in the macro is missing.82/// ```rust83/// # use hypervisor_test_macro::global_asm_data;84/// mod my_mod {85/// // This use is needed to import the global_asm_data macro to this module.86/// use super::*;87///88/// global_asm_data!(89/// // pub is needed so that my_mod::my_code is visible to the outer scope.90/// pub my_code,91/// ".code64",92/// "add eax, ebx",93/// "hlt",94/// );95/// }96/// # fn main() {97/// assert_eq!([0x01, 0xd8, 0xf4], my_mod::my_code::data());98/// # }99/// ```100#[proc_macro]101pub fn global_asm_data(item: TokenStream) -> TokenStream {102let args = parse_macro_input!(item as GlobalAsmDataArgs);103global_asm_data_impl(args).unwrap_or_else(|e| e.to_compile_error().into())104}105106struct GlobalAsmDataArgs {107visibility: Visibility,108mod_name: Ident,109global_asm_strings: Vec<LitStr>,110global_asm_rest_args: TokenStream2,111}112113impl Parse for GlobalAsmDataArgs {114fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {115// The first argument is visibilty + identifier, e.g. my_code or pub my_code. The identifier116// will be used as the name of the gnerated module.117let visibility: Visibility = input.parse()?;118let mod_name: Ident = input.parse()?;119// There must be following arguments, so we consume the first argument separator here.120input.parse::<Token![,]>()?;121122// Retrieve the input assemblies, which are a list of comma separated string literals. We123// need to obtain the list of assemblies explicitly, so that we can insert the begin tag and124// the end tag to the global_asm! call when we generate the result code.125let mut global_asm_strings = vec![];126loop {127let lookahead = input.lookahead1();128if !lookahead.peek(LitStr) {129// If the upcoming tokens are not string literal, we hit the end of the input130// assemblies.131break;132}133global_asm_strings.push(input.parse::<LitStr>()?);134135if input.is_empty() {136// In case the current string literal is the last argument.137break;138}139input.parse::<Token![,]>()?;140if input.is_empty() {141// In case the current string literal is the last argument with a trailing comma.142break;143}144}145146// We store the rest of the arguments, and we will forward them as is to global_asm!.147let global_asm_rest_args: TokenStream2 = input.parse()?;148Ok(Self {149visibility,150mod_name,151global_asm_strings,152global_asm_rest_args,153})154}155}156157static COUNTER: AtomicU64 = AtomicU64::new(0);158159fn global_asm_data_impl(160GlobalAsmDataArgs {161visibility,162mod_name,163global_asm_strings,164global_asm_rest_args,165}: GlobalAsmDataArgs,166) -> Result<TokenStream, Error> {167let span = Span::call_site();168169// Generate the unique tags based on the macro input, code location and a random number to avoid170// symbol collision.171let tag_base_name = {172let content_id = {173let mut hasher = DefaultHasher::new();174span.source_text().hash(&mut hasher);175hasher.finish()176};177let location_id = format!(178"{}_{}_{}_{}",179span.start().line,180span.start().column,181span.end().line,182span.end().column183);184let rand_id: u64 = rand::random();185let static_counter_id = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);186let prefix = "crosvm_hypervisor_test_macro_global_asm_data";187format!("{prefix}_{mod_name}_{content_id}_{location_id}_{static_counter_id}_{rand_id}")188};189let start_tag = format!("{tag_base_name}_start");190let end_tag = format!("{tag_base_name}_end");191192let global_directive = LitStr::new(&format!(".global {start_tag}, {end_tag}"), span);193let start_tag_asm = LitStr::new(&format!("{start_tag}:"), span);194let end_tag_asm = LitStr::new(&format!("{end_tag}:"), span);195let start_tag_ident = Ident::new(&start_tag, span);196let end_tag_ident = Ident::new(&end_tag, span);197198Ok(quote! {199#visibility mod #mod_name {200use super::*;201202extern {203static #start_tag_ident: u8;204static #end_tag_ident: u8;205}206207std::arch::global_asm!(208#global_directive,209#start_tag_asm,210#(#global_asm_strings),*,211#end_tag_asm,212#global_asm_rest_args213);214pub fn data() -> &'static [u8] {215// SAFETY:216// * The extern statics are u8, and any arbitrary bit patterns are valid for u8.217// * The data starting from start to end is valid u8.218// * Without unsafe block, one can't mutate the value between start and end. In219// addition, it is likely that the data is written to a readonly block, and can't220// be mutated at all.221// * The address shouldn't be too large, and won't wrap around.222unsafe {223let ptr = std::ptr::addr_of!(#start_tag_ident);224let len = std::ptr::addr_of!(#end_tag_ident).offset_from(ptr);225std::slice::from_raw_parts(226ptr,227len.try_into().expect("length must be positive")228)229}230}231}232}233.into())234}235236237