1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
use binaryninja::binary_view::BinaryView;
use binaryninja::data_renderer::{
register_data_renderer, render_lines_for_data, CustomDataRenderer, RegistrationType,
TypeContext,
};
use binaryninja::disassembly::{
DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind,
};
use binaryninja::headless::Session;
use binaryninja::types::Type;
use std::path::PathBuf;
struct StructRenderer {}
impl CustomDataRenderer for StructRenderer {
const REGISTRATION_TYPE: RegistrationType = RegistrationType::Specific;
fn is_valid_for_data(
&self,
_view: &BinaryView,
_addr: u64,
type_: &Type,
_types: &[TypeContext],
) -> bool {
type_.get_structure().is_some()
}
fn lines_for_data(
&self,
_view: &BinaryView,
addr: u64,
type_: &Type,
_prefix: Vec<InstructionTextToken>,
width: usize,
_types_ctx: &[TypeContext],
_language: &str,
) -> Vec<DisassemblyTextLine> {
let name = type_.registered_name().map(|name| name.name().to_string());
let Some(type_) = type_.get_structure() else {
unreachable!();
};
let mut output = vec![
DisassemblyTextLine::new(vec![InstructionTextToken::new(
format!(
"Struct{}{} width {} or {width} {addr}",
name.as_ref().map(|_| " ").unwrap_or(""),
name.as_ref().map(String::as_str).unwrap_or(""),
type_.width()
),
InstructionTextTokenKind::Comment { target: addr },
)]),
DisassemblyTextLine::new(vec![InstructionTextToken::new(
"{",
InstructionTextTokenKind::Text,
)]),
];
let members = type_.members();
let offset_size =
usize::try_from(members.last().map(|last| last.offset.ilog(16)).unwrap_or(0) + 3)
.unwrap();
for member in members {
let line = [
InstructionTextToken::new(
format!("{:#0width$x}", member.offset, width = offset_size),
InstructionTextTokenKind::StructOffset {
offset: member.offset,
type_names: vec![member.name.clone()],
},
),
InstructionTextToken::new("|", InstructionTextTokenKind::Text),
InstructionTextToken::new(
member.name.clone(),
InstructionTextTokenKind::FieldName {
offset: member.offset,
type_names: vec![member.name.clone()],
},
),
InstructionTextToken::new(",", InstructionTextTokenKind::Text),
];
output.push(DisassemblyTextLine::new(line.to_vec()));
}
output.push(DisassemblyTextLine::new(vec![InstructionTextToken::new(
"}",
InstructionTextTokenKind::Text,
)]));
output
}
}
#[test]
fn test_data_renderer_basic() {
let _session = Session::new().expect("Failed to initialize session");
let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap();
let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view");
let _ = register_data_renderer(StructRenderer {});
// This will use all available data renderers, so we are also verifying that our custom renderer is being used.
let lines = render_lines_for_data(
&view,
0x362e9,
&view.type_by_name("_ABC").unwrap(),
vec![],
100,
&[],
None,
);
// TODO: This is not really checking all possible issues that could occur with round-tripping.
// TODO: But it is a good start to just make sure it visually is what we expect.
let lines_str = lines.iter().map(ToString::to_string).collect::<Vec<_>>();
assert_eq!(
lines_str,
vec![
"Struct _ABC width 12 or 100 221929",
"{",
"0x0|abcA,",
"0x4|abcB,",
"0x8|abcC,",
"}"
]
)
}
|