From 4d9784f44178e8a3c1a44feb5ef8176239ac5c1c Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Wed, 24 Dec 2025 21:57:46 -0500 Subject: [PATCH 1/2] [Rust] Add `FlowGraphLayout` and the accompanying APIs for custom flow graph layouts --- Cargo.lock | 47 +++++ Cargo.toml | 1 + rust/examples/flowgraph.rs | 4 +- .../flowgraph_layout/Cargo.toml | 13 ++ .../flowgraph_layout/README.md | 16 ++ .../plugin_examples/flowgraph_layout/build.rs | 25 +++ .../flowgraph_layout/src/lib.rs | 149 ++++++++++++++++ rust/src/flowgraph.rs | 79 ++++++++- rust/src/flowgraph/edge.rs | 2 + rust/src/flowgraph/layout.rs | 164 ++++++++++++++++++ rust/src/flowgraph/node.rs | 48 ++++- rust/src/function.rs | 18 ++ rust/tests/flowgraph_layout.rs | 46 +++++ 13 files changed, 605 insertions(+), 7 deletions(-) create mode 100644 rust/plugin_examples/flowgraph_layout/Cargo.toml create mode 100644 rust/plugin_examples/flowgraph_layout/README.md create mode 100644 rust/plugin_examples/flowgraph_layout/build.rs create mode 100644 rust/plugin_examples/flowgraph_layout/src/lib.rs create mode 100644 rust/src/flowgraph/layout.rs create mode 100644 rust/tests/flowgraph_layout.rs diff --git a/Cargo.lock b/Cargo.lock index ed74c1416c..b4df4bbf15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -924,6 +924,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "example_flowgraph_layout" +version = "0.1.0" +dependencies = [ + "binaryninja", + "binaryninjacore-sys", + "petgraph", + "rust-sugiyama", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -951,6 +961,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flatbuffers" version = "25.2.10" @@ -977,6 +993,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1163,6 +1185,9 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] [[package]] name = "heck" @@ -1927,6 +1952,18 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.4", + "indexmap", + "serde", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2276,6 +2313,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rust-sugiyama" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a81a3e22e7b3e9218ae4d1a3640a7e7ad142e120acd03e5e66d231d5861598b1" +dependencies = [ + "log", + "petgraph", +] + [[package]] name = "rustc-demangle" version = "0.1.25" diff --git a/Cargo.toml b/Cargo.toml index 5cf7ac75e5..6a7a0af1cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "arch/riscv", "arch/msp430", "rust/plugin_examples/data_renderer", + "rust/plugin_examples/flowgraph_layout", "view/minidump", "plugins/dwarf/dwarf_import", "plugins/dwarf/dwarf_import/demo", diff --git a/rust/examples/flowgraph.rs b/rust/examples/flowgraph.rs index cb03234e73..1bb54a2eb8 100644 --- a/rust/examples/flowgraph.rs +++ b/rust/examples/flowgraph.rs @@ -13,6 +13,7 @@ use binaryninja::{ disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind}, flowgraph::{EdgePenStyle, FlowGraph, ThemeColor}, }; +use std::time::Duration; pub struct GraphPrinter; @@ -134,8 +135,9 @@ fn main() { test_graph(); for func in bv.functions().iter().take(5) { - // TODO: Why are the nodes empty? Python its empty until its shown... let graph = func.create_graph(FunctionViewType::MediumLevelIL, None); + // It is important to call this, otherwise no nodes will be placed. + graph.request_layout_and_wait(Duration::from_secs(5)); let func_name = func.symbol().short_name(); let title = func_name.to_string_lossy(); bv.show_graph_report(&title, &graph); diff --git a/rust/plugin_examples/flowgraph_layout/Cargo.toml b/rust/plugin_examples/flowgraph_layout/Cargo.toml new file mode 100644 index 0000000000..b6371399e3 --- /dev/null +++ b/rust/plugin_examples/flowgraph_layout/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example_flowgraph_layout" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +binaryninjacore-sys = { path = "../../binaryninjacore-sys" } +binaryninja = { path = "../.." } +rust-sugiyama = "0.4.0" +petgraph = "0.8" \ No newline at end of file diff --git a/rust/plugin_examples/flowgraph_layout/README.md b/rust/plugin_examples/flowgraph_layout/README.md new file mode 100644 index 0000000000..c2ad0df942 --- /dev/null +++ b/rust/plugin_examples/flowgraph_layout/README.md @@ -0,0 +1,16 @@ +# FlowGraph Layout Example + +This example implements a simple flow graph layout using the rust crate `rust-sugiyama`. After building and placing this +in the plugin directory, override the default flow graph layout setting `rendering.graph.defaultLayout`. + +This example is complete _except_ for edge routing, we simply draw the edge from the outgoing nodes bottom to the +incoming nodes top, a real layout would route the edges around nodes to avoid overlaps and other rendering oddities. + +## Building + +```sh +# Build from the root directory (binaryninja-api) +cargo build -p example_flowgraph_layout +# Link binary on macOS +ln -sf $PWD/target/debug/libexample_data_renderer.dylib ~/Library/Application\ Support/Binary\ Ninja/plugins +``` diff --git a/rust/plugin_examples/flowgraph_layout/build.rs b/rust/plugin_examples/flowgraph_layout/build.rs new file mode 100644 index 0000000000..9006f16a69 --- /dev/null +++ b/rust/plugin_examples/flowgraph_layout/build.rs @@ -0,0 +1,25 @@ +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/plugin_examples/flowgraph_layout/src/lib.rs b/rust/plugin_examples/flowgraph_layout/src/lib.rs new file mode 100644 index 0000000000..4b568c48c6 --- /dev/null +++ b/rust/plugin_examples/flowgraph_layout/src/lib.rs @@ -0,0 +1,149 @@ +use binaryninja::flowgraph::edge::Point; +use binaryninja::flowgraph::layout::{register_flowgraph_layout, FlowGraphLayout}; +use binaryninja::flowgraph::{FlowGraph, FlowGraphNode}; +use binaryninja::rc::Ref; +use std::collections::HashMap; + +pub struct StableGraphBuilder; + +impl StableGraphBuilder { + pub fn new() -> Self { + Self {} + } + + pub fn build( + self, + nodes: &[FlowGraphNode], + ) -> petgraph::stable_graph::StableDiGraph, ()> { + let mut graph = petgraph::stable_graph::StableDiGraph::, ()>::new(); + let mut node_idx_map = HashMap::, petgraph::graph::NodeIndex>::new(); + for node in nodes { + let owned_node = node.to_owned(); + node_idx_map.insert(owned_node.clone(), graph.add_node(owned_node)); + } + for node in nodes { + let node_idx = node_idx_map.get(node).unwrap(); + for edge in &node.outgoing_edges() { + let target_node_idx = node_idx_map.get(&edge.target).unwrap(); + graph.add_edge(*node_idx, *target_node_idx, ()); + } + } + graph + } +} + +struct SugiyamaLayout; + +impl FlowGraphLayout for SugiyamaLayout { + fn layout(&self, graph: &FlowGraph, nodes: &[FlowGraphNode]) -> bool { + let mut config = rust_sugiyama::configure::Config::default(); + config.vertex_spacing = 5.0; + + let vertex_size = |_, node: &Ref| { + let (width, height) = node.size(); + (width as f64 * 1.2, height as f64) + }; + let pet_graph = StableGraphBuilder::new().build(nodes); + let layouts = rust_sugiyama::from_graph(&pet_graph, &vertex_size, &config); + + // Position graph nodes + for (nodes, _, _) in &layouts { + for (node_idx, (x, y)) in nodes { + let node = pet_graph.node_weight(*node_idx).unwrap(); + node.set_position(*x as i32, *y as i32); + } + } + + // Add edges to graph nodes + for (nodes, _, _) in &layouts { + for (node_idx, (x, y)) in nodes { + let node = pet_graph.node_weight(*node_idx).unwrap(); + let (width, height) = node.size(); + for (edge_idx, edge) in node.outgoing_edges().iter().enumerate() { + let from_point_x = x + (width as f64 / 2.0); + let from_point_y = y + height as f64; + let from_point = Point { + x: from_point_x as f32, + y: from_point_y as f32, + }; + let (target_node_x, target_node_y) = edge.target.position(); + let (target_node_width, _) = edge.target.size(); + let to_point_x = target_node_x as f64 + (target_node_width as f64 / 2.0); + let to_point_y = target_node_y; + let to_point = Point { + x: to_point_x as f32, + y: to_point_y as f32, + }; + // NOTE: This does not do proper routing, this will add edge points from the outgoing node + // to the target node, this will lead to lines overlapping nodes and other rendering oddities. + // The reason we do not do proper routing is because that is quite a bit more code with some + // dependence on a navigation algorithm like a-star. + node.set_outgoing_edge_points(edge_idx, &[from_point, to_point]); + } + } + } + + // Calculate graph size and node visibility + let mut min_x = f32::MAX; + let mut min_y = f32::MAX; + let mut max_x = f32::MIN; + let mut max_y = f32::MIN; + + for node in nodes { + let (node_x, node_y) = node.position(); + let (node_width, node_height) = node.size(); + + // Initialize per-node bounds based on the node's current box + let mut min_node_x = node_x; + let mut max_node_x = node_x + node_width; + let mut min_node_y = node_y; + let mut max_node_y = node_y + node_height; + + for edge in &node.outgoing_edges() { + for point in &edge.points { + let px = point.x; + let py = point.y; + + // Update Global Graph Bounds + min_x = min_x.min(px); + min_y = min_y.min(py); + max_x = max_x.max(px + 1.0); + max_y = max_y.max(py + 1.0); + + // Update Node Visibility Bounds + min_node_x = min_node_x.min(px as i32); + max_node_x = max_node_x.max(px as i32 + 1); + min_node_y = min_node_y.min(py as i32); + max_node_y = max_node_y.max(py as i32 + 1); + } + } + + node.set_visibility_region( + min_node_x, + min_node_y, + (max_node_x - min_node_x), + (max_node_y - min_node_y), + ); + } + + // Set final graph dimensions + if min_x != f32::MAX { + let (horiz_node_margin, vert_node_margin) = graph.node_margins(); + let final_graph_width = (max_x - min_x) as i32 + horiz_node_margin * 2; + let final_graph_height = (max_y - min_y) as i32 + vert_node_margin * 2; + graph.set_size(final_graph_width, final_graph_height); + } + + true + } +} + +/// # 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 { + // Register flow graph layout + register_flowgraph_layout("Sugiyama", SugiyamaLayout); + true +} diff --git a/rust/src/flowgraph.rs b/rust/src/flowgraph.rs index eab6e84d1b..927cfaba2e 100644 --- a/rust/src/flowgraph.rs +++ b/rust/src/flowgraph.rs @@ -14,18 +14,22 @@ //! Interfaces for creating and displaying pretty CFGs in Binary Ninja. -use binaryninjacore_sys::*; - use crate::high_level_il::HighLevelILFunction; use crate::low_level_il::LowLevelILRegularFunction; use crate::medium_level_il::MediumLevelILFunction; use crate::rc::*; use crate::render_layer::CoreRenderLayer; +use binaryninjacore_sys::*; +use std::ffi::c_void; +use std::ptr::NonNull; +use std::time::Duration; pub mod edge; +pub mod layout; pub mod node; use crate::binary_view::BinaryView; +use crate::flowgraph::layout::FlowGraphLayoutRequest; use crate::function::Function; use crate::string::IntoCStr; pub use edge::EdgeStyle; @@ -36,6 +40,7 @@ pub type EdgePenStyle = BNEdgePenStyle; pub type ThemeColor = BNThemeColor; pub type FlowGraphOption = BNFlowGraphOption; +#[repr(transparent)] #[derive(PartialEq, Eq, Hash)] pub struct FlowGraph { pub(crate) handle: *mut BNFlowGraph, @@ -50,10 +55,41 @@ impl FlowGraph { Ref::new(Self { handle: raw }) } + /// Create an empty flowgraph. + /// + /// If you instead want to create a flowgraph of a given [`Function`], use [`Function::create_graph`]. pub fn new() -> Ref { unsafe { FlowGraph::ref_from_raw(BNCreateFlowGraph()) } } + /// Requests the flowgraph to be laid out, positioning nodes and routing edges. + /// + /// This function returns immediately, with `on_complete` being called when the layout has been + /// completed, to wait for the request to be completed use [`FlowGraph::request_layout_and_wait`]. + pub fn request_layout(&self, on_complete: C) -> Ref { + let context = Box::into_raw(Box::new(on_complete)); + let request_raw_ptr = unsafe { + BNStartFlowGraphLayout(self.handle, context as *mut _, Some(cb_on_complete::)) + }; + let request_ptr = + NonNull::new(request_raw_ptr).expect("BNStartFlowGraphLayout returned null"); + unsafe { FlowGraphLayoutRequest::ref_from_raw(request_ptr) } + } + + /// Blocks until the flow graph layout is complete or until the `timeout` has elapsed, returning + /// `true` if the layout completed within the timeout, `false` otherwise. + /// + /// Use [`FlowGraph::request_layout`] instead if you want to provide a callback when the layout + /// has been completed and return immediately. + pub fn request_layout_and_wait(&self, timeout: Duration) -> bool { + let (tx, rx) = std::sync::mpsc::channel(); + // IMPORTANT: named `_request` to keep from dropping before function return. + let _request = self.request_layout(move || { + let _ = tx.send(()); + }); + rx.recv_timeout(timeout).is_ok() + } + pub fn has_updates(&self) -> bool { let query_mode = unsafe { BNFlowGraphUpdateQueryMode(self.handle) }; match query_mode { @@ -70,6 +106,10 @@ impl FlowGraph { Some(unsafe { FlowGraph::ref_from_raw(new_graph) }) } + /// Sends the [`FlowGraph`] to the interaction handlers to display. + /// + /// - On headless this is a no-op unless you register a [`crate::interaction::handler::InteractionHandler`]. + /// - On UI this will create a new tab to display the graph. pub fn show(&self, title: &str) { let raw_title = title.to_cstr(); match self.view() { @@ -82,14 +122,19 @@ impl FlowGraph { } } - /// Whether flow graph layout is complete. + /// Whether the flow graph layout is complete. pub fn is_layout_complete(&self) -> bool { unsafe { BNIsFlowGraphLayoutComplete(self.handle) } } + // TODO: A [`FlowGraphLayoutRequest::abort`] does not actually abort the layout, it sets a flag + // TODO: in the associated [`FlowGraph`], but we have no way to observe that flag. See the + // TODO: issue filed here: https://github.com/Vector35/binaryninja-api/issues/7826. + // pub fn is_aborted(&self) -> bool {} + pub fn nodes(&self) -> Array { let mut count: usize = 0; - let nodes_ptr = unsafe { BNGetFlowGraphNodes(self.handle, &mut count as *mut usize) }; + let nodes_ptr = unsafe { BNGetFlowGraphNodes(self.handle, &mut count) }; unsafe { Array::new(nodes_ptr, count, ()) } } @@ -181,6 +226,12 @@ impl FlowGraph { (width, height) } + /// Set the size of the graph. + pub fn set_size(&self, width: i32, height: i32) { + unsafe { BNFlowGraphSetWidth(self.handle, width) }; + unsafe { BNFlowGraphSetHeight(self.handle, height) }; + } + /// Returns the graph margins between nodes. pub fn node_margins(&self) -> (i32, i32) { let horizontal = unsafe { BNGetHorizontalFlowGraphNodeMargin(self.handle) }; @@ -193,14 +244,27 @@ impl FlowGraph { unsafe { BNSetFlowGraphNodeMargins(self.handle, horizontal, vertical) }; } + pub fn is_node_valid(&self, node: &FlowGraphNode) -> bool { + unsafe { BNIsNodeValidForFlowGraph(self.handle, node.handle) } + } + + /// Add a [`FlowGraphNode`] to the graph, returning its index. + /// + /// This only works before the flow graph layout is complete, inside [`layout::FlowGraphLayout::layout`]. pub fn append(&self, node: &FlowGraphNode) -> usize { unsafe { BNAddFlowGraphNode(self.handle, node.handle) } } + /// Replaces the node at the given index with the provided [`FlowGraphNode`]. + /// + /// This only works before the flow graph layout is complete, inside [`layout::FlowGraphLayout::layout`]. pub fn replace(&self, index: usize, node: &FlowGraphNode) { unsafe { BNReplaceFlowGraphNode(self.handle, index, node.handle) } } + /// Removes all nodes from the graph. + /// + /// This only works before the flow graph layout is complete, inside [`layout::FlowGraphLayout::layout`]. pub fn clear(&self) { unsafe { BNClearFlowGraphNodes(self.handle) } } @@ -254,3 +318,10 @@ impl ToOwned for FlowGraph { unsafe { RefCountable::inc_ref(self) } } } + +unsafe extern "C" fn cb_on_complete(ctxt: *mut c_void) { + // Take ownership of the ctxt so that we do not leak, we assume this callback to always + // be called so that the ctxt may be freed. + let ctxt: Box = unsafe { Box::from_raw(ctxt as *mut C) }; + ctxt(); +} diff --git a/rust/src/flowgraph/edge.rs b/rust/src/flowgraph/edge.rs index 38e0ae9ee5..a891026718 100644 --- a/rust/src/flowgraph/edge.rs +++ b/rust/src/flowgraph/edge.rs @@ -1,3 +1,5 @@ +//! Represents the connection between two [`FlowGraphNode`]s. + use crate::architecture::BranchType; use crate::flowgraph::node::FlowGraphNode; use crate::flowgraph::{EdgePenStyle, ThemeColor}; diff --git a/rust/src/flowgraph/layout.rs b/rust/src/flowgraph/layout.rs new file mode 100644 index 0000000000..cd71c492d2 --- /dev/null +++ b/rust/src/flowgraph/layout.rs @@ -0,0 +1,164 @@ +//! The [`FlowGraphLayout`] trait allows you to customize the layout of flow graphs. + +use crate::flowgraph::{FlowGraph, FlowGraphNode}; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable}; +use crate::string::{BnString, IntoCStr}; +use binaryninjacore_sys::*; +use std::ffi::c_void; +use std::ptr::NonNull; + +/// Registers a custom [`FlowGraphLayout`], this allows you to customize the layout of flow graphs. +pub fn register_flowgraph_layout( + name: &str, + custom: C, +) -> (&'static mut C, CoreFlowGraphLayout) { + let renderer = Box::leak(Box::new(custom)); + let mut callbacks = BNCustomFlowGraphLayout { + context: renderer as *mut _ as *mut c_void, + layout: Some(cb_layout::), + }; + let name_raw = name.to_cstr(); + let result = unsafe { BNRegisterFlowGraphLayout(name_raw.as_ptr(), &mut callbacks) }; + let core = unsafe { CoreFlowGraphLayout::from_raw(NonNull::new(result).unwrap()) }; + (renderer, core) +} + +/// The interface responsible for laying out a [`FlowGraph`]. +pub trait FlowGraphLayout: Sized + Sync + Send + 'static { + /// Perform the flow graph layout, returning `true` if successful. + /// + /// The implementation is responsible for doing four main things (usually in this order): + /// + /// 1. Adjusting `nodes` positions using [`FlowGraphNode::set_position`]. + /// 2. Setting the edge points (e.g. the lines between nodes) using [`FlowGraphNode::set_outgoing_edge_points`]. + /// 3. Setting the `nodes` visibility region using [`FlowGraphNode::set_visibility_region`]. + /// 4. Setting the size of the graph using [`FlowGraph::set_size`]. + fn layout(&self, graph: &FlowGraph, nodes: &[FlowGraphNode]) -> bool; +} + +pub struct CoreFlowGraphLayout { + pub(crate) handle: NonNull, +} + +impl CoreFlowGraphLayout { + pub(crate) unsafe fn from_raw(handle: NonNull) -> CoreFlowGraphLayout { + Self { handle } + } + + pub fn all() -> Array { + let mut count = 0; + let result = unsafe { BNGetFlowGraphLayouts(&mut count) }; + unsafe { Array::new(result, count, ()) } + } + + pub fn by_name(name: &str) -> Option { + let name_raw = name.to_cstr(); + let layout_ptr = unsafe { BNGetFlowGraphLayoutByName(name_raw.as_ptr()) }; + Some(unsafe { CoreFlowGraphLayout::from_raw(NonNull::new(layout_ptr)?) }) + } + + pub fn name(&self) -> String { + unsafe { BnString::into_string(BNGetFlowGraphLayoutName(self.handle.as_ptr())) } + } +} + +impl FlowGraphLayout for CoreFlowGraphLayout { + fn layout(&self, graph: &FlowGraph, nodes: &[FlowGraphNode]) -> bool { + // SAFETY: FlowGraphNode to *mut BNFlowGraphNode is safe (repr transparent) + unsafe { + BNFlowGraphLayoutLayout( + self.handle.as_ptr(), + graph.handle, + nodes.as_ptr() as *mut _, + nodes.len(), + ) + } + } +} + +impl CoreArrayProvider for CoreFlowGraphLayout { + type Raw = *mut BNFlowGraphLayout; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for CoreFlowGraphLayout { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeFlowGraphLayoutList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO: Because handle is a NonNull we should prob make Self::Raw that as well... + let handle = NonNull::new(*raw).unwrap(); + CoreFlowGraphLayout::from_raw(handle) + } +} + +unsafe impl Send for CoreFlowGraphLayout {} +unsafe impl Sync for CoreFlowGraphLayout {} + +/// Represents a queued flow graph layout request, given out by [`FlowGraph::request_layout`]. +pub struct FlowGraphLayoutRequest { + pub(crate) handle: NonNull, +} + +impl FlowGraphLayoutRequest { + pub(crate) unsafe fn ref_from_raw( + handle: NonNull, + ) -> Ref { + Ref::new(Self { handle }) + } + + /// The flow graph that this request is for. + pub fn graph(&self) -> Ref { + unsafe { + FlowGraph::ref_from_raw(BNGetGraphForFlowGraphLayoutRequest(self.handle.as_ptr())) + } + } + + /// Returns `true` if the layout request has completed. + pub fn is_complete(&self) -> bool { + unsafe { BNIsFlowGraphLayoutRequestComplete(self.handle.as_ptr()) } + } + + /// Removes the request from the flow graphs layout queue, and sets [`FlowGraph::is_layout_complete`] + pub fn abort(&self) { + unsafe { BNAbortFlowGraphLayoutRequest(self.handle.as_ptr()) } + } +} + +impl ToOwned for FlowGraphLayoutRequest { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for FlowGraphLayoutRequest { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: NonNull::new(BNNewFlowGraphLayoutRequestReference(handle.handle.as_ptr())) + .unwrap(), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeFlowGraphLayoutRequest(handle.handle.as_ptr()); + } +} + +unsafe extern "C" fn cb_layout( + ctxt: *mut c_void, + graph: *mut BNFlowGraph, + nodes: *mut *mut BNFlowGraphNode, + node_count: usize, +) -> bool { + let ctxt = ctxt as *mut C; + let nodes_slice = core::slice::from_raw_parts(nodes, node_count); + let nodes: Vec<_> = nodes_slice + .iter() + .map(|ptr| unsafe { FlowGraphNode::from_raw(*ptr) }) + .collect(); + (*ctxt).layout(&FlowGraph::from_raw(graph), &nodes) +} diff --git a/rust/src/flowgraph/node.rs b/rust/src/flowgraph/node.rs index 2823367092..22e8ad4d3d 100644 --- a/rust/src/flowgraph/node.rs +++ b/rust/src/flowgraph/node.rs @@ -1,13 +1,26 @@ +//! Represents a single node in a flow graph, typically backed by a [`BasicBlock`]. + use crate::architecture::BranchType; use crate::basic_block::{BasicBlock, BlockContext}; use crate::disassembly::DisassemblyTextLine; -use crate::flowgraph::edge::{EdgeStyle, FlowGraphEdge}; +use crate::flowgraph::edge::{EdgeStyle, FlowGraphEdge, Point}; use crate::flowgraph::FlowGraph; use crate::function::HighlightColor; use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable}; use binaryninjacore_sys::*; use std::fmt::{Debug, Formatter}; - +use std::hash::Hash; + +// Used for documentation purposes. +#[allow(unused)] +use crate::flowgraph::layout::FlowGraphLayout; + +/// The node of a flow graph containing lines of text tokens, and edges flowing into and out of it. +/// +/// A node can also be backed by a [`BasicBlock`], which makes the node function as a basic block node. +/// +/// A node is positioned absolutely within the flow graph and is typically positioned by a [`FlowGraphLayout`]. +#[repr(transparent)] #[derive(PartialEq, Eq, Hash)] pub struct FlowGraphNode { pub(crate) handle: *mut BNFlowGraphNode, @@ -69,6 +82,13 @@ impl FlowGraphNode { (pos_x, pos_y) } + /// Returns the size of the node in width, height form. + pub fn size(&self) -> (i32, i32) { + let w = unsafe { BNGetFlowGraphNodeWidth(self.handle) }; + let h = unsafe { BNGetFlowGraphNodeHeight(self.handle) }; + (w, h) + } + /// Sets the graph position of the node. pub fn set_position(&self, x: i32, y: i32) { unsafe { BNFlowGraphNodeSetX(self.handle, x) }; @@ -84,6 +104,7 @@ impl FlowGraphNode { unsafe { BNSetFlowGraphNodeHighlight(self.handle, highlight.into()) }; } + /// Edges flowing _into_ this node. pub fn incoming_edges(&self) -> Array { let mut count = 0; let result = unsafe { BNGetFlowGraphNodeIncomingEdges(self.handle, &mut count) }; @@ -91,6 +112,7 @@ impl FlowGraphNode { unsafe { Array::new(result, count, ()) } } + /// Edges flowing _out of_ this node. pub fn outgoing_edges(&self) -> Array { let mut count = 0; let result = unsafe { BNGetFlowGraphNodeOutgoingEdges(self.handle, &mut count) }; @@ -109,6 +131,28 @@ impl FlowGraphNode { BNAddFlowGraphNodeOutgoingEdge(self.handle, type_, target.handle, edge_style.into()) } } + + /// Sets the outgoing edge points for `edge_idx`. + /// + /// This is typically called when laying out a [`FlowGraph`] using [`FlowGraphLayout`], without + /// calling this for all given node edges in a graph; the nodes will be rendered without any + /// edges between them, as edge routing is _not_ automatic. + pub fn set_outgoing_edge_points(&self, edge_idx: usize, points: &[Point]) { + let mut points_raw: Vec = points.iter().map(|p| (*p).into()).collect(); + unsafe { + BNFlowGraphNodeSetOutgoingEdgePoints( + self.handle, + edge_idx, + points_raw.as_mut_ptr(), + points.len(), + ) + }; + } + + // TODO: Document that this is for flow graph layout + pub fn set_visibility_region(&self, x: i32, y: i32, w: i32, h: i32) { + unsafe { BNFlowGraphNodeSetVisibilityRegion(self.handle, x, y, w, h) }; + } } impl Debug for FlowGraphNode { diff --git a/rust/src/function.rs b/rust/src/function.rs index c19e2107a2..8c1e188f97 100644 --- a/rust/src/function.rs +++ b/rust/src/function.rs @@ -2560,6 +2560,24 @@ impl Function { (!graph.is_null()).then(|| unsafe { FlowGraph::ref_from_raw(graph) }) } + /// Create a [`FlowGraph`] of the function at the specified `view_type`. + /// + /// This will **NOT** populate the [`FlowGraph::nodes`], to populate the nodes and position + /// them, you must call [`FlowGraph::request_layout`] or [`FlowGraph::request_layout_and_wait`]. + /// + /// ```no_run + /// # use std::time::Duration; + /// # use binaryninja::function::{Function, FunctionViewType}; + /// # let func: Function = todo!() + /// let graph = func.create_graph(FunctionViewType::MediumLevelIL, None); + /// assert!(graph.request_layout_and_wait(Duration::from_secs(5)), "Took too long to create graph"); + /// assert!(graph.is_layout_complete(), "Should always be true if request_layout_and_wait returned true"); + /// for node in &graph.nodes() { + /// for line in &node.lines() { + /// println!("{}", line); + /// } + ///} + /// ``` pub fn create_graph( &self, view_type: FunctionViewType, diff --git a/rust/tests/flowgraph_layout.rs b/rust/tests/flowgraph_layout.rs new file mode 100644 index 0000000000..4431819619 --- /dev/null +++ b/rust/tests/flowgraph_layout.rs @@ -0,0 +1,46 @@ +use binaryninja::binary_view::BinaryViewExt; +use binaryninja::flowgraph::layout::{register_flowgraph_layout, FlowGraphLayout}; +use binaryninja::flowgraph::{FlowGraph, FlowGraphNode}; +use binaryninja::function::FunctionViewType; +use binaryninja::headless::Session; +use std::path::PathBuf; +use std::time::Duration; + +struct FlowGraphLayoutTest {} + +impl FlowGraphLayout for FlowGraphLayoutTest { + fn layout(&self, graph: &FlowGraph, nodes: &[FlowGraphNode]) -> bool { + todo!() + } +} + +#[test] +fn test_flowgraph_layout() { + let _session = Session::new().expect("Failed to initialize session"); + let out_dir = env!("OUT_DIR").parse::().unwrap(); + let view = binaryninja::load(out_dir.join("atox.obj")).expect("Failed to create view"); + + let function = view.entry_point_function().expect("Entry point exists"); + let graph = function.create_graph(FunctionViewType::MediumLevelIL, None); + assert!( + graph.request_layout_and_wait(Duration::from_secs(5)), + "Took too long to create graph" + ); + assert!( + graph.is_layout_complete(), + "Should always be true if request_layout_and_wait returned true" + ); + assert!(graph.nodes().len() > 0, "Graph should have nodes"); + + let (_, layout) = register_flowgraph_layout("test", FlowGraphLayoutTest {}); + let other_function = view + .function_at(&view.default_platform().unwrap(), 0x3b440) + .unwrap(); + let graph = other_function.create_graph_immediate(FunctionViewType::MediumLevelIL, None); + let nodes = graph + .nodes() + .iter() + .map(|n| n.to_owned()) + .collect::>(); + layout.layout(&graph, nodes); +} From 11f3f49c8f06a646854a7dc53d9f0d69c3128dea Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Sun, 26 Apr 2026 17:08:43 -0700 Subject: [PATCH 2/2] [Rust] Update misc license metadata --- about.toml | 3 ++- plugins/bntl_utils/cli/Cargo.toml | 2 ++ plugins/warp/examples/headless/Cargo.toml | 2 ++ rust/plugin_examples/data_renderer/Cargo.toml | 1 + rust/plugin_examples/flowgraph_layout/Cargo.toml | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/about.toml b/about.toml index a0954a66a3..d3f5b285d6 100644 --- a/about.toml +++ b/about.toml @@ -9,5 +9,6 @@ accepted = [ "LicenseRef-scancode-google-patent-license-fuchsia", "MPL-2.0", "LicenseRef-scancode-unknown-license-reference", - "BSD-2-Clause" + "BSD-2-Clause", + "Zlib" ] \ No newline at end of file diff --git a/plugins/bntl_utils/cli/Cargo.toml b/plugins/bntl_utils/cli/Cargo.toml index 863b950d36..eb210cc0e7 100644 --- a/plugins/bntl_utils/cli/Cargo.toml +++ b/plugins/bntl_utils/cli/Cargo.toml @@ -2,6 +2,8 @@ name = "bntl_cli" version = "0.1.0" edition = "2024" +license = "Apache-2.0" +publish = false [dependencies] binaryninja.workspace = true diff --git a/plugins/warp/examples/headless/Cargo.toml b/plugins/warp/examples/headless/Cargo.toml index a32796beb5..b656a1a1a8 100644 --- a/plugins/warp/examples/headless/Cargo.toml +++ b/plugins/warp/examples/headless/Cargo.toml @@ -2,6 +2,8 @@ name = "warp_headless" version = "0.1.0" edition = "2021" +license = "Apache-2.0" +publish = false [dependencies] clap = { version = "4.5", features = ["derive"] } diff --git a/rust/plugin_examples/data_renderer/Cargo.toml b/rust/plugin_examples/data_renderer/Cargo.toml index 2887b738b6..0ad09f986f 100644 --- a/rust/plugin_examples/data_renderer/Cargo.toml +++ b/rust/plugin_examples/data_renderer/Cargo.toml @@ -2,6 +2,7 @@ name = "example_data_renderer" version = "0.1.0" edition = "2021" +license = "Apache-2.0" publish = false [lib] diff --git a/rust/plugin_examples/flowgraph_layout/Cargo.toml b/rust/plugin_examples/flowgraph_layout/Cargo.toml index b6371399e3..99e8a2283c 100644 --- a/rust/plugin_examples/flowgraph_layout/Cargo.toml +++ b/rust/plugin_examples/flowgraph_layout/Cargo.toml @@ -2,6 +2,8 @@ name = "example_flowgraph_layout" version = "0.1.0" edition = "2021" +license = "Apache-2.0" +publish = false [lib] crate-type = ["cdylib"]