binaryninja/
data_renderer.rs

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