Skip to content

Commit 8ef8ab4

Browse files
committed
Support pointers of other sizes using a width field attribute
We accomplish this by generating alternative struct layouts with swapped out field types, and recursively calculating size and alignment using associated constants on the `AbstractType` trait
1 parent f033045 commit 8ef8ab4

File tree

5 files changed

+125
-34
lines changed

5 files changed

+125
-34
lines changed

rust/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ libc = "0.2"
1414
rayon = { version = "1.8", optional = true }
1515
binaryninjacore-sys = { path = "binaryninjacore-sys" }
1616
binaryninja-derive = { path = "binaryninja-derive" }
17+
elain = "0.3.0"
1718

1819
[workspace]
1920
members = [

rust/binaryninja-derive/src/lib.rs

Lines changed: 113 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
use proc_macro2::TokenStream;
22
use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt};
3-
use quote::quote;
3+
use quote::{format_ident, quote};
44
use syn::spanned::Spanned;
55
use syn::{
6-
parenthesized, parse_macro_input, token, Attribute, Data, DeriveInput, Field, Fields,
7-
FieldsNamed, Ident, LitInt, Path, Type, Variant,
6+
parenthesized, parse_macro_input, token, Attribute, Data, DeriveInput, Expr, Field, Fields,
7+
FieldsNamed, Ident, Lit, LitInt, Path, Type, Variant,
88
};
99

1010
type Result<T> = std::result::Result<T, Diagnostic>;
1111

1212
struct AbstractField {
1313
ty: Type,
14+
width: Option<Type>,
1415
ident: Ident,
1516
named: bool,
16-
pointer: bool,
1717
}
1818

1919
impl AbstractField {
@@ -22,16 +22,38 @@ impl AbstractField {
2222
return Err(field.span().error("field must be named"));
2323
};
2424
let named = field.attrs.iter().any(|attr| attr.path().is_ident("named"));
25-
let (ty, pointer) = match field.ty {
26-
Type::Ptr(ty) => (*ty.elem, true),
27-
_ => (field.ty, false),
28-
};
29-
Ok(Self {
30-
ty,
31-
ident,
32-
named,
33-
pointer,
34-
})
25+
let width = field
26+
.attrs
27+
.iter()
28+
.find(|attr| attr.path().is_ident("width"));
29+
if let Type::Ptr(ty) = field.ty {
30+
if let Some(attr) = width {
31+
if let Expr::Lit(expr) = &attr.meta.require_name_value()?.value {
32+
if let Lit::Str(lit_str) = &expr.lit {
33+
return Ok(Self {
34+
ty: *ty.elem,
35+
width: Some(lit_str.parse()?),
36+
ident,
37+
named,
38+
});
39+
}
40+
}
41+
}
42+
Err(ident.span()
43+
.error("pointer field must have explicit `#[width = \"<type>\"]` attribute, for example: `u64`"))
44+
} else {
45+
match width {
46+
Some(attr) => Err(attr
47+
.span()
48+
.error("`#[width]` attribute can only be applied to pointer fields")),
49+
None => Ok(Self {
50+
ty: field.ty,
51+
width: None,
52+
ident,
53+
named,
54+
}),
55+
}
56+
}
3557
}
3658

