Skip to content

Commit 3379254

Browse files
authored
feat: Allow Location Lattice Elements in CFGs (#132)
* feat: Allow CFGs to have lattice types as locations, not just concrete addresses * initial steps * Just playing around * refactor * more stuff * fixes * Some fixes, but something is broken in direct location and it's not evaluating all paths * refactor and fix location analysis * remove file saving * auto-impls of cfgstate * more fixes, finally looking good * clippy * fix * Clippy * example fixes * Fix compound merge stuff * clippy * Get rid of old example * Turn down logging and add padding * some display tweaks * in-progress * various cfg fixes * valuation tweak * fmt
1 parent f790462 commit 3379254

File tree

32 files changed

+1552
-742
lines changed

32 files changed

+1552
-742
lines changed

jingle/examples/location.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
#![allow(unused)]
22

3-
use jingle::analysis::bounded_visit::BoundedStepLocationAnalysis;
3+
use jingle::analysis::bounded_branch::BoundedBranchAnalysis;
4+
use jingle::analysis::cpa::RunnableConfigurableProgramAnalysis;
5+
use jingle::analysis::cpa::reducer::CfgReducer;
6+
use jingle::analysis::cpa::residue::Residue;
7+
use jingle::analysis::cpa::state::LocationState;
8+
use jingle::analysis::direct_location::{CallBehavior, DirectLocationAnalysis};
49
use jingle::analysis::{Analysis, RunnableAnalysis};
510
use jingle::modeling::machine::cpu::concrete::ConcretePcodeAddress;
611
use jingle_sleigh::context::image::gimli::load_with_gimli;
12+
use petgraph::dot::Dot;
713
use std::env;
814

915
const FUNC_LINE: u64 = 0x100000460;
@@ -19,13 +25,25 @@ fn main() {
1925
.join("Documents/test_funcs/build/example");
2026
let loaded = load_with_gimli(bin_path, "/Applications/ghidra").unwrap();
2127

22-
let mut direct = BoundedStepLocationAnalysis::new(&loaded, 20);
23-
let _states = direct.run(&loaded, ConcretePcodeAddress::from(FUNC_NESTED));
24-
let pcode_graph = direct.take_cfg();
28+
let mut direct = DirectLocationAnalysis::new(CallBehavior::Branch);
29+
// Run the analysis. For a `ResidueWrapper` with `CfgReducer`, `run` returns
30+
// the built `PcodeCfg` as the reducer output, so capture it here.
31+
let pcode_graph = direct.run(&loaded, ConcretePcodeAddress::from(FUNC_NESTED));
2532
let addrs = pcode_graph.nodes().collect::<Vec<_>>();
26-
for addr in addrs {
27-
println!("{:x}", addr);
33+
for node in addrs {
34+
// `node` is a tuple like `(DirectLocationState, BoundedBranchState)`.
35+
// Call `get_location` on the first element (the location-carrying component)
36+
// to avoid requiring trait-method resolution on the tuple itself.
37+
match node.get_location() {
38+
Some(a) => println!("{:x}", a),
39+
None => println!("(no location)"),
40+
}
2841
}
2942
let leaf = pcode_graph.leaf_nodes().collect::<Vec<_>>();
30-
println!("{:x?}", leaf);
43+
for node in leaf {
44+
match node.get_location() {
45+
Some(a) => println!("leaf: {:x}", a),
46+
None => println!("leaf: (no location)"),
47+
}
48+
}
3149
}

jingle/examples/stack_offset.rs

Lines changed: 112 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
#![allow(unused)]
22

3-
use jingle::analysis::direct_location::DirectLocationAnalysis;
3+
use jingle::analysis::cpa::RunnableConfigurableProgramAnalysis;
4+
use jingle::analysis::cpa::lattice::pcode::PcodeAddressLattice;
5+
use jingle::analysis::cpa::reducer::CfgReducer;
6+
use jingle::analysis::cpa::residue::Residue;
7+
use jingle::analysis::cpa::state::LocationState;
8+
use jingle::analysis::direct_location::{CallBehavior, DirectLocationAnalysis};
49
use jingle::analysis::direct_valuation::{
510
DirectValuationAnalysis, DirectValuationState, VarnodeValue,
611
};
712
use jingle::analysis::pcode_store::PcodeStore;
813
use jingle::analysis::{Analysis, RunnableAnalysis};
14+
use jingle::display::JingleDisplayable;
915
use jingle::modeling::machine::cpu::concrete::ConcretePcodeAddress;
1016
use jingle_sleigh::VarNode;
1117
use jingle_sleigh::context::image::gimli::load_with_gimli;
1218
use std::collections::HashMap;
1319
use std::env;
1420

21+
/// Addresses of various test functions in the example binary.
1522
const FUNC_LINE: u64 = 0x100000460;
1623
const FUNC_BRANCH: u64 = 0x100000480;
1724
const FUNC_SWITCH: u64 = 0x1000004a0;
@@ -20,16 +27,17 @@ const FUNC_NESTED: u64 = 0x100000588;
2027
const FUNC_GOTO: u64 = 0x100000610;
2128

2229
fn main() {
23-
// Initialize tracing with TRACE level
30+
// Initialize tracing for debug output
2431
tracing_subscriber::fmt()
25-
.with_max_level(tracing::Level::DEBUG)
32+
.with_max_level(tracing::Level::INFO)
2633
.with_target(false)
2734
.with_thread_ids(false)
2835
.with_line_number(true)
2936
.init();
3037

3138
tracing::info!("Starting stack offset analysis using DirectValuationAnalysis");
3239

40+
// Load binary via gimli-backed image context (adjust paths to your setup)
3341
let bin_path = env::home_dir()
3442
.unwrap()
3543
.join("Documents/test_funcs/build/example");
@@ -39,56 +47,50 @@ fn main() {
3947

4048
// Create the stack pointer varnode (RSP on x86-64)
4149
let stack_pointer = VarNode {
42-
space_index: 4, // Register space
43-
offset: 8, // RSP offset on x86-64
50+
space_index: 4, // Register space index for registers (depends on sleigh description)
51+
offset: 8, // RSP offset in the register space for this target
4452
size: 8, // 8 bytes for 64-bit
4553
};
4654

47-
// Create a compound analysis: DirectLocationAnalysis + DirectValuationAnalysis
48-
// DirectValuationAnalysis will track the stack pointer as an Entry value
49-
let location_analysis = DirectLocationAnalysis::new(&loaded);
50-
let valuation_analysis = DirectValuationAnalysis::with_entry_varnode(
51-
loaded.arch_info().clone(),
52-
stack_pointer.clone(),
53-
);
55+
// Build a compound analysis: DirectLocationAnalysis (left) + DirectValuationAnalysis (right).
56+
// Wrap the compound with a CfgReducer so `run` returns the constructed CFG.
57+
let location_analysis = DirectLocationAnalysis::new(CallBehavior::Branch);
58+
let valuation_analysis = DirectValuationAnalysis::new(loaded.arch_info().clone());
5459

55-
let mut compound_analysis = (location_analysis, valuation_analysis);
60+
// The tuple implements Analysis via the compound machinery; wrap it with the CfgReducer
61+
let mut compound_with_cfg =
62+
(location_analysis, valuation_analysis).with_residue(CfgReducer::new());
5663

5764
tracing::info!("Starting analysis run at address 0x{:x}", FUNC_NESTED);
5865

59-
// Run the compound analysis - construct the compound initial state explicitly
60-
// (new Analysis API requires passing a value convertible into the CPA `State`)
61-
62-
let compound_states = compound_analysis.run(&loaded, ConcretePcodeAddress::from(FUNC_NESTED));
63-
64-
tracing::info!("Analysis completed with {} states", compound_states.len());
65-
66-
// Extract the CFG from the DirectLocationAnalysis (left side of compound)
67-
let cfg = compound_analysis.0.take_cfg();
68-
69-
// Extract valuation information from the compound states
70-
let mut stack_offsets = HashMap::new();
71-
let mut direct_valuations = HashMap::new();
72-
73-
for state in &compound_states {
74-
// Extract location from the outermost left (PcodeAddressLattice)
75-
if let jingle::analysis::cpa::lattice::flat::FlatLattice::Value(addr) = &state.0.inner() {
76-
// state.right is DirectValuationState
77-
// Extract stack pointer offset if available
78-
if let Some(sp_value) = state.1.get_value(&stack_pointer) {
79-
stack_offsets.insert(*addr, sp_value.clone());
66+
// Run the analysis. The Residue/CfgReducer final output is a `PcodeCfg<(DirectLocationState, DirectValuationState)>`
67+
let cfg = compound_with_cfg.run(&loaded, ConcretePcodeAddress::from(FUNC_NESTED));
68+
69+
// We'll collect valuation info keyed by concrete addresses encountered in the CFG.
70+
let mut stack_offsets: HashMap<ConcretePcodeAddress, VarnodeValue> = HashMap::new();
71+
let mut direct_valuations: HashMap<ConcretePcodeAddress, DirectValuationState> = HashMap::new();
72+
73+
// `cfg.nodes()` yields `&N` where N = (DirectLocationState, DirectValuationState).
74+
// Use `cloned()` to get owned tuples so we can inspect and store values.
75+
for node in cfg.nodes().cloned() {
76+
// Extract the concrete program location (if any) from the left component.
77+
if let Some(addr) = node.0.get_location() {
78+
// Extract stack pointer info from the DirectValuationState (right component).
79+
if let Some(sp_value) = node.1.get_value(&stack_pointer) {
80+
stack_offsets.insert(addr, sp_value.clone());
8081
}
81-
direct_valuations.insert(*addr, state.1.clone());
82+
direct_valuations.insert(addr, node.1.clone());
8283
}
8384
}
8485

85-
// Print results
86+
// Print summary header
8687
println!("Stack Offset Analysis Results using DirectValuationAnalysis:");
8788
println!("=============================================================\n");
8889

89-
// Collect and sort locations for consistent output
90-
let mut locations = cfg.nodes().collect::<Vec<_>>();
91-
locations.sort();
90+
// List CFG nodes (program locations)
91+
let mut locations: Vec<ConcretePcodeAddress> =
92+
cfg.nodes().filter_map(|n| n.get_location()).collect();
93+
locations.sort_by_key(|a| *a);
9294

9395
println!("CFG nodes (program locations): {}", locations.len());
9496
for loc in &locations {
@@ -103,11 +105,11 @@ fn main() {
103105
})
104106
.unwrap_or_default();
105107

106-
// Show a sample of tracked constant values at this location
107108
let val_count = direct_valuations
108109
.get(loc)
109110
.map(|v: &DirectValuationState| v.written_locations().len())
110111
.unwrap_or(0);
112+
111113
let val_info = if val_count > 0 {
112114
format!(" [tracked varnodes: {}]", val_count)
113115
} else {
@@ -117,25 +119,44 @@ fn main() {
117119
println!(" 0x{:x}{}{}", loc, offset_info, val_info);
118120
}
119121

122+
// Print CFG edges: show edges between concrete locations when available
120123
println!("\nCFG edges:");
121-
let nodes = cfg.nodes().collect::<Vec<_>>();
122-
for node in nodes {
124+
// Collect references to nodes so we can call `cfg.successors(...)`
125+
let node_refs = cfg.nodes().collect::<Vec<_>>();
126+
for node in node_refs {
127+
let origin_str = node
128+
.get_location()
129+
.map(|a| format!("0x{:x}", a))
130+
.unwrap_or_else(|| "(no loc)".to_string());
131+
123132
if let Some(successors) = cfg.successors(node) {
124133
for succ in successors {
134+
let succ_str = succ
135+
.get_location()
136+
.map(|a| format!("0x{:x}", a))
137+
.unwrap_or_else(|| "(no loc)".to_string());
138+
125139
let op_str = cfg
126140
.get_op_at(node)
127141
.map(|o: &jingle_sleigh::PcodeOperation| format!("{}", o))
128142
.unwrap_or_else(|| "no-op".to_string());
129-
println!(" 0x{:x} -> 0x{:x}: {}", node, succ, op_str);
143+
144+
println!(" {} -> {}: {}", origin_str, succ_str, op_str);
130145
}
131146
}
132147
}
133148

149+
// Leaf nodes (nodes with no outgoing edges)
134150
let leaf_nodes = cfg.leaf_nodes().collect::<Vec<_>>();
135151
println!("\nLeaf nodes: {}", leaf_nodes.len());
136152
for leaf in &leaf_nodes {
137-
let offset_info = stack_offsets
138-
.get(leaf)
153+
let leaf_loc = leaf
154+
.get_location()
155+
.map(|a| format!("0x{:x}", a))
156+
.unwrap_or_else(|| "(no loc)".to_string());
157+
let offset_info = leaf
158+
.get_location()
159+
.and_then(|a| stack_offsets.get(&a))
139160
.map(|value| match value {
140161
VarnodeValue::Entry(_) => " [stack: Entry (0)]".to_string(),
141162
VarnodeValue::Offset(_, off) => format!(" [stack: {:+}]", off),
@@ -144,13 +165,49 @@ fn main() {
144165
_ => " [stack: unknown]".to_string(),
145166
})
146167
.unwrap_or_default();
147-
println!(" 0x{:x}{}", leaf, offset_info);
168+
169+
println!(" {}{}", leaf_loc, offset_info);
170+
171+
// Print detailed DirectValuationState for this leaf (if available)
172+
if let Some(addr) = leaf.get_location() {
173+
if let Some(state) = direct_valuations.get(&addr) {
174+
let count = state.written_locations().len();
175+
println!(" DirectValuationState: {} tracked varnode(s)", count);
176+
177+
if count == 0 {
178+
println!(" (no written locations)");
179+
} else {
180+
for (vn, val) in state.written_locations() {
181+
println!(
182+
" {} = {}",
183+
vn.display(loaded.arch_info()),
184+
val.display(loaded.arch_info())
185+
);
186+
}
187+
}
188+
} else {
189+
println!(" (no DirectValuationState recorded for this location)");
190+
}
191+
} else {
192+
println!(
193+
" Computed loc: {}",
194+
leaf.0.inner().display(loaded.arch_info())
195+
);
196+
println!(" Valuations:");
197+
for ele in leaf.1.written_locations() {
198+
println!(
199+
" {} = {}",
200+
ele.0.display(loaded.arch_info()),
201+
ele.1.display(loaded.arch_info())
202+
)
203+
}
204+
}
148205
}
149206

207+
// Final summary statistics
150208
println!("\nAnalysis Summary:");
151209
println!(" Total program locations: {}", stack_offsets.len());
152210

153-
// Count how many locations have concrete stack offsets
154211
let concrete_offsets = stack_offsets
155212
.values()
156213
.filter(|v| matches!(v, VarnodeValue::Entry(_) | VarnodeValue::Offset(_, _)))
@@ -165,9 +222,12 @@ fn main() {
165222
.sum::<usize>()
166223
);
167224

168-
println!("\n DirectValuationAnalysis acts as a lightweight pcode interpreter that tracks");
169-
println!(" all directly-written varnodes. The stack pointer is initialized as an Entry");
170-
println!(" value, and the analysis tracks how it changes through the program as");
171-
println!(" Offset(sp, delta) values. This replaces the need for a separate");
172-
println!(" StackOffsetAnalysis.");
225+
println!("\n Notes:");
226+
println!(" - `DirectValuationAnalysis` acts as a lightweight p-code interpreter that tracks");
227+
println!(" directly-written varnodes. The stack pointer can be seeded as an Entry value");
228+
println!(
229+
" and the analysis will track `Offset(sp, delta)` values as the program modifies it."
230+
);
231+
println!(" - This example demonstrates how to run a compound analysis and extract both the");
232+
println!(" constructed CFG (via `CfgReducer`) and per-location valuation information.");
173233
}

jingle/examples/unwind.rs

Lines changed: 0 additions & 76 deletions
This file was deleted.

0 commit comments

Comments
 (0)