diff --git a/docs/dev/abstract-type.md b/docs/dev/abstract-type.md new file mode 100644 index 000000000..3ea002dfa --- /dev/null +++ b/docs/dev/abstract-type.md @@ -0,0 +1,191 @@ +# Defining types using native Rust syntax + +Writing a Binary Ninja plugin often involves defining one or more types inside a Binary View. The easiest way to do this using the C++ or Python APIs is to use the `TypeBuilder` class, or one of its variants, like `StructureBuilder` or `EnumerationBuilder`. The Rust API also has equivalent builders for this. However, the newly added `AbstractType` trait allows you to automatically generate a type object ready for ingestion into Binary Ninja by simply decorating a Rust type definition with `#[derive(AbstractType)]`, with no additional effort required! + +As an example, say you'd like to define the following type inside of a Binary View: + +```c +struct MyStruct { + uint8_t first; + uint32_t second; + int16_t third[2]; +}; +``` + +Using the `StructureBuilder` API, you could generate the type as follows: + +```rust +use binaryninja::types::{Structure, Type}; + +let ty = Type::structure( + Structure::builder() + .with_members([ + (&Type::int(1, false), "first"), + (&Type::int(4, false), "second"), + (&Type::array(&Type::int(2, true), 2), "third"), + ]) + .finalize() + .as_ref(), +); +``` + +Or, you could generate the same type using a native Rust struct definition instead: + +```rust +use binaryninja::types::AbstractType; + +#[derive(AbstractType)] +#[repr(C)] +struct MyStruct { + first: u8, + second: u32, + third: [i16; 2], +} + +let ty = MyStruct::resolve_type(); +``` + +By deriving the `AbstractType` trait for a type `T`, the `resolve_type` method will automatically construct a `Type` corresponding to the layout of `T`. This has multiple benefits, the first of which is improved readability. Another is that if your plugin performs some additional processing that makes use of `T`, you can define it once in Rust and use that definition both for processing actual data as well as defining types inside of Binary Ninja. + +## Deriving `AbstractType` for a type + +While the trait itself is public, the derive macro for `AbstractType` is gated behind the `derive` crate feature. In order to make use of it, include the following line in your `Cargo.toml`: + +```toml +[dependencies] +binaryninja = { git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev", features = ["derive"] } +``` + +Furthermore, in order for `AbstractType::resolve_type` to produce unambiguous results, some restrictions are enforced when deriving the trait that ensure the generated implementation correctly produces the intended corresponding C type. + +### Structs and Unions + +Structs and unions must be marked `#[repr(C)]`. This is because the `AbstractType` derive macro relies on compiler-generated layout information in order to accurately generate equivalent C type definitions. Because we are targeting the C ABI (and because the Rust ABI is not stable), deriving `AbstractType` requires opting into the C ABI as well. + +### Enums +In contrast to structs, the fundamental representation of enums in Rust is different compared to C; decorating a Rust enum with `#[repr(C)]` produces a "tagged union" whose layout is not the same as a C-style enum. Therefore, Rust enums that derive `AbstractType` must instead be decorated with `#[repr()]`, for example `u32` or `u64`. Additionally, their variants must not contain any data, and all variants must have an explicitly assigned discriminant. As an example: + +```rust +#[derive(AbstractType)] +#[repr(u32)] +enum Color { + Red = 0xff0000, + Green = 0x00ff00, + Blue = 0x0000ff, +} +``` + +## Special cases + +### Pointers + +Creating pointers using the Binary Ninja API requires either defining them with respect to a specific architecture (if using the `Type::pointer` constructor), or otherwise manually specifying their width using `Type::pointer_of_width`. Likewise, deriving `AbstractType` for a type `T` that contains any pointer fields requires decorating `T` with a `#[binja(pointer_width)]` attribute: + +```rust +#[derive(AbstractType)] +#[binja(pointer_width = 4)] // Explicitly required because `A` contains pointers +#[repr(C)] +struct A { + first: u8, + second: *const u64, // 4 bytes wide + third: *const u32, // also 4 bytes wide - all pointers inside `A` are given the same width +} +``` + +Part of the reason for this requirement is that the architecture of the Binary View may be different than the host system - therefore, the Rust compiler would otherwise report an incorrect size for any pointers compared to what the Binary View expects. + +### Named types + +If you wish to define a type containing a non-primitive field, by default the type of that field will be defined inline in Binja, which may initially feel surprising. As an example, let's say we want to express the following construct: + +```c +struct A { + uint8_t first; + struct B second; +} + +struct B { + uint16_t third; + uint32_t fourth; +} +``` + +If we simply define the types `A` and `B` in Rust like so: + +```rust +#[derive(AbstractType)] +#[repr(C)] +struct A { + first: u8, + second: B, +} + +#[derive(AbstractType)] +#[repr(C)] +struct B { + third: u16, + fourth: u32, +} +``` + +...then, calling `A::resolve_type()` and passing the result to a Binary View will result in the following definition in the view: + +```c +struct A { + uint8_t first; + struct { + uint16_t third; + uint32_t fourth; + } second; +} +``` + +Obviously, this is not quite what we intended. To fix this, decorate the `A::second` field with a `#[binja(named)]` attribute to signal to the compiler to used a named type for the field rather than inlining the type's definition: + +```rust +#[derive(AbstractType)] +#[repr(C)] +struct A { + first: u8, + #[binja(named)] + second: B, +} +``` + +...resulting in the correct C definition: + +```c +struct A { + uint8_t first; + struct B second; +} +``` + +The `named` attribute will use the name of the Rust type (in this case, `B`) as the name for the defined type in Binja. If you would like a different name to be used, you can explicitly specify it by instead using the `#[binja(name = "...")]` attribute: + +```rust +#[derive(AbstractType)] +#[repr(C)] +struct A { + first: u8, + #[binja(name = "C")] + second: B, +} +``` + +...which will result in the following C-type: + +```c +struct A { + uint8_t first; + struct C second; +} +``` + +Note that defining structs with named fields does not require that the types with the specified names are already defined inside the Binary View. In other words, in the example above, the order in which you define `A` and `B` (e.g. by calling `BinaryView::define_user_type`) does not matter. + +## Additional Notes + +### Modifying default alignment + +Decorating a Rust type with `#[repr(packed)]` or `#[repr(align)]` can change the alignment of a struct and its fields. These changes will also be reflected inside Binary Ninja. For example, decorating a struct with `#[repr(packed)]` will cause it to be marked `__packed` when defined in the Binary View. diff --git a/rust/Cargo.lock b/rust/Cargo.lock index dfccee5a2..e28155d28 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,6 +2,13 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "abstract-type" +version = "0.1.0" +dependencies = [ + "binaryninja", +] + [[package]] name = "adler" version = "1.0.2" @@ -100,13 +107,25 @@ dependencies = [ name = "binaryninja" version = "0.1.0" dependencies = [ + "binaryninja-derive", "binaryninjacore-sys", + "elain", "lazy_static", "libc", "log", "rayon", ] +[[package]] +name = "binaryninja-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.52", +] + [[package]] name = "binaryninjacore-sys" version = "0.1.0" @@ -347,6 +366,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "elain" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3522094fae7d65c8313a135fe45fa7e22ec2110c9d387063b66d235281f7f771" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -744,6 +769,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "version_check", +] + [[package]] name = "quote" version = "1.0.35" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index b827b9bb5..e8c350ef2 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [features] noexports = [] +derive = ["binaryninja-derive", "elain"] [dependencies] lazy_static = "1.4.0" @@ -13,6 +14,9 @@ log = "0.4" libc = "0.2" rayon = { version = "1.8", optional = true } binaryninjacore-sys = { path = "binaryninjacore-sys" } +binaryninja-derive = { path = "binaryninja-derive", optional = true } +# Const-generic alignment gadgetry used by the `AbstractType` derive macro +elain = { version = "0.3.0", optional = true } [patch.crates-io] # Patched pdb crate to implement some extra structures @@ -20,6 +24,7 @@ pdb = { path = "./examples/pdb-ng/pdb-0.8.0-patched" } [workspace] members = [ + "examples/abstract-type", "examples/basic_script", "examples/decompile", "examples/dwarf/dwarf_export", diff --git a/rust/binaryninja-derive/Cargo.toml b/rust/binaryninja-derive/Cargo.toml new file mode 100644 index 000000000..7e4f72349 --- /dev/null +++ b/rust/binaryninja-derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "binaryninja-derive" +version = "0.1.0" +edition = "2021" + +[dependencies] +syn = "2.0" +quote = "1" +proc-macro2 = "1.0" +proc-macro2-diagnostics = { version = "0.10", default-features = false } + +[lib] +proc-macro = true diff --git a/rust/binaryninja-derive/src/lib.rs b/rust/binaryninja-derive/src/lib.rs new file mode 100644 index 000000000..4fa6e74ab --- /dev/null +++ b/rust/binaryninja-derive/src/lib.rs @@ -0,0 +1,542 @@ +use proc_macro2::{Span, TokenStream}; +use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt}; +use quote::{format_ident, quote}; +use std::cell::OnceCell; +use syn::spanned::Spanned; +use syn::{ + parenthesized, parse_macro_input, token, Attribute, Data, DeriveInput, Expr, Field, Fields, + FieldsNamed, Ident, Lit, LitInt, Meta, Path, Type, Variant, +}; + +type Result = std::result::Result; + +enum FieldKind { + Ptr(Type, usize), + Ty(Type), +} + +impl FieldKind { + fn ty(&self) -> &Type { + match self { + FieldKind::Ptr(ty, _) | FieldKind::Ty(ty) => &ty, + } + } +} + +struct AbstractField { + kind: FieldKind, + ident: Ident, + name: Option, +} + +impl AbstractField { + fn from_field(field: Field, parent_name: &Ident, pointer_width: Option) -> Result { + let Some(ident) = field.ident else { + return Err(field.span().error("field must be named")); + }; + + // If the field is a pointer, we want the type being pointed at, not the pointer itself. + let kind = match field.ty { + Type::Ptr(ty) => { + let Some(width) = pointer_width else { + return Err(parent_name.span().error( + // broken up to make rustfmt happy + "types containing pointer fields must be \ + decorated with `#[binja(pointer_width = )]`", + )); + }; + FieldKind::Ptr(*ty.elem, width) + } + _ => FieldKind::Ty(field.ty), + }; + + // Fields may be decorated with either `#[binja(name = "...")]` or `#[binja(named)]`. + let name = find_binja_attr(&field.attrs)? + .map(|attr| match attr.kind { + BinjaAttrKind::PointerWidth(_) => Err(attr.span.error( + // broken up to make rustfmt happy + "invalid attribute, expected either \ + `#[binja(named)]` or `#[binja(name = \"...\")]`", + )), + BinjaAttrKind::Named(Some(name)) => Ok(name), + BinjaAttrKind::Named(None) => { + let ty = kind.ty(); + Ok(quote!(#ty).to_string()) + } + }) + .transpose()?; + + Ok(Self { kind, ident, name }) + } + + /// Transforms the `AbstractField` into a token stream that constructs a binja `Type` object + fn resolved_ty(&self) -> TokenStream { + let ty = self.kind.ty(); + let mut resolved = quote! { <#ty as ::binaryninja::types::AbstractType>::resolve_type() }; + if let Some(name) = &self.name { + resolved = quote! { + ::binaryninja::types::Type::named_type_from_type(#name, &#resolved) + } + } + if let FieldKind::Ptr(_, width) = self.kind { + resolved = quote! { + ::binaryninja::types::Type::pointer_of_width(&#resolved, #width, false, false, None) + } + } + resolved + } +} + +#[derive(Debug)] +struct BinjaAttr { + kind: BinjaAttrKind, + span: Span, +} + +#[derive(Debug)] +enum BinjaAttrKind { + PointerWidth(usize), + Named(Option), +} + +/// Given a list of attributes, look for a `#[binja(...)]` attribute. At most one copy of the +/// attribute is allowed to decorate an item (i.e. a type or field). If more than one copy is +/// present, we throw an error. +/// +/// Three properties are supported, and for any given item they are mutually exclusive: +/// - `pointer_width`: Expects an integer literal. Only allowed on types, not fields. +/// - `name`: Expects a string literal. Only allowed on fields. +/// - `named`: Must be a bare path. Only allowed on fields. +fn find_binja_attr(attrs: &[Attribute]) -> Result> { + // Use a `OnceCell` to assert that we only allow a single `#[binja(...)]` attribute per-item. + let binja_attr = OnceCell::new(); + + // Wrapper function for setting the value of the `OnceCell` above. + let set_attr = |attr: BinjaAttr| { + let span = attr.span; + binja_attr + .set(attr) + .map_err(|_| span.error("conflicting `#[binja(...)]` attributes")) + }; + + for attr in attrs { + let Some(ident) = attr.path().get_ident() else { + continue; + }; + if ident == "binja" { + let meta = attr.parse_args::()?; + let meta_ident = meta.path().require_ident()?; + if meta_ident == "pointer_width" { + // #[binja(pointer_width = )] + let value = &meta.require_name_value()?.value; + if let Expr::Lit(expr) = &value { + if let Lit::Int(val) = &expr.lit { + set_attr(BinjaAttr { + kind: BinjaAttrKind::PointerWidth(val.base10_parse()?), + span: attr.span(), + })?; + continue; + } + } + return Err(value.span().error("expected integer literal")); + } else if meta_ident == "name" { + // #[binja(name = "...")] + let value = &meta.require_name_value()?.value; + if let Expr::Lit(expr) = &value { + if let Lit::Str(lit) = &expr.lit { + set_attr(BinjaAttr { + kind: BinjaAttrKind::Named(Some(lit.value())), + span: attr.span(), + })?; + continue; + } + } + return Err(value.span().error(r#"expected string literal"#)); + } else if meta_ident == "named" { + // #[binja(named)] + meta.require_path_only()?; + set_attr(BinjaAttr { + kind: BinjaAttrKind::Named(None), + span: attr.span(), + })?; + } else { + return Err(meta + .span() + .error(format!("unrecognized property `{meta_ident}`"))); + } + } + } + Ok(binja_attr.into_inner()) +} + +/// Struct representing the contents of all `#[repr(...)]` attributes decorating a type. +struct Repr { + c: bool, + packed: Option>, + align: Option, + primitive: Option<(Path, bool)>, +} + +impl Repr { + /// Scan through a list of attributes and finds every instance of a `#[repr(...)]` attribute, + /// then initialize `Self` based off the collective contents of those attributes. + fn from_attrs(attrs: &[Attribute]) -> Result { + let mut c = false; + let mut packed = None; + let mut align = None; + let mut primitive = None; + for attr in attrs { + let Some(ident) = attr.path().get_ident() else { + continue; + }; + if ident == "repr" { + attr.parse_nested_meta(|meta| { + if let Some(ident) = meta.path.get_ident() { + if ident == "C" { + c = true; + } else if ident == "packed" { + if meta.input.peek(token::Paren) { + let content; + parenthesized!(content in meta.input); + packed = Some(Some(content.parse()?)); + } else { + packed = Some(None); + } + } else if ident == "align" { + let content; + parenthesized!(content in meta.input); + align = Some(content.parse()?); + } else if ident_in_list(ident, ["u8", "u16", "u32", "u64", "u128"]) { + primitive = Some((meta.path.clone(), false)); + } else if ident_in_list(ident, ["i8", "i16", "i32", "i64", "i128"]) { + primitive = Some((meta.path.clone(), true)); + } else if ident_in_list(ident, ["usize", "isize"]) { + return Err(ident + .span() + .error(format!("`repr({ident})` types are not supported")) + .into()); + } + } + Ok(()) + })?; + } + } + + Ok(Self { + c, + packed, + align, + primitive, + }) + } +} + +fn ident_in_list(ident: &Ident, list: [&'static str; N]) -> bool { + list.iter().any(|id| ident == id) +} + +/// Entry point to the proc-macro. +#[proc_macro_derive(AbstractType, attributes(binja))] +pub fn abstract_type_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + // Transforming the error diagnostic into tokens for emission allows the business logic to + // return `Result` and make use of the `?` operator like any normal Rust program + match impl_abstract_type(input) { + Ok(tokens) => tokens.into(), + Err(diag) => diag.emit_as_item_tokens().into(), + } +} + +/// Main business logic of the macro. Parses any relevant attributes decorating the type, then +/// defers execution based on the kind of type: struct, enum, or union. +fn impl_abstract_type(ast: DeriveInput) -> Result { + let repr = Repr::from_attrs(&ast.attrs)?; + let width = find_binja_attr(&ast.attrs)? + .map(|attr| match attr.kind { + BinjaAttrKind::PointerWidth(width) => { + if let Data::Enum(_) = ast.data { + Err(attr.span.error("`#[binja(pointer_width)]` is only supported on structs and unions, not enums")) + } else { + Ok(width) + } + } + BinjaAttrKind::Named(Some(_)) => Err(attr + .span + .error(r#"`#[binja(name)] is only supported on fields"#)), + BinjaAttrKind::Named(None) => Err(attr + .span + .error("`#[binja(named)]` is only supported on fields")), + }) + .transpose()?; + + if !ast.generics.params.is_empty() { + return Err(ast.generics.span().error("type must not be generic")); + } + + match ast.data { + Data::Struct(s) => match s.fields { + Fields::Named(fields) => { + impl_abstract_structure_type(ast.ident, fields, repr, width, StructureKind::Struct) + } + Fields::Unnamed(_) => Err(s.fields.span().error( + "tuple structs are unsupported; \ + struct must have named fields", + )), + Fields::Unit => Err(ast.ident.span().error( + "unit structs are unsupported; \ + provide at least one named field", + )), + }, + Data::Enum(e) => impl_abstract_enum_type(ast.ident, e.variants, repr), + Data::Union(u) => { + impl_abstract_structure_type(ast.ident, u.fields, repr, width, StructureKind::Union) + } + } +} + +enum StructureKind { + Struct, + Union, +} + +/// Implements the `AbstractType` trait for either a struct or union, based on the value of `kind`. +/// +/// Unlike C-style enums, structs and unions can contain other types within them that affect their +/// size and alignment. For example, the size of a struct is at least the sum of the sizes of its +/// fields (plus any padding), and its alignment is equal to that of the most-aligned field. +/// Likewise, a union's size is at least that of its largest field. +/// +/// Normally this would be fine, because the compiler can give you size and alignment information +/// using `std::mem::{size_of, align_of}`. However, the `#[binja(pointer_width)]` attribute allows +/// users to change the width of pointer fields to be different in Binja compared to the host CPU +/// architecture, meaning the value calculated by the compiler will be wrong in that case. What's +/// worse, is that a pointer field with custom width not only affects the size/alignment of its +/// parent struct, but anything that contains *that* struct, and so on up the tree. +/// +/// So, we need a way to propagate the modified layout information at compile-time. To accomplish +/// this, we use the `AbstractType::LAYOUT` associated constant, which by default matches the +/// layout of the struct as calculated by the compiler, but which can be swapped out for any other +/// valid `std::alloc::Layout` object when implementing the `AbstractType` trait. We then create a +/// mock-type with the desired custom layout and use that for propagation. +/// +/// In order to mock a type, we make use of the following construction: +/// +/// ```ignore +/// #[repr(C)] +/// struct Mock +/// where: +/// elain::Align: elain::Alignment, +/// { +/// t: [u8; SIZE], +/// _align: elain::Align +/// } +/// ``` +/// +/// The `elain::Align` type is a zero-size type with a const-generic parameter specifying its +/// alignment. The trait bound serves to restrict the possible values of `ALIGN` to only those +/// valid for specifying alignment (powers of two). Additionally, we know that `[u8; SIZE]` is +/// always of size `SIZE`, and alignment 1. Therefore, the `Mock` type is guaranteed to be of size +/// `SIZE` and alignment equal to `ALIGN`. +/// +/// This constructed `Mock` type allows us to generate a struct with arbitrary layout, which we can +/// use to mimic the layout of another struct: +/// +/// ```ignore +/// #[derive(AbstractType)] +/// #[repr(C)] +/// struct S { +/// first: u8, +/// second: u16, +/// third: u64, +/// } +/// +/// // Identical layout to `S` above +/// #[repr(C)] +/// struct __S_layout { +/// first: Mock<1, 1>, +/// second: Mock<2, 2>, +/// third: Mock<8, 8>, +/// } +/// ``` +/// +/// Then, we can propagate any changes in the layout of `S` (due to custom pointer widths) by +/// setting the `S::LAYOUT` constant equal to `alloc::Layout<__S_layout>` rather than the default +/// value of `alloc::Layout`. Then, when mocking fields of type `S`, we use `S::LAYOUT.size()` +/// and `S::LAYOUT.align()` for the const-generic parameters of `Mock`, instead of just integers. +fn impl_abstract_structure_type( + name: Ident, + fields: FieldsNamed, + repr: Repr, + pointer_width: Option, + kind: StructureKind, +) -> Result { + if !repr.c { + let msg = match kind { + StructureKind::Struct => "struct must be `repr(C)`", + StructureKind::Union => "union must be `repr(C)`", + }; + return Err(name.span().error(msg)); + } + + let abstract_fields = fields + .named + .into_iter() + .map(|field| AbstractField::from_field(field, &name, pointer_width)) + .collect::>>()?; + + if abstract_fields.is_empty() { + return Err(name.span().error("expected at least one named field")); + } + + // Generate the arguments to `StructureBuilder::insert`. Luckily `mem::offset_of!` was stabilized in + // Rust 1.77 or otherwise this would be a lot more complicated. + let layout_name = format_ident!("__{name}_layout"); + let args = abstract_fields + .iter() + .map(|field| { + let ident = &field.ident; + let resolved_ty = field.resolved_ty(); + quote! { + &#resolved_ty, + stringify!(#ident), + ::std::mem::offset_of!(#layout_name, #ident) as u64, + false, + ::binaryninja::types::MemberAccess::NoAccess, + ::binaryninja::types::MemberScope::NoScope, + } + }) + .collect::>(); + + // Calculate size and alignment for each field - these may differ from the compiler's + // calculated values so we use the construction discussed above to mock/propagate them. + let field_wrapper = format_ident!("__{name}_field_wrapper"); + let layout_fields = abstract_fields + .iter() + .map(|field| { + let ident = &field.ident; + let (size, align) = match &field.kind { + // Since pointers can be of arbitrary size as specified by the user, we manually + // calculate size/alignment for them. + FieldKind::Ptr(_, width) => { + let align = width.next_power_of_two(); + (quote! { #width }, quote! { #align }) + } + // All other types defer to the value of Self::LAYOUT + FieldKind::Ty(ty) => ( + quote! { { <#ty as ::binaryninja::types::AbstractType>::LAYOUT.size() } }, + quote! { { <#ty as ::binaryninja::types::AbstractType>::LAYOUT.align() } }, + ), + }; + quote! { #ident: #field_wrapper<#size, #align> } + }) + .collect::>(); + + // If the struct/union is marked `#[repr(packed)]` or `#[repr(align(...))]`, we decorate the + // mocked layout type with those as well + let is_packed = repr.packed.is_some(); + let packed = repr.packed.map(|size| match size { + Some(n) => quote! { #[repr(packed(#n))] }, + None => quote! { #[repr(packed)] }, + }); + let align = repr.align.map(|n| quote! { #[repr(align(#n))] }); + + // Distinguish between structs and unions + let (kind, set_union) = match kind { + StructureKind::Struct => (quote! { struct }, None), + StructureKind::Union => ( + quote! { union }, + Some(quote! { + .set_structure_type(::binaryninja::types::StructureType::UnionStructureType) + }), + ), + }; + + Ok(quote! { + #[repr(C)] + #[derive(Copy, Clone)] + struct #field_wrapper + where + ::binaryninja::elain::Align: ::binaryninja::elain::Alignment + { + t: [u8; SIZE], + _align: ::binaryninja::elain::Align, + } + + #[repr(C)] + #packed + #align + #kind #layout_name { + #(#layout_fields),* + } + + impl ::binaryninja::types::AbstractType for #name { + const LAYOUT: ::std::alloc::Layout = ::std::alloc::Layout::new::<#layout_name>(); + fn resolve_type() -> ::binaryninja::rc::Ref<::binaryninja::types::Type> { + ::binaryninja::types::Type::structure( + &::binaryninja::types::Structure::builder() + .set_packed(#is_packed) + .set_width(Self::LAYOUT.size() as u64) + .set_alignment(Self::LAYOUT.align()) + #set_union + #(.insert(#args))* + .finalize() + ) + } + } + }) +} + +/// Implements the `AbstractType` trait for an enum. +fn impl_abstract_enum_type( + name: Ident, + variants: impl IntoIterator, + repr: Repr, +) -> Result { + if repr.c { + return Err(name.span().error("`repr(C)` enums are not supported")); + } + if repr.align.is_some() { + // No way to set custom alignment for enums in Binja + return Err(name + .span() + .error("`repr(align(...))` on enums is not supported")); + } + + let Some((primitive, signed)) = repr.primitive else { + return Err(name + .span() + .error("must provide a primitive `repr` type, e.g. `u32`")); + }; + + // Extract the variant names and the value of their discriminants. Variants must not hold any + // nested data (in other words, they must be simple C-style identifiers). + let variants = variants + .into_iter() + .map(|variant| { + if !variant.fields.is_empty() { + return Err(variant.span().error("variant must not have any fields")); + } + let Some((_, discriminant)) = variant.discriminant else { + return Err(variant + .span() + .error("variant must have an explicit discriminant")); + }; + let ident = variant.ident; + Ok(quote! { stringify!(#ident), #discriminant as u64 }) + }) + .collect::>>()?; + + Ok(quote! { + impl ::binaryninja::types::AbstractType for #name { + fn resolve_type() -> ::binaryninja::rc::Ref<::binaryninja::types::Type> { + ::binaryninja::types::Type::enumeration( + &::binaryninja::types::Enumeration::builder() + #(.insert(#variants))* + .finalize(), + ::std::mem::size_of::<#primitive>(), + #signed + ) + } + } + }) +} diff --git a/rust/examples/abstract-type/Cargo.toml b/rust/examples/abstract-type/Cargo.toml new file mode 100644 index 000000000..bab72cea3 --- /dev/null +++ b/rust/examples/abstract-type/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "abstract-type" +version = "0.1.0" +edition = "2021" + +[dependencies] +binaryninja = { path="../../", features = ["derive"] } diff --git a/rust/examples/abstract-type/build.rs b/rust/examples/abstract-type/build.rs new file mode 100644 index 000000000..5ba9bcde4 --- /dev/null +++ b/rust/examples/abstract-type/build.rs @@ -0,0 +1,68 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +#[cfg(target_os = "macos")] +static LASTRUN_PATH: (&str, &str) = ("HOME", "Library/Application Support/Binary Ninja/lastrun"); + +#[cfg(target_os = "linux")] +static LASTRUN_PATH: (&str, &str) = ("HOME", ".binaryninja/lastrun"); + +#[cfg(windows)] +static LASTRUN_PATH: (&str, &str) = ("APPDATA", "Binary Ninja\\lastrun"); + +// Check last run location for path to BinaryNinja; Otherwise check the default install locations +fn link_path() -> PathBuf { + use std::io::prelude::*; + + let home = PathBuf::from(env::var(LASTRUN_PATH.0).unwrap()); + let lastrun = PathBuf::from(&home).join(LASTRUN_PATH.1); + + File::open(lastrun) + .and_then(|f| { + let mut binja_path = String::new(); + let mut reader = BufReader::new(f); + + reader.read_line(&mut binja_path)?; + Ok(PathBuf::from(binja_path.trim())) + }) + .unwrap_or_else(|_| { + #[cfg(target_os = "macos")] + return PathBuf::from("/Applications/Binary Ninja.app/Contents/MacOS"); + + #[cfg(target_os = "linux")] + return home.join("binaryninja"); + + #[cfg(windows)] + return PathBuf::from(env::var("PROGRAMFILES").unwrap()) + .join("Vector35\\BinaryNinja\\"); + }) +} + +fn main() { + // Use BINARYNINJADIR first for custom BN builds/configurations (BN devs/build server), fallback on defaults + let install_path = env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()); + + #[cfg(target_os = "linux")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-l:libbinaryninjacore.so.1", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "macos")] + println!( + "cargo:rustc-link-arg=-Wl,-rpath,{},-L{},-lbinaryninjacore", + install_path.to_str().unwrap(), + install_path.to_str().unwrap(), + ); + + #[cfg(target_os = "windows")] + { + println!("cargo:rustc-link-lib=binaryninjacore"); + println!("cargo:rustc-link-search={}", install_path.to_str().unwrap()); + } +} diff --git a/rust/examples/abstract-type/src/main.rs b/rust/examples/abstract-type/src/main.rs new file mode 100644 index 000000000..e8df03159 --- /dev/null +++ b/rust/examples/abstract-type/src/main.rs @@ -0,0 +1,297 @@ +use binaryninja::rc::Ref; +use binaryninja::types::{AbstractType, EnumerationBuilder, StructureBuilder, StructureType, Type}; + +fn create_struct(f: F) -> Ref +where + F: FnOnce(&StructureBuilder) -> &StructureBuilder, +{ + Type::structure(&f(&StructureBuilder::new()).finalize()) +} + +fn create_enum(width: usize, signed: bool, f: F) -> Ref +where + F: FnOnce(&EnumerationBuilder) -> &EnumerationBuilder, +{ + Type::enumeration(&f(&EnumerationBuilder::new()).finalize(), width, signed) +} + +fn primitive() { + assert_eq!(u8::resolve_type(), Type::int(1, false)); + assert_eq!(u16::resolve_type(), Type::int(2, false)); + assert_eq!(u32::resolve_type(), Type::int(4, false)); + assert_eq!(u64::resolve_type(), Type::int(8, false)); + assert_eq!(u128::resolve_type(), Type::int(16, false)); + + assert_eq!(i8::resolve_type(), Type::int(1, true)); + assert_eq!(i16::resolve_type(), Type::int(2, true)); + assert_eq!(i32::resolve_type(), Type::int(4, true)); + assert_eq!(i64::resolve_type(), Type::int(8, true)); + assert_eq!(i128::resolve_type(), Type::int(16, true)); + + assert_eq!(f32::resolve_type(), Type::float(4)); + assert_eq!(f64::resolve_type(), Type::float(8)); +} + +fn basic_struct() { + #[derive(AbstractType)] + #[repr(C)] + struct A { + first: u8, + second: u32, + third: u16, + } + + assert_eq!( + A::resolve_type(), + create_struct(|s| { + s.with_members([ + (&Type::int(1, false), "first"), + (&Type::int(4, false), "second"), + (&Type::int(2, false), "third"), + ]) + }) + ); +} + +fn packed_struct() { + #[derive(AbstractType)] + #[repr(C, packed)] + struct A { + first: u8, + second: u32, + third: u16, + } + + assert_eq!( + A::resolve_type(), + create_struct(|s| { + s.set_packed(true).with_members([ + (&Type::int(1, false), "first"), + (&Type::int(4, false), "second"), + (&Type::int(2, false), "third"), + ]) + }) + ); +} + +fn custom_alignment() { + #[derive(AbstractType)] + #[repr(C, align(16))] + struct A { + first: u8, + second: u32, + third: u16, + } + + assert_eq!( + A::resolve_type(), + create_struct(|s| { + s.set_alignment(16).with_members([ + (&Type::int(1, false), "first"), + (&Type::int(4, false), "second"), + (&Type::int(2, false), "third"), + ]) + }) + ); +} + +fn named_field() { + #[derive(AbstractType)] + #[repr(C)] + struct A { + first: u8, + #[binja(named)] + second: B, + } + + #[derive(AbstractType)] + #[repr(C)] + struct B { + third: u16, + } + + assert_eq!( + A::resolve_type(), + create_struct(|s| { + s.with_members([ + (&Type::int(1, false), "first"), + ( + &Type::named_type_from_type("B", &B::resolve_type()), + "second", + ), + ]) + }) + ); + assert_eq!( + B::resolve_type(), + create_struct(|s| { s.with_members([(&Type::int(2, false), "third")]) }) + ); +} + +fn pointer_field() { + #[derive(AbstractType)] + #[repr(C)] + #[binja(pointer_width = 4)] + struct A { + first: u8, + second: *const u32, + } + + assert_eq!( + A::resolve_type(), + create_struct(|s| { + s.with_members([ + (&Type::int(1, false), "first"), + ( + &Type::pointer_of_width(&Type::int(4, false), 4, false, false, None), + "second", + ), + ]) + }) + ); +} + +fn nested_pointer_field() { + #[derive(AbstractType)] + #[repr(C)] + struct A { + first: u8, + #[binja(named)] + second: B, + } + + #[derive(AbstractType)] + #[repr(C)] + #[binja(pointer_width = 4)] + struct B { + third: u32, + fourth: *const u16, + } + + assert_eq!( + A::resolve_type(), + create_struct(|s| { + s.with_members([ + (&Type::int(1, false), "first"), + ( + &Type::named_type_from_type("B", &B::resolve_type()), + "second", + ), + ]) + }) + ); + assert_eq!( + B::resolve_type(), + create_struct(|s| { + s.with_members([ + (&Type::int(4, false), "third"), + ( + &Type::pointer_of_width(&Type::int(2, false), 4, false, false, None), + "fourth", + ), + ]) + }) + ); +} + +fn named_pointer_field() { + #[derive(AbstractType)] + #[repr(C)] + #[binja(pointer_width = 4)] + struct A { + first: u8, + #[binja(named)] + second: *const B, + } + + #[derive(AbstractType)] + #[repr(C)] + struct B { + third: u32, + fourth: u16, + } + + assert_eq!( + A::resolve_type(), + create_struct(|s| { + s.with_members([ + (&Type::int(1, false), "first"), + ( + &Type::pointer_of_width( + &Type::named_type_from_type("B", &B::resolve_type()), + 4, + false, + false, + None, + ), + "second", + ), + ]) + }) + ); + assert_eq!( + B::resolve_type(), + create_struct(|s| { + s.with_members([ + (&Type::int(4, false), "third"), + (&Type::int(2, false), "fourth"), + ]) + }) + ) +} + +fn union() { + #[derive(AbstractType)] + #[repr(C)] + union A { + first: u32, + second: [u16; 2], + third: [u8; 4], + } + + assert_eq!( + A::resolve_type(), + create_struct(|s| { + s.set_structure_type(StructureType::UnionStructureType) + .with_members([ + (&Type::int(4, false), "first"), + (&Type::array(&Type::int(2, false), 2), "second"), + (&Type::array(&Type::int(1, false), 4), "third"), + ]) + }) + ); +} + +fn enumeration() { + #[derive(AbstractType)] + #[repr(u32)] + #[allow(dead_code)] + enum Color { + Red = 0xff0000, + Green = 0x00ff00, + Blue = 0x0000ff, + } + + assert_eq!( + Color::resolve_type(), + create_enum(4, false, |e| { + e.insert("Red", 0xff0000) + .insert("Green", 0x00ff00) + .insert("Blue", 0x0000ff) + }) + ); +} + +fn main() { + let _ = binaryninja::headless::Session::new(); + primitive(); + basic_struct(); + packed_struct(); + custom_alignment(); + named_field(); + pointer_field(); + nested_pointer_field(); + named_pointer_field(); + union(); + enumeration(); +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e24a3f784..32920c838 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -169,6 +169,8 @@ use std::path::PathBuf; pub use binaryninjacore_sys::BNBranchType as BranchType; pub use binaryninjacore_sys::BNEndianness as Endianness; use binaryview::BinaryView; +#[cfg(feature = "derive")] +pub use elain; use metadata::Metadata; use metadata::MetadataType; use string::BnStrCompatible; diff --git a/rust/src/types.rs b/rust/src/types.rs index b42c7039c..ec6e80471 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -33,6 +33,7 @@ use crate::{ use lazy_static::lazy_static; use std::ptr::null_mut; use std::{ + alloc::Layout, borrow::{Borrow, Cow}, collections::{HashMap, HashSet}, ffi::CStr, @@ -695,6 +696,48 @@ impl Drop for TypeBuilder { ////////// // Type +#[cfg(feature = "derive")] +pub use binaryninja_derive::*; +pub trait AbstractType: Sized { + #[doc(hidden)] + const LAYOUT: Layout = Layout::new::(); + + fn resolve_type() -> Ref; +} + +macro_rules! abstract_type { + ($($t:ty => $e:expr),+) => { + $( + impl AbstractType for $t { + fn resolve_type() -> Ref { + $e + } + } + )+ + } +} + +abstract_type! { + u8 => Type::int(1, false), + u16 => Type::int(2, false), + u32 => Type::int(4, false), + u64 => Type::int(8, false), + u128 => Type::int(16, false), + i8 => Type::int(1, true), + i16 => Type::int(2, true), + i32 => Type::int(4, true), + i64 => Type::int(8, true), + i128 => Type::int(16, true), + f32 => Type::float(4), + f64 => Type::float(8) +} + +impl AbstractType for [T; N] { + fn resolve_type() -> Ref { + Type::array(&T::resolve_type(), N as u64) + } +} + #[repr(transparent)] pub struct Type { pub(crate) handle: *mut BNType,