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 } ;
49use jingle:: analysis:: direct_valuation:: {
510 DirectValuationAnalysis , DirectValuationState , VarnodeValue ,
611} ;
712use jingle:: analysis:: pcode_store:: PcodeStore ;
813use jingle:: analysis:: { Analysis , RunnableAnalysis } ;
14+ use jingle:: display:: JingleDisplayable ;
915use jingle:: modeling:: machine:: cpu:: concrete:: ConcretePcodeAddress ;
1016use jingle_sleigh:: VarNode ;
1117use jingle_sleigh:: context:: image:: gimli:: load_with_gimli;
1218use std:: collections:: HashMap ;
1319use std:: env;
1420
21+ /// Addresses of various test functions in the example binary.
1522const FUNC_LINE : u64 = 0x100000460 ;
1623const FUNC_BRANCH : u64 = 0x100000480 ;
1724const FUNC_SWITCH : u64 = 0x1000004a0 ;
@@ -20,16 +27,17 @@ const FUNC_NESTED: u64 = 0x100000588;
2027const FUNC_GOTO : u64 = 0x100000610 ;
2128
2229fn 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 ! ( "\n CFG 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 ! ( "\n Leaf 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 ! ( "\n Analysis 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}
0 commit comments