Skip to content

Commit 02609d8

Browse files
committed
Improve VarNode and Pcode formatting and add display tests
1 parent 8bba334 commit 02609d8

File tree

3 files changed

+90
-9
lines changed

3 files changed

+90
-9
lines changed

jingle/src/display.rs

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::varnode::{ResolvedIndirectVarNode, ResolvedVarnode};
22
use jingle_sleigh::{
3-
GeneralizedVarNode, IndirectVarNode, Instruction, PcodeOperation, SleighArchInfo, VarNode,
3+
GeneralizedVarNode, IndirectVarNode, Instruction, PcodeOperation, SleighArchInfo, SpaceType,
4+
VarNode,
45
};
56
use std::fmt::{Display, Formatter};
67
use z3::ast::Ast;
@@ -51,13 +52,17 @@ impl JingleDisplay<ResolvedIndirectVarNode> {
5152
impl JingleDisplayable for VarNode {
5253
fn fmt_jingle(&self, f: &mut Formatter<'_>, ctx: &SleighArchInfo) -> std::fmt::Result {
5354
if self.space_index == VarNode::CONST_SPACE_INDEX {
54-
write!(f, "{:x}:{:x}", self.offset, self.size)
55+
write!(f, "{:#x}:{}", self.offset, self.size)
5556
} else if let Some(name) = ctx.register_name(self) {
5657
write!(f, "{name}")
58+
} else if let Some(SpaceType::IPTR_INTERNAL) =
59+
ctx.get_space(self.space_index).map(|s| s._type)
60+
{
61+
write!(f, "$U{:x}:{}", self.offset, self.size)
5762
} else {
5863
write!(
5964
f,
60-
"{}[{:x}]:{:x}",
65+
"[{}]{:#x}:{}",
6166
ctx.get_space(self.space_index).ok_or(std::fmt::Error)?.name,
6267
self.offset,
6368
self.size
@@ -70,12 +75,11 @@ impl JingleDisplayable for IndirectVarNode {
7075
fn fmt_jingle(&self, f: &mut Formatter<'_>, ctx: &SleighArchInfo) -> std::fmt::Result {
7176
write!(
7277
f,
73-
"*({}[{}]:{})",
78+
"{}({})",
7479
ctx.get_space(self.pointer_space_index)
7580
.ok_or(std::fmt::Error)?
7681
.name,
77-
self.pointer_location,
78-
self.access_size_bytes
82+
self.pointer_location.display(ctx)
7983
)
8084
}
8185
}
@@ -93,12 +97,11 @@ impl JingleDisplayable for ResolvedIndirectVarNode {
9397
fn fmt_jingle(&self, f: &mut Formatter<'_>, ctx: &SleighArchInfo) -> std::fmt::Result {
9498
write!(
9599
f,
96-
"{}[{}]:{}",
100+
"{}({})",
97101
ctx.get_space(self.pointer_space_idx)
98102
.ok_or(std::fmt::Error)?
99103
.name,
100104
self.pointer.simplify(),
101-
self.access_size_bytes
102105
)
103106
}
104107
}
@@ -136,3 +139,80 @@ impl JingleDisplayable for Instruction {
136139
Ok(())
137140
}
138141
}
142+
143+
#[cfg(test)]
144+
mod tests {
145+
use super::*;
146+
use jingle_sleigh::context::SleighContextBuilder;
147+
use jingle_sleigh::{PcodeOperation, VarNode};
148+
149+
fn make_sleigh() -> jingle_sleigh::context::SleighContext {
150+
// Mirrors other tests in the repo which expect a local Ghidra checkout at this path.
151+
let ctx_builder =
152+
SleighContextBuilder::load_ghidra_installation("/Applications/ghidra").unwrap();
153+
ctx_builder.build("x86:LE:64:default").unwrap()
154+
}
155+
156+
#[test]
157+
fn test_varnode_display_cases() {
158+
let sleigh = make_sleigh();
159+
let info = sleigh.arch_info().clone();
160+
161+
// const varnode should display as "<hex_offset>:<size>"
162+
let const_vn = VarNode {
163+
space_index: VarNode::CONST_SPACE_INDEX,
164+
offset: 0x42,
165+
size: 1,
166+
};
167+
assert_eq!(format!("{}", const_vn.display(&info)), "0x42:1");
168+
169+
// registers: if registers exist, pretty display should equal the register name
170+
let regs: Vec<_> = sleigh.arch_info().registers().collect();
171+
if !regs.is_empty() {
172+
let (vn, name) = regs[0].clone();
173+
assert_eq!(format!("{}", vn.display(&info)), name);
174+
}
175+
176+
// other space: we expect a bracketed form like "[<space>]offset:size" or internal $U...
177+
let other = VarNode {
178+
space_index: 1,
179+
offset: 0x10,
180+
size: 4,
181+
};
182+
let s = format!("{}", other.display(&info));
183+
// be permissive: ensure it contains the size and either bracket or unique prefix
184+
assert!(s.contains(":4"));
185+
}
186+
187+
#[test]
188+
fn test_pcode_display_and_round_trip_copy() {
189+
let sleigh = make_sleigh();
190+
let info = sleigh.arch_info().clone();
191+
192+
let output = VarNode {
193+
space_index: 4,
194+
offset: 0x20,
195+
size: 4,
196+
};
197+
let input = VarNode {
198+
space_index: VarNode::CONST_SPACE_INDEX,
199+
offset: 0x1,
200+
size: 4,
201+
};
202+
203+
let op = PcodeOperation::Copy {
204+
input: input.clone(),
205+
output: output.clone(),
206+
};
207+
208+
// display formatting contains the opcode name and operands
209+
let s = format!("{}", op.display(&info));
210+
assert_eq!(s, "ESP = COPY 0x1:4");
211+
// Try to round-trip: render to textual pcode and parse with SleighContext.parse_pcode_listing
212+
// Use the same textual format that `display` produces for varnodes.
213+
214+
let parsed = sleigh.parse_pcode_listing(s).unwrap();
215+
assert_eq!(parsed.len(), 1);
216+
assert_eq!(parsed[0], op);
217+
}
218+
}

jingle_sleigh/src/context/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl Default for ModelingBehavior {
6161

6262
/// A naive representation of the effects of a function
6363
#[derive(Debug, Clone, Default, Hash, PartialEq, Eq, Serialize, Deserialize)]
64-
#[pyclass]
64+
#[cfg_attr(feature = "pyo3", pyclass)]
6565
pub struct CallInfo {
6666
pub args: Vec<VarNode>,
6767
pub outputs: Option<Vec<VarNode>>,

jingle_sleigh/src/pcode/parse/helpers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ pub fn parse_varnode_location(pair: Pair<Rule>) -> Result<(String, u64), JingleS
111111
let space = pairs[0].as_str().to_string();
112112
let offset = pairs[1].as_str();
113113
let radix = if offset.starts_with("0x") { 16 } else { 10 };
114+
let offset = offset.strip_prefix("0x").unwrap_or(offset);
114115
let offset = u64::from_str_radix(offset, radix).unwrap();
115116
return Ok((space, offset));
116117
}

0 commit comments

Comments
 (0)