diff options
| author | LukBukkit <luk.bukkit@gmail.com> | 2025-04-24 16:20:32 +0000 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-10-07 16:47:33 -0400 |
| commit | 64633f61f7b9e03be9437b5f4896bbe122f7a7a2 (patch) | |
| tree | 8fafed1395a8d2e29e5a6b8b7f7d7b8a2af2731a /rust/src/data_renderer.rs | |
| parent | e6e4ebea5d0ea7842766c7e686e0dfd45cb54bb8 (diff) | |
[Rust] Implement custom data renderer API
Also adds an example plugin and misc rust fixes / documentation.
This is a continuation of https://github.com/Vector35/binaryninja-api/pull/6721
Co-authored-by: rbran <lgit@rubens.io>
Diffstat (limited to 'rust/src/data_renderer.rs')
| -rw-r--r-- | rust/src/data_renderer.rs | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/rust/src/data_renderer.rs b/rust/src/data_renderer.rs new file mode 100644 index 00000000..6fa19b59 --- /dev/null +++ b/rust/src/data_renderer.rs @@ -0,0 +1,242 @@ +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<C: CustomDataRenderer>( + 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::<C>), + isValidForData: Some(cb_is_valid_for_data::<C>), + getLinesForData: Some(cb_get_lines_for_data::<C>), + 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<InstructionTextToken>, + width: usize, + types_ctx: &[TypeContext], + language: Option<&str>, +) -> Vec<DisassemblyTextLine> { + let bn_prefix: Vec<BNInstructionTextToken> = 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<DisassemblyTextLine> = 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<InstructionTextToken>, + width: usize, + types_ctx: &[TypeContext], + language: &str, + ) -> Vec<DisassemblyTextLine>; +} + +pub struct CoreDataRenderer { + pub(crate) handle: NonNull<BNDataRenderer>, +} + +impl CoreDataRenderer { + pub(crate) unsafe fn from_raw(handle: NonNull<BNDataRenderer>) -> 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<C: CustomDataRenderer>(ctxt: *mut c_void) { + let _ = Box::from_raw(ctxt as *mut C); +} + +unsafe extern "C" fn cb_is_valid_for_data<C: CustomDataRenderer>( + 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<C: CustomDataRenderer>( + 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::<Vec<_>>(); + 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(core::slice::from_raw_parts_mut(lines, count)); + for line in lines { + let _ = DisassemblyTextLine::from_raw(&line); + } +} |
