pub use pcode_ir::{AddressSpaceId, ConstructorSpan, DecodeError, Instruction, PcodeOp, Varnode};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Architecture {
X86_64,
X86_32,
AArch64,
ARM32,
MIPS32,
RiscV64,
}
impl Architecture {
pub fn addr_size(&self) -> u32 {
match self {
Architecture::X86_64 | Architecture::AArch64 | Architecture::RiscV64 => 8,
Architecture::X86_32 | Architecture::ARM32 | Architecture::MIPS32 => 4,
}
}
pub fn register_name(&self, offset: u64, size: u32) -> Option<&'static str> {
match self {
Architecture::X86_64 => x86_root::register_name(offset, size),
Architecture::X86_32 => x86_32_root::register_name(offset, size),
Architecture::AArch64 => aarch64_root::register_name(offset, size),
Architecture::ARM32 => arm32_root::register_name(offset, size),
Architecture::MIPS32 => mips_root::register_name(offset, size),
Architecture::RiscV64 => riscv_root::register_name(offset, size),
}
}
}
pub struct Decoder {
arch: Architecture,
inner: DecoderInner,
}
enum DecoderInner {
X86_64 {
context: x86_root::ContextMemory,
global_set: x86_root::GlobalSet,
},
X86_32 {
context: x86_32_root::ContextMemory,
global_set: x86_32_root::GlobalSet,
},
AArch64 {
context: aarch64_root::ContextMemory,
global_set: aarch64_root::GlobalSet,
},
ARM32 {
context: arm32_root::ContextMemory,
global_set: arm32_root::GlobalSet,
},
MIPS32 {
context: mips_root::ContextMemory,
global_set: mips_root::GlobalSet,
},
RiscV64 {
context: riscv_root::ContextMemory,
global_set: riscv_root::GlobalSet,
},
}
impl Decoder {
pub fn new(arch: Architecture) -> Self {
let inner = match arch {
Architecture::X86_64 => {
let mut ctx = x86_root::ContextMemory::default();
ctx.write_longMode(1);
ctx.write_addrsize(2);
ctx.write_opsize(1);
let gs = x86_root::GlobalSet::new({
let mut c = x86_root::ContextMemory::default();
c.write_longMode(1);
c.write_addrsize(2);
c.write_opsize(1);
c
});
DecoderInner::X86_64 {
context: ctx,
global_set: gs,
}
}
Architecture::X86_32 => {
let mut ctx = x86_32_root::ContextMemory::default();
ctx.write_addrsize(1);
ctx.write_opsize(1);
let gs = x86_32_root::GlobalSet::new({
let mut c = x86_32_root::ContextMemory::default();
c.write_addrsize(1);
c.write_opsize(1);
c
});
DecoderInner::X86_32 {
context: ctx,
global_set: gs,
}
}
Architecture::AArch64 => DecoderInner::AArch64 {
context: aarch64_root::ContextMemory::default(),
global_set: aarch64_root::GlobalSet::new(aarch64_root::ContextMemory::default()),
},
Architecture::ARM32 => DecoderInner::ARM32 {
context: arm32_root::ContextMemory::default(),
global_set: arm32_root::GlobalSet::new(arm32_root::ContextMemory::default()),
},
Architecture::MIPS32 => DecoderInner::MIPS32 {
context: mips_root::ContextMemory::default(),
global_set: mips_root::GlobalSet::new(mips_root::ContextMemory::default()),
},
Architecture::RiscV64 => DecoderInner::RiscV64 {
context: riscv_root::ContextMemory::default(),
global_set: riscv_root::GlobalSet::new(riscv_root::ContextMemory::default()),
},
};
Self { arch, inner }
}
pub fn architecture(&self) -> Architecture {
self.arch
}
pub fn set_arm_thumb(&mut self, thumb: bool) {
if let DecoderInner::ARM32 {
context,
global_set,
} = &mut self.inner
{
context.write_TMode(if thumb { 1 } else { 0 });
*global_set = arm32_root::GlobalSet::new(*context);
}
}
pub fn decode(&mut self, bytes: &[u8], addr: u64) -> Result<Instruction, DecodeError> {
let mut instruction = self.decode_unoptimized(bytes, addr)?;
pcode_ir::optimize(&mut instruction.ops);
Ok(instruction)
}
pub fn decode_unoptimized(
&mut self,
bytes: &[u8],
addr: u64,
) -> Result<Instruction, DecodeError> {
match &mut self.inner {
DecoderInner::X86_64 {
context,
global_set,
} => {
let mut ctx = *context;
if let Some((inst_next, display, ops, constructor)) =
x86_root::parse_instruction_with_constructor(bytes, &mut ctx, addr, global_set)
{
Ok(Instruction {
len: inst_next - addr,
disassembly: format_display(&display),
ops,
constructor: Some(constructor),
})
} else {
Err(DecodeError::UnknownInstruction)
}
}
DecoderInner::X86_32 {
context,
global_set,
} => {
let mut ctx = *context;
let addr32 = addr as u32;
let (inst_next, display, ops, constructor) =
x86_32_root::parse_instruction_with_constructor(
bytes, &mut ctx, addr32, global_set,
)
.ok_or(DecodeError::UnknownInstruction)?;
Ok(Instruction {
len: (inst_next - addr32) as u64,
disassembly: format_display(&display),
ops,
constructor: Some(constructor),
})
}
DecoderInner::AArch64 {
context,
global_set,
} => {
let mut ctx = *context;
let (inst_next, display, ops, constructor) =
aarch64_root::parse_instruction_with_constructor(
bytes, &mut ctx, addr, global_set,
)
.ok_or(DecodeError::UnknownInstruction)?;
Ok(Instruction {
len: inst_next - addr,
disassembly: format_display(&display),
ops,
constructor: Some(constructor),
})
}
DecoderInner::ARM32 {
context,
global_set,
} => {
let mut ctx = *context;
let addr32 = addr as u32;
let (inst_next, display, ops, constructor) =
arm32_root::parse_instruction_with_constructor(
bytes, &mut ctx, addr32, global_set,
)
.ok_or(DecodeError::UnknownInstruction)?;
Ok(Instruction {
len: (inst_next - addr32) as u64,
disassembly: format_display(&display),
ops,
constructor: Some(constructor),
})
}
DecoderInner::MIPS32 {
context,
global_set,
} => {
let mut ctx = *context;
let addr32 = addr as u32;
let (inst_next, display, ops, constructor) =
mips_root::parse_instruction_with_constructor(
bytes, &mut ctx, addr32, global_set,
)
.ok_or(DecodeError::UnknownInstruction)?;
Ok(Instruction {
len: (inst_next - addr32) as u64,
disassembly: format_display(&display),
ops,
constructor: Some(constructor),
})
}
DecoderInner::RiscV64 {
context,
global_set,
} => {
let mut ctx = *context;
let (inst_next, display, ops, constructor) =
riscv_root::parse_instruction_with_constructor(
bytes, &mut ctx, addr, global_set,
)
.ok_or(DecodeError::UnknownInstruction)?;
Ok(Instruction {
len: inst_next - addr,
disassembly: format_display(&display),
ops,
constructor: Some(constructor),
})
}
}
}
}
fn format_display(elements: &[impl core::fmt::Display]) -> String {
elements.iter().map(|d| format!("{}", d)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn with_decoder_stack(test: impl FnOnce() + Send + 'static) {
std::thread::Builder::new()
.stack_size(32 * 1024 * 1024)
.spawn(test)
.expect("spawn decoder test")
.join()
.expect("decoder test panicked");
}
#[test]
fn generated_x86_64_decodes_mov_r12d_rsp_disp32() {
with_decoder_stack(|| {
let mut dec = Decoder::new(Architecture::X86_64);
let inst = dec
.decode(&[0x44, 0x8b, 0xa4, 0x24, 0x88, 0x00, 0x00, 0x00], 0x1000)
.expect("decode MOV R12D,[RSP+0x88]");
assert_eq!(inst.len, 8);
assert!(inst.disassembly.contains("R12D"), "{}", inst.disassembly);
assert!(inst.ops.iter().any(|op| {
matches!(
op,
PcodeOp::Load {
out,
space: pcode_ir::AddressSpaceId::Ram,
..
} if *out == Varnode::register(0xa0, 4)
)
}));
let span = inst.constructor.expect("generated constructor provenance");
assert!(span.source.contains("slaspec/x86/"), "{}", span.source);
});
}
fn has_x86_64_parent_clear(ops: &[PcodeOp], expected_offset: u64) -> bool {
let expected = Varnode::register(expected_offset, 8);
ops.iter().any(|op| match op {
PcodeOp::IntZext { out, .. } => *out == expected,
PcodeOp::Copy { out, input } => *out == expected
&& input.space == AddressSpaceId::Unique
&& ops.iter().any(
|candidate| matches!(candidate, PcodeOp::IntZext { out, .. } if out == input),
),
_ => false,
})
}
#[test]
fn generated_x86_64_mov_r32_clears_the_destination_parent() {
with_decoder_stack(|| {
let cases: &[(&[u8], u64)] = &[
(&[0x89, 0xf9], 0x08), (&[0x89, 0xfb], 0x18), (&[0x89, 0xfe], 0x30), (&[0x89, 0xff], 0x38), (&[0x41, 0x89, 0xf8], 0x80), (&[0x41, 0x89, 0xf9], 0x88), (&[0x41, 0x89, 0xfa], 0x90), (&[0x41, 0x89, 0xfb], 0x98), (&[0x41, 0x89, 0xfc], 0xa0), (&[0x41, 0x89, 0xfd], 0xa8), (&[0x41, 0x89, 0xfe], 0xb0), (&[0x41, 0x89, 0xff], 0xb8), ];
let mut decoder = Decoder::new(Architecture::X86_64);
for &(bytes, expected_offset) in cases {
let instruction = decoder
.decode_unoptimized(bytes, 0x1000)
.unwrap_or_else(|error| panic!("decode {bytes:02x?}: {error:?}"));
assert!(
has_x86_64_parent_clear(&instruction.ops, expected_offset),
"bytes={bytes:02x?} expected={:?} ops={:#?}",
Varnode::register(expected_offset, 8),
instruction.ops
);
assert!(instruction.constructor.is_some());
}
});
}
#[test]
fn diagnostic_decode_preserves_pre_optimization_pcode() {
with_decoder_stack(|| {
let bytes = [0xc2, 0x08, 0x00]; let raw = Decoder::new(Architecture::X86_64)
.decode_unoptimized(&bytes, 0x1000)
.expect("raw decode RET 8");
let optimized = Decoder::new(Architecture::X86_64)
.decode(&bytes, 0x1000)
.expect("optimized decode RET 8");
assert!(
raw.ops.len() > optimized.ops.len(),
"raw={:?} optimized={:?}",
raw.ops,
optimized.ops
);
});
}
#[test]
fn arm32_backward_bl_has_no_spurious_call_tag() {
with_decoder_stack(|| {
let mut dec = Decoder::new(Architecture::ARM32);
let inst = dec
.decode(&[0xfe, 0xff, 0xff, 0xeb], 0x1000)
.expect("decode ARM32 BL to self");
assert!(
inst.ops.iter().any(|op| {
matches!(
op,
PcodeOp::Call { dest }
if dest.space == AddressSpaceId::Ram && dest.offset == 0x1000
)
}),
"{:?}",
inst.ops
);
assert!(inst.constructor.is_some());
});
}
#[test]
fn arm32_bx_lr_emits_mode_switch_state() {
with_decoder_stack(|| {
let mut decoder = Decoder::new(Architecture::ARM32);
let instruction = decoder
.decode_unoptimized(&[0x1e, 0xff, 0x2f, 0xe1], 0x1000)
.expect("decode ARM32 BX LR");
let mode_value = instruction
.ops
.iter()
.find_map(|op| match op {
PcodeOp::IntNotEq { out, .. } => Some(out.clone()),
_ => None,
})
.expect("BX LR computes the next instruction-set mode");
let mut source = Varnode::register(0x78, 1);
for _ in 0..instruction.ops.len() {
if source == mode_value {
break;
}
source = instruction
.ops
.iter()
.find_map(|op| match op {
PcodeOp::Copy { out, input } if *out == source => Some(input.clone()),
_ => None,
})
.unwrap_or_else(|| panic!("ISAModeSwitch copy chain: {:#?}", instruction.ops));
}
assert_eq!(source, mode_value, "{:#?}", instruction.ops);
assert!(
instruction.ops.iter().any(|op| {
matches!(
op,
PcodeOp::Copy { out, input }
if *out == Varnode::register(0x69, 1)
&& *input == Varnode::register(0x78, 1)
)
}),
"{:#?}",
instruction.ops
);
assert!(
instruction
.ops
.iter()
.any(|op| matches!(op, PcodeOp::CallOther { .. })),
"{:#?}",
instruction.ops
);
assert!(instruction.constructor.is_some());
});
}
}