Skip to content

Commit eb25577

Browse files
committed
Add dev guide for AbstractType trait
1 parent 975b875 commit eb25577

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

docs/dev/abstract-type.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Defining types using native Rust syntax
2+
3+
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!
4+
5+
As an example, say you'd like to define the following type inside of a Binary View:
6+
7+
```c
8+
struct MyStruct {
9+
uint8_t first;
10+
uint32_t second;
11+
int16_t third[2];
12+
};
13+
```
14+
15+
Using the `StructureBuilder` API, you could generate the type as follows:
16+
17+
```rust
18+
use binaryninja::types::{Structure, Type};
19+
20+
let ty = Type::structure(
21+
Structure::builder()
22+
.with_members([
23+
(&Type::int(1, false), "first"),
24+
(&Type::int(4, false), "second"),
25+
(&Type::array(&Type::int(2, true), 2), "third"),
26+
])
27+
.finalize()
28+
.as_ref(),
29+
);
30+
```
31+
32+
Or, you could generate the same type using a native Rust struct definition instead:
33+
34+
```rust
35+
use binaryninja::types::AbstractType;
36+
37+
#[derive(AbstractType)]
38+
#[repr(C)]
39+
struct MyStruct {
40+
first: u8,
41+
second: u32,
42+
third: [i16; 2],
43+
}
44+
45+
let ty = MyStruct::resolve_type();
46+
```
47+
48+
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.
49+
50+
## Deriving `AbstractType` for a type
51+
52+
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`:
53+
54+
```toml
55+
[dependencies]
56+
binaryninja = { git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev", features = ["derive"] }
57+
```
58+
59+
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.
60+
61+
### Structs and Unions
62+
63+
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.
64+
65+
### Enums
66+
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(<int>)]`, 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:
67+
68+
```rust
69+
#[derive(AbstractType)]
70+
#[repr(u32)]
71+
enum Color {
72+
Red = 0xff0000,
73+
Green = 0x00ff00,
74+
Blue = 0x0000ff,
75+
}
76+
```
77+
78+
## Special cases
79+
80+
### Pointers
81+
82+
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:
83+
84+
```rust
85+
#[derive(AbstractType)]
86+
#[binja(pointer_width = 4)] // Explicitly required because `A` contains pointers
87+
#[repr(C)]
88+
struct A {
89+
first: u8,
90+
second: *const u64, // 4 bytes wide
91+
third: *const u32, // also 4 bytes wide - all pointer inside `A` are given the same width
92+
}
93+
```
94+
95+
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.
96+
97+
### Named types
98+
99+
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:
100+
101+
```c
102+
struct A {
103+
uint8_t first;
104+
struct B second;
105+
}
106+
107+
struct B {
108+
uint16_t third;
109+
uint32_t fourth;
110+
}
111+
```
112+
113+
If we simply define the types `A` and `B` in Rust like so:
114+
115+
```rust
116+
#[derive(AbstractType)]
117+
#[repr(C)]
118+
struct A {
119+
first: u8,
120+
second: B,
121+
}
122+
123+
#[derive(AbstractType)]
124+
#[repr(C)]
125+
struct B {
126+
third: u16,
127+
fourth: u32,
128+
}
129+
```
130+
131+
...then, calling `A::resolve_type()` and passing the result to a Binary View will result in the following definition in the view:
132+
133+
```c
134+
struct A {
135+
uint8_t first;
136+
struct {
137+
uint16_t third;
138+
uint32_t fourth;
139+
} second;
140+
}
141+
```
142+
143+
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:
144+
145+
```rust
146+
#[derive(AbstractType)]
147+
#[repr(C)]
148+
struct A {
149+
first: u8,
150+
#[binja(named)]
151+
second: B,
152+
}
153+
```
154+
155+
...resulting in the correct C definition:
156+
157+
```c
158+
struct A {
159+
uint8_t first;
160+
struct B second;
161+
}
162+
```
163+
164+
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:
165+
166+
```rust
167+
#[derive(AbstractType)]
168+
#[repr(C)]
169+
struct A {
170+
first: u8,
171+
#[binja(name = "C")]
172+
second: B,
173+
}
174+
```
175+
176+
...which will result in the following C-type:
177+
178+
```c
179+
struct A {
180+
uint8_t first;
181+
struct C second;
182+
}
183+
```
184+
185+
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.
186+
187+
## Additional Notes
188+
189+
### Modifying default alignment
190+
191+
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.

0 commit comments

Comments
 (0)