Skip to content

Commit 7b3bf51

Browse files
committed
test
1 parent ba13f6e commit 7b3bf51

File tree

12 files changed

+570
-7
lines changed

12 files changed

+570
-7
lines changed

Cargo.lock

Lines changed: 48 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"arch/riscv",
1010
"arch/msp430",
1111
"rust/plugin_examples/data_renderer",
12+
"rust/plugin_examples/flowgraph_layout",
1213
"view/minidump",
1314
"plugins/dwarf/dwarf_import",
1415
"plugins/dwarf/dwarf_import/demo",

rust/examples/flowgraph.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use binaryninja::{
1212
disassembly::{DisassemblyTextLine, InstructionTextToken, InstructionTextTokenKind},
1313
flowgraph::{EdgePenStyle, FlowGraph, ThemeColor},
1414
};
15+
use std::time::Duration;
1516

1617
pub struct GraphPrinter;
1718

@@ -131,8 +132,9 @@ fn main() {
131132
test_graph();
132133

133134
for func in bv.functions().iter().take(5) {
134-
// TODO: Why are the nodes empty? Python its empty until its shown...
135135
let graph = func.create_graph(FunctionViewType::MediumLevelIL, None);
136+
// It is important to call this, otherwise no nodes will be placed.
137+
graph.request_layout_and_wait(Duration::from_secs(5));
136138
let func_name = func.symbol().short_name();
137139
let title = func_name.to_string_lossy();
138140
bv.show_graph_report(&title, &graph);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "example_flowgraph_layout"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
binaryninjacore-sys = { path = "../../binaryninjacore-sys" }
11+
binaryninja = { path = "../.." }
12+
rust-sugiyama = "0.4.0"
13+
petgraph = "0.8"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# FlowGraph Layout Example
2+
3+
This example implements a simple data renderer for the Mach-O load command LC_UUID.
4+
You can try the renderer by loading the `/bin/cat` binary from macOS.
5+
6+
We're implementing a functionality similar to the one described in the Python data renderer blog post:
7+
https://binary.ninja/2024/04/08/customizing-data-display.html.
8+
9+
## Building
10+
11+
```sh
12+
# Build from the root directory (binaryninja-api)
13+
cargo build -p example_flowgraph_layout
14+
# Link binary on macOS
15+
ln -sf $PWD/target/debug/libexample_data_renderer.dylib ~/Library/Application\ Support/Binary\ Ninja/plugins
16+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
fn main() {
2+
let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH")
3+
.expect("DEP_BINARYNINJACORE_PATH not specified");
4+
5+
println!("cargo::rustc-link-lib=dylib=binaryninjacore");
6+
println!("cargo::rustc-link-search={}", link_path.to_str().unwrap());
7+
8+
#[cfg(target_os = "linux")]
9+
{
10+
println!(
11+
"cargo::rustc-link-arg=-Wl,-rpath,{0},-L{0}",
12+
link_path.to_string_lossy()
13+
);
14+
}
15+
16+
#[cfg(target_os = "macos")]
17+
{
18+
let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME not set");
19+
let lib_name = crate_name.replace('-', "_");
20+
println!(
21+
"cargo::rustc-link-arg=-Wl,-install_name,@rpath/lib{}.dylib",
22+
lib_name
23+
);
24+
}
25+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use binaryninja::flowgraph::edge::Point;
2+
use binaryninja::flowgraph::layout::{register_flowgraph_layout, FlowGraphLayout};
3+
use binaryninja::flowgraph::{FlowGraph, FlowGraphNode};
4+
use binaryninja::rc::Ref;
5+
use petgraph::prelude::EdgeRef;
6+
use std::cmp::min;
7+
use std::collections::{HashMap, HashSet};
8+
9+
pub struct StableGraphBuilder;
10+
11+
impl StableGraphBuilder {
12+
pub fn new() -> Self {
13+
Self {}
14+
}
15+
16+
pub fn build(
17+
self,
18+
nodes: &[FlowGraphNode],
19+
) -> petgraph::stable_graph::StableDiGraph<Ref<FlowGraphNode>, ()> {
20+
let mut graph = petgraph::stable_graph::StableDiGraph::<Ref<FlowGraphNode>, ()>::new();
21+
let mut node_idx_map = HashMap::<Ref<FlowGraphNode>, petgraph::graph::NodeIndex>::new();
22+
for node in nodes {
23+
let owned_node = node.to_owned();
24+
node_idx_map.insert(owned_node.clone(), graph.add_node(owned_node));
25+
}
26+
for node in nodes {
27+
let node_idx = node_idx_map.get(node).unwrap();
28+
for edge in &node.outgoing_edges() {
29+
let target_node_idx = node_idx_map.get(&edge.target).unwrap();
30+
graph.add_edge(*node_idx, *target_node_idx, ());
31+
}
32+
}
33+
graph
34+
}
35+
}
36+
37+
struct SugiyamaLayout;
38+
39+
impl FlowGraphLayout for SugiyamaLayout {
40+
fn layout(&self, graph: &FlowGraph, nodes: &[FlowGraphNode]) -> bool {
41+
let mut config = rust_sugiyama::configure::Config::default();
42+
config.vertex_spacing = 5.0;
43+
44+
let vertex_size = |_, node: &Ref<FlowGraphNode>| {
45+
let (width, height) = node.size();
46+
(width as f64 * 1.2, height as f64)
47+
};
48+
let pet_graph = StableGraphBuilder::new().build(nodes);
49+
let layouts = rust_sugiyama::from_graph(&pet_graph, &vertex_size, &config);
50+
51+
// Position graph nodes
52+
for (nodes, _, _) in &layouts {
53+
for (node_idx, (x, y)) in nodes {
54+
let node = pet_graph.node_weight(*node_idx).unwrap();
55+
node.set_position(*x as i32, *y as i32);
56+
}
57+
}
58+
59+
// Add edges to graph nodes
60+
for (nodes, _, _) in &layouts {
61+
for (node_idx, (x, y)) in nodes {
62+
let node = pet_graph.node_weight(*node_idx).unwrap();
63+
let (width, height) = node.size();
64+
for (edge_idx, edge) in node.outgoing_edges().iter().enumerate() {
65+
let from_point_x = x + (width as f64 / 2.0);
66+
let from_point_y = y + height as f64;
67+
let from_point = Point {
68+
x: from_point_x as f32,
69+
y: from_point_y as f32,
70+
};
71+
let (target_node_x, target_node_y) = edge.target.position();
72+
let (target_node_width, _) = edge.target.size();
73+
let to_point_x = target_node_x as f64 + (target_node_width as f64 / 2.0);
74+
let to_point_y = target_node_y;
75+
let to_point = Point {
76+
x: to_point_x as f32,
77+
y: to_point_y as f32,
78+
};
79+
node.set_outgoing_edge_points(edge_idx, &[from_point, to_point]);
80+
}
81+
}
82+
}
83+
84+
// Calculate graph size and node visibility
85+
let mut min_x = f32::MAX;
86+
let mut min_y = f32::MAX;
87+
let mut max_x = f32::MIN;
88+
let mut max_y = f32::MIN;
89+
90+
for node in nodes {
91+
let (node_x, node_y) = node.position();
92+
let (node_width, node_height) = node.size();
93+
94+
// Initialize per-node bounds based on the node's current box
95+
let mut min_node_x = node_x;
96+
let mut max_node_x = node_x + node_width;
97+
let mut min_node_y = node_y;
98+
let mut max_node_y = node_y + node_height;
99+
100+
for edge in &node.outgoing_edges() {
101+
for point in &edge.points {
102+
let px = point.x;
103+
let py = point.y;
104+
105+
// Update Global Graph Bounds
106+
min_x = min_x.min(px);
107+
min_y = min_y.min(py);
108+
max_x = max_x.max(px + 1.0);
109+
max_y = max_y.max(py + 1.0);
110+
111+
// Update Node Visibility Bounds
112+
min_node_x = min_node_x.min(px as i32);
113+
max_node_x = max_node_x.max(px as i32 + 1);
114+
min_node_y = min_node_y.min(py as i32);
115+
max_node_y = max_node_y.max(py as i32 + 1);
116+
}
117+
}
118+
119+
node.set_visibility_region(
120+
min_node_x,
121+
min_node_y,
122+
(max_node_x - min_node_x),
123+
(max_node_y - min_node_y),
124+
);
125+
}
126+
127+
// Set final graph dimensions
128+
if min_x != f32::MAX {
129+
let (horiz_node_margin, vert_node_margin) = graph.node_margins();
130+
let final_graph_width = (max_x - min_x) as i32 + horiz_node_margin * 2;
131+
let final_graph_height = (max_y - min_y) as i32 + vert_node_margin * 2;
132+
graph.set_size(final_graph_width, final_graph_height);
133+
}
134+
135+
true
136+
}
137+
}
138+
139+
/// # Safety
140+
/// This function is called from Binary Ninja once to initialize the plugin.
141+
#[allow(non_snake_case)]
142+
#[unsafe(no_mangle)]
143+
pub unsafe extern "C" fn CorePluginInit() -> bool {
144+
// Register flow graph layout
145+
register_flowgraph_layout("Sugiyama", SugiyamaLayout);
146+
true
147+
}

0 commit comments

Comments
 (0)