3759
fn resolved_ty(&self) -> TokenStream {
@@ -45,9 +67,15 @@ impl AbstractField {
4567
)
4668
};
4769
}
48-
if self.pointer {
70+
if let Some(width) = &self.width {
4971
resolved = quote! {
50-
::binaryninja::types::Type::pointer_of_width(&#resolved, 8, false, false, None)
72+
::binaryninja::types::Type::pointer_of_width(
73+
&#resolved,
74+
::std::mem::size_of::<#width>(),
75+
false,
76+
false,
77+
None
78+
)
5179
}
5280
}
5381
resolved
@@ -56,8 +84,8 @@ impl AbstractField {
5684

5785
struct Repr {
5886
c: bool,
59-
packed: Option<usize>,
60-
align: Option<usize>,
87+
packed: Option<Option<LitInt>>,
88+
align: Option<LitInt>,
6189
primitive: Option<(Path, bool)>,
6290
}
6391

@@ -75,6 +103,10 @@ impl Repr {
75103
return Err(attr
76104
.span()
77105
.error("`#[named]` attribute can only be applied to fields"));
106+
} else if ident == "width" {
107+
return Err(attr
108+
.span()
109+
.error("`#[width]` attribute can only be applied to pointer fields"));
78110
} else if ident == "repr" {
79111
attr.parse_nested_meta(|meta| {
80112
if let Some(ident) = meta.path.get_ident() {
@@ -84,14 +116,14 @@ impl Repr {
84116
if meta.input.peek(token::Paren) {
85117
let content;
86118
parenthesized!(content in meta.input);
87-
packed = Some(content.parse::<LitInt>()?.base10_parse()?);
119+
packed = Some(Some(content.parse::<LitInt>()?));
88120
} else {
89-
packed = Some(1);
121+
packed = Some(None);
90122
}
91123
} else if ident == "align" {
92124
let content;
93125
parenthesized!(content in meta.input);
94-
align = Some(content.parse::<LitInt>()?.base10_parse()?);
126+
align = Some(content.parse::<LitInt>()?);
95127
} else if ident_in_list(ident, ["u8", "u16", "u32", "u64"]) {
96128
primitive = Some((meta.path.clone(), false));
97129
} else if ident_in_list(ident, ["i8", "i16", "i32", "i64"]) {
@@ -121,7 +153,7 @@ fn ident_in_list<const N: usize>(ident: &Ident, list: [&'static str; N]) -> bool
121153
list.iter().any(|id| ident == id)
122154
}
123155

124-
#[proc_macro_derive(AbstractType, attributes(named))]
156+
#[proc_macro_derive(AbstractType, attributes(named, width))]
125157
pub fn abstract_type_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
126158
let input = parse_macro_input!(input as DeriveInput);
127159
match impl_abstract_type(input) {
@@ -175,43 +207,91 @@ fn impl_abstract_structure_type(
175207
return Err(name.span().error(msg));
176208
}
177209

178-
let fields = fields
210+
let abstract_fields = fields
179211
.named
180212
.into_iter()
181213
.map(AbstractField::from_field)
182214
.collect::<Result<Vec<_>>>()?;
183-
let args = fields
215+
let layout_name = format_ident!("__{name}_layout");
216+
let field_wrapper = format_ident!("__{name}_field_wrapper");
217+
let layout_fields = abstract_fields
218+
.iter()
219+
.map(|field| {
220+
let ident = &field.ident;
221+
let layout_ty = field.width.as_ref().unwrap_or(&field.ty);
222+
quote! {
223+
#ident: #field_wrapper<
224+
[u8; <#layout_ty as ::binaryninja::types::AbstractType>::SIZE],
225+
{ <#layout_ty as ::binaryninja::types::AbstractType>::ALIGN },
226+
>
227+
}
228+
})
229+
.collect::<Vec<_>>();
230+
let args = abstract_fields
184231
.iter()
185232
.map(|field| {
186233
let ident = &field.ident;
187234
let resolved_ty = field.resolved_ty();
188235
quote! {
189236
&#resolved_ty,
190237
stringify!(#ident),
191-
::std::mem::offset_of!(#name, #ident) as u64,
238+
::std::mem::offset_of!(#layout_name, #ident) as u64,
192239
false,
193240
::binaryninja::types::MemberAccess::NoAccess,
194241
::binaryninja::types::MemberScope::NoScope,
195242
}
196243
})
197244
.collect::<Vec<_>>();
198245
let is_packed = repr.packed.is_some();
199-
let set_alignment = repr.align.map(|align| quote! { .set_alignment(#align) });
200-
let set_union = match kind {
201-
StructureKind::Struct => None,
202-
StructureKind::Union => Some(quote! {
203-
.set_structure_type(
204-
::binaryninja::types::StructureType::UnionStructureType
246+
let packed = repr.packed.map(|size| match size {
247+
Some(n) => quote! { #[repr(packed(#n))] },
248+
None => quote! { #[repr(packed)] },
249+
});
250+
let (align, set_alignment) = repr
251+
.align
252+
.map(|n| {
253+
(
254+
quote! { #[repr(align(#n))] },
255+
quote! { .set_alignment(Self::ALIGN) },
205256
)
206-
}),
257+
})
258+
.unzip();
259+
let (kind, set_union) = match kind {
260+
StructureKind::Struct => (quote! { struct }, None),
261+
StructureKind::Union => (
262+
quote! { union },
263+
Some(quote! {
264+
.set_structure_type(
265+
::binaryninja::types::StructureType::UnionStructureType
266+
)
267+
}),
268+
),
207269
};
208270
Ok(quote! {
271+
#[derive(Copy, Clone)]
272+
struct #field_wrapper<T, const N: usize>
273+
where
274+
::binaryninja::elain::Align<N>: ::binaryninja::elain::Alignment
275+
{
276+
t: T,
277+
_align: ::binaryninja::elain::Align<N>,
278+
}
279+
280+
#[repr(C)]
281+
#packed
282+
#align
283+
#kind #layout_name {
284+
#(#layout_fields),*
285+
}
286+
209287
impl ::binaryninja::types::AbstractType for #name {
288+
const SIZE: usize = ::std::mem::size_of::<#layout_name>();
289+
const ALIGN: usize = ::std::mem::align_of::<#layout_name>();
210290
fn resolve_type() -> ::binaryninja::rc::Ref<::binaryninja::types::Type> {
211291
::binaryninja::types::Type::structure(
212292
&::binaryninja::types::Structure::builder()
213293
#(.insert(#args))*
214-
.set_width(::std::mem::size_of::<#name>() as u64)
294+
.set_width(Self::SIZE as u64)
215295
.set_packed(#is_packed)
216296
#set_alignment
217297
#set_union

rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ use std::path::PathBuf;
171171
pub use binaryninjacore_sys::BNBranchType as BranchType;
172172
pub use binaryninjacore_sys::BNEndianness as Endianness;
173173
use binaryview::BinaryView;
174+
pub use elain;
174175
use metadata::Metadata;
175176
use metadata::MetadataType;
176177
use rc::Ref;

rust/src/types.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,9 @@ impl Drop for TypeBuilder {
699699
// Type
700700

701701
pub use binaryninja_derive::*;
702-
pub trait AbstractType {
702+
pub trait AbstractType: Sized {
703+
const SIZE: usize = std::mem::size_of::<Self>();
704+
const ALIGN: usize = std::mem::align_of::<Self>();
703705
fn resolve_type() -> Ref<Type>;
704706
}
705707

0 commit comments

Comments
 (0)