binaryninja/
data_renderer.rs

1use binaryninjacore_sys::*;
2use core::ffi;
3use ffi::c_void;
4use std::fmt::Debug;
5use std::ptr::NonNull;
6
7use crate::binary_view::BinaryView;
8use crate::disassembly::{DisassemblyTextLine, InstructionTextToken};
9use crate::rc::Array;
10use crate::string::BnString;
11use crate::types::Type;
12
13/// Registers a custom data renderer, this allows you to customize the representation of data variables.
14pub fn register_data_renderer<C: CustomDataRenderer>(
15    custom: C,
16) -> (&'static mut C, CoreDataRenderer) {
17    let renderer = Box::leak(Box::new(custom));
18    let mut callbacks = BNCustomDataRenderer {
19        context: renderer as *mut _ as *mut c_void,
20        freeObject: Some(cb_free_object::<C>),
21        isValidForData: Some(cb_is_valid_for_data::<C>),
22        getLinesForData: Some(cb_get_lines_for_data::<C>),
23        freeLines: Some(cb_free_lines),
24    };
25    let result = unsafe { BNCreateDataRenderer(&mut callbacks) };
26    let core = unsafe { CoreDataRenderer::from_raw(NonNull::new(result).unwrap()) };
27    let container = DataRendererContainer::get();
28    match C::REGISTRATION_TYPE {
29        RegistrationType::Generic => container.register_data_renderer(&core),
30        RegistrationType::Specific => container.register_specific_data_renderer(&core),
31    }
32    (renderer, core)
33}
34
35/// Renders the data at the given address using the registered data renderers, returning associated lines.
36pub fn render_lines_for_data(
37    view: &BinaryView,
38    addr: u64,
39    type_: &Type,
40    prefix: Vec<InstructionTextToken>,
41    width: usize,
42    types_ctx: &[TypeContext],
43    language: Option<&str>,
44) -> Vec<DisassemblyTextLine> {
45    let bn_prefix: Vec<BNInstructionTextToken> = prefix
46        .into_iter()
47        .map(InstructionTextToken::into_raw)
48        .collect();
49    let bn_language = BnString::from(language.unwrap_or(""));
50
51    let mut count: usize = 0;
52    let lines_ptr = unsafe {
53        BNRenderLinesForData(
54            view.handle,
55            addr,
56            type_.handle,
57            bn_prefix.as_ptr(),
58            bn_prefix.len(),
59            width,
60            &mut count as *mut usize,
61            types_ctx.as_ptr() as *mut BNTypeContext,
62            types_ctx.len(),
63            bn_language.as_ptr(),
64        )
65    };
66
67    for token in bn_prefix {
68        InstructionTextToken::free_raw(token);
69    }
70
71    let lines_arr: Array<DisassemblyTextLine> = unsafe { Array::new(lines_ptr, count, ()) };
72    lines_arr.to_vec()
73}
74
75#[derive(Clone, Copy)]
76struct DataRendererContainer {
77    pub(crate) handle: *mut BNDataRendererContainer,
78}
79
80impl DataRendererContainer {
81    pub fn get() -> Self {
82        Self {
83            handle: unsafe { BNGetDataRendererContainer() },
84        }
85    }
86
87    pub fn register_data_renderer(&self, renderer: &CoreDataRenderer) {
88        unsafe { BNRegisterGenericDataRenderer(self.handle, renderer.handle.as_ptr()) };
89    }
90
91    pub fn register_specific_data_renderer(&self, renderer: &CoreDataRenderer) {
92        unsafe { BNRegisterTypeSpecificDataRenderer(self.handle, renderer.handle.as_ptr()) };
93    }
94}
95
96/// Used by [`CustomDataRenderer`] to determine the priority of the renderer relative to other registered renderers.
97pub enum RegistrationType {
98    Generic,
99    /// This data renderer wants to run before any generic data renderers.
100    ///
101    /// Use this if you want to take priority over rendering of specific types.
102    Specific,
103}
104
105pub trait CustomDataRenderer: Sized + Sync + Send + 'static {
106    /// The registration type for the renderer really only determines the priority for the renderer.
107    ///
108    /// If you are overriding the behavior of a specific type, you should use [`RegistrationType::Specific`].
109    const REGISTRATION_TYPE: RegistrationType;
110
111    fn is_valid_for_data(
112        &self,
113        view: &BinaryView,
114        addr: u64,
115        type_: &Type,
116        types: &[TypeContext],
117    ) -> bool;
118
119    fn lines_for_data(
120        &self,
121        view: &BinaryView,
122        addr: u64,
123        type_: &Type,
124        prefix: Vec<InstructionTextToken>,
125        width: usize,
126        types_ctx: &[TypeContext],
127        language: &str,
128    ) -> Vec<DisassemblyTextLine>;
129}
130
131pub struct CoreDataRenderer {
132    pub(crate) handle: NonNull<BNDataRenderer>,
133}
134
135impl CoreDataRenderer {
136    pub(crate) unsafe fn from_raw(handle: NonNull<BNDataRenderer>) -> CoreDataRenderer {
137        Self { handle }
138    }
139}
140
141/// Data renderers are recursive, so we keep track of observed types.
142///
143/// This can be used to influence the rendering of structure fields and related nested types.
144#[repr(transparent)]
145pub struct TypeContext {
146    handle: BNTypeContext,
147}
148
149impl TypeContext {
150    /// The [`Type`] in the context.
151    pub fn ty(&self) -> &Type {
152        // SAFETY Type and `*mut BNType` are transparent, and the type is expected to be valid for the lifetime of the context.
153        unsafe { core::mem::transmute::<&*mut BNType, &Type>(&self.handle.type_) }
154    }
155
156    /// The offset with which the type is associated.
157    ///
158    /// The offset in many cases refers to a structure byte offset.
159    pub fn offset(&self) -> usize {
160        self.handle.offset
161    }
162}
163
164impl Debug for TypeContext {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        f.debug_struct("TypeContext")
167            .field("ty", &self.ty())
168            .field("offset", &self.offset())
169            .finish()
170    }
171}
172
173unsafe extern "C" fn cb_free_object<C: CustomDataRenderer>(ctxt: *mut c_void) {
174    let _ = Box::from_raw(ctxt as *mut C);
175}
176
177unsafe extern "C" fn cb_is_valid_for_data<C: CustomDataRenderer>(
178    ctxt: *mut c_void,
179    view: *mut BNBinaryView,
180    addr: u64,
181    type_: *mut BNType,
182    type_ctx: *mut BNTypeContext,
183    ctx_count: usize,
184) -> bool {
185    let ctxt = ctxt as *mut C;
186    // SAFETY BNTypeContext and TypeContext are transparent
187    let types = core::slice::from_raw_parts(type_ctx as *mut TypeContext, ctx_count);
188    (*ctxt).is_valid_for_data(
189        &BinaryView::from_raw(view),
190        addr,
191        &Type::from_raw(type_),
192        types,
193    )
194}
195
196unsafe extern "C" fn cb_get_lines_for_data<C: CustomDataRenderer>(
197    ctxt: *mut c_void,
198    view: *mut BNBinaryView,
199    addr: u64,
200    type_: *mut BNType,
201    prefix: *const BNInstructionTextToken,
202    prefix_count: usize,
203    width: usize,
204    count: *mut usize,
205    type_ctx: *mut BNTypeContext,
206    ctx_count: usize,
207    language: *const ffi::c_char,
208) -> *mut BNDisassemblyTextLine {
209    let ctxt = ctxt as *mut C;
210    // SAFETY BNTypeContext and TypeContext are transparent
211    let types = core::slice::from_raw_parts(type_ctx as *mut TypeContext, ctx_count);
212    let prefix = core::slice::from_raw_parts(prefix, prefix_count)
213        .iter()
214        .map(InstructionTextToken::from_raw)
215        .collect::<Vec<_>>();
216    let result = (*ctxt).lines_for_data(
217        &BinaryView::from_raw(view),
218        addr,
219        &Type::from_raw(type_),
220        prefix,
221        width,
222        types,
223        ffi::CStr::from_ptr(language).to_str().unwrap(),
224    );
225    let result: Box<[BNDisassemblyTextLine]> = result
226        .into_iter()
227        .map(DisassemblyTextLine::into_raw)
228        .collect();
229    *count = result.len();
230    Box::leak(result).as_mut_ptr()
231}
232
233unsafe extern "C" fn cb_free_lines(
234    _ctx: *mut c_void,
235    lines: *mut BNDisassemblyTextLine,
236    count: usize,
237) {
238    let lines = Box::from_raw(core::slice::from_raw_parts_mut(lines, count));
239    for line in lines {
240        let _ = DisassemblyTextLine::from_raw(&line);
241    }
242}