mirror of https://github.com/aya-rs/aya
Add aya-gen
aya-gen can be used to generate bindings for kernel types, eg: aya-gen btf-types ethhdr --probe-read-getters Will generate: // ... #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct ethhdr { pub h_dest: [::aya_bpf_cty::c_uchar; 6usize], pub h_source: [::aya_bpf_cty::c_uchar; 6usize], pub h_proto: __be16, } impl ethhdr { pub fn h_dest(&self) -> Option<[::aya_bpf_cty::c_uchar; 6usize]> { unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.h_dest) }.ok() } pub fn h_source(&self) -> Option<[::aya_bpf_cty::c_uchar; 6usize]> { unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.h_source) }.ok() } pub fn h_proto(&self) -> Option<__be16> { unsafe { ::aya_bpf::helpers::bpf_probe_read(&self.h_proto) }.ok() } }pull/1/head
parent
7815711196
commit
b66a73f6c7
@ -1,2 +1,2 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["aya", "xtask"]
|
members = ["aya", "aya-gen", "xtask"]
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "aya-gen"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Alessandro Decina <alessandro.d@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bindgen = "0.57"
|
||||||
|
structopt = {version = "0.3", default-features = false }
|
||||||
|
anyhow = "1"
|
||||||
|
thiserror = "1"
|
||||||
|
syn = "1"
|
||||||
|
quote = "1"
|
||||||
|
proc-macro2 = "1"
|
||||||
|
indexmap = "1.6"
|
@ -0,0 +1,45 @@
|
|||||||
|
use aya_gen::btf_types;
|
||||||
|
|
||||||
|
use std::{path::PathBuf, process::exit};
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
pub struct Options {
|
||||||
|
#[structopt(subcommand)]
|
||||||
|
command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
enum Command {
|
||||||
|
#[structopt(name = "btf-types")]
|
||||||
|
BtfTypes {
|
||||||
|
#[structopt(long, default_value = "/sys/kernel/btf/vmlinux")]
|
||||||
|
btf: PathBuf,
|
||||||
|
#[structopt(long)]
|
||||||
|
probe_read_getters: bool,
|
||||||
|
names: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if let Err(e) = try_main() {
|
||||||
|
eprintln!("{:#}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_main() -> Result<(), anyhow::Error> {
|
||||||
|
let opts = Options::from_args();
|
||||||
|
match opts.command {
|
||||||
|
Command::BtfTypes {
|
||||||
|
btf,
|
||||||
|
probe_read_getters,
|
||||||
|
names,
|
||||||
|
} => {
|
||||||
|
let bindings = btf_types::generate(&btf, &names, probe_read_getters)?;
|
||||||
|
println!("{}", bindings);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
use std::{io, path::Path, process::Command, str::from_utf8};
|
||||||
|
|
||||||
|
use bindgen::builder;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
getters::{generate_getters_for_items, probe_read_getter},
|
||||||
|
rustfmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("error executing bpftool")]
|
||||||
|
BpfTool(#[source] io::Error),
|
||||||
|
|
||||||
|
#[error("{stderr}\nbpftool failed with exit code {code}")]
|
||||||
|
BpfToolExit { code: i32, stderr: String },
|
||||||
|
|
||||||
|
#[error("bindgen failed")]
|
||||||
|
Bindgen,
|
||||||
|
|
||||||
|
#[error("rustfmt failed")]
|
||||||
|
Rustfmt(#[source] io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate<T: AsRef<str>>(
|
||||||
|
btf_file: &Path,
|
||||||
|
types: &[T],
|
||||||
|
probe_read_getters: bool,
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
let mut bindgen = builder()
|
||||||
|
.use_core()
|
||||||
|
.ctypes_prefix("::aya_bpf_cty")
|
||||||
|
.layout_tests(false)
|
||||||
|
.clang_arg("-Wno-unknown-attributes");
|
||||||
|
|
||||||
|
let c_header = c_header_from_btf(btf_file)?;
|
||||||
|
bindgen = bindgen.header_contents("kernel_types.h", &c_header);
|
||||||
|
|
||||||
|
for ty in types {
|
||||||
|
bindgen = bindgen.whitelist_type(ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bindings = bindgen.generate().or(Err(Error::Bindgen))?.to_string();
|
||||||
|
if !probe_read_getters {
|
||||||
|
return Ok(bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = syn::parse_str::<syn::File>(&bindings).unwrap();
|
||||||
|
let bpf_probe_read = syn::parse_str::<syn::Path>("::aya_bpf::helpers::bpf_probe_read").unwrap();
|
||||||
|
let getters = generate_getters_for_items(&tree.items, |getter| {
|
||||||
|
probe_read_getter(getter, &bpf_probe_read)
|
||||||
|
});
|
||||||
|
let getters =
|
||||||
|
rustfmt::format(&getters.to_string()).map_err(|io_error| Error::Rustfmt(io_error))?;
|
||||||
|
|
||||||
|
let bindings = format!("{}\n{}", bindings, getters);
|
||||||
|
|
||||||
|
Ok(bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn c_header_from_btf(path: &Path) -> Result<String, Error> {
|
||||||
|
let output = Command::new("bpftool")
|
||||||
|
.args(&["btf", "dump", "file"])
|
||||||
|
.arg(path)
|
||||||
|
.args(&["format", "c"])
|
||||||
|
.output()
|
||||||
|
.map_err(|io_error| Error::BpfTool(io_error))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(Error::BpfToolExit {
|
||||||
|
code: output.status.code().unwrap(),
|
||||||
|
stderr: from_utf8(&output.stderr).unwrap().to_owned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(from_utf8(&output.stdout).unwrap().to_owned())
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
use indexmap::IndexMap;
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::{quote, TokenStreamExt};
|
||||||
|
use syn::{
|
||||||
|
self, Fields, FieldsNamed, Generics, Ident, Item, ItemStruct, ItemUnion, Path, Type, TypePath,
|
||||||
|
Visibility,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct GetterList<'a> {
|
||||||
|
slf: Ident,
|
||||||
|
item_fields: IndexMap<Ident, (&'a Item, &'a FieldsNamed)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GetterList<'a> {
|
||||||
|
pub fn new(items: &'a [Item]) -> GetterList<'a> {
|
||||||
|
let item_fields = items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
unpack_item(item).map(|(ident, _generics, fields)| (ident.clone(), (item, fields)))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
GetterList {
|
||||||
|
slf: Ident::new("self", Span::call_site()),
|
||||||
|
item_fields,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&'a Item, Vec<Getter<'_>>)> {
|
||||||
|
self.item_fields
|
||||||
|
.values()
|
||||||
|
.map(move |(item, fields)| (*item, self.getters(&self.slf, fields)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getters(&self, ident: &'a Ident, fields: &'a FieldsNamed) -> Vec<Getter<'a>> {
|
||||||
|
let mut getters = Vec::new();
|
||||||
|
for field in &fields.named {
|
||||||
|
if let Visibility::Inherited = field.vis {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let field_ident = field.ident.as_ref().unwrap();
|
||||||
|
let field_s = field_ident.to_string();
|
||||||
|
|
||||||
|
// FIXME: bindgen generates fields named `_bitfield_N` for bitfields. If a type T has
|
||||||
|
// two or more unions with bitfields, the getters for the bitfields - generated in impl
|
||||||
|
// T - will clash. To avoid that we skip getters for bitfields altogether for now.
|
||||||
|
// See sk_reuseport_md for an example where the clash happens.
|
||||||
|
if field_s.starts_with("_bitfield") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if field_s.starts_with("__bindgen_anon") {
|
||||||
|
let field_ty_ident = match &field.ty {
|
||||||
|
Type::Path(TypePath {
|
||||||
|
path: Path { segments, .. },
|
||||||
|
..
|
||||||
|
}) => &segments.first().unwrap().ident,
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
let sub_fields = self
|
||||||
|
.item_fields
|
||||||
|
.get(field_ty_ident)
|
||||||
|
.expect(&field_ty_ident.to_string())
|
||||||
|
.1;
|
||||||
|
getters.extend(self.getters(field_ident, sub_fields).drain(..).map(
|
||||||
|
|mut getter| {
|
||||||
|
getter.prefix.insert(0, ident);
|
||||||
|
getter
|
||||||
|
},
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
getters.push(Getter {
|
||||||
|
ident: field_ident,
|
||||||
|
prefix: vec![ident],
|
||||||
|
ty: &field.ty,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_getters_for_items(
|
||||||
|
items: &[Item],
|
||||||
|
gen_getter: impl Fn(&Getter<'_>) -> TokenStream,
|
||||||
|
) -> TokenStream {
|
||||||
|
let mut tokens = TokenStream::new();
|
||||||
|
tokens.append_all(GetterList::new(&items).iter().map(|(item, getters)| {
|
||||||
|
let getters = getters.iter().map(&gen_getter);
|
||||||
|
let (ident, generics, _) = unpack_item(item).unwrap();
|
||||||
|
quote! {
|
||||||
|
impl#generics #ident#generics {
|
||||||
|
#(#getters)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn probe_read_getter(getter: &Getter<'_>, bpf_probe_read: &Path) -> TokenStream {
|
||||||
|
let ident = getter.ident;
|
||||||
|
let ty = getter.ty;
|
||||||
|
let prefix = &getter.prefix;
|
||||||
|
match ty {
|
||||||
|
Type::Ptr(_) => {
|
||||||
|
quote! {
|
||||||
|
pub fn #ident(&self) -> Option<#ty> {
|
||||||
|
let v = unsafe { #bpf_probe_read(&#(#prefix).*.#ident) }.ok()?;
|
||||||
|
if v.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
quote! {
|
||||||
|
pub fn #ident(&self) -> Option<#ty> {
|
||||||
|
unsafe { #bpf_probe_read(&#(#prefix).*.#ident) }.ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Getter<'a> {
|
||||||
|
pub ident: &'a Ident,
|
||||||
|
pub prefix: Vec<&'a Ident>,
|
||||||
|
pub ty: &'a Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack_item(item: &Item) -> Option<(&Ident, &Generics, &FieldsNamed)> {
|
||||||
|
match item {
|
||||||
|
Item::Struct(ItemStruct {
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
fields: Fields::Named(fields),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
| Item::Union(ItemUnion {
|
||||||
|
ident,
|
||||||
|
generics,
|
||||||
|
fields,
|
||||||
|
..
|
||||||
|
}) => Some((ident, generics, fields)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
pub mod btf_types;
|
||||||
|
pub mod getters;
|
||||||
|
pub mod rustfmt;
|
@ -0,0 +1,25 @@
|
|||||||
|
use std::{
|
||||||
|
io::{self, Write},
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn format(code: &str) -> Result<String, io::Error> {
|
||||||
|
let mut child = Command::new("rustfmt")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
let stdin = child.stdin.as_mut().unwrap();
|
||||||
|
stdin.write_all(code.as_bytes())?;
|
||||||
|
|
||||||
|
let output = child.wait_with_output()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!(
|
||||||
|
"rustfmt failed with exit code: {}",
|
||||||
|
output.status.code().unwrap()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(String::from_utf8(output.stdout).unwrap())
|
||||||
|
}
|
Loading…
Reference in New Issue