diff --git a/Cargo.lock b/Cargo.lock index 92577f86c8..d68fde7901 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,7 +691,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -847,6 +847,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" @@ -874,6 +884,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" @@ -900,6 +916,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 = "fuchsia-cprng" version = "0.1.1" @@ -1075,6 +1097,9 @@ name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] [[package]] name = "heck" @@ -1671,6 +1696,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" @@ -1977,6 +2014,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 7061c201be..04f3a98c07 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 75c854a761..96baf948a0 100644 --- a/rust/examples/flowgraph.rs +++ b/rust/examples/flowgraph.rs @@ -12,6 +12,7 @@ use binaryninja::{ disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind}, flowgraph::{EdgePenStyle, FlowGraph, ThemeColor}, }; +use std::time::Duration; pub struct GraphPrinter; @@ -131,8 +132,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 02a79fa317..248115161c 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/layout.rs b/rust/src/flowgraph/layout.rs new file mode 100644 index 0000000000..b9f8d945fc --- /dev/null +++ b/rust/src/flowgraph/layout.rs @@ -0,0 +1,159 @@ +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) +} + +pub trait FlowGraphLayout: Sized + Sync + Send + 'static { + /// Perform 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 {} + +// TODO: This needs documentation +pub struct FlowGraphLayoutRequest { + pub(crate) handle: NonNull, +} + +impl FlowGraphLayoutRequest { + pub(crate) unsafe fn ref_from_raw( + handle: NonNull, + ) -> Ref { + Ref::new(Self { handle }) + } + + pub fn graph(&self) -> Ref { + unsafe { + FlowGraph::ref_from_raw(BNGetGraphForFlowGraphLayoutRequest(self.handle.as_ptr())) + } + } + + 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..ceccd5b516 100644 --- a/rust/src/flowgraph/node.rs +++ b/rust/src/flowgraph/node.rs @@ -1,13 +1,15 @@ 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; +#[repr(transparent)] #[derive(PartialEq, Eq, Hash)] pub struct FlowGraphNode { pub(crate) handle: *mut BNFlowGraphNode, @@ -69,6 +71,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 +93,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 +101,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 +120,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 [`super::layout::FlowGraphLayout`], + /// without calling this for all given node edges in a graph, the nodes will be rendered without + /// any edges between them, routing is _not_ automatic. + pub fn set_outgoing_edge_points(&self, edge_idx: usize, points: &[Point]) { + let mut points_raw: Vec = points.into_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 06da39dc80..a417a974a9 100644 --- a/rust/src/function.rs +++ b/rust/src/function.rs @@ -2559,6 +2559,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); +}