From 378d9630ac92f3b088e8e9e0a18feda8006a9688 Mon Sep 17 00:00:00 2001 From: rbran Date: Thu, 24 Apr 2025 16:20:32 +0000 Subject: [PATCH 01/10] 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 3194ae14ca..e1d33e019a 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -39,6 +39,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 df1ea2706587e65d42566a52259d86b4ec2baab9 Mon Sep 17 00:00:00 2001 From: rbran Date: Tue, 6 May 2025 12:18:38 +0000 Subject: [PATCH 02/10] 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 +} From 45f24b68a2b961038daabf2f305803615723c6be Mon Sep 17 00:00:00 2001 From: LukBukkit Date: Fri, 5 Sep 2025 21:44:23 +0200 Subject: [PATCH 03/10] Models CoreDataRenderer after CoreTypePrinter to prevent crashes The previous approach of reference counting did lead to crashes as the renderer was freed after registration :/ --- rust/src/data_renderer.rs | 118 +++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 66 deletions(-) diff --git a/rust/src/data_renderer.rs b/rust/src/data_renderer.rs index 24c43a9a1c..f32e74ff14 100644 --- a/rust/src/data_renderer.rs +++ b/rust/src/data_renderer.rs @@ -1,26 +1,31 @@ use core::ffi; - +use ffi::c_void; +use std::ptr::NonNull; +use log::debug; 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); +pub struct TypeContext { + handle: BNTypeContext +} impl TypeContext { pub fn type_(&self) -> &Type { + // debug!("TypeContext type_"); // SAFETY Type and `*mut BNType` are transparent - unsafe { core::mem::transmute::<&*mut BNType, &Type>(&self.0.type_) } + unsafe { core::mem::transmute::<&*mut BNType, &Type>(&self.handle.type_) } } pub fn offset(&self) -> usize { - self.0.offset + // debug!("TypeContext offset"); + self.handle.offset } } @@ -46,18 +51,20 @@ pub trait CustomDataRenderer: Sized + Sync + Send + 'static { } trait CustomDataRendererFFI: CustomDataRenderer { - unsafe extern "C" fn free_object_ffi(ctxt: *mut ffi::c_void) { + unsafe extern "C" fn free_object_ffi(ctxt: *mut c_void) { + // debug!("free_object_ffi"); drop(Box::from_raw(ctxt as *mut Self)) } unsafe extern "C" fn is_valid_for_data_ffi( - ctxt: *mut ffi::c_void, + ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64, type_: *mut BNType, type_ctx: *mut BNTypeContext, ctx_count: usize, ) -> bool { + // debug!("is_valid_for_data_ffi"); 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); @@ -70,7 +77,7 @@ trait CustomDataRendererFFI: CustomDataRenderer { } unsafe extern "C" fn get_lines_for_data_ffi( - ctxt: *mut ffi::c_void, + ctxt: *mut c_void, view: *mut BNBinaryView, addr: u64, type_: *mut BNType, @@ -82,6 +89,7 @@ trait CustomDataRendererFFI: CustomDataRenderer { ctx_count: usize, language: *const ffi::c_char, ) -> *mut BNDisassemblyTextLine { + // debug!("get_lines_for_data_ffi"); 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); @@ -104,10 +112,11 @@ trait CustomDataRendererFFI: CustomDataRenderer { } unsafe extern "C" fn free_lines_ffi( - _ctx: *mut ffi::c_void, + _ctx: *mut c_void, lines: *mut BNDisassemblyTextLine, count: usize, ) { + // debug!("free_lines_ffi"); let lines = Box::from_raw(core::slice::from_raw_parts_mut(lines, count)); drop( lines @@ -120,78 +129,55 @@ trait CustomDataRendererFFI: CustomDataRenderer { 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 - } +pub struct CoreDataRenderer { + pub(crate) handle: NonNull, } -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) } +impl CoreDataRenderer { + pub(crate) unsafe fn from_raw(handle: NonNull) -> CoreDataRenderer { + Self { handle } } } -fn create_custom_data_renderer(custom: C) -> Ref { - let custom = Box::leak(Box::new(custom)); +fn create_custom_data_renderer(renderer: T) -> (&'static mut T, CoreDataRenderer) { + let renderer = Box::leak(Box::new(renderer)); 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), + context: renderer as *mut _ as *mut 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)) } + let result = unsafe { BNCreateDataRenderer(&mut callbacks) }; + let core = unsafe { CoreDataRenderer::from_raw(NonNull::new(result).unwrap()) }; + (renderer, core) } -pub fn register_generic_data_renderer(custom: C) -> Ref { - let renderer = create_custom_data_renderer(custom); - register_generic_renderer(&renderer); - renderer +pub fn register_generic_data_renderer(custom: T) -> (&'static mut T, CoreDataRenderer) { + let (renderer, core) = create_custom_data_renderer(custom); + // debug!("register_generic_data_renderer: core={:?}", core.handle); + let container = DataRendererContainer::get(); + unsafe { BNRegisterGenericDataRenderer(container.handle, core.handle.as_ptr()) } + (renderer, core) } -pub fn register_specific_data_renderer(custom: C) -> Ref { - let renderer = create_custom_data_renderer(custom); - register_specific_renderer(&renderer); - renderer +pub fn register_specific_data_renderer(custom: C) -> (&'static mut C, CoreDataRenderer) { + let (renderer, core) = create_custom_data_renderer(custom); + // debug!("register_specific_data_renderer: core={:?}", core.handle); + let container = DataRendererContainer::get(); + unsafe { BNRegisterTypeSpecificDataRenderer(container.handle, core.handle.as_ptr()) } + (renderer, core) } #[derive(Clone, Copy)] -struct DataRendererContainer(*mut BNDataRendererContainer); +struct DataRendererContainer { + pub(crate) handle: *mut BNDataRendererContainer +} impl DataRendererContainer { - pub(crate) fn as_raw(&self) -> *mut BNDataRendererContainer { - self.0 - } - pub fn get() -> Self { - Self(unsafe { BNGetDataRendererContainer() }) + Self { + handle: 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()) } -} +} \ No newline at end of file From 5c097ce300fab1e3c6b55a0b9135ab32092f0c06 Mon Sep 17 00:00:00 2001 From: LukBukkit Date: Fri, 5 Sep 2025 23:06:44 +0200 Subject: [PATCH 04/10] Adds new_with_addr initializer to DisassemblyTextLine --- rust/src/disassembly.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs index c1021197d0..25d7d8cab7 100644 --- a/rust/src/disassembly.rs +++ b/rust/src/disassembly.rs @@ -142,6 +142,14 @@ impl DisassemblyTextLine { ..Default::default() } } + + pub fn new_with_addr(tokens: Vec, addr: u64) -> Self { + Self { + address: addr, + tokens, + ..Default::default() + } + } } impl From<&str> for DisassemblyTextLine { From 1da522118c861d4e717c927734e6794ce5dfb202 Mon Sep 17 00:00:00 2001 From: LukBukkit Date: Fri, 5 Sep 2025 23:07:30 +0200 Subject: [PATCH 05/10] Updates documentation for code & data symbol instruction text token kinds --- rust/src/disassembly.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/src/disassembly.rs b/rust/src/disassembly.rs index 25d7d8cab7..76a45c373c 100644 --- a/rust/src/disassembly.rs +++ b/rust/src/disassembly.rs @@ -501,13 +501,13 @@ pub enum InstructionTextTokenKind { hash: Option, }, CodeSymbol { - // TODO: Value of what? + // Target address of the symbol value: u64, // TODO: Size of what? size: usize, // TODO: Operand? }, DataSymbol { - // TODO: Value of what? + // Target address of the symbol value: u64, // TODO: Size of what? size: usize, // TODO: Operand? From 7386c1116c53269249f4497e2d1258d9db395ba4 Mon Sep 17 00:00:00 2001 From: LukBukkit Date: Sun, 28 Sep 2025 17:33:46 +0200 Subject: [PATCH 06/10] Provides all prefix tokens in a vector --- rust/src/data_renderer.rs | 12 ++++++------ rust/tests/data_renderer.rs | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/rust/src/data_renderer.rs b/rust/src/data_renderer.rs index f32e74ff14..353036bab4 100644 --- a/rust/src/data_renderer.rs +++ b/rust/src/data_renderer.rs @@ -1,8 +1,7 @@ +use binaryninjacore_sys::*; use core::ffi; use ffi::c_void; use std::ptr::NonNull; -use log::debug; -use binaryninjacore_sys::*; use crate::binary_view::BinaryView; use crate::disassembly::{DisassemblyTextLine, InstructionTextToken}; @@ -42,8 +41,7 @@ pub trait CustomDataRenderer: Sized + Sync + Send + 'static { view: &BinaryView, addr: u64, type_: &Type, - prefix: &InstructionTextToken, - prefix_count: usize, + prefix: Vec, width: usize, types_ctx: &[TypeContext], language: &str, @@ -93,12 +91,14 @@ trait CustomDataRendererFFI: CustomDataRenderer { 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 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_), - &InstructionTextToken::from_raw(&*prefix), - prefix_count, + prefix, width, types, ffi::CStr::from_ptr(language).to_str().unwrap(), diff --git a/rust/tests/data_renderer.rs b/rust/tests/data_renderer.rs index 46b050d550..d589073c69 100644 --- a/rust/tests/data_renderer.rs +++ b/rust/tests/data_renderer.rs @@ -28,8 +28,7 @@ fn test_data_renderer_basic() { _view: &BinaryView, addr: u64, type_: &Type, - _prefix: &InstructionTextToken, - _prefix_count: usize, + _prefix: Vec, width: usize, _types_ctx: &[TypeContext], _language: &str, From 2b0d3cc3bf67c809ce6ba0a9134cf0cf0b7263e3 Mon Sep 17 00:00:00 2001 From: LukBukkit Date: Sun, 28 Sep 2025 17:55:51 +0200 Subject: [PATCH 07/10] Adds data renderer UUID example --- Cargo.lock | 14 +++- Cargo.toml | 1 + rust/examples/data_renderer/Cargo.toml | 13 +++ rust/examples/data_renderer/README.md | 63 +++++++++++++++ rust/examples/data_renderer/build.rs | 22 +++++ rust/examples/data_renderer/src/lib.rs | 108 +++++++++++++++++++++++++ 6 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 rust/examples/data_renderer/Cargo.toml create mode 100644 rust/examples/data_renderer/README.md create mode 100644 rust/examples/data_renderer/build.rs create mode 100644 rust/examples/data_renderer/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 93b6283d1d..4a843fe306 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,6 +749,16 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" +[[package]] +name = "example_data_renderer" +version = "0.1.0" +dependencies = [ + "binaryninja", + "binaryninjacore-sys", + "log", + "uuid", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -3015,9 +3025,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 2811850a56..534d5f5ca9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "rust", "arch/riscv", "arch/msp430", + "rust/examples/data_renderer", "view/minidump", "plugins/dwarf/dwarf_import", "plugins/dwarf/dwarf_export", diff --git a/rust/examples/data_renderer/Cargo.toml b/rust/examples/data_renderer/Cargo.toml new file mode 100644 index 0000000000..3917763de4 --- /dev/null +++ b/rust/examples/data_renderer/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example_data_renderer" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +uuid = "1.18.1" +binaryninjacore-sys = { path = "../../binaryninjacore-sys", default-features = false } +binaryninja = { path = "../../" } +log = "0.4.27" \ No newline at end of file diff --git a/rust/examples/data_renderer/README.md b/rust/examples/data_renderer/README.md new file mode 100644 index 0000000000..1c5a8679b6 --- /dev/null +++ b/rust/examples/data_renderer/README.md @@ -0,0 +1,63 @@ +# Data Renderer Example + +This example implements a simple data renderer for the Mach-O load command LC_UUID. +You can try the renderer by loading the `/bin/cat` binary from macOS. + +We're implementing a functionality similar to the one described in the Python data renderer blog post: +https://binary.ninja/2024/04/08/customizing-data-display.html. + +## Building + +```sh +# Build from the root directory (binaryninja-api) +cargo build --manifest-path rust/examples/data_renderer/Cargo.toml +# Link binary on macOS +ln -sf $PWD/target/debug/libexample_data_renderer.dylib ~/Library/Application\ Support/Binary\ Ninja/plugins +``` + +## Result + +The following Mach-O load command be will be transformed from + +```c +struct uuid __macho_load_command_[10] = +{ + enum load_command_type_t cmd = LC_UUID + uint32_t cmdsize = 0x18 + uint8_t uuid[0x10] = + { + [0x0] = 0x74 + [0x1] = 0xa0 + [0x2] = 0x3a + [0x3] = 0xbd + [0x4] = 0x1e + [0x5] = 0x19 + [0x6] = 0x32 + [0x7] = 0x67 + [0x8] = 0x9a + [0x9] = 0xdc + [0xa] = 0x42 + [0xb] = 0x99 + [0xc] = 0x4e + [0xd] = 0x26 + [0xe] = 0xa2 + [0xf] = 0xb7 + } +} +``` + +into the following representation + +```c +struct uuid __macho_load_command_[10] = +{ + enum load_command_type_t cmd = LC_UUID + uint32_t cmdsize = 0x18 + uint8_t uuid[0x10] = UUID("74a03abd-1e19-3267-9adc-42994e26a2b7") +} +``` + +You can compare the shown UUID with the output of otool: +```sh +otool -arch all -l /bin/cat +``` \ No newline at end of file diff --git a/rust/examples/data_renderer/build.rs b/rust/examples/data_renderer/build.rs new file mode 100644 index 0000000000..22b3964e85 --- /dev/null +++ b/rust/examples/data_renderer/build.rs @@ -0,0 +1,22 @@ +fn main() { + let link_path = + std::env::var_os("DEP_BINARYNINJACORE_PATH").expect("DEP_BINARYNINJACORE_PATH not specified"); + + println!("cargo::rustc-link-lib=dylib=binaryninjacore"); + println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); + + #[cfg(target_os = "linux")] + { + println!( + "cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}", + link_path.to_string_lossy() + ); + } + + #[cfg(target_os = "macos")] + { + let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set"); + let lib_name = crate_name.replace('-', "_"); + println!("cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib", lib_name); + } +} diff --git a/rust/examples/data_renderer/src/lib.rs b/rust/examples/data_renderer/src/lib.rs new file mode 100644 index 0000000000..5b2eb81486 --- /dev/null +++ b/rust/examples/data_renderer/src/lib.rs @@ -0,0 +1,108 @@ +use binaryninja::binary_view::{BinaryView, BinaryViewBase}; +use binaryninja::data_renderer::{ + CustomDataRenderer, TypeContext, register_specific_data_renderer, +}; +use binaryninja::disassembly::{ + DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind, +}; +use binaryninja::types::{Type, TypeClass}; +use log::debug; +use uuid::Uuid; + +struct UuidDataRenderer {} + +impl CustomDataRenderer for UuidDataRenderer { + fn is_valid_for_data( + &self, + view: &BinaryView, + addr: u64, + type_: &Type, + types: &[TypeContext], + ) -> bool { + // We only want to render arrays with a size of 16 elements + if type_.type_class() != TypeClass::ArrayTypeClass { + return false; + } + if type_.count() != 0x10 { + return false; + } + + // The array elements must be of the type uint8_t + let Some(element_type_conf) = type_.element_type() else { + return false; + }; + let element_type = element_type_conf.contents; + if element_type.type_class() != TypeClass::IntegerTypeClass { + return false; + } + if element_type.width() != 1 { + return false; + } + + // The array should be embedded in a named type reference with the id macho:["uuid"] + for type_ctx in types { + if type_ctx.type_().type_class() != TypeClass::NamedTypeReferenceClass { + continue; + } + + let Some(name_ref) = type_ctx.type_().get_named_type_reference() else { + continue; + }; + + if name_ref.id() == "macho:[\"uuid\"]" { + return true; + } + } + + false + } + + fn lines_for_data( + &self, + view: &BinaryView, + addr: u64, + type_: &Type, + prefix: Vec, + width: usize, + types_ctx: &[TypeContext], + language: &str, + ) -> Vec { + let mut tokens = prefix.clone(); + + let mut buf = [0u8; 0x10]; + let bytes_read = view.read(&mut buf, addr); + + // Make sure that we've read all UUID bytes and convert them to token + if bytes_read == 0x10 { + tokens.extend([ + InstructionTextToken::new("UUID(\"", InstructionTextTokenKind::Text), + InstructionTextToken::new( + Uuid::from_bytes(buf).to_string(), + InstructionTextTokenKind::String { value: 0 }, + ), + InstructionTextToken::new("\")", InstructionTextTokenKind::Text), + ]); + } else { + tokens.push(InstructionTextToken::new( + "error: cannot read 0x10 bytes", + InstructionTextTokenKind::Annotation, + )); + } + + vec![DisassemblyTextLine::new_with_addr(tokens, addr)] + } +} + +#[allow(non_snake_case)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn CorePluginInit() -> bool { + // Initialize logging + binaryninja::logger::Logger::new("UUID Data Renderer") + .with_level(log::LevelFilter::Debug) + .init(); + + // Register data renderer + register_specific_data_renderer(UuidDataRenderer {}); + + true +} From ba766ff75b3d025d41dd1ff12712539ada21e3ee Mon Sep 17 00:00:00 2001 From: LukBukkit Date: Sun, 28 Sep 2025 18:08:32 +0200 Subject: [PATCH 08/10] Lower Rust level of crate & Applies clippy suggestions --- rust/examples/data_renderer/Cargo.toml | 4 ++-- rust/examples/data_renderer/src/lib.rs | 15 ++++++++------- rust/tests/data_renderer.rs | 2 -- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/rust/examples/data_renderer/Cargo.toml b/rust/examples/data_renderer/Cargo.toml index 3917763de4..dd692c73ce 100644 --- a/rust/examples/data_renderer/Cargo.toml +++ b/rust/examples/data_renderer/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "example_data_renderer" version = "0.1.0" -edition = "2024" +edition = "2021" [lib] crate-type = ["cdylib"] @@ -10,4 +10,4 @@ crate-type = ["cdylib"] uuid = "1.18.1" binaryninjacore-sys = { path = "../../binaryninjacore-sys", default-features = false } binaryninja = { path = "../../" } -log = "0.4.27" \ No newline at end of file +log = "0.4.27" diff --git a/rust/examples/data_renderer/src/lib.rs b/rust/examples/data_renderer/src/lib.rs index 5b2eb81486..23c5b48707 100644 --- a/rust/examples/data_renderer/src/lib.rs +++ b/rust/examples/data_renderer/src/lib.rs @@ -6,7 +6,6 @@ use binaryninja::disassembly::{ DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind, }; use binaryninja::types::{Type, TypeClass}; -use log::debug; use uuid::Uuid; struct UuidDataRenderer {} @@ -14,8 +13,8 @@ struct UuidDataRenderer {} impl CustomDataRenderer for UuidDataRenderer { fn is_valid_for_data( &self, - view: &BinaryView, - addr: u64, + _view: &BinaryView, + _addr: u64, type_: &Type, types: &[TypeContext], ) -> bool { @@ -61,11 +60,11 @@ impl CustomDataRenderer for UuidDataRenderer { &self, view: &BinaryView, addr: u64, - type_: &Type, + _type_: &Type, prefix: Vec, - width: usize, - types_ctx: &[TypeContext], - language: &str, + _width: usize, + _types_ctx: &[TypeContext], + _language: &str, ) -> Vec { let mut tokens = prefix.clone(); @@ -93,6 +92,8 @@ impl CustomDataRenderer for UuidDataRenderer { } } +/// # Safety +/// This function is called from Binary Ninja once to initialize the plugin. #[allow(non_snake_case)] #[unsafe(no_mangle)] pub unsafe extern "C" fn CorePluginInit() -> bool { diff --git a/rust/tests/data_renderer.rs b/rust/tests/data_renderer.rs index d589073c69..a941f3693b 100644 --- a/rust/tests/data_renderer.rs +++ b/rust/tests/data_renderer.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use binaryninja::binary_view::BinaryView; use binaryninja::data_renderer::{ register_specific_data_renderer, CustomDataRenderer, TypeContext, From 081197a5879d49fdcb3fbef19f73667a4c7105d5 Mon Sep 17 00:00:00 2001 From: LukBukkit Date: Sun, 28 Sep 2025 18:15:12 +0200 Subject: [PATCH 09/10] Applies Rust format to data_renderer --- rust/examples/data_renderer/Cargo.toml | 2 +- rust/examples/data_renderer/build.rs | 9 ++++++--- rust/examples/data_renderer/src/lib.rs | 2 +- rust/src/data_renderer.rs | 23 +++++++++++++++-------- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/rust/examples/data_renderer/Cargo.toml b/rust/examples/data_renderer/Cargo.toml index dd692c73ce..4ea72d3878 100644 --- a/rust/examples/data_renderer/Cargo.toml +++ b/rust/examples/data_renderer/Cargo.toml @@ -9,5 +9,5 @@ crate-type = ["cdylib"] [dependencies] uuid = "1.18.1" binaryninjacore-sys = { path = "../../binaryninjacore-sys", default-features = false } -binaryninja = { path = "../../" } +binaryninja = { path = "../.." } log = "0.4.27" diff --git a/rust/examples/data_renderer/build.rs b/rust/examples/data_renderer/build.rs index 22b3964e85..9006f16a69 100644 --- a/rust/examples/data_renderer/build.rs +++ b/rust/examples/data_renderer/build.rs @@ -1,6 +1,6 @@ fn main() { - let link_path = - std::env::var_os("DEP_BINARYNINJACORE_PATH").expect("DEP_BINARYNINJACORE_PATH not specified"); + let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH") + .expect("DEP_BINARYNINJACORE_PATH not specified"); println!("cargo::rustc-link-lib=dylib=binaryninjacore"); println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); @@ -17,6 +17,9 @@ fn main() { { let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set"); let lib_name = crate_name.replace('-', "_"); - println!("cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib", lib_name); + println!( + "cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib", + lib_name + ); } } diff --git a/rust/examples/data_renderer/src/lib.rs b/rust/examples/data_renderer/src/lib.rs index 23c5b48707..8e16bb1f2c 100644 --- a/rust/examples/data_renderer/src/lib.rs +++ b/rust/examples/data_renderer/src/lib.rs @@ -1,6 +1,6 @@ use binaryninja::binary_view::{BinaryView, BinaryViewBase}; use binaryninja::data_renderer::{ - CustomDataRenderer, TypeContext, register_specific_data_renderer, + register_specific_data_renderer, CustomDataRenderer, TypeContext, }; use binaryninja::disassembly::{ DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind, diff --git a/rust/src/data_renderer.rs b/rust/src/data_renderer.rs index 353036bab4..75e43f286a 100644 --- a/rust/src/data_renderer.rs +++ b/rust/src/data_renderer.rs @@ -12,7 +12,7 @@ use crate::types::Type; // don't have a *from_raw function, it don't need to worry about drop it. #[repr(transparent)] pub struct TypeContext { - handle: BNTypeContext + handle: BNTypeContext, } impl TypeContext { @@ -92,7 +92,8 @@ trait CustomDataRendererFFI: CustomDataRenderer { // 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) + .iter() + .map(InstructionTextToken::from_raw) .collect::>(); let result = (*ctxt).lines_for_data( &BinaryView::from_raw(view), @@ -139,7 +140,9 @@ impl CoreDataRenderer { } } -fn create_custom_data_renderer(renderer: T) -> (&'static mut T, CoreDataRenderer) { +fn create_custom_data_renderer( + renderer: T, +) -> (&'static mut T, CoreDataRenderer) { let renderer = Box::leak(Box::new(renderer)); let mut callbacks = BNCustomDataRenderer { context: renderer as *mut _ as *mut c_void, @@ -153,7 +156,9 @@ fn create_custom_data_renderer(renderer: T) -> (&'static (renderer, core) } -pub fn register_generic_data_renderer(custom: T) -> (&'static mut T, CoreDataRenderer) { +pub fn register_generic_data_renderer( + custom: T, +) -> (&'static mut T, CoreDataRenderer) { let (renderer, core) = create_custom_data_renderer(custom); // debug!("register_generic_data_renderer: core={:?}", core.handle); let container = DataRendererContainer::get(); @@ -161,7 +166,9 @@ pub fn register_generic_data_renderer(custom: T) -> (&'st (renderer, core) } -pub fn register_specific_data_renderer(custom: C) -> (&'static mut C, CoreDataRenderer) { +pub fn register_specific_data_renderer( + custom: C, +) -> (&'static mut C, CoreDataRenderer) { let (renderer, core) = create_custom_data_renderer(custom); // debug!("register_specific_data_renderer: core={:?}", core.handle); let container = DataRendererContainer::get(); @@ -171,13 +178,13 @@ pub fn register_specific_data_renderer(custom: C) -> (&'s #[derive(Clone, Copy)] struct DataRendererContainer { - pub(crate) handle: *mut BNDataRendererContainer + pub(crate) handle: *mut BNDataRendererContainer, } impl DataRendererContainer { pub fn get() -> Self { Self { - handle: unsafe { BNGetDataRendererContainer() } + handle: unsafe { BNGetDataRendererContainer() }, } } -} \ No newline at end of file +} From 3cb96b4a8cae4576b63c4f3b76893f0e20b53b70 Mon Sep 17 00:00:00 2001 From: LukBukkit Date: Mon, 29 Sep 2025 10:17:38 +0200 Subject: [PATCH 10/10] Moves Rust data renderer example to plugin_examples --- Cargo.toml | 2 +- rust/{examples => plugin_examples}/data_renderer/Cargo.toml | 0 rust/{examples => plugin_examples}/data_renderer/README.md | 2 +- rust/{examples => plugin_examples}/data_renderer/build.rs | 0 rust/{examples => plugin_examples}/data_renderer/src/lib.rs | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename rust/{examples => plugin_examples}/data_renderer/Cargo.toml (100%) rename rust/{examples => plugin_examples}/data_renderer/README.md (95%) rename rust/{examples => plugin_examples}/data_renderer/build.rs (100%) rename rust/{examples => plugin_examples}/data_renderer/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 534d5f5ca9..5bd540ad8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ "rust", "arch/riscv", "arch/msp430", - "rust/examples/data_renderer", + "rust/plugin_examples/*", "view/minidump", "plugins/dwarf/dwarf_import", "plugins/dwarf/dwarf_export", diff --git a/rust/examples/data_renderer/Cargo.toml b/rust/plugin_examples/data_renderer/Cargo.toml similarity index 100% rename from rust/examples/data_renderer/Cargo.toml rename to rust/plugin_examples/data_renderer/Cargo.toml diff --git a/rust/examples/data_renderer/README.md b/rust/plugin_examples/data_renderer/README.md similarity index 95% rename from rust/examples/data_renderer/README.md rename to rust/plugin_examples/data_renderer/README.md index 1c5a8679b6..794b93b950 100644 --- a/rust/examples/data_renderer/README.md +++ b/rust/plugin_examples/data_renderer/README.md @@ -10,7 +10,7 @@ https://binary.ninja/2024/04/08/customizing-data-display.html. ```sh # Build from the root directory (binaryninja-api) -cargo build --manifest-path rust/examples/data_renderer/Cargo.toml +cargo build --manifest-path rust/plugin_examples/data_renderer/Cargo.toml # Link binary on macOS ln -sf $PWD/target/debug/libexample_data_renderer.dylib ~/Library/Application\ Support/Binary\ Ninja/plugins ``` diff --git a/rust/examples/data_renderer/build.rs b/rust/plugin_examples/data_renderer/build.rs similarity index 100% rename from rust/examples/data_renderer/build.rs rename to rust/plugin_examples/data_renderer/build.rs diff --git a/rust/examples/data_renderer/src/lib.rs b/rust/plugin_examples/data_renderer/src/lib.rs similarity index 100% rename from rust/examples/data_renderer/src/lib.rs rename to rust/plugin_examples/data_renderer/src/lib.rs