1use std::collections::BTreeMap;
17
18use r2smt_common::Arch;
19use r2smt_ir::expr::{Expr, Var};
20use r2smt_ir::stmt::IrStmt;
21
22use crate::parse::{ParseError, PcodeOp, Varnode, parse_pcode};
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum PcodeError {
27 Parse(String),
29 UnsupportedOpcode(String),
31 BadVarnode(String),
33 Arity(String),
35}
36
37impl From<ParseError> for PcodeError {
38 fn from(e: ParseError) -> Self {
39 Self::Parse(e.0)
40 }
41}
42
43#[derive(Debug, Clone)]
46pub struct PcodeLift {
47 pub statements: Vec<IrStmt>,
49}
50
51pub fn lift_pcode(text: &str, arch: Arch) -> Result<PcodeLift, PcodeError> {
58 let insns = parse_pcode(text)?;
59 let mut m = Machine::new(arch);
60 for insn in &insns {
61 for op in &insn.ops {
62 m.step(op)?;
63 }
64 }
65 Ok(PcodeLift {
66 statements: m.statements,
67 })
68}
69
70struct Machine {
71 arch: Arch,
72 widths: BTreeMap<String, u16>,
75 statements: Vec<IrStmt>,
76}
77
78impl Machine {
79 fn new(arch: Arch) -> Self {
80 Self {
81 arch,
82 widths: BTreeMap::new(),
83 statements: Vec::new(),
84 }
85 }
86
87 fn step(&mut self, op: &PcodeOp) -> Result<(), PcodeError> {
88 match op.opcode.as_str() {
89 "STORE" => self.lift_store(op),
90 "BRANCH" | "CBRANCH" | "BRANCHIND" | "CALL" | "CALLIND" | "CALLOTHER" | "RETURN" => {
94 Ok(())
95 }
96 _ => self.lift_defining(op),
97 }
98 }
99
100 fn lift_store(&mut self, op: &PcodeOp) -> Result<(), PcodeError> {
101 let [addr, value] = op.inputs.as_slice() else {
102 return Err(PcodeError::Arity(format!("STORE inputs: {:?}", op.inputs)));
103 };
104 let addr_expr = self.read_mem_addr(addr)?;
105 let val_expr = self.read(value)?;
106 let bits = self.var_width(value);
107 self.statements.push(IrStmt::StoreMem {
108 address: addr_expr,
109 value: val_expr,
110 bits,
111 });
112 Ok(())
113 }
114
115 fn lift_defining(&mut self, op: &PcodeOp) -> Result<(), PcodeError> {
116 let Some(out) = &op.out else {
117 return Err(PcodeError::Arity(format!(
118 "{} expects an output",
119 op.opcode
120 )));
121 };
122
123 if op.opcode == "LOAD" {
124 let [src] = op.inputs.as_slice() else {
125 return Err(PcodeError::Arity(format!("LOAD inputs: {:?}", op.inputs)));
126 };
127 let addr = self.read_mem_addr(src)?;
128 let (dst, bits) = self.define(out, varnode_bits(out, self.arch));
129 self.statements.push(IrStmt::LoadMem {
130 dst,
131 address: addr,
132 bits,
133 });
134 return Ok(());
135 }
136
137 let out_bits = varnode_bits(out, self.arch);
138 let expr = self.eval(&op.opcode, &op.inputs, out_bits)?;
139 let (dst, _) = self.define(out, expr_bits(&expr, out_bits));
140 self.statements.push(IrStmt::Assign { dst, src: expr });
141 Ok(())
142 }
143
144 fn eval(
146 &mut self,
147 opcode: &str,
148 inputs: &[Varnode],
149 out_bits: u16,
150 ) -> Result<Expr, PcodeError> {
151 let bin = |m: &mut Self| -> Result<(Expr, Expr), PcodeError> {
152 let [a, b] = inputs else {
153 return Err(PcodeError::Arity(format!("{opcode} needs 2 inputs")));
154 };
155 let pb = m.ptr_bits();
164 let (wa, wb) = (m.operand_bits(a), m.operand_bits(b));
165 Ok((
166 m.read_operand(a, wb.unwrap_or(pb))?,
167 m.read_operand(b, wa.unwrap_or(pb))?,
168 ))
169 };
170 let un = |m: &mut Self| -> Result<Expr, PcodeError> {
171 let [a] = inputs else {
172 return Err(PcodeError::Arity(format!("{opcode} needs 1 input")));
173 };
174 m.read(a)
175 };
176 let all_ones: u128 = if out_bits >= 128 {
177 u128::MAX
178 } else {
179 (1u128 << out_bits) - 1
180 };
181
182 match opcode {
183 "COPY" => un(self),
184 "INT_ADD" => bin(self).map(|(a, b)| Expr::add(a, b)),
185 "INT_SUB" => bin(self).map(|(a, b)| Expr::sub(a, b)),
186 "INT_MULT" => bin(self).map(|(a, b)| Expr::mul(a, b)),
187 "INT_AND" => bin(self).map(|(a, b)| Expr::bv_and(a, b)),
188 "INT_OR" => bin(self).map(|(a, b)| Expr::bv_or(a, b)),
189 "INT_XOR" => bin(self).map(|(a, b)| Expr::bv_xor(a, b)),
190 "INT_LEFT" => bin(self).map(|(a, b)| Expr::shl(a, b)),
191 "INT_RIGHT" => bin(self).map(|(a, b)| Expr::lshr(a, b)),
192 "INT_SRIGHT" => bin(self).map(|(a, b)| Expr::ashr(a, b)),
193 "INT_NEGATE" => un(self).map(|a| Expr::bv_xor(a, Expr::konst(all_ones, out_bits))),
194 "INT_2COMP" => un(self).map(|a| Expr::sub(Expr::konst(0, out_bits), a)),
195 "INT_ZEXT" => un(self).map(|a| Expr::zero_ext(a, out_bits)),
196 "INT_SEXT" => un(self).map(|a| Expr::sign_ext(a, out_bits)),
197 "INT_EQUAL" => bin(self).map(|(a, b)| Expr::eq(a, b)),
198 "INT_NOTEQUAL" => bin(self).map(|(a, b)| Expr::ne(a, b)),
199 "INT_LESS" => bin(self).map(|(a, b)| Expr::ult(a, b)),
200 "INT_LESSEQUAL" => bin(self).map(|(a, b)| Expr::ule(a, b)),
201 "INT_SLESS" => bin(self).map(|(a, b)| Expr::slt(a, b)),
202 "INT_SLESSEQUAL" => bin(self).map(|(a, b)| Expr::sle(a, b)),
203 "BOOL_NEGATE" => un(self).map(|a| Expr::Ite {
204 cond: Box::new(Expr::eq(a, Expr::konst(0, 1))),
205 then_expr: Box::new(Expr::konst(1, 1)),
206 else_expr: Box::new(Expr::konst(0, 1)),
207 }),
208 "BOOL_AND" => bin(self).map(|(a, b)| Expr::bv_and(a, b)),
209 "BOOL_OR" => bin(self).map(|(a, b)| Expr::bv_or(a, b)),
210 "BOOL_XOR" => bin(self).map(|(a, b)| Expr::bv_xor(a, b)),
211 "SUBPIECE" => {
212 let [a, off] = inputs else {
213 return Err(PcodeError::Arity("SUBPIECE needs 2 inputs".into()));
214 };
215 let Varnode::Const {
216 value: byte_off, ..
217 } = off
218 else {
219 return Err(PcodeError::BadVarnode(format!(
220 "SUBPIECE offset must be const: {off:?}"
221 )));
222 };
223 let src = self.read(a)?;
224 let lo = u16::try_from(byte_off.saturating_mul(8))
225 .map_err(|_| PcodeError::BadVarnode("SUBPIECE offset too large".into()))?;
226 let hi = lo.saturating_add(out_bits.saturating_sub(1));
227 Ok(Expr::extract(src, hi, lo))
228 }
229 other => Err(PcodeError::UnsupportedOpcode(other.to_string())),
230 }
231 }
232
233 fn operand_bits(&self, vn: &Varnode) -> Option<u16> {
237 match vn {
238 Varnode::Const { size: None, .. } => None,
239 _ => Some(varnode_bits(vn, self.arch)),
240 }
241 }
242
243 fn read_operand(&mut self, vn: &Varnode, sizeless_const_bits: u16) -> Result<Expr, PcodeError> {
246 if let Varnode::Const { value, size: None } = vn {
247 return Ok(Expr::konst(u128::from(*value), sizeless_const_bits));
248 }
249 self.read(vn)
250 }
251
252 fn read(&mut self, vn: &Varnode) -> Result<Expr, PcodeError> {
254 match vn {
255 Varnode::Const { value, size } => {
256 let bits = size.map_or(self.ptr_bits(), |s| u16::from(s).saturating_mul(8));
257 Ok(Expr::konst(u128::from(*value), bits))
258 }
259 Varnode::Register(_) | Varnode::Unique { .. } => {
260 let name = Self::var_name(vn);
261 let bits = self.var_width(vn);
262 Ok(Expr::Var(Var::new(name, bits)))
263 }
264 Varnode::Ram(_) | Varnode::CodeAddr(_) => Err(PcodeError::BadVarnode(format!(
265 "value position cannot be {vn:?}"
266 ))),
267 }
268 }
269
270 fn read_mem_addr(&mut self, vn: &Varnode) -> Result<Expr, PcodeError> {
271 match vn {
272 Varnode::Ram(inner) => self.read(inner),
273 Varnode::Register(_) | Varnode::Unique { .. } => self.read(vn),
274 _ => Err(PcodeError::BadVarnode(format!("bad mem address: {vn:?}"))),
275 }
276 }
277
278 fn define(&mut self, out: &Varnode, bits: u16) -> (Var, u16) {
280 let name = Self::var_name(out);
281 self.widths.insert(name.clone(), bits);
282 (Var::new(name, bits), bits)
283 }
284
285 fn var_width(&self, vn: &Varnode) -> u16 {
286 let name = Self::var_name(vn);
287 if let Some(w) = self.widths.get(&name) {
288 return *w;
289 }
290 varnode_bits(vn, self.arch)
291 }
292
293 fn var_name(vn: &Varnode) -> String {
297 match vn {
298 Varnode::Unique { offset, size } => format!("u_{offset:x}_{size}"),
299 Varnode::Register(r) => map_register(r),
300 other => format!("?{other:?}"),
301 }
302 }
303
304 fn ptr_bits(&self) -> u16 {
305 match self.arch {
306 Arch::X86 | Arch::Arm => 32,
307 _ => 64,
308 }
309 }
310}
311
312fn map_register(r: &str) -> String {
314 match r {
315 "ZR" | "tmpZR" => "ZF".to_string(),
317 "NG" | "tmpNG" => "pc_ng".to_string(),
320 "CY" | "tmpCY" => "pc_cy".to_string(),
321 "OV" | "tmpOV" => "pc_ov".to_string(),
322 _ => r.to_string(),
329 }
330}
331
332fn varnode_bits(vn: &Varnode, arch: Arch) -> u16 {
334 match vn {
335 Varnode::Unique { size, .. } => u16::from(*size).saturating_mul(8).max(1),
336 Varnode::Const { size, .. } => size.map_or(64, |s| u16::from(s).saturating_mul(8)).max(1),
337 Varnode::Register(r) => register_bits(r, arch),
338 Varnode::Ram(_) | Varnode::CodeAddr(_) => 64,
339 }
340}
341
342fn register_bits(r: &str, arch: Arch) -> u16 {
343 let ptr = arch.pointer_bits();
351 match r {
352 "ZR" | "tmpZR" | "NG" | "tmpNG" | "CY" | "tmpCY" | "OV" | "tmpOV" => 1,
353 "sp" | "lr" | "fp" | "pc" => ptr,
354 _ if r.starts_with('x') && r[1..].chars().all(|c| c.is_ascii_digit()) => 64,
355 _ if r.starts_with('w') && r[1..].chars().all(|c| c.is_ascii_digit()) => 32,
356 _ if r.starts_with('r') && r[1..].chars().all(|c| c.is_ascii_digit()) => ptr,
357 _ => ptr,
358 }
359}
360
361fn expr_bits(expr: &Expr, fallback: u16) -> u16 {
362 match expr {
363 Expr::Const { bits, .. } => *bits,
364 Expr::Var(v) => v.bits,
365 Expr::ZeroExtend { to_bits, .. } | Expr::SignExtend { to_bits, .. } => *to_bits,
366 Expr::Eq(..)
367 | Expr::Ne(..)
368 | Expr::Ult(..)
369 | Expr::Ule(..)
370 | Expr::Slt(..)
371 | Expr::Sle(..) => 1,
372 Expr::Ite { then_expr, .. } => expr_bits(then_expr, fallback),
373 Expr::Extract { hi, lo, .. } => hi.saturating_sub(*lo).saturating_add(1),
374 _ => fallback,
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
381
382 use super::*;
383
384 #[test]
385 fn test_lift_int_arith_chain_to_assigns() {
386 let txt = "\
3870x100: mul w8, w8, w9
388 (unique,0x2ae80,4) = INT_MULT w8, w9
389 x8 = INT_ZEXT (unique,0x2ae80,4)";
390 let lift = lift_pcode(txt, Arch::Aarch64).unwrap();
391 assert_eq!(lift.statements.len(), 2);
392 match &lift.statements[0] {
393 IrStmt::Assign { dst, src } => {
394 assert_eq!(dst.name, "u_2ae80_4");
395 assert_eq!(dst.bits, 32);
396 assert_eq!(
397 *src,
398 Expr::mul(Expr::Var(Var::new("w8", 32)), Expr::Var(Var::new("w9", 32)))
399 );
400 }
401 other => panic!("expected Assign, got {other:?}"),
402 }
403 }
404
405 #[test]
406 fn size_less_shift_amount_takes_the_register_width_not_pointer_width() {
407 let txt = "\
4120x100: asr w8, w9, #4
413 w8 = INT_SRIGHT w9, 0x4";
414 let lift = lift_pcode(txt, Arch::Aarch64).unwrap();
415 let IrStmt::Assign { src, .. } = &lift.statements[0] else {
416 panic!("expected Assign");
417 };
418 assert_eq!(
419 *src,
420 Expr::ashr(Expr::Var(Var::new("w9", 32)), Expr::konst(4, 32))
421 );
422 }
423
424 #[test]
425 fn size_less_signed_compare_immediate_takes_the_register_width() {
426 let txt = "\
4300x100: cmp w8, w9
431 (unique,0x10,1) = INT_SLESS w8, 0xffffffff";
432 let lift = lift_pcode(txt, Arch::Aarch64).unwrap();
433 let IrStmt::Assign { src, .. } = &lift.statements[0] else {
434 panic!("expected Assign");
435 };
436 assert_eq!(
437 *src,
438 Expr::slt(Expr::Var(Var::new("w8", 32)), Expr::konst(0xffff_ffff, 32))
439 );
440 }
441
442 #[test]
443 fn arm32_gpr_is_32_bit_not_defaulted_to_64() {
444 let txt = "\
4490x100: add r0, r0, r1
450 r0 = INT_ADD r0, r1";
451 let lift = lift_pcode(txt, Arch::Arm).unwrap();
452 let IrStmt::Assign { dst, src } = &lift.statements[0] else {
453 panic!("expected Assign");
454 };
455 assert_eq!(dst.bits, 32);
456 assert_eq!(
457 *src,
458 Expr::add(Expr::Var(Var::new("r0", 32)), Expr::Var(Var::new("r1", 32)))
459 );
460 }
461
462 #[test]
463 fn test_lift_z_flag_maps_to_canonical_zf() {
464 let txt = "\
4670x100: subs w8, w8, #2
468 (unique,0x10,4) = INT_SUB w8, 0x2
469 tmpZR = INT_EQUAL (unique,0x10,4), 0x0
470 ZR = COPY tmpZR";
471 let lift = lift_pcode(txt, Arch::Aarch64).unwrap();
472 let IrStmt::Assign { dst, .. } = &lift.statements[2] else {
473 panic!("expected Assign");
474 };
475 assert_eq!(dst.name, "ZF");
476 assert_eq!(dst.bits, 1);
477 }
478
479 #[test]
480 fn test_lift_ncv_flags_stay_non_canonical() {
481 let txt = "\
4830x100: subs w8, w8, #2
484 tmpCY = INT_LESSEQUAL 0x2, w8
485 CY = COPY tmpCY";
486 let lift = lift_pcode(txt, Arch::Aarch64).unwrap();
487 let IrStmt::Assign { dst, .. } = &lift.statements[1] else {
488 panic!("expected Assign");
489 };
490 assert_eq!(dst.name, "pc_cy");
491 assert_ne!(dst.name, "CF");
492 }
493
494 #[test]
495 fn test_lift_load_store_emit_mem_stmts() {
496 let txt = "\
4970x100: ldr w8, [sp, #8]
498 (unique,0x60,8) = INT_ADD sp, 0x8
499 (unique,0x247,4) = LOAD ram[(unique,0x60,8)]
5000x104: str w8, [sp, #8]
501 STORE ram[(unique,0x60,8)] = w8";
502 let lift = lift_pcode(txt, Arch::Aarch64).unwrap();
503 assert!(matches!(
504 lift.statements[1],
505 IrStmt::LoadMem { bits: 32, .. }
506 ));
507 assert!(matches!(
508 lift.statements[2],
509 IrStmt::StoreMem { bits: 32, .. }
510 ));
511 }
512
513 #[test]
514 fn test_unsupported_opcode_errors_for_fallback() {
515 let txt = "\
5160x100: fmul d0, d0, d1
517 d0 = FLOAT_MULT d0, d1";
518 let err = lift_pcode(txt, Arch::Aarch64).unwrap_err();
519 assert_eq!(err, PcodeError::UnsupportedOpcode("FLOAT_MULT".into()));
520 }
521
522 #[test]
523 fn test_parse_error_propagates_as_pcode_error() {
524 let err = lift_pcode(" orphan = INT_ADD a, b", Arch::Aarch64).unwrap_err();
525 assert!(matches!(err, PcodeError::Parse(_)));
526 }
527}