From f09dd6a92c0f11c02d48fe3a64d0e0ecedd6ee43 Mon Sep 17 00:00:00 2001 From: rbran Date: Thu, 24 Apr 2025 16:20:32 +0000 Subject: [PATCH 1/2] implement rust custom data renderer --- rust/src/data_renderer.rs | 197 ++++++++++++++++++++++++++++++++++++++ rust/src/lib.rs | 1 + 2 files changed, 198 insertions(+) create mode 100644 rust/src/data_renderer.rs diff --git a/rust/src/data_renderer.rs b/rust/src/data_renderer.rs new file mode 100644 index 0000000000..24c43a9a1c --- /dev/null +++ b/rust/src/data_renderer.rs @@ -0,0 +1,197 @@ +use core::ffi; + +use binaryninjacore_sys::*; + +use crate::binary_view::BinaryView; +use crate::disassembly::{DisassemblyTextLine, InstructionTextToken}; +use crate::rc::{Ref, RefCountable}; +use crate::types::Type; + +// NOTE the type_ inside the context can be both owned or borrowed, because +// this type only exist as a reference and is never created by itself (AKA +// don't have a *from_raw function, it don't need to worry about drop it. +#[repr(transparent)] +pub struct TypeContext(BNTypeContext); + +impl TypeContext { + pub fn type_(&self) -> &Type { + // SAFETY Type and `*mut BNType` are transparent + unsafe { core::mem::transmute::<&*mut BNType, &Type>(&self.0.type_) } + } + + pub fn offset(&self) -> usize { + self.0.offset + } +} + +pub trait CustomDataRenderer: Sized + Sync + Send + 'static { + 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: &InstructionTextToken, + prefix_count: usize, + width: usize, + types_ctx: &[TypeContext], + language: &str, + ) -> Vec; +} + +trait CustomDataRendererFFI: CustomDataRenderer { + unsafe extern "C" fn free_object_ffi(ctxt: *mut ffi::c_void) { + drop(Box::from_raw(ctxt as *mut Self)) + } + + unsafe extern "C" fn is_valid_for_data_ffi( + ctxt: *mut ffi::c_void, + view: *mut BNBinaryView, + addr: u64, + type_: *mut BNType, + type_ctx: *mut BNTypeContext, + ctx_count: usize, + ) -> bool { + let ctxt = ctxt as *mut Self; + // 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 get_lines_for_data_ffi( + ctxt: *mut ffi::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 Self; + // SAFETY BNTypeContext and TypeContext are transparent + let types = core::slice::from_raw_parts(type_ctx as *mut TypeContext, ctx_count); + let result = (*ctxt).lines_for_data( + &BinaryView::from_raw(view), + addr, + &Type::from_raw(type_), + &InstructionTextToken::from_raw(&*prefix), + prefix_count, + 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 free_lines_ffi( + _ctx: *mut ffi::c_void, + lines: *mut BNDisassemblyTextLine, + count: usize, + ) { + let lines = Box::from_raw(core::slice::from_raw_parts_mut(lines, count)); + drop( + lines + .iter() + .map(DisassemblyTextLine::from_raw) + .collect::>(), + ); + } +} + +impl CustomDataRendererFFI for C {} + +pub struct CoreDataRenderer(*mut BNDataRenderer); + +impl CoreDataRenderer { + pub(crate) unsafe fn ref_from_raw(raw: *mut BNDataRenderer) -> Ref { + Ref::new(Self(raw)) + } + pub(crate) fn as_raw(&self) -> *mut BNDataRenderer { + self.0 + } +} + +unsafe impl RefCountable for CoreDataRenderer { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::ref_from_raw(BNNewDataRendererReference(handle.0)) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeDataRenderer(handle.0); + } +} + +impl ToOwned for CoreDataRenderer { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { ::inc_ref(self) } + } +} + +fn create_custom_data_renderer(custom: C) -> Ref { + let custom = Box::leak(Box::new(custom)); + let mut callbacks = BNCustomDataRenderer { + context: custom as *mut C as *mut ffi::c_void, + freeObject: Some(::free_object_ffi), + isValidForData: Some(::is_valid_for_data_ffi), + getLinesForData: Some(::get_lines_for_data_ffi), + freeLines: Some(::free_lines_ffi), + }; + unsafe { CoreDataRenderer::ref_from_raw(BNCreateDataRenderer(&mut callbacks)) } +} + +pub fn register_generic_data_renderer(custom: C) -> Ref { + let renderer = create_custom_data_renderer(custom); + register_generic_renderer(&renderer); + renderer +} + +pub fn register_specific_data_renderer(custom: C) -> Ref { + let renderer = create_custom_data_renderer(custom); + register_specific_renderer(&renderer); + renderer +} + +#[derive(Clone, Copy)] +struct DataRendererContainer(*mut BNDataRendererContainer); + +impl DataRendererContainer { + pub(crate) fn as_raw(&self) -> *mut BNDataRendererContainer { + self.0 + } + + pub fn get() -> Self { + Self(unsafe { BNGetDataRendererContainer() }) + } +} + +fn register_generic_renderer(renderer: &CoreDataRenderer) { + let container = DataRendererContainer::get(); + unsafe { BNRegisterGenericDataRenderer(container.as_raw(), renderer.as_raw()) } +} + +fn register_specific_renderer(renderer: &CoreDataRenderer) { + let container = DataRendererContainer::get(); + unsafe { BNRegisterTypeSpecificDataRenderer(container.as_raw(), renderer.as_raw()) } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index a3df0cd350..e72bfebd47 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -42,6 +42,7 @@ pub mod component; pub mod confidence; pub mod custom_binary_view; pub mod data_buffer; +pub mod data_renderer; pub mod database; pub mod debuginfo; pub mod demangle; From 9d80001349834aede66605d9d8e2fc5124f25b7f Mon Sep 17 00:00:00 2001 From: rbran Date: Tue, 6 May 2025 12:18:38 +0000 Subject: [PATCH 2/2] add test stub for data_renderer --- rust/tests/data_renderer.rs | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 rust/tests/data_renderer.rs diff --git a/rust/tests/data_renderer.rs b/rust/tests/data_renderer.rs new file mode 100644 index 0000000000..46b050d550 --- /dev/null +++ b/rust/tests/data_renderer.rs @@ -0,0 +1,92 @@ +use std::path::PathBuf; + +use binaryninja::binary_view::BinaryView; +use binaryninja::data_renderer::{ + register_specific_data_renderer, CustomDataRenderer, TypeContext, +}; +use binaryninja::disassembly::{ + DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind, +}; +use binaryninja::types::Type; + +#[test] +fn test_data_renderer_basic() { + struct StructRenderer {} + impl CustomDataRenderer for StructRenderer { + 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: &InstructionTextToken, + _prefix_count: usize, + width: usize, + _types_ctx: &[TypeContext], + _language: &str, + ) -> Vec { + 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 + } + } + + let _renderer = register_specific_data_renderer(StructRenderer {}); + // TODO render a Type +}