Skip to main content

rsleigh_api/
lib.rs

1//! Spectra integration API for rsleigh.
2//!
3//! Provides a unified `Decoder` interface across all supported architectures,
4//! managing context/global-set lifecycle and returning optimized P-code.
5//!
6//! # Usage
7//!
8//! ```no_run
9//! use rsleigh_api::{Decoder, Architecture};
10//!
11//! let mut decoder = Decoder::new(Architecture::X86_64);
12//! let inst = decoder.decode(&[0x48, 0x89, 0xd8], 0x1000).unwrap();
13//! assert_eq!(inst.disassembly, "MOV RAX,RBX");
14//! assert_eq!(inst.len, 3);
15//! ```
16//!
17//! # Stability
18//!
19//! This crate is the **stable** entry point for embedding rsleigh as a
20//! decoder/lifter. Audit P2 #2: the surface listed below is covered by
21//! semver and changes go through deprecation; everything outside this
22//! list (the `rsleigh-decompile` analysis crate, the `rsleigh-cli`
23//! binary, signature/FID heuristics, printer text rewrites) is
24//! experimental and may change without notice.
25//!
26//! Stable surface:
27//!
28//! - [`Decoder`], [`Decoder::new`], [`Decoder::decode`],
29//!   [`Decoder::architecture`]
30//! - [`Architecture`] (variants may be added; existing variants stay)
31//! - [`Architecture::addr_size`], [`Architecture::register_name`]
32//! - Re-exports from `pcode-ir`: `Instruction`, `PcodeOp`, `Varnode`,
33//!   `AddressSpaceId`, `DecodeError`
34//!
35//! Anything else in this crate (helper functions, internal context
36//! types, generated-crate re-exports) is implementation detail.
37//! `rsleigh_decompile::*`, `rsleigh_decompile::printer::*`,
38//! `rsleigh_decompile::fold::*`, etc. are **not** covered by this
39//! stability promise — pin a specific patch version if you depend on
40//! their shape.
41
42pub use pcode_ir::{AddressSpaceId, DecodeError, Instruction, PcodeOp, Varnode};
43
44/// Supported CPU architectures.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub enum Architecture {
47    /// x86-64 (AMD64 / Intel 64), 64-bit mode.
48    X86_64,
49    /// x86-32 (IA-32), 32-bit protected mode.
50    X86_32,
51    /// AArch64 (ARMv8-A, 64-bit).
52    AArch64,
53    /// ARM32 (ARMv7 + Thumb).
54    ARM32,
55    /// MIPS32 (big-endian).
56    MIPS32,
57    /// RISC-V 64-bit (RV64GC).
58    RiscV64,
59}
60
61impl Architecture {
62    /// Address size in bytes for this architecture.
63    pub fn addr_size(&self) -> u32 {
64        match self {
65            Architecture::X86_64 | Architecture::AArch64 | Architecture::RiscV64 => 8,
66            Architecture::X86_32 | Architecture::ARM32 | Architecture::MIPS32 => 4,
67        }
68    }
69
70    /// Look up a register name by its Ghidra offset and size.
71    ///
72    /// Returns `None` if no register matches the given (offset, size) pair.
73    pub fn register_name(&self, offset: u64, size: u32) -> Option<&'static str> {
74        match self {
75            Architecture::X86_64 => x86_root::register_name(offset, size),
76            Architecture::X86_32 => x86_32_root::register_name(offset, size),
77            Architecture::AArch64 => aarch64_root::register_name(offset, size),
78            Architecture::ARM32 => arm32_root::register_name(offset, size),
79            Architecture::MIPS32 => mips_root::register_name(offset, size),
80            Architecture::RiscV64 => riscv_root::register_name(offset, size),
81        }
82    }
83}
84
85/// Unified instruction decoder.
86///
87/// Manages the per-architecture context memory and global set. Create one
88/// `Decoder` per architecture, reuse it across many `decode()` calls.
89pub struct Decoder {
90    arch: Architecture,
91    inner: DecoderInner,
92}
93
94enum DecoderInner {
95    X86_64 {
96        context: x86_root::ContextMemory,
97        global_set: x86_root::GlobalSet,
98    },
99    X86_32 {
100        context: x86_32_root::ContextMemory,
101        global_set: x86_32_root::GlobalSet,
102    },
103    AArch64 {
104        context: aarch64_root::ContextMemory,
105        global_set: aarch64_root::GlobalSet,
106    },
107    ARM32 {
108        context: arm32_root::ContextMemory,
109        global_set: arm32_root::GlobalSet,
110    },
111    MIPS32 {
112        context: mips_root::ContextMemory,
113        global_set: mips_root::GlobalSet,
114    },
115    RiscV64 {
116        context: riscv_root::ContextMemory,
117        global_set: riscv_root::GlobalSet,
118    },
119}
120
121impl Decoder {
122    /// Create a new decoder for the given architecture with default context.
123    pub fn new(arch: Architecture) -> Self {
124        let inner = match arch {
125            Architecture::X86_64 => {
126                let mut ctx = x86_root::ContextMemory::default();
127                // Default to 64-bit mode
128                ctx.write_longMode(1);
129                ctx.write_addrsize(2);
130                ctx.write_opsize(1);
131                let gs = x86_root::GlobalSet::new({
132                    let mut c = x86_root::ContextMemory::default();
133                    c.write_longMode(1);
134                    c.write_addrsize(2);
135                    c.write_opsize(1);
136                    c
137                });
138                DecoderInner::X86_64 {
139                    context: ctx,
140                    global_set: gs,
141                }
142            }
143            Architecture::X86_32 => {
144                // x86-32 uses its own slaspec with native 32-bit registers (ESP not RSP)
145                // Default: 32-bit protected mode (addrsize=1, opsize=1)
146                let mut ctx = x86_32_root::ContextMemory::default();
147                ctx.write_addrsize(1);
148                ctx.write_opsize(1);
149                let gs = x86_32_root::GlobalSet::new({
150                    let mut c = x86_32_root::ContextMemory::default();
151                    c.write_addrsize(1);
152                    c.write_opsize(1);
153                    c
154                });
155                DecoderInner::X86_32 {
156                    context: ctx,
157                    global_set: gs,
158                }
159            }
160            Architecture::AArch64 => DecoderInner::AArch64 {
161                context: aarch64_root::ContextMemory::default(),
162                global_set: aarch64_root::GlobalSet::new(aarch64_root::ContextMemory::default()),
163            },
164            Architecture::ARM32 => DecoderInner::ARM32 {
165                context: arm32_root::ContextMemory::default(),
166                global_set: arm32_root::GlobalSet::new(arm32_root::ContextMemory::default()),
167            },
168            Architecture::MIPS32 => DecoderInner::MIPS32 {
169                context: mips_root::ContextMemory::default(),
170                global_set: mips_root::GlobalSet::new(mips_root::ContextMemory::default()),
171            },
172            Architecture::RiscV64 => DecoderInner::RiscV64 {
173                context: riscv_root::ContextMemory::default(),
174                global_set: riscv_root::GlobalSet::new(riscv_root::ContextMemory::default()),
175            },
176        };
177        Self { arch, inner }
178    }
179
180    /// The architecture this decoder is configured for.
181    pub fn architecture(&self) -> Architecture {
182        self.arch
183    }
184
185    /// Decode a single instruction from `bytes` at virtual address `addr`.
186    ///
187    /// Returns the decoded instruction with optimized P-code, or an error
188    /// if the bytes don't match any known encoding.
189    ///
190    /// The `bytes` slice should contain at least enough bytes for the longest
191    /// possible instruction (15 for x86, 4 for fixed-width ISAs). Extra bytes
192    /// are ignored.
193    pub fn decode(&mut self, bytes: &[u8], addr: u64) -> Result<Instruction, DecodeError> {
194        // Each instruction gets a fresh copy of context. SLEIGH context changes
195        // during pattern matching (e.g. REX prefix bits) are local to each
196        // instruction and must not leak to the next decode call. Only globalset
197        // changes (stored in GlobalSet, not ContextMemory) persist across instructions.
198        match &mut self.inner {
199            DecoderInner::X86_64 {
200                context,
201                global_set,
202            } => {
203                if let Some(inst) = fallback_x86_64_mov_from_rsp_sib(bytes, addr) {
204                    return Ok(inst);
205                }
206
207                let mut ctx = *context;
208                if let Some((inst_next, display, mut ops)) =
209                    x86_root::parse_instruction(bytes, &mut ctx, addr, global_set)
210                {
211                    pcode_ir::optimize(&mut ops);
212                    Ok(Instruction {
213                        len: inst_next - addr,
214                        disassembly: format_display(&display),
215                        ops,
216                        constructor: None,
217                    })
218                } else {
219                    Err(DecodeError::UnknownInstruction)
220                }
221            }
222            DecoderInner::X86_32 {
223                context,
224                global_set,
225            } => {
226                let mut ctx = *context;
227                let addr32 = addr as u32;
228                let (inst_next, display, mut ops) =
229                    x86_32_root::parse_instruction(bytes, &mut ctx, addr32, global_set)
230                        .ok_or(DecodeError::UnknownInstruction)?;
231                pcode_ir::optimize(&mut ops);
232                Ok(Instruction {
233                    len: (inst_next - addr32) as u64,
234                    disassembly: format_display(&display),
235                    ops,
236                    constructor: None,
237                })
238            }
239            DecoderInner::AArch64 {
240                context,
241                global_set,
242            } => {
243                let mut ctx = *context;
244                let (inst_next, display, mut ops) =
245                    aarch64_root::parse_instruction(bytes, &mut ctx, addr, global_set)
246                        .ok_or(DecodeError::UnknownInstruction)?;
247                pcode_ir::optimize(&mut ops);
248                Ok(Instruction {
249                    len: inst_next - addr,
250                    disassembly: format_display(&display),
251                    ops,
252                    constructor: None,
253                })
254            }
255            DecoderInner::ARM32 {
256                context,
257                global_set,
258            } => {
259                let mut ctx = *context;
260                let addr32 = addr as u32;
261                let (inst_next, display, mut ops) =
262                    arm32_root::parse_instruction(bytes, &mut ctx, addr32, global_set)
263                        .ok_or(DecodeError::UnknownInstruction)?;
264                pcode_ir::optimize(&mut ops);
265                Ok(Instruction {
266                    len: (inst_next - addr32) as u64,
267                    disassembly: format_display(&display),
268                    ops,
269                    constructor: None,
270                })
271            }
272            DecoderInner::MIPS32 {
273                context,
274                global_set,
275            } => {
276                let mut ctx = *context;
277                let addr32 = addr as u32;
278                let (inst_next, display, mut ops) =
279                    mips_root::parse_instruction(bytes, &mut ctx, addr32, global_set)
280                        .ok_or(DecodeError::UnknownInstruction)?;
281                pcode_ir::optimize(&mut ops);
282                Ok(Instruction {
283                    len: (inst_next - addr32) as u64,
284                    disassembly: format_display(&display),
285                    ops,
286                    constructor: None,
287                })
288            }
289            DecoderInner::RiscV64 {
290                context,
291                global_set,
292            } => {
293                let mut ctx = *context;
294                let (inst_next, display, mut ops) =
295                    riscv_root::parse_instruction(bytes, &mut ctx, addr, global_set)
296                        .ok_or(DecodeError::UnknownInstruction)?;
297                pcode_ir::optimize(&mut ops);
298                Ok(Instruction {
299                    len: inst_next - addr,
300                    disassembly: format_display(&display),
301                    ops,
302                    constructor: None,
303                })
304            }
305        }
306    }
307}
308
309fn fallback_x86_64_mov_from_rsp_sib(bytes: &[u8], addr: u64) -> Option<Instruction> {
310    let mut pos = 0usize;
311    let mut rex = 0u8;
312    if bytes
313        .first()
314        .copied()
315        .is_some_and(|b| (0x40..=0x4f).contains(&b))
316    {
317        rex = bytes[0];
318        pos += 1;
319    }
320    if bytes.get(pos).copied()? != 0x8b {
321        return None;
322    }
323    pos += 1;
324
325    let modrm = bytes.get(pos).copied()?;
326    pos += 1;
327    let mode = modrm >> 6;
328    let reg = ((modrm >> 3) & 7) | ((rex & 0x04) << 1);
329    let rm = modrm & 7;
330    if rm != 4 || !matches!(mode, 1 | 2) {
331        return None;
332    }
333
334    let sib = bytes.get(pos).copied()?;
335    pos += 1;
336    let index = (sib >> 3) & 7;
337    let base = (sib & 7) | ((rex & 0x01) << 3);
338    if index != 4 || (rex & 0x02) != 0 || !matches!(base, 4 | 12) {
339        return None;
340    }
341
342    let disp = match mode {
343        1 => {
344            let d = *bytes.get(pos)? as i8 as i64;
345            pos += 1;
346            d
347        }
348        2 => {
349            let raw = bytes.get(pos..pos + 4)?;
350            pos += 4;
351            i32::from_le_bytes(raw.try_into().ok()?) as i64
352        }
353        _ => return None,
354    };
355
356    let size = if rex & 0x08 != 0 { 8 } else { 4 };
357    let dest = x86_reg_varnode(reg, size)?;
358    let base_vn = x86_reg_varnode(base, 8)?;
359    let size_name = if size == 8 { "qword" } else { "dword" };
360    let disassembly = if disp == 0 {
361        format!(
362            "MOV {},{} ptr [{}]",
363            x86_reg_name(reg, size)?,
364            size_name,
365            x86_reg_name(base, 8)?,
366        )
367    } else {
368        format!(
369            "MOV {},{} ptr [{} {} {:#x}]",
370            x86_reg_name(reg, size)?,
371            size_name,
372            x86_reg_name(base, 8)?,
373            if disp < 0 { "-" } else { "+" },
374            disp.unsigned_abs()
375        )
376    };
377
378    let mut ops = Vec::new();
379    let ptr = if disp == 0 {
380        base_vn
381    } else {
382        let ptr = Varnode::unique((addr << 16).wrapping_add(0x8000), 8);
383        ops.push(PcodeOp::IntAdd {
384            out: ptr,
385            left: base_vn,
386            right: Varnode::constant(disp as u64, 8),
387        });
388        ptr
389    };
390    ops.push(PcodeOp::Load {
391        out: dest,
392        space: pcode_ir::AddressSpaceId::Ram,
393        ptr,
394    });
395
396    Some(Instruction {
397        len: pos as u64,
398        disassembly,
399        ops,
400        constructor: None,
401    })
402}
403
404fn x86_reg_varnode(reg: u8, size: u32) -> Option<Varnode> {
405    let offset = match reg {
406        0..=7 => u64::from(reg) * 8,
407        8..=15 => 0x80 + (u64::from(reg) - 8) * 8,
408        _ => return None,
409    };
410    Some(Varnode::register(offset, size))
411}
412
413fn x86_reg_name(reg: u8, size: u32) -> Option<&'static str> {
414    const R32: [&str; 16] = [
415        "EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI", "R8D", "R9D", "R10D", "R11D",
416        "R12D", "R13D", "R14D", "R15D",
417    ];
418    const R64: [&str; 16] = [
419        "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI", "R8", "R9", "R10", "R11", "R12",
420        "R13", "R14", "R15",
421    ];
422    match size {
423        4 => R32.get(reg as usize).copied(),
424        8 => R64.get(reg as usize).copied(),
425        _ => None,
426    }
427}
428
429/// Format display elements into a disassembly string.
430fn format_display(elements: &[impl core::fmt::Display]) -> String {
431    elements.iter().map(|d| format!("{}", d)).collect()
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn x86_64_fallback_decodes_mov_r12d_rsp_disp32() {
440        let mut dec = Decoder::new(Architecture::X86_64);
441        let inst = dec
442            .decode(&[0x44, 0x8b, 0xa4, 0x24, 0x88, 0x00, 0x00, 0x00], 0x1000)
443            .expect("decode MOV R12D,[RSP+0x88]");
444
445        assert_eq!(inst.len, 8);
446        assert!(inst.disassembly.contains("R12D"), "{}", inst.disassembly);
447        assert!(inst.ops.iter().any(|op| {
448            matches!(
449                op,
450                PcodeOp::Load {
451                    out,
452                    space: pcode_ir::AddressSpaceId::Ram,
453                    ..
454                } if *out == Varnode::register(0xa0, 4)
455            )
456        }));
457    }
458}