//! Render data variables using builtin renderers as well as add custom rendering. use binaryninjacore_sys::*; use core::ffi; use ffi::c_void; use std::fmt::Debug; use std::ptr::NonNull; use crate::binary_view::BinaryView; use crate::disassembly::{DisassemblyTextLine, InstructionTextToken}; use crate::rc::Array; use crate::string::BnString; use crate::types::Type; /// Registers a custom data renderer, this allows you to customize the representation of data variables. pub fn register_data_renderer( custom: C, ) -> (&'static mut C, CoreDataRenderer) { let renderer = Box::leak(Box::new(custom)); let mut callbacks = BNCustomDataRenderer { context: renderer as *mut _ as *mut c_void, freeObject: Some(cb_free_object::), isValidForData: Some(cb_is_valid_for_data::), getLinesForData: Some(cb_get_lines_for_data::), freeLines: Some(cb_free_lines), }; let result = unsafe { BNCreateDataRenderer(&mut callbacks) }; let core = unsafe { CoreDataRenderer::from_raw(NonNull::new(result).unwrap()) }; let container = DataRendererContainer::get(); match C::REGISTRATION_TYPE { RegistrationType::Generic => container.register_data_renderer(&core), RegistrationType::Specific => container.register_specific_data_renderer(&core), } (renderer, core) } /// Renders the data at the given address using the registered data renderers, returning associated lines. pub fn render_lines_for_data( view: &BinaryView, addr: u64, type_: &Type, prefix: Vec, width: usize, types_ctx: &[TypeContext], language: Option<&str>, ) -> Vec { let bn_prefix: Vec = prefix .into_iter() .map(InstructionTextToken::into_raw) .collect(); let bn_language = BnString::from(language.unwrap_or("")); let mut count: usize = 0; let lines_ptr = unsafe { BNRenderLinesForData( view.handle, addr, type_.handle, bn_prefix.as_ptr(), bn_prefix.len(), width, &mut count as *mut usize, types_ctx.as_ptr() as *mut BNTypeContext, types_ctx.len(), bn_language.as_ptr(), ) }; for token in bn_prefix { InstructionTextToken::free_raw(token); } let lines_arr: Array = unsafe { Array::new(lines_ptr, count, ()) }; lines_arr.to_vec() } #[derive(Clone, Copy)] struct DataRendererContainer { pub(crate) handle: *mut BNDataRendererContainer, } impl DataRendererContainer { pub fn get() -> Self { Self { handle: unsafe { BNGetDataRendererContainer() }, } } pub fn register_data_renderer(&self, renderer: &CoreDataRenderer) { unsafe { BNRegisterGenericDataRenderer(self.handle, renderer.handle.as_ptr()) }; } pub fn register_specific_data_renderer(&self, renderer: &CoreDataRenderer) { unsafe { BNRegisterTypeSpecificDataRenderer(self.handle, renderer.handle.as_ptr()) }; } } /// Used by [`CustomDataRenderer`] to determine the priority of the renderer relative to other registered renderers. pub enum RegistrationType { Generic, /// This data renderer wants to run before any generic data renderers. /// /// Use this if you want to take priority over rendering of specific types. Specific, } pub trait CustomDataRenderer: Sized + Sync + Send + 'static { /// The registration type for the renderer really only determines the priority for the renderer. /// /// If you are overriding the behavior of a specific type, you should use [`RegistrationType::Specific`]. const REGISTRATION_TYPE: RegistrationType; fn is_valid_for_data( &self, view: &BinaryView, addr: u64, type_: &Type, types: &[TypeContext], ) -> bool; fn lines_for_data( &self, view: &BinaryView, addr: u64, type_: &Type, prefix: Vec, width: usize, types_ctx: &[TypeContext], language: &str, ) -> Vec; } pub struct CoreDataRenderer { pub(crate) handle: NonNull, } impl CoreDataRenderer { pub(crate) unsafe fn from_raw(handle: NonNull) -> CoreDataRenderer { Self { handle } } } /// Data renderers are recursive, so we keep track of observed types. /// /// This can be used to influence the rendering of structure fields and related nested types. #[repr(transparent)] pub struct TypeContext { handle: BNTypeContext, } impl TypeContext { /// The [`Type`] in the context. pub fn ty(&self) -> &Type { // SAFETY Type and `*mut BNType` are transparent, and the type is expected to be valid for the lifetime of the context. unsafe { core::mem::transmute::<&*mut BNType, &Type>(&self.handle.type_) } } /// The offset with which the type is associated. /// /// The offset in many cases refers to a structure byte offset. pub fn offset(&self) -> usize { self.handle.offset } } impl Debug for TypeContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TypeContext") .field("ty", &self.ty()) .field("offset", &self.offset()) .finish() } } unsafe extern "C" fn cb_free_object(ctxt: *mut c_void) { let _ = Box::from_raw(ctxt as *mut C); } unsafe extern "C" fn cb_is_valid_for_data( ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64, type_: *mut BNType, type_ctx: *mut BNTypeContext, ctx_count: usize, ) -> bool { let ctxt = ctxt as *mut C; // SAFETY BNTypeContext and TypeContext are transparent let types = core::slice::from_raw_parts(type_ctx as *mut TypeContext, ctx_count); (*ctxt).is_valid_for_data( &BinaryView::from_raw(view), addr, &Type::from_raw(type_), types, ) } unsafe extern "C" fn cb_get_lines_for_data( ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64, type_: *mut BNType, prefix: *const BNInstructionTextToken, prefix_count: usize, width: usize, count: *mut usize, type_ctx: *mut BNTypeContext, ctx_count: usize, language: *const ffi::c_char, ) -> *mut BNDisassemblyTextLine { let ctxt = ctxt as *mut C; // SAFETY BNTypeContext and TypeContext are transparent let types = core::slice::from_raw_parts(type_ctx as *mut TypeContext, ctx_count); let prefix = core::slice::from_raw_parts(prefix, prefix_count) .iter() .map(InstructionTextToken::from_raw) .collect::>(); let result = (*ctxt).lines_for_data( &BinaryView::from_raw(view), addr, &Type::from_raw(type_), prefix, width, types, ffi::CStr::from_ptr(language).to_str().unwrap(), ); let result: Box<[BNDisassemblyTextLine]> = result .into_iter() .map(DisassemblyTextLine::into_raw) .collect(); *count = result.len(); Box::leak(result).as_mut_ptr() } unsafe extern "C" fn cb_free_lines( _ctx: *mut c_void, lines: *mut BNDisassemblyTextLine, count: usize, ) { let lines = Box::from_raw(std::ptr::slice_from_raw_parts_mut(lines, count)); for line in lines { let _ = DisassemblyTextLine::from_raw(&line); } }