1#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ParseError(pub String);
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum Varnode {
28 Register(String),
30 Unique {
32 offset: u64,
34 size: u8,
36 },
37 Const {
40 value: u64,
42 size: Option<u8>,
44 },
45 Ram(Box<Varnode>),
48 CodeAddr(u64),
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct PcodeOp {
55 pub out: Option<Varnode>,
57 pub opcode: String,
59 pub inputs: Vec<Varnode>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct PcodeInsn {
66 pub address: u64,
68 pub ops: Vec<PcodeOp>,
70}
71
72pub fn parse_pcode(text: &str) -> Result<Vec<PcodeInsn>, ParseError> {
80 let mut out: Vec<PcodeInsn> = Vec::new();
81 for raw in text.lines() {
82 let line = raw.trim_end();
83 if line.trim().is_empty() {
84 continue;
85 }
86 if line.starts_with(char::is_whitespace) {
87 let op = parse_op_line(line.trim())?;
88 let Some(current) = out.last_mut() else {
89 return Err(ParseError(format!(
90 "op line before any instruction header: {line:?}"
91 )));
92 };
93 current.ops.push(op);
94 } else {
95 let Some(addr) = parse_header_address(line) else {
97 continue;
101 };
102 out.push(PcodeInsn {
103 address: addr,
104 ops: Vec::new(),
105 });
106 }
107 }
108 Ok(out)
109}
110
111fn parse_header_address(line: &str) -> Option<u64> {
112 let (addr_tok, _rest) = line.split_once(':')?;
113 let hex = addr_tok.trim().strip_prefix("0x")?;
114 u64::from_str_radix(hex, 16).ok()
115}
116
117fn parse_op_line(line: &str) -> Result<PcodeOp, ParseError> {
118 if let Some(rest) = line.strip_prefix("STORE ") {
120 let (addr_part, value_part) = rest
121 .split_once(" = ")
122 .ok_or_else(|| ParseError(format!("malformed STORE: {line:?}")))?;
123 return Ok(PcodeOp {
124 out: None,
125 opcode: "STORE".to_string(),
126 inputs: vec![
127 parse_varnode(addr_part.trim())?,
128 parse_varnode(value_part.trim())?,
129 ],
130 });
131 }
132
133 if let Some((lhs, rhs)) = split_assignment(line) {
135 let out = parse_varnode(lhs.trim())?;
136 let mut toks = rhs.trim().splitn(2, char::is_whitespace);
137 let opcode = toks
138 .next()
139 .ok_or_else(|| ParseError(format!("missing opcode: {line:?}")))?
140 .to_string();
141 let inputs = match toks.next() {
142 Some(args) => parse_input_list(args.trim())?,
143 None => Vec::new(),
144 };
145 return Ok(PcodeOp {
146 out: Some(out),
147 opcode,
148 inputs,
149 });
150 }
151
152 let mut toks = line.splitn(2, char::is_whitespace);
155 let opcode = toks
156 .next()
157 .ok_or_else(|| ParseError(format!("empty op line: {line:?}")))?
158 .to_string();
159 if !opcode.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
160 return Err(ParseError(format!("unrecognised op line: {line:?}")));
161 }
162 let inputs = match toks.next() {
163 Some(args) => parse_input_list(args.trim())?,
164 None => Vec::new(),
165 };
166 Ok(PcodeOp {
167 out: None,
168 opcode,
169 inputs,
170 })
171}
172
173fn split_assignment(line: &str) -> Option<(&str, &str)> {
177 let bytes = line.as_bytes();
178 let mut depth: i32 = 0;
179 let mut i = 0;
180 while i + 2 < bytes.len() {
181 match bytes[i] {
182 b'(' | b'[' => depth += 1,
183 b')' | b']' => depth -= 1,
184 b' ' if depth == 0 && bytes[i + 1] == b'=' && bytes[i + 2] == b' ' => {
185 return Some((&line[..i], &line[i + 3..]));
186 }
187 _ => {}
188 }
189 i += 1;
190 }
191 None
192}
193
194fn parse_input_list(s: &str) -> Result<Vec<Varnode>, ParseError> {
196 let mut parts: Vec<&str> = Vec::new();
197 let bytes = s.as_bytes();
198 let mut depth: i32 = 0;
199 let mut start = 0;
200 for (i, &b) in bytes.iter().enumerate() {
201 match b {
202 b'(' | b'[' => depth += 1,
203 b')' | b']' => depth -= 1,
204 b',' if depth == 0 => {
205 parts.push(&s[start..i]);
206 start = i + 1;
207 }
208 _ => {}
209 }
210 }
211 parts.push(&s[start..]);
212 parts
213 .into_iter()
214 .map(str::trim)
215 .filter(|p| !p.is_empty())
216 .map(parse_varnode)
217 .collect()
218}
219
220fn parse_varnode(tok: &str) -> Result<Varnode, ParseError> {
221 let tok = tok.trim();
222 if let Some(inner) = tok.strip_prefix("ram[").and_then(|s| s.strip_suffix(']')) {
223 return Ok(Varnode::Ram(Box::new(parse_varnode(inner)?)));
224 }
225 if let Some(inner) = tok
226 .strip_prefix("qword_ptr(")
227 .and_then(|s| s.strip_suffix(')'))
228 {
229 let hex = inner.trim().strip_prefix("0x").unwrap_or(inner.trim());
230 let v = u64::from_str_radix(hex, 16)
231 .map_err(|_| ParseError(format!("bad code addr: {tok:?}")))?;
232 return Ok(Varnode::CodeAddr(v));
233 }
234 if let Some(inner) = tok.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
235 let mut f = inner.split(',');
237 let space = f.next().unwrap_or("").trim();
238 let off_tok = f.next().unwrap_or("").trim();
239 let size_tok = f.next().unwrap_or("").trim();
240 let offset =
241 parse_u64(off_tok).ok_or_else(|| ParseError(format!("bad varnode offset: {tok:?}")))?;
242 let size: u8 = size_tok
243 .parse()
244 .map_err(|_| ParseError(format!("bad varnode size: {tok:?}")))?;
245 return match space {
246 "unique" => Ok(Varnode::Unique { offset, size }),
247 "const" => Ok(Varnode::Const {
248 value: offset,
249 size: Some(size),
250 }),
251 _ => Ok(Varnode::Register(format!("{space}_{offset:#x}"))),
252 };
253 }
254 if let Some(v) = parse_u64(tok) {
255 return Ok(Varnode::Const {
256 value: v,
257 size: None,
258 });
259 }
260 if !tok.is_empty()
261 && tok
262 .chars()
263 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.')
264 {
265 return Ok(Varnode::Register(tok.to_string()));
266 }
267 Err(ParseError(format!("unrecognised varnode: {tok:?}")))
268}
269
270fn parse_u64(tok: &str) -> Option<u64> {
271 if let Some(hex) = tok.strip_prefix("0x") {
272 u64::from_str_radix(hex, 16).ok()
273 } else {
274 tok.parse::<u64>().ok()
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
281
282 use super::*;
283
284 const FIXTURE: &str = "\
2850x00000328: sub sp, sp, #0x10
286 sp = INT_SUB sp, 0x10
2870x00000338: mul w8, w8, w9
288 (unique,0x2ae80,4) = INT_MULT w8, w9
289 x8 = INT_ZEXT (unique,0x2ae80,4)
2900x00000344: and w8, w8, #0x1
291 (unique,0x12180,4) = INT_AND w8, 0x1
292 x8 = INT_ZEXT (unique,0x12180,4)
2930x0000034c: b.ne 0x360
294 (unique,0xa00,1) = BOOL_NEGATE ZR
295 CBRANCH qword_ptr(0x00000360), (unique,0xa00,1)
2960x00000360: ldr w8, [sp, #0x8]
297 (unique,0x6000,8) = INT_ADD sp, 0x8
298 (unique,0x24700,4) = LOAD ram[(unique,0x6000,8)]
299 x8 = INT_ZEXT (unique,0x24700,4)
3000x00000370: str w8, [sp, #0xc]
301 STORE ram[(unique,0x6000,8)] = w8";
302
303 #[test]
304 fn test_parse_groups_ops_under_instruction_headers() {
305 let p = parse_pcode(FIXTURE).unwrap();
306 assert_eq!(p.len(), 6);
307 assert_eq!(p[0].address, 0x328);
308 assert_eq!(p[0].ops.len(), 1);
309 assert_eq!(p[2].address, 0x344);
310 assert_eq!(p[2].ops.len(), 2);
311 }
312
313 #[test]
314 fn test_parse_assignment_op_varnodes() {
315 let p = parse_pcode(FIXTURE).unwrap();
316 let op = &p[1].ops[0]; assert_eq!(
318 op.out,
319 Some(Varnode::Unique {
320 offset: 0x2ae80,
321 size: 4
322 })
323 );
324 assert_eq!(op.opcode, "INT_MULT");
325 assert_eq!(
326 op.inputs,
327 vec![
328 Varnode::Register("w8".into()),
329 Varnode::Register("w9".into())
330 ]
331 );
332 }
333
334 #[test]
335 fn test_parse_const_input() {
336 let p = parse_pcode(FIXTURE).unwrap();
337 let op = &p[2].ops[0]; assert_eq!(
339 op.inputs[1],
340 Varnode::Const {
341 value: 1,
342 size: None
343 }
344 );
345 }
346
347 #[test]
348 fn test_parse_cbranch_and_store_have_no_output() {
349 let p = parse_pcode(FIXTURE).unwrap();
350 let cbr = &p[3].ops[1];
351 assert_eq!(cbr.opcode, "CBRANCH");
352 assert_eq!(cbr.out, None);
353 assert_eq!(cbr.inputs[0], Varnode::CodeAddr(0x360));
354
355 let store = p[5].ops.last().unwrap();
356 assert_eq!(store.opcode, "STORE");
357 assert_eq!(store.out, None);
358 assert!(matches!(store.inputs[0], Varnode::Ram(_)));
359 assert_eq!(store.inputs[1], Varnode::Register("w8".into()));
360 }
361
362 #[test]
363 fn test_parse_load_ram_address() {
364 let p = parse_pcode(FIXTURE).unwrap();
365 let ld = &p[4].ops[1]; assert_eq!(ld.opcode, "LOAD");
367 assert_eq!(
368 ld.inputs[0],
369 Varnode::Ram(Box::new(Varnode::Unique {
370 offset: 0x6000,
371 size: 8
372 }))
373 );
374 }
375
376 #[test]
377 fn test_parse_rejects_orphan_op_line() {
378 let err = parse_pcode(" sp = INT_SUB sp, 0x10").unwrap_err();
379 assert!(err.0.contains("before any instruction header"));
380 }
381}