summaryrefslogtreecommitdiff
path: root/arch/riscv/src
diff options
context:
space:
mode:
authorRusty Wagner <rusty.wagner@gmail.com>2023-12-19 22:39:33 -0700
committerRusty Wagner <rusty.wagner@gmail.com>2024-01-04 12:42:11 -0700
commita9cfac7ff14d93ff22092366f99eb2dd3213ea7e (patch)
treeb61163e052a1827846b0c3b8330b0289aac8cabf /arch/riscv/src
parenta82a2105fd1b1a99bddb835ed1d9dbf2e2a5c56f (diff)
Add RISC-V architecture plugin
Diffstat (limited to 'arch/riscv/src')
-rw-r--r--arch/riscv/src/lib.rs2570
-rw-r--r--arch/riscv/src/liftcheck.rs427
2 files changed, 2997 insertions, 0 deletions
diff --git a/arch/riscv/src/lib.rs b/arch/riscv/src/lib.rs
new file mode 100644
index 00000000..20136114
--- /dev/null
+++ b/arch/riscv/src/lib.rs
@@ -0,0 +1,2570 @@
+// Option -> Result
+// rework operands/instruction text
+// helper func for reading/writing to registers
+// do the amo max/min instructions
+// Platform api
+
+use std::borrow::Cow;
+use std::fmt;
+use std::marker::PhantomData;
+
+use binaryninja::{
+ add_optional_plugin_dependency, architecture,
+ architecture::{
+ llvm_assemble, Architecture, ArchitectureExt, CoreArchitecture, CustomArchitectureHandle,
+ ImplicitRegisterExtend, InstructionInfo, LlvmServicesCodeModel, LlvmServicesDialect,
+ LlvmServicesRelocMode, Register as Reg, RegisterInfo, UnusedFlag, UnusedRegisterStack,
+ UnusedRegisterStackInfo,
+ },
+ binaryview::{BinaryView, BinaryViewExt},
+ callingconvention::{register_calling_convention, CallingConventionBase, ConventionBuilder},
+ custombinaryview::{BinaryViewType, BinaryViewTypeExt},
+ disassembly::{InstructionTextToken, InstructionTextTokenContents},
+ function::Function,
+ functionrecognizer::FunctionRecognizer,
+ llil,
+ llil::{
+ ExprInfo, InstrInfo, Label, Liftable, LiftableWithSize, LiftedNonSSA, Lifter, Mutable,
+ NonSSA,
+ },
+ rc::Ref,
+ relocation::{
+ CoreRelocationHandler, CustomRelocationHandlerHandle, RelocationHandler, RelocationInfo,
+ RelocationType,
+ },
+ string::BnString,
+ symbol::{Symbol, SymbolType},
+ types::{max_confidence, min_confidence, Conf, NameAndType, Type},
+};
+
+use riscv_dis::{
+ FloatReg, FloatRegType, Instr, IntRegType, Op, RegFile, Register as RiscVRegister,
+ RiscVDisassembler, RoundMode,
+};
+
+enum RegType {
+ Integer(u32),
+ Float(u32),
+}
+
+#[repr(u32)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum Intrinsic {
+ Uret,
+ Sret,
+ Mret,
+ Wfi,
+ Csrrw,
+ Csrwr,
+ Csrrd,
+ Csrrs,
+ Csrrc,
+ Fadd(u8, RoundMode),
+ Fsub(u8, RoundMode),
+ Fmul(u8, RoundMode),
+ Fdiv(u8, RoundMode),
+ Fsqrt(u8, RoundMode),
+ Fsgnj(u8),
+ Fsgnjn(u8),
+ Fsgnjx(u8),
+ Fmin(u8),
+ Fmax(u8),
+ Fclass(u8),
+ FcvtFToF(u8, u8, RoundMode),
+ FcvtIToF(u8, u8, RoundMode),
+ FcvtFToI(u8, u8, RoundMode),
+ FcvtUToF(u8, u8, RoundMode),
+ FcvtFToU(u8, u8, RoundMode),
+ Fence,
+}
+
+#[derive(Copy, Clone)]
+struct Register<D: 'static + RiscVDisassembler> {
+ id: u32,
+ _dis: PhantomData<D>,
+}
+
+#[derive(Copy, Clone)]
+struct RiscVIntrinsic<D: 'static + RiscVDisassembler> {
+ id: Intrinsic,
+ _dis: PhantomData<D>,
+}
+
+impl<D: 'static + RiscVDisassembler> Register<D> {
+ fn new(id: u32) -> Self {
+ Self {
+ id,
+ _dis: PhantomData,
+ }
+ }
+
+ fn reg_type(&self) -> RegType {
+ let int_reg_count = <D::RegFile as RegFile>::int_reg_count();
+
+ if self.id < int_reg_count {
+ RegType::Integer(self.id)
+ } else {
+ RegType::Float(self.id - int_reg_count)
+ }
+ }
+}
+
+impl<D: 'static + RiscVDisassembler> From<riscv_dis::IntReg<D>> for Register<D> {
+ fn from(reg: riscv_dis::IntReg<D>) -> Self {
+ Self {
+ id: reg.id(),
+ _dis: PhantomData,
+ }
+ }
+}
+
+impl<D: 'static + RiscVDisassembler> From<FloatReg<D>> for Register<D> {
+ fn from(reg: FloatReg<D>) -> Self {
+ let int_reg_count = <D::RegFile as RegFile>::int_reg_count();
+
+ Self {
+ id: reg.id() + int_reg_count,
+ _dis: PhantomData,
+ }
+ }
+}
+
+impl<D: 'static + RiscVDisassembler> Into<llil::Register<Register<D>>> for Register<D> {
+ fn into(self) -> llil::Register<Register<D>> {
+ llil::Register::ArchReg(self)
+ }
+}
+
+impl<D: 'static + RiscVDisassembler> RegisterInfo for Register<D> {
+ type RegType = Self;
+
+ fn parent(&self) -> Option<Self> {
+ None
+ }
+
+ fn size(&self) -> usize {
+ match self.reg_type() {
+ RegType::Integer(_) => <D::RegFile as RegFile>::Int::width(),
+ RegType::Float(_) => <D::RegFile as RegFile>::Float::width(),
+ }
+ }
+
+ fn offset(&self) -> usize {
+ 0
+ }
+ fn implicit_extend(&self) -> ImplicitRegisterExtend {
+ ImplicitRegisterExtend::NoExtend
+ }
+}
+
+impl<D: 'static + RiscVDisassembler> architecture::Register for Register<D> {
+ type InfoType = Self;
+
+ fn name(&self) -> Cow<str> {
+ match self.reg_type() {
+ RegType::Integer(id) => match id {
+ 0 => "zero".into(),
+ 1 => "ra".into(),
+ 2 => "sp".into(),
+ 3 => "gp".into(),
+ 4 => "tp".into(),
+ r @ 5..=7 => format!("t{}", r - 5).into(),
+ r @ 8..=9 => format!("s{}", r - 8).into(),
+ r @ 10..=17 => format!("a{}", r - 10).into(),
+ r @ 18..=27 => format!("s{}", r - 16).into(),
+ r @ 28..=31 => format!("t{}", r - 25).into(),
+ _ => unreachable!(),
+ },
+ RegType::Float(id) => match id {
+ r @ 0..=7 => format!("ft{}", r).into(),
+ r @ 8..=9 => format!("fs{}", r - 8).into(),
+ r @ 10..=17 => format!("fa{}", r - 10).into(),
+ r @ 18..=27 => format!("fs{}", r - 16).into(),
+ r @ 28..=31 => format!("ft{}", r - 20).into(),
+ _ => unreachable!(),
+ },
+ }
+ }
+
+ fn info(&self) -> Self {
+ *self
+ }
+
+ fn id(&self) -> u32 {
+ self.id
+ }
+}
+
+impl<'a, D: 'static + RiscVDisassembler + Send + Sync> Liftable<'a, RiscVArch<D>> for Register<D> {
+ type Result = llil::ValueExpr;
+
+ fn lift(
+ il: &'a llil::Lifter<RiscVArch<D>>,
+ reg: Self,
+ ) -> llil::Expression<'a, RiscVArch<D>, Mutable, NonSSA<LiftedNonSSA>, Self::Result> {
+ match reg.reg_type() {
+ RegType::Integer(0) => il.const_int(reg.size(), 0),
+ RegType::Integer(_) => il.reg(reg.size(), reg),
+ _ => il.unimplemented(),
+ }
+ }
+}
+
+impl<'a, D: 'static + RiscVDisassembler + Send + Sync> LiftableWithSize<'a, RiscVArch<D>>
+ for Register<D>
+{
+ fn lift_with_size(
+ il: &'a llil::Lifter<RiscVArch<D>>,
+ reg: Self,
+ size: usize,
+ ) -> llil::Expression<'a, RiscVArch<D>, Mutable, NonSSA<LiftedNonSSA>, llil::ValueExpr> {
+ #[cfg(debug_assertions)]
+ {
+ if reg.size() < size {
+ log::warn!(
+ "il @ {:x} attempted to lift {} byte register as {} byte expr",
+ il.current_address(),
+ reg.size(),
+ size
+ );
+ }
+ }
+
+ match reg.reg_type() {
+ RegType::Integer(0) => il.const_int(size, 0),
+ RegType::Integer(_) => {
+ let expr = il.reg(reg.size(), reg);
+
+ if size < reg.size() {
+ il.low_part(size, expr).build()
+ } else {
+ expr
+ }
+ }
+ _ => il.unimplemented(),
+ }
+ }
+}
+
+impl<D: 'static + RiscVDisassembler + Send + Sync> fmt::Debug for Register<D> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(self.name().as_ref())
+ }
+}
+
+impl<D: RiscVDisassembler> RiscVIntrinsic<D> {
+ fn id_from_parts(id: u32, sz1: Option<u8>, sz2: Option<u8>, rm: Option<RoundMode>) -> u32 {
+ let sz1 = sz1.unwrap_or(0);
+ let sz2 = sz2.unwrap_or(0);
+ let rm = match rm {
+ None | Some(RoundMode::Dynamic) => 0,
+ Some(RoundMode::RoundNearestEven) => 1,
+ Some(RoundMode::RoundTowardZero) => 2,
+ Some(RoundMode::RoundDown) => 3,
+ Some(RoundMode::RoundUp) => 4,
+ Some(RoundMode::RoundMaxMagnitude) => 5,
+ };
+
+ let mut id = id << 20;
+ id |= sz1 as u32;
+ id |= (sz2 as u32) << 8;
+ id |= (rm as u32) << 16;
+ id
+ }
+
+ fn parts_from_id(id: u32) -> Option<(u32, u8, u8, RoundMode)> {
+ let sz1 = (id & 0xff) as u8;
+ let sz2 = ((id >> 8) & 0xff) as u8;
+ let rm = match (id >> 16) & 0xf {
+ 0 => RoundMode::Dynamic,
+ 1 => RoundMode::RoundNearestEven,
+ 2 => RoundMode::RoundTowardZero,
+ 3 => RoundMode::RoundDown,
+ 4 => RoundMode::RoundUp,
+ 5 => RoundMode::RoundMaxMagnitude,
+ _ => return None,
+ };
+ Some(((id >> 20) & 0xfff, sz1, sz2, rm))
+ }
+
+ fn from_id(id: u32) -> Option<RiscVIntrinsic<D>> {
+ match Self::parts_from_id(id) {
+ Some((0, _, _, _)) => Some(Intrinsic::Uret.into()),
+ Some((1, _, _, _)) => Some(Intrinsic::Sret.into()),
+ Some((2, _, _, _)) => Some(Intrinsic::Mret.into()),
+ Some((3, _, _, _)) => Some(Intrinsic::Wfi.into()),
+ Some((4, _, _, _)) => Some(Intrinsic::Csrrw.into()),
+ Some((5, _, _, _)) => Some(Intrinsic::Csrwr.into()),
+ Some((6, _, _, _)) => Some(Intrinsic::Csrrd.into()),
+ Some((7, _, _, _)) => Some(Intrinsic::Csrrs.into()),
+ Some((8, _, _, _)) => Some(Intrinsic::Csrrc.into()),
+ Some((9, size, _, rm)) => Some(Intrinsic::Fadd(size, rm).into()),
+ Some((10, size, _, rm)) => Some(Intrinsic::Fsub(size, rm).into()),
+ Some((11, size, _, rm)) => Some(Intrinsic::Fmul(size, rm).into()),
+ Some((12, size, _, rm)) => Some(Intrinsic::Fdiv(size, rm).into()),
+ Some((13, size, _, rm)) => Some(Intrinsic::Fsqrt(size, rm).into()),
+ Some((14, size, _, _)) => Some(Intrinsic::Fsgnj(size).into()),
+ Some((15, size, _, _)) => Some(Intrinsic::Fsgnjn(size).into()),
+ Some((16, size, _, _)) => Some(Intrinsic::Fsgnjx(size).into()),
+ Some((17, size, _, _)) => Some(Intrinsic::Fmin(size).into()),
+ Some((18, size, _, _)) => Some(Intrinsic::Fmax(size).into()),
+ Some((19, size, _, _)) => Some(Intrinsic::Fclass(size).into()),
+ Some((20, ssize, dsize, rm)) => Some(Intrinsic::FcvtFToF(ssize, dsize, rm).into()),
+ Some((21, isize, fsize, rm)) => Some(Intrinsic::FcvtIToF(isize, fsize, rm).into()),
+ Some((22, fsize, isize, rm)) => Some(Intrinsic::FcvtFToI(fsize, isize, rm).into()),
+ Some((23, usize, fsize, rm)) => Some(Intrinsic::FcvtUToF(usize, fsize, rm).into()),
+ Some((24, fsize, usize, rm)) => Some(Intrinsic::FcvtFToU(fsize, usize, rm).into()),
+ Some((25, _, _, _)) => Some(Intrinsic::Fence.into()),
+ _ => None,
+ }
+ }
+
+ fn int_size_suffix(size: u8) -> &'static str {
+ match size {
+ 4 => "_i32",
+ 8 => "_i64",
+ _ => unreachable!(),
+ }
+ }
+
+ fn uint_size_suffix(size: u8) -> &'static str {
+ match size {
+ 4 => "_u32",
+ 8 => "_u64",
+ _ => unreachable!(),
+ }
+ }
+
+ fn float_size_suffix(size: u8) -> &'static str {
+ match size {
+ 4 => "_s",
+ 8 => "_d",
+ 16 => "_q",
+ _ => unreachable!(),
+ }
+ }
+
+ fn round_mode_suffix(rm: RoundMode) -> &'static str {
+ match rm {
+ RoundMode::RoundNearestEven => "_rne",
+ RoundMode::RoundTowardZero => "_rtz",
+ RoundMode::RoundDown => "_rdn",
+ RoundMode::RoundUp => "_rup",
+ RoundMode::RoundMaxMagnitude => "_rmm",
+ RoundMode::Dynamic => "",
+ }
+ }
+}
+
+impl<D: RiscVDisassembler> From<Intrinsic> for RiscVIntrinsic<D> {
+ fn from(id: Intrinsic) -> Self {
+ Self {
+ id,
+ _dis: PhantomData,
+ }
+ }
+}
+
+impl<D: RiscVDisassembler> architecture::Intrinsic for RiscVIntrinsic<D> {
+ fn name(&self) -> Cow<str> {
+ match self.id {
+ Intrinsic::Uret => "_uret".into(),
+ Intrinsic::Sret => "_sret".into(),
+ Intrinsic::Mret => "_mret".into(),
+ Intrinsic::Wfi => "_wfi".into(),
+ Intrinsic::Csrrw => "_csrrw".into(),
+ Intrinsic::Csrwr => "_csrwr".into(),
+ Intrinsic::Csrrd => "_csrrd".into(),
+ Intrinsic::Csrrs => "_csrrs".into(),
+ Intrinsic::Csrrc => "_csrrc".into(),
+ Intrinsic::Fadd(size, rm) => format!(
+ "_fadd{}{}",
+ Self::float_size_suffix(size),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::Fsub(size, rm) => format!(
+ "_fsub{}{}",
+ Self::float_size_suffix(size),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::Fmul(size, rm) => format!(
+ "_fmul{}{}",
+ Self::float_size_suffix(size),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::Fdiv(size, rm) => format!(
+ "_fdiv{}{}",
+ Self::float_size_suffix(size),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::Fsqrt(size, rm) => format!(
+ "_fsqrt{}{}",
+ Self::float_size_suffix(size),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::Fsgnj(size) => format!("_fsgnj{}", Self::float_size_suffix(size)).into(),
+ Intrinsic::Fsgnjn(size) => format!("_fsgnjn{}", Self::float_size_suffix(size)).into(),
+ Intrinsic::Fsgnjx(size) => format!("_fsgnjx{}", Self::float_size_suffix(size)).into(),
+ Intrinsic::Fmin(size) => format!("_fmin{}", Self::float_size_suffix(size)).into(),
+ Intrinsic::Fmax(size) => format!("_fmax{}", Self::float_size_suffix(size)).into(),
+ Intrinsic::Fclass(size) => format!("_fclass{}", Self::float_size_suffix(size)).into(),
+ Intrinsic::FcvtFToF(usize, fsize, rm) => format!(
+ "_fcvt{}_to{}{}",
+ Self::float_size_suffix(usize),
+ Self::float_size_suffix(fsize),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::FcvtIToF(isize, fsize, rm) => format!(
+ "_fcvt{}_to{}{}",
+ Self::int_size_suffix(isize),
+ Self::float_size_suffix(fsize),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::FcvtFToI(fsize, isize, rm) => format!(
+ "_fcvt{}_to{}{}",
+ Self::float_size_suffix(fsize),
+ Self::int_size_suffix(isize),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::FcvtUToF(usize, fsize, rm) => format!(
+ "_fcvt{}_to{}{}",
+ Self::uint_size_suffix(usize),
+ Self::float_size_suffix(fsize),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::FcvtFToU(fsize, usize, rm) => format!(
+ "_fcvt{}_to{}{}",
+ Self::float_size_suffix(fsize),
+ Self::uint_size_suffix(usize),
+ Self::round_mode_suffix(rm)
+ )
+ .into(),
+ Intrinsic::Fence => "_fence".into(),
+ }
+ }
+
+ fn id(&self) -> u32 {
+ match self.id {
+ Intrinsic::Uret => Self::id_from_parts(0, None, None, None),
+ Intrinsic::Sret => Self::id_from_parts(1, None, None, None),
+ Intrinsic::Mret => Self::id_from_parts(2, None, None, None),
+ Intrinsic::Wfi => Self::id_from_parts(3, None, None, None),
+ Intrinsic::Csrrw => Self::id_from_parts(4, None, None, None),
+ Intrinsic::Csrwr => Self::id_from_parts(5, None, None, None),
+ Intrinsic::Csrrd => Self::id_from_parts(6, None, None, None),
+ Intrinsic::Csrrs => Self::id_from_parts(7, None, None, None),
+ Intrinsic::Csrrc => Self::id_from_parts(8, None, None, None),
+ Intrinsic::Fadd(size, rm) => Self::id_from_parts(9, Some(size), None, Some(rm)),
+ Intrinsic::Fsub(size, rm) => Self::id_from_parts(10, Some(size), None, Some(rm)),
+ Intrinsic::Fmul(size, rm) => Self::id_from_parts(11, Some(size), None, Some(rm)),
+ Intrinsic::Fdiv(size, rm) => Self::id_from_parts(12, Some(size), None, Some(rm)),
+ Intrinsic::Fsqrt(size, rm) => Self::id_from_parts(13, Some(size), None, Some(rm)),
+ Intrinsic::Fsgnj(size) => Self::id_from_parts(14, Some(size), None, None),
+ Intrinsic::Fsgnjn(size) => Self::id_from_parts(15, Some(size), None, None),
+ Intrinsic::Fsgnjx(size) => Self::id_from_parts(16, Some(size), None, None),
+ Intrinsic::Fmin(size) => Self::id_from_parts(17, Some(size), None, None),
+ Intrinsic::Fmax(size) => Self::id_from_parts(18, Some(size), None, None),
+ Intrinsic::Fclass(size) => Self::id_from_parts(19, Some(size), None, None),
+ Intrinsic::FcvtFToF(ssize, dsize, rm) => {
+ Self::id_from_parts(20, Some(ssize), Some(dsize), Some(rm))
+ }
+ Intrinsic::FcvtIToF(isize, fsize, rm) => {
+ Self::id_from_parts(21, Some(isize), Some(fsize), Some(rm))
+ }
+ Intrinsic::FcvtFToI(fsize, isize, rm) => {
+ Self::id_from_parts(22, Some(isize), Some(fsize), Some(rm))
+ }
+ Intrinsic::FcvtUToF(usize, fsize, rm) => {
+ Self::id_from_parts(23, Some(usize), Some(fsize), Some(rm))
+ }
+ Intrinsic::FcvtFToU(fsize, usize, rm) => {
+ Self::id_from_parts(24, Some(usize), Some(fsize), Some(rm))
+ }
+ Intrinsic::Fence => Self::id_from_parts(25, None, None, None),
+ }
+ }
+
+ fn inputs(&self) -> Vec<NameAndType<String>> {
+ match self.id {
+ Intrinsic::Uret | Intrinsic::Sret | Intrinsic::Mret | Intrinsic::Wfi => {
+ vec![]
+ }
+ Intrinsic::Csrrd => {
+ vec![NameAndType::new(
+ "csr".into(),
+ &Type::int(4, false),
+ max_confidence(),
+ )]
+ }
+ Intrinsic::Csrrw | Intrinsic::Csrwr | Intrinsic::Csrrs | Intrinsic::Csrrc => {
+ vec![
+ NameAndType::new("csr".into(), &Type::int(4, false), max_confidence()),
+ NameAndType::new(
+ "value".into(),
+ &Type::int(<D::RegFile as RegFile>::Int::width(), false),
+ min_confidence(),
+ ),
+ ]
+ }
+ Intrinsic::Fadd(size, _)
+ | Intrinsic::Fsub(size, _)
+ | Intrinsic::Fmul(size, _)
+ | Intrinsic::Fdiv(size, _)
+ | Intrinsic::Fsgnj(size)
+ | Intrinsic::Fsgnjn(size)
+ | Intrinsic::Fsgnjx(size)
+ | Intrinsic::Fmin(size)
+ | Intrinsic::Fmax(size) => {
+ vec![
+ NameAndType::new("".into(), &Type::float(size as usize), max_confidence()),
+ NameAndType::new("".into(), &Type::float(size as usize), max_confidence()),
+ ]
+ }
+ Intrinsic::Fsqrt(size, _)
+ | Intrinsic::Fclass(size)
+ | Intrinsic::FcvtFToF(size, _, _)
+ | Intrinsic::FcvtFToI(size, _, _)
+ | Intrinsic::FcvtFToU(size, _, _) => {
+ vec![NameAndType::new(
+ "".into(),
+ &Type::float(size as usize),
+ max_confidence(),
+ )]
+ }
+ Intrinsic::FcvtIToF(size, _, _) => {
+ vec![NameAndType::new(
+ "".into(),
+ &Type::int(size as usize, true),
+ max_confidence(),
+ )]
+ }
+ Intrinsic::FcvtUToF(size, _, _) => {
+ vec![NameAndType::new(
+ "".into(),
+ &Type::int(size as usize, false),
+ max_confidence(),
+ )]
+ }
+ Intrinsic::Fence => {
+ vec![NameAndType::new(
+ "".into(),
+ &Type::int(4, false),
+ min_confidence(),
+ )]
+ }
+ }
+ }
+
+ fn outputs(&self) -> Vec<Conf<Ref<Type>>> {
+ match self.id {
+ Intrinsic::Uret
+ | Intrinsic::Sret
+ | Intrinsic::Mret
+ | Intrinsic::Wfi
+ | Intrinsic::Csrwr
+ | Intrinsic::Fence => {
+ vec![]
+ }
+ Intrinsic::Csrrw | Intrinsic::Csrrd | Intrinsic::Csrrs | Intrinsic::Csrrc => {
+ vec![Conf::new(
+ Type::int(<D::RegFile as RegFile>::Int::width(), false),
+ min_confidence(),
+ )]
+ }
+ Intrinsic::Fadd(size, _)
+ | Intrinsic::Fsub(size, _)
+ | Intrinsic::Fmul(size, _)
+ | Intrinsic::Fdiv(size, _)
+ | Intrinsic::Fsqrt(size, _)
+ | Intrinsic::Fsgnj(size)
+ | Intrinsic::Fsgnjn(size)
+ | Intrinsic::Fsgnjx(size)
+ | Intrinsic::Fmin(size)
+ | Intrinsic::Fmax(size)
+ | Intrinsic::FcvtFToF(_, size, _)
+ | Intrinsic::FcvtIToF(_, size, _)
+ | Intrinsic::FcvtUToF(_, size, _) => {
+ vec![Conf::new(Type::float(size as usize), max_confidence())]
+ }
+ Intrinsic::Fclass(_) => {
+ vec![Conf::new(Type::int(4, false), min_confidence())]
+ }
+ Intrinsic::FcvtFToI(_, size, _) => {
+ vec![Conf::new(Type::int(size as usize, true), max_confidence())]
+ }
+ Intrinsic::FcvtFToU(_, size, _) => {
+ vec![Conf::new(Type::int(size as usize, false), max_confidence())]
+ }
+ }
+ }
+}
+
+struct RiscVArch<D: 'static + RiscVDisassembler + Send + Sync> {
+ handle: CoreArchitecture,
+ custom_handle: CustomArchitectureHandle<RiscVArch<D>>,
+ _dis: PhantomData<D>,
+}
+
+impl<D: 'static + RiscVDisassembler + Send + Sync> architecture::Architecture for RiscVArch<D> {
+ type Handle = CustomArchitectureHandle<Self>;
+
+ type RegisterInfo = Register<D>;
+ type Register = Register<D>;
+ type RegisterStackInfo = UnusedRegisterStackInfo<Self::Register>;
+ type RegisterStack = UnusedRegisterStack<Self::Register>;
+
+ type Flag = UnusedFlag;
+ type FlagWrite = UnusedFlag;
+ type FlagClass = UnusedFlag;
+ type FlagGroup = UnusedFlag;
+
+ type Intrinsic = RiscVIntrinsic<D>;
+
+ fn endianness(&self) -> binaryninja::Endianness {
+ binaryninja::Endianness::LittleEndian
+ }
+
+ fn address_size(&self) -> usize {
+ <D::RegFile as RegFile>::Int::width()
+ }
+
+ fn default_integer_size(&self) -> usize {
+ <D::RegFile as RegFile>::Int::width()
+ }
+
+ fn instruction_alignment(&self) -> usize {
+ use riscv_dis::StandardExtension;
+
+ if D::CompressedExtension::supported() {
+ 2
+ } else {
+ 4
+ }
+ }
+
+ fn max_instr_len(&self) -> usize {
+ 4
+ }
+
+ fn opcode_display_len(&self) -> usize {
+ self.max_instr_len()
+ }
+
+ fn associated_arch_by_addr(&self, _addr: &mut u64) -> CoreArchitecture {
+ self.handle
+ }
+
+ fn instruction_info(&self, data: &[u8], addr: u64) -> Option<InstructionInfo> {
+ use architecture::BranchInfo;
+
+ let (inst_len, op) = match D::decode(addr, data) {
+ Ok(Instr::Rv16(op)) => (2, op),
+ Ok(Instr::Rv32(op)) => (4, op),
+ _ => return None,
+ };
+
+ let mut res = InstructionInfo::new(inst_len, false);
+
+ match op {
+ Op::Jal(ref j) => {
+ let target = addr.wrapping_add(j.imm() as i64 as u64);
+
+ let branch = if j.rd().id() == 0 {
+ BranchInfo::Unconditional(target)
+ } else {
+ BranchInfo::Call(target)
+ };
+
+ res.add_branch(branch, None);
+ }
+ Op::Jalr(ref i) => {
+ // TODO handle the calls with rs1 == 0?
+ if i.rd().id() == 0 {
+ let branch_type = if i.rs1().id() == 1 {
+ BranchInfo::FunctionReturn
+ } else {
+ BranchInfo::Unresolved
+ };
+
+ res.add_branch(branch_type, None);
+ }
+ }
+ Op::Beq(ref b)
+ | Op::Bne(ref b)
+ | Op::Blt(ref b)
+ | Op::Bge(ref b)
+ | Op::BltU(ref b)
+ | Op::BgeU(ref b) => {
+ res.add_branch(BranchInfo::False(addr.wrapping_add(inst_len as u64)), None);
+ res.add_branch(
+ BranchInfo::True(addr.wrapping_add(b.imm() as i64 as u64)),
+ None,
+ );
+ }
+ Op::Ecall => {
+ res.add_branch(BranchInfo::SystemCall, None);
+ }
+ Op::Ebreak => {
+ // TODO is this valid, or should lifting handle this?
+ res.add_branch(BranchInfo::Unresolved, None);
+ }
+ Op::Uret | Op::Sret | Op::Mret => {
+ res.add_branch(BranchInfo::FunctionReturn, None);
+ }
+ _ => {}
+ }
+
+ Some(res)
+ }
+
+ fn instruction_text(
+ &self,
+ data: &[u8],
+ addr: u64,
+ ) -> Option<(usize, Vec<InstructionTextToken>)> {
+ use riscv_dis::Operand;
+ use InstructionTextTokenContents::*;
+
+ let inst = match D::decode(addr, data) {
+ Ok(i) => i,
+ _ => return None,
+ };
+
+ let (inst_len, op) = match inst {
+ Instr::Rv16(op) => (2, op),
+ Instr::Rv32(op) => (4, op),
+ };
+
+ let mut res = Vec::new();
+ let mut mnem = format!("{}", inst.mnem());
+ let mut pad_len = 8usize.saturating_sub(mnem.len());
+ let mut operands = inst.operands();
+
+ // Handle pseudo-instructions. Only single instruction pseudo-instructions are handled.
+ match op {
+ Op::AddI(i) => {
+ // addi zero, zero, 0 => nop
+ if i.rd().id() == 0 && i.rs1().id() == 0 && i.imm() == 0 {
+ mnem = "nop".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.clear();
+ }
+ // addi rd, zero, imm => li rd, imm
+ else if i.rs1().id() == 0 {
+ mnem = "li".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ // addi rd, rs, 0 => mv rd, rs
+ else if i.imm() == 0 {
+ mnem = "mv".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ }
+ }
+ Op::AddIW(i) => {
+ // addiw rd, rs, 0 => sext.w rd, rs
+ if i.imm() == 0 {
+ mnem = "sext.w".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ }
+ }
+ Op::Beq(i) => {
+ // beq rs, zero, offset => beqz rs, offset
+ if i.rs2().id() == 0 {
+ mnem = "beqz".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ }
+ Op::Bne(i) => {
+ // bne rs, zero, offset => bnez rs, offset
+ if i.rs2().id() == 0 {
+ mnem = "bnez".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ }
+ Op::Bge(i) => {
+ // bge zero, rs, offset => blez rs, offset
+ if i.rs1().id() == 0 {
+ mnem = "blez".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(0);
+ }
+ // bge rs, zero, offset => bgez rs, offset
+ else if i.rs2().id() == 0 {
+ mnem = "bgez".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ }
+ Op::Blt(i) => {
+ // blt zero, rs, offset => bgtz rs, offset
+ if i.rs1().id() == 0 {
+ mnem = "bgtz".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(0);
+ }
+ // blt rs, zero, offset => bltz rs, offset
+ else if i.rs2().id() == 0 {
+ mnem = "bltz".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ }
+ Op::Jal(i) => {
+ // jal zero, offset => j offset
+ if i.rd().id() == 0 {
+ mnem = "j".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(0);
+ }
+ // jal ra, offset => jal offset
+ else if i.rd().id() == 0 {
+ operands.remove(0);
+ }
+ }
+ Op::Jalr(i) => {
+ // jalr zero, ra, 0 => ret
+ if i.rd().id() == 0 && i.rs1().id() == 1 && i.imm() == 0 {
+ mnem = "ret".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.clear();
+ }
+ // jalr zero, rs, 0 => jr rs
+ else if i.rd().id() == 0 && i.imm() == 0 {
+ mnem = "jr".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ operands.remove(0);
+ }
+ // jalr ra, rs, 0 => jalr rs
+ else if i.rd().id() == 1 && i.imm() == 0 {
+ mnem = "jalr".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ operands.remove(0);
+ }
+ }
+ Op::Slt(i) => {
+ // slt rd, rs, zero => sltz rd, rs
+ if i.rs2().id() == 0 {
+ mnem = "sltz".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ }
+ // slt rd, zero, rs => sgtz rd, rs
+ else if i.rs1().id() == 0 {
+ mnem = "sgtz".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ }
+ Op::SltU(i) => {
+ // sltu rd, zero, rs => snez rd, rs
+ if i.rs1().id() == 0 {
+ mnem = "snez".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ }
+ Op::SltIU(i) => {
+ // sltiu rd, rs, 1 => seqz rd, rs
+ if i.imm() == 1 {
+ mnem = "seqz".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ }
+ }
+ Op::Sub(i) => {
+ // sub rd, zero, rs => neg rd, rs
+ if i.rs1().id() == 0 {
+ mnem = "neg".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ }
+ Op::SubW(i) => {
+ // subw rd, zero, rs => negw rd, rs
+ if i.rs1().id() == 0 {
+ mnem = "negw".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(1);
+ }
+ }
+ Op::XorI(i) => {
+ // xori rd, rs, -1 => not rd, rs
+ if i.imm() == -1 {
+ mnem = "not".into();
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ }
+ }
+ Op::Fsgnj(i) => {
+ // fsgnj rd, rs, rs => fmv rd, rs
+ if i.rs1().id() == i.rs2().id() {
+ mnem = match i.width() {
+ 4 => "fmv.s".into(),
+ 8 => "fmv.d".into(),
+ 16 => "fmv.q".into(),
+ _ => unreachable!(),
+ };
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ }
+ }
+ Op::Fsgnjn(i) => {
+ // fsgnjn rd, rs, rs => fneg rd, rs
+ if i.rs1().id() == i.rs2().id() {
+ mnem = match i.width() {
+ 4 => "fneg.s".into(),
+ 8 => "fneg.d".into(),
+ 16 => "fneg.q".into(),
+ _ => unreachable!(),
+ };
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ }
+ }
+ Op::Fsgnjx(i) => {
+ // fsgnjx rd, rs, rs => fabs rd, rs
+ if i.rs1().id() == i.rs2().id() {
+ mnem = match i.width() {
+ 4 => "fabs.s".into(),
+ 8 => "fabs.d".into(),
+ 16 => "fabs.q".into(),
+ _ => unreachable!(),
+ };
+ pad_len = 8usize.saturating_sub(mnem.len());
+ operands.remove(2);
+ }
+ }
+ _ => (),
+ }
+
+ res.push(InstructionTextToken::new(BnString::new(mnem), Instruction));
+
+ for (i, oper) in operands.iter().enumerate() {
+ if i == 0 {
+ res.push(InstructionTextToken::new(
+ BnString::new(format!("{:1$}", " ", pad_len)),
+ Text,
+ ));
+ } else {
+ res.push(InstructionTextToken::new(
+ BnString::new(","),
+ OperandSeparator,
+ ));
+ res.push(InstructionTextToken::new(BnString::new(" "), Text));
+ }
+
+ match *oper {
+ Operand::R(r) => {
+ let reg = self::Register::from(r);
+
+ res.push(InstructionTextToken::new(
+ BnString::new(&reg.name()),
+ Register,
+ ));
+ }
+ Operand::F(r) => {
+ let reg = self::Register::from(r);
+
+ res.push(InstructionTextToken::new(
+ BnString::new(&reg.name()),
+ Register,
+ ));
+ }
+ Operand::I(i) => {
+ match op {
+ Op::Beq(..)
+ | Op::Bne(..)
+ | Op::Blt(..)
+ | Op::Bge(..)
+ | Op::BltU(..)
+ | Op::BgeU(..)
+ | Op::Jal(..) => {
+ // BRANCH or JAL
+ let target = addr.wrapping_add(i as i64 as u64);
+
+ res.push(InstructionTextToken::new(
+ BnString::new(format!("0x{:x}", target)),
+ CodeRelativeAddress(target),
+ ));
+ }
+ _ => {
+ res.push(InstructionTextToken::new(
+ BnString::new(match i {
+ -0x8_0000..=-1 => format!("-0x{:x}", -i),
+ _ => format!("0x{:x}", i),
+ }),
+ Integer(i as u64),
+ ));
+ }
+ }
+ }
+ Operand::M(i, b) => {
+ let reg = self::Register::from(b);
+
+ res.push(InstructionTextToken::new(
+ BnString::new(""),
+ BeginMemoryOperand,
+ ));
+ res.push(InstructionTextToken::new(
+ BnString::new(if i < 0 {
+ format!("-0x{:x}", -i)
+ } else {
+ format!("0x{:x}", i)
+ }),
+ Integer(i as u64),
+ ));
+
+ res.push(InstructionTextToken::new(BnString::new("("), Text));
+ res.push(InstructionTextToken::new(
+ BnString::new(&reg.name()),
+ Register,
+ ));
+ res.push(InstructionTextToken::new(BnString::new(")"), Text));
+ res.push(InstructionTextToken::new(
+ BnString::new(""),
+ EndMemoryOperand,
+ ));
+ }
+ Operand::RM(r) => {
+ res.push(InstructionTextToken::new(BnString::new(r.name()), Register));
+ }
+ }
+ }
+
+ Some((inst_len, res))
+ }
+
+ fn instruction_llil(
+ &self,
+ data: &[u8],
+ addr: u64,
+ il: &mut llil::Lifter<Self>,
+ ) -> Option<(usize, bool)> {
+ let max_width = self.default_integer_size();
+
+ let (inst_len, op) = match D::decode(addr, data) {
+ Ok(Instr::Rv16(op)) => (2, op),
+ Ok(Instr::Rv32(op)) => (4, op),
+ _ => return None,
+ };
+
+ macro_rules! set_reg_or_append_fallback {
+ ($op:ident, $t:expr, $f:expr) => {{
+ let rd = Register::from($op.rd());
+ match rd.id {
+ 0 => $f.append(),
+ _ => il.set_reg(rd.size(), rd, $t).append(),
+ }
+ }};
+ }
+
+ macro_rules! simple_op {
+ ($op:ident, no_discard $f:expr) => {{
+ let expr = $f;
+ set_reg_or_append_fallback!($op, expr, expr)
+ }};
+ ($op:ident, $f:expr) => {
+ set_reg_or_append_fallback!($op, $f, il.nop())
+ };
+ }
+
+ macro_rules! simple_i {
+ ($i:ident, $f:expr ) => {{
+ let rs1 = Register::from($i.rs1());
+ simple_op!($i, $f(rs1, $i.imm()))
+ }};
+ }
+
+ macro_rules! simple_r {
+ ($r:ident, $f:expr ) => {{
+ let rs1 = Register::from($r.rs1());
+ let rs2 = Register::from($r.rs2());
+ simple_op!($r, $f(rs1, rs2))
+ }};
+ }
+
+ match op {
+ Op::Load(l) => simple_op!(l, no_discard {
+ let size = l.width();
+ let rs1 = Register::from(l.rs1());
+
+ let src_expr = il.add(max_width, rs1, l.imm());
+ let load_expr = il.load(size, src_expr)
+ .with_source_operand(1);
+
+ match (size < max_width, l.zx()) {
+ (false, _) => load_expr,
+ (true, true) => il.zx(max_width, load_expr).build(),
+ (true, false) => il.sx(max_width, load_expr).build(),
+ }
+ }),
+ Op::Store(s) => {
+ let size = s.width();
+ let dest = il.add(max_width, Register::from(s.rs1()), s.imm());
+ let mut src = il
+ .expression(Register::from(s.rs2()))
+ .with_source_operand(0);
+
+ if size < max_width {
+ src = il.low_part(size, src).build();
+ }
+
+ il.store(size, dest, src).with_source_operand(1).append();
+ }
+
+ Op::AddI(i) => simple_i!(i, |rs1, imm| il.add(max_width, rs1, imm)),
+ Op::SltI(i) => simple_i!(i, |rs1, imm| il
+ .bool_to_int(max_width, il.cmp_slt(max_width, rs1, imm))),
+ Op::SltIU(i) => simple_i!(i, |rs1, imm| il
+ .bool_to_int(max_width, il.cmp_ult(max_width, rs1, imm))),
+ Op::XorI(i) => simple_i!(i, |rs1, imm| il.xor(max_width, rs1, imm)),
+ Op::OrI(i) => simple_i!(i, |rs1, imm| il.or(max_width, rs1, imm)),
+ Op::AndI(i) => simple_i!(i, |rs1, imm| il.and(max_width, rs1, imm)),
+ Op::SllI(i) => simple_i!(i, |rs1, imm| il.lsl(max_width, rs1, imm)),
+ Op::SrlI(i) => simple_i!(i, |rs1, imm| il.lsr(max_width, rs1, imm)),
+ Op::SraI(i) => simple_i!(i, |rs1, imm| il.asr(max_width, rs1, imm)),
+
+ // r-type
+ Op::Add(r) => simple_r!(r, |rs1, rs2| il.add(max_width, rs1, rs2)),
+ Op::Sll(r) => simple_r!(r, |rs1, rs2| il.lsl(max_width, rs1, rs2)),
+ Op::Slt(r) => simple_r!(r, |rs1, rs2| il
+ .bool_to_int(max_width, il.cmp_slt(max_width, rs1, rs2))),
+ Op::SltU(r) => simple_r!(r, |rs1, rs2| il
+ .bool_to_int(max_width, il.cmp_ult(max_width, rs1, rs2))),
+ Op::Xor(r) => simple_r!(r, |rs1, rs2| il.xor(max_width, rs1, rs2)),
+ Op::Srl(r) => simple_r!(r, |rs1, rs2| il.lsr(max_width, rs1, rs2)),
+ Op::Or(r) => simple_r!(r, |rs1, rs2| il.or(max_width, rs1, rs2)),
+ Op::And(r) => simple_r!(r, |rs1, rs2| il.and(max_width, rs1, rs2)),
+ Op::Sub(r) => simple_r!(r, |rs1, rs2| il.sub(max_width, rs1, rs2)),
+ Op::Sra(r) => simple_r!(r, |rs1, rs2| il.asr(max_width, rs1, rs2)),
+
+ // i-type 32-bit
+ Op::AddIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.add(4, rs1, imm))),
+ Op::SllIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.lsl(4, rs1, imm))),
+ Op::SrlIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.lsr(4, rs1, imm))),
+ Op::SraIW(i) => simple_i!(i, |rs1, imm| il.sx(max_width, il.asr(4, rs1, imm))),
+
+ // r-type 32-bit
+ Op::AddW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.add(4, rs1, rs2))),
+ Op::SllW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.lsl(4, rs1, rs2))),
+ Op::SrlW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.lsr(4, rs1, rs2))),
+ Op::SubW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.sub(4, rs1, rs2))),
+ Op::SraW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.asr(4, rs1, rs2))),
+
+ Op::Mul(r) => simple_r!(r, |rs1, rs2| il.mul(max_width, rs1, rs2)),
+ /*
+ Op::MulH(r) =>
+ Op::MulHU(r) =>
+ Op::MulHSU(r) =>
+ */
+ Op::Div(r) => simple_r!(r, |rs1, rs2| il.divs(max_width, rs1, rs2)),
+ Op::DivU(r) => simple_r!(r, |rs1, rs2| il.divu(max_width, rs1, rs2)),
+ Op::Rem(r) => simple_r!(r, |rs1, rs2| il.mods(max_width, rs1, rs2)),
+ Op::RemU(r) => simple_r!(r, |rs1, rs2| il.modu(max_width, rs1, rs2)),
+
+ Op::MulW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.mul(4, rs1, rs2))),
+ Op::DivW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.divs(4, rs1, rs2))),
+ Op::DivUW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.divu(4, rs1, rs2))),
+ Op::RemW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.mods(4, rs1, rs2))),
+ Op::RemUW(r) => simple_r!(r, |rs1, rs2| il.sx(max_width, il.modu(4, rs1, rs2))),
+
+ Op::Lui(u) => simple_op!(u, il.const_int(max_width, u.imm() as i64 as u64)),
+ Op::Auipc(u) => simple_op!(u, il.const_ptr(addr.wrapping_add(u.imm() as i64 as u64))),
+
+ Op::Jal(j) => {
+ let target = addr.wrapping_add(j.imm() as i64 as u64);
+
+ match (j.rd().id(), il.label_for_address(target)) {
+ (0, Some(l)) => il.goto(l),
+ (0, None) => il.jump(il.const_ptr(target)),
+ (_, _) => il.call(il.const_ptr(target)),
+ }
+ .append();
+ }
+ Op::Jalr(i) => {
+ let rd = i.rd();
+ let rs1 = i.rs1();
+ let imm = i.imm();
+
+ let target = il.add(max_width, Register::from(rs1), imm).build();
+
+ match (rd.id(), rs1.id(), imm) {
+ (0, 1, 0) => il.ret(target).append(), // jalr zero, ra, 0
+ (1, _, _) => il.call(target).append(), // indirect call
+ (0, _, _) => il.jump(target).append(), // indirect jump
+ (_, _, _) => {
+ // indirect jump with storage of next address to non-`ra` register
+ il.set_reg(
+ max_width,
+ Register::from(rd),
+ il.const_ptr(addr.wrapping_add(inst_len)),
+ )
+ .append();
+ il.jump(target).append();
+ }
+ }
+ }
+
+ Op::Beq(b) | Op::Bne(b) | Op::Blt(b) | Op::Bge(b) | Op::BltU(b) | Op::BgeU(b) => {
+ let left = Register::from(b.rs1());
+ let right = Register::from(b.rs2());
+
+ let cond_expr = match op {
+ Op::Beq(..) => il.cmp_e(max_width, left, right),
+ Op::Bne(..) => il.cmp_ne(max_width, left, right),
+ Op::Blt(..) => il.cmp_slt(max_width, left, right),
+ Op::Bge(..) => il.cmp_sge(max_width, left, right),
+ Op::BltU(..) => il.cmp_ult(max_width, left, right),
+ Op::BgeU(..) => il.cmp_uge(max_width, left, right),
+ _ => unreachable!(),
+ };
+
+ let mut new_false: Option<Label> = None;
+ let mut new_true: Option<Label> = None;
+
+ let ft = addr.wrapping_add(inst_len);
+ let tt = addr.wrapping_add(b.imm() as i64 as u64);
+
+ {
+ let f = il.label_for_address(ft).unwrap_or_else(|| {
+ new_false = Some(Label::new());
+ new_false.as_ref().unwrap()
+ });
+
+ let t = il.label_for_address(tt).unwrap_or_else(|| {
+ new_true = Some(Label::new());
+ new_true.as_ref().unwrap()
+ });
+
+ il.if_expr(cond_expr, t, f).append();
+ }
+
+ if let Some(t) = new_true.as_mut() {
+ il.mark_label(t);
+
+ il.jump(il.const_ptr(tt)).append();
+ }
+
+ if let Some(f) = new_false.as_mut() {
+ il.mark_label(f);
+ }
+ }
+
+ Op::Ecall => il.syscall().append(),
+ Op::Ebreak => il.bp().append(),
+ Op::Uret => {
+ il.intrinsic(
+ Lifter::<Self>::NO_OUTPUTS,
+ Intrinsic::Uret,
+ Lifter::<Self>::NO_INPUTS,
+ )
+ .append();
+ il.no_ret().append();
+ }
+ Op::Sret => {
+ il.intrinsic(
+ Lifter::<Self>::NO_OUTPUTS,
+ Intrinsic::Sret,
+ Lifter::<Self>::NO_INPUTS,
+ )
+ .append();
+ il.no_ret().append();
+ }
+ Op::Mret => {
+ il.intrinsic(
+ Lifter::<Self>::NO_OUTPUTS,
+ Intrinsic::Mret,
+ Lifter::<Self>::NO_INPUTS,
+ )
+ .append();
+ il.no_ret().append();
+ }
+ Op::Wfi => il
+ .intrinsic(
+ Lifter::<Self>::NO_OUTPUTS,
+ Intrinsic::Wfi,
+ Lifter::<Self>::NO_INPUTS,
+ )
+ .append(),
+ Op::Fence(i) => il
+ .intrinsic(
+ Lifter::<Self>::NO_OUTPUTS,
+ Intrinsic::Fence,
+ [il.const_int(4, i.imm() as u32 as u64)],
+ )
+ .append(),
+
+ Op::Csrrw(i) => {
+ let rd = Register::from(i.rd());
+ let rs1 = Liftable::lift(il, Register::from(i.rs1()));
+ let csr = il.const_int(4, i.imm() as u64);
+
+ if i.rd().id() == 0 {
+ il.intrinsic(Lifter::<Self>::NO_OUTPUTS, Intrinsic::Csrwr, [csr, rs1])
+ .append();
+ } else {
+ il.intrinsic([rd], Intrinsic::Csrrw, [rs1]).append();
+ }
+ }
+ Op::Csrrs(i) => {
+ let rd = Register::from(i.rd());
+ let rs1 = Liftable::lift(il, Register::from(i.rs1()));
+ let csr = il.const_int(4, i.imm() as u64);
+
+ if i.rs1().id() == 0 {
+ il.intrinsic([rd], Intrinsic::Csrrd, [csr]).append();
+ } else {
+ il.intrinsic([rd], Intrinsic::Csrrs, [csr, rs1]).append();
+ }
+ }
+ Op::Csrrc(i) => {
+ let rd = Register::from(i.rd());
+ let rs1 = Liftable::lift(il, Register::from(i.rs1()));
+ let csr = il.const_int(4, i.imm() as u64);
+
+ if i.rs1().id() == 0 {
+ il.intrinsic([rd], Intrinsic::Csrrd, [csr]).append();
+ } else {
+ il.intrinsic([rd], Intrinsic::Csrrc, [csr, rs1]).append();
+ }
+ }
+ Op::CsrrwI(i) => {
+ let rd = Register::from(i.rd());
+ let csr = il.const_int(4, i.csr() as u64);
+ let imm = il.const_int(max_width, i.imm() as u64);
+
+ if i.rd().id() == 0 {
+ il.intrinsic(Lifter::<Self>::NO_OUTPUTS, Intrinsic::Csrwr, [csr, imm])
+ .append();
+ } else {
+ il.intrinsic([rd], Intrinsic::Csrrw, [csr, imm]).append();
+ }
+ }
+ Op::CsrrsI(i) => {
+ let rd = Register::from(i.rd());
+ let csr = il.const_int(4, i.csr() as u64);
+ let imm = il.const_int(max_width, i.imm() as u64);
+
+ if i.imm() == 0 {
+ il.intrinsic([rd], Intrinsic::Csrrd, [csr]).append();
+ } else {
+ il.intrinsic([rd], Intrinsic::Csrrs, [csr, imm]).append();
+ }
+ }
+ Op::CsrrcI(i) => {
+ let rd = Register::from(i.rd());
+ let csr = il.const_int(4, i.csr() as u64);
+ let imm = il.const_int(max_width, i.imm() as u64);
+
+ if i.imm() == 0 {
+ il.intrinsic([rd], Intrinsic::Csrrd, [csr]).append();
+ } else {
+ il.intrinsic([rd], Intrinsic::Csrrc, [csr, imm]).append();
+ }
+ }
+
+ Op::Lr(a) => simple_op!(a, no_discard {
+ let size = a.width();
+ let load_expr = il.load(size, Register::from(a.rs1()))
+ .with_source_operand(1);
+
+ match size == max_width {
+ true => load_expr,
+ false => il.sx(max_width, load_expr).build(),
+ }
+ }),
+ Op::Sc(a) => {
+ let size = a.width();
+ let rd = a.rd();
+
+ let dest_reg = match rd.id() {
+ 0 => llil::Register::Temp(0),
+ _ => Register::from(rd).into(),
+ };
+
+ // set rd (or a temp register) to an indeterminate value,
+ // which signals to the application whether the conditional
+ // store was successful. by clobbering first, we can then
+ // emit conditionals based on its value to lift the conditional
+ // nature of the store -- dataflow will give up
+ il.set_reg(max_width, dest_reg, il.unimplemented()).append();
+
+ let mut new_false: Option<Label> = None;
+ let mut t = Label::new();
+
+ {
+ let cond_expr = il.cmp_e(max_width, dest_reg, 0u64);
+
+ let ft = addr.wrapping_add(inst_len);
+ let f = il.label_for_address(ft).unwrap_or_else(|| {
+ new_false = Some(Label::new());
+ new_false.as_ref().unwrap()
+ });
+
+ il.if_expr(cond_expr, &t, f).append();
+ }
+
+ il.mark_label(&mut t);
+
+ il.store(size, Register::from(a.rs1()), Register::from(a.rs2()))
+ .with_source_operand(2)
+ .append();
+
+ if let Some(f) = new_false.as_mut() {
+ il.mark_label(f);
+ }
+ }
+ Op::AmoSwap(a)
+ | Op::AmoAdd(a)
+ | Op::AmoXor(a)
+ | Op::AmoAnd(a)
+ | Op::AmoOr(a)
+ | Op::AmoMin(a)
+ | Op::AmoMax(a)
+ | Op::AmoMinU(a)
+ | Op::AmoMaxU(a) => {
+ let size = a.width();
+ let rd = a.rd();
+ let rs1 = a.rs1();
+ let rs2 = a.rs2();
+
+ let dest_reg = match rd.id() {
+ 0 => llil::Register::Temp(0),
+ _ => Register::from(rd).into(),
+ };
+
+ let mut next_temp_reg = 1;
+ let mut alloc_reg = |rs: riscv_dis::IntReg<D>| match (rs.id(), rd.id()) {
+ (id, r) if id != 0 && id == r => {
+ let reg = llil::Register::Temp(next_temp_reg);
+ next_temp_reg += 1;
+
+ il.set_reg(max_width, reg, Register::from(rs)).append();
+
+ reg
+ }
+ _ => Register::from(rs).into(),
+ };
+
+ let reg_with_address = alloc_reg(rs1);
+ let reg_with_val = alloc_reg(rs2);
+
+ let mut load_expr = il.load(size, Register::from(rs1)).with_source_operand(2);
+
+ if size < max_width {
+ load_expr = il.sx(max_width, load_expr).build();
+ }
+
+ il.set_reg(max_width, dest_reg, load_expr).append();
+
+ let val_expr = LiftableWithSize::lift_with_size(il, reg_with_val, size);
+ let dest_reg_val = LiftableWithSize::lift_with_size(il, dest_reg, size);
+
+ let val_to_store = match op {
+ Op::AmoSwap(..) => val_expr,
+ Op::AmoAdd(..) => il.add(size, dest_reg_val, val_expr).build(),
+ Op::AmoXor(..) => il.xor(size, dest_reg_val, val_expr).build(),
+ Op::AmoAnd(..) => il.and(size, dest_reg_val, val_expr).build(),
+ Op::AmoOr(..) => il.or(size, dest_reg_val, val_expr).build(),
+ Op::AmoMin(..) | Op::AmoMax(..) | Op::AmoMinU(..) | Op::AmoMaxU(..) => {
+ il.unimplemented()
+ }
+ _ => unreachable!(),
+ };
+
+ il.store(size, reg_with_address, val_to_store).append()
+ }
+
+ Op::LoadFp(m) => {
+ let rd = Register::from(m.fr());
+ let rs1 = Register::from(m.rs1());
+
+ let load_expr = il
+ .load(m.width(), il.add(max_width, rs1, m.imm()))
+ .with_source_operand(1);
+
+ il.set_reg(m.width(), rd, load_expr).append();
+ }
+ Op::StoreFp(m) => {
+ let rs1 = Register::from(m.rs1());
+ let rs2 = Register::from(m.fr());
+
+ let dest_expr = il.add(max_width, rs1, m.imm());
+
+ il.store(m.width(), dest_expr, il.reg(m.width(), rs2))
+ .with_source_operand(1)
+ .append();
+ }
+ Op::Fmadd(f) | Op::Fmsub(f) | Op::Fnmadd(f) | Op::Fnmsub(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rs2 = Register::from(f.rs2());
+ let rs3 = Register::from(f.rs2());
+ let width = f.width() as usize;
+ if f.rm() == RoundMode::Dynamic {
+ let product = il.fmul(width, il.reg(width, rs1), il.reg(width, rs2));
+ let result = match op {
+ Op::Fmadd(..) => il.fadd(width, product, il.reg(width, rs3)),
+ Op::Fmsub(..) => il.fsub(width, product, il.reg(width, rs3)),
+ Op::Fnmadd(..) => {
+ il.fsub(width, il.fneg(width, product), il.reg(width, rs3))
+ }
+ Op::Fnmsub(..) => {
+ il.fadd(width, il.fneg(width, product), il.reg(width, rs3))
+ }
+ _ => unreachable!(),
+ };
+ il.set_reg(width, rd, result).append();
+ } else {
+ let product = llil::Register::Temp(0);
+ il.intrinsic(
+ [product],
+ Intrinsic::Fmul(f.width(), f.rm()),
+ [il.reg(width, rs1), il.reg(width, rs2)],
+ )
+ .append();
+ match op {
+ Op::Fmadd(..) => il
+ .intrinsic(
+ [rd],
+ Intrinsic::Fmul(f.width(), f.rm()),
+ [il.reg(width, product), il.reg(width, rs3)],
+ )
+ .append(),
+ Op::Fmsub(..) => il
+ .intrinsic(
+ [rd],
+ Intrinsic::Fsub(f.width(), f.rm()),
+ [il.reg(width, product), il.reg(width, rs3)],
+ )
+ .append(),
+ Op::Fnmadd(..) => il
+ .intrinsic(
+ [rd],
+ Intrinsic::Fsub(f.width(), f.rm()),
+ [
+ il.fneg(width, il.reg(width, product)).build(),
+ il.reg(width, rs3),
+ ],
+ )
+ .append(),
+ Op::Fnmsub(..) => il
+ .intrinsic(
+ [rd],
+ Intrinsic::Fadd(f.width(), f.rm()),
+ [
+ il.fneg(width, il.reg(width, product)).build(),
+ il.reg(width, rs3),
+ ],
+ )
+ .append(),
+ _ => unreachable!(),
+ };
+ }
+ }
+ Op::Fadd(f) | Op::Fsub(f) | Op::Fmul(f) | Op::Fdiv(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rs2 = Register::from(f.rs2());
+ let width = f.width() as usize;
+ if f.rm() == RoundMode::Dynamic {
+ let result = match op {
+ Op::Fadd(..) => il.fadd(width, il.reg(width, rs1), il.reg(width, rs2)),
+ Op::Fsub(..) => il.fsub(width, il.reg(width, rs1), il.reg(width, rs2)),
+ Op::Fmul(..) => il.fmul(width, il.reg(width, rs1), il.reg(width, rs2)),
+ Op::Fdiv(..) => il.fdiv(width, il.reg(width, rs1), il.reg(width, rs2)),
+ _ => unreachable!(),
+ };
+ il.set_reg(width, rd, result).append();
+ } else {
+ let intrinsic = match op {
+ Op::Fadd(..) => Intrinsic::Fadd(f.width(), f.rm()),
+ Op::Fsub(..) => Intrinsic::Fsub(f.width(), f.rm()),
+ Op::Fmul(..) => Intrinsic::Fmul(f.width(), f.rm()),
+ Op::Fdiv(..) => Intrinsic::Fdiv(f.width(), f.rm()),
+ _ => unreachable!(),
+ };
+ il.intrinsic([rd], intrinsic, [il.reg(width, rs1), il.reg(width, rs2)])
+ .append();
+ }
+ }
+ Op::Fsgnj(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rs2 = Register::from(f.rs2());
+ let width = f.width() as usize;
+ if f.rs1().id() == f.rs2().id() {
+ il.set_reg(width, rd, il.reg(width, rs1)).append();
+ } else {
+ il.intrinsic(
+ [rd],
+ Intrinsic::Fsgnj(f.width()),
+ [il.reg(width, rs1), il.reg(width, rs2)],
+ )
+ .append();
+ }
+ }
+ Op::Fsgnjn(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rs2 = Register::from(f.rs2());
+ let width = f.width() as usize;
+ if f.rs1().id() == f.rs2().id() {
+ il.set_reg(width, rd, il.fneg(width, il.reg(width, rs1)))
+ .append();
+ } else {
+ il.intrinsic(
+ [rd],
+ Intrinsic::Fsgnjn(f.width()),
+ [il.reg(width, rs1), il.reg(width, rs2)],
+ )
+ .append();
+ }
+ }
+ Op::Fsgnjx(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rs2 = Register::from(f.rs2());
+ let width = f.width() as usize;
+ if f.rs1().id() == f.rs2().id() {
+ il.set_reg(width, rd, il.fabs(width, il.reg(width, rs1)))
+ .append();
+ } else {
+ il.intrinsic(
+ [rd],
+ Intrinsic::Fsgnjx(f.width()),
+ [il.reg(width, rs1), il.reg(width, rs2)],
+ )
+ .append();
+ }
+ }
+ Op::Fsqrt(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let width = f.width() as usize;
+ let result = il.fsqrt(width, il.reg(width, rs1));
+ il.set_reg(width, rd, result).append();
+ }
+ Op::Fmin(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rs2 = Register::from(f.rs2());
+ let width = f.width() as usize;
+ il.intrinsic(
+ [rd],
+ Intrinsic::Fmin(f.width()),
+ [il.reg(width, rs1), il.reg(width, rs2)],
+ )
+ .append();
+ }
+ Op::Fmax(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rs2 = Register::from(f.rs2());
+ let width = f.width() as usize;
+ il.intrinsic(
+ [rd],
+ Intrinsic::Fmax(f.width()),
+ [il.reg(width, rs1), il.reg(width, rs2)],
+ )
+ .append();
+ }
+ Op::Fle(f) | Op::Flt(f) | Op::Feq(f) => {
+ let rd = match f.rd().id() {
+ 0 => llil::Register::Temp(0),
+ _ => Register::from(f.rd()).into(),
+ };
+ let left = Register::from(f.rs1());
+ let right = Register::from(f.rs2());
+ let width = f.width() as usize;
+ let cond_expr = match op {
+ Op::Fle(..) => il.fcmp_le(width, il.reg(width, left), il.reg(width, right)),
+ Op::Flt(..) => il.fcmp_lt(width, il.reg(width, left), il.reg(width, right)),
+ Op::Feq(..) => il.fcmp_e(width, il.reg(width, left), il.reg(width, right)),
+ _ => unreachable!(),
+ };
+ let result = il.bool_to_int(max_width, cond_expr);
+ il.set_reg(max_width, rd, result).append();
+ }
+ Op::Fcvt(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rd_width = f.rd_width() as usize;
+ let rs1_width = f.rs1_width() as usize;
+ if f.rm() == RoundMode::Dynamic {
+ let src = il.float_conv(rd_width, il.reg(rs1_width, rs1));
+ il.set_reg(rd_width, rd, src).append();
+ } else {
+ il.intrinsic(
+ [rd],
+ Intrinsic::FcvtFToF(f.rs1_width(), f.rd_width(), f.rm()),
+ [il.reg(rs1_width, rs1)],
+ )
+ .append();
+ }
+ }
+ Op::FcvtToInt(f) => {
+ let rd = match f.rd().id() {
+ 0 => llil::Register::Temp(0),
+ _ => Register::from(f.rd()).into(),
+ };
+ let rs1 = Register::from(f.rs1());
+ let rd_width = f.rd_width() as usize;
+ let rs1_width = f.rs1_width() as usize;
+ if f.zx() {
+ il.intrinsic(
+ [rd],
+ Intrinsic::FcvtFToU(f.rs1_width(), f.rd_width(), f.rm()),
+ [il.reg(rs1_width, rs1)],
+ )
+ .append();
+ } else if f.rm() != RoundMode::Dynamic {
+ il.intrinsic(
+ [rd],
+ Intrinsic::FcvtFToI(f.rs1_width(), f.rd_width(), f.rm()),
+ [il.reg(rs1_width, rs1)],
+ )
+ .append();
+ } else {
+ let conv = il.float_to_int(rd_width, il.reg(rs1_width, rs1));
+ let src = if rd_width < max_width {
+ il.sx(max_width, conv)
+ } else {
+ conv
+ };
+ il.set_reg(max_width, rd, src).append();
+ }
+ }
+ Op::FcvtFromInt(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let rd_width = f.rd_width() as usize;
+ let rs1_width = f.rs1_width() as usize;
+ let rs1 = LiftableWithSize::lift_with_size(il, rs1, rs1_width);
+ if f.zx() {
+ il.intrinsic(
+ [rd],
+ Intrinsic::FcvtUToF(f.rs1_width(), f.rd_width(), f.rm()),
+ [rs1],
+ )
+ .append();
+ } else if f.rm() != RoundMode::Dynamic {
+ il.intrinsic(
+ [rd],
+ Intrinsic::FcvtIToF(f.rs1_width(), f.rd_width(), f.rm()),
+ [rs1],
+ )
+ .append();
+ } else {
+ il.set_reg(rd_width, rd, il.int_to_float(rd_width, rs1))
+ .append();
+ }
+ }
+ Op::FmvToInt(f) => {
+ let rd = match f.rd().id() {
+ 0 => llil::Register::Temp(0),
+ _ => Register::from(f.rd()).into(),
+ };
+ let rs1 = Register::from(f.rs1());
+ let width = f.width() as usize;
+ let src = if width < max_width {
+ il.sx(max_width, il.reg(width, rs1)).build()
+ } else {
+ il.reg(width, rs1)
+ };
+ il.set_reg(max_width, rd, src).append();
+ }
+ Op::FmvFromInt(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let width = f.width() as usize;
+ let rs1 = LiftableWithSize::lift_with_size(il, rs1, width);
+ il.set_reg(width, rd, rs1).append();
+ }
+ Op::Fclass(f) => {
+ let rd = Register::from(f.rd());
+ let rs1 = Register::from(f.rs1());
+ let width = f.width() as usize;
+ il.intrinsic([rd], Intrinsic::Fclass(f.width()), [il.reg(width, rs1)])
+ .append();
+ }
+
+ _ => il.unimplemented().append(),
+ };
+
+ Some((inst_len as usize, true))
+ }
+
+ fn registers_all(&self) -> Vec<Self::Register> {
+ let mut reg_count = <D::RegFile as RegFile>::int_reg_count();
+
+ if <D::RegFile as RegFile>::Float::present() {
+ reg_count += 32;
+ }
+
+ let mut res = Vec::with_capacity(reg_count as usize);
+
+ for i in 0..reg_count {
+ res.push(Register::new(i));
+ }
+
+ res
+ }
+
+ fn registers_full_width(&self) -> Vec<Self::Register> {
+ self.registers_all()
+ }
+
+ fn registers_global(&self) -> Vec<Self::Register> {
+ let mut regs = Vec::with_capacity(2);
+
+ for i in &[3, 4] {
+ regs.push(Register::new(*i));
+ }
+
+ regs
+ }
+
+ fn stack_pointer_reg(&self) -> Option<Self::Register> {
+ Some(Register::new(2))
+ }
+
+ fn link_reg(&self) -> Option<Self::Register> {
+ Some(Register::new(1))
+ }
+
+ fn register_from_id(&self, id: u32) -> Option<Self::Register> {
+ let mut reg_count = <D::RegFile as RegFile>::int_reg_count();
+
+ if <D::RegFile as RegFile>::Float::present() {
+ reg_count += 32;
+ }
+
+ if id > reg_count {
+ None
+ } else {
+ Some(Register::new(id))
+ }
+ }
+
+ fn intrinsics(&self) -> Vec<Self::Intrinsic> {
+ let mut res = Vec::new();
+
+ res.extend_from_slice(&[
+ Intrinsic::Uret,
+ Intrinsic::Sret,
+ Intrinsic::Mret,
+ Intrinsic::Wfi,
+ Intrinsic::Csrrw,
+ Intrinsic::Csrwr,
+ Intrinsic::Csrrd,
+ Intrinsic::Csrrs,
+ Intrinsic::Csrrc,
+ ]);
+
+ if <D::RegFile as RegFile>::Float::present() {
+ let mut float_sizes = vec![4];
+ if <D::RegFile as RegFile>::Float::width() >= 8 {
+ float_sizes.push(8);
+ }
+ if <D::RegFile as RegFile>::Float::width() >= 16 {
+ float_sizes.push(16);
+ }
+ let mut int_sizes = vec![4];
+ if <D::RegFile as RegFile>::Int::width() >= 8 {
+ int_sizes.push(8);
+ }
+
+ for fsize in &float_sizes {
+ res.extend_from_slice(&[
+ Intrinsic::Fsgnj(*fsize),
+ Intrinsic::Fsgnjn(*fsize),
+ Intrinsic::Fsgnjx(*fsize),
+ Intrinsic::Fmin(*fsize),
+ Intrinsic::Fmax(*fsize),
+ Intrinsic::Fclass(*fsize),
+ ]);
+ for rm in RoundMode::all() {
+ if *rm != RoundMode::Dynamic {
+ res.extend_from_slice(&[
+ Intrinsic::Fadd(*fsize, *rm),
+ Intrinsic::Fsub(*fsize, *rm),
+ Intrinsic::Fmul(*fsize, *rm),
+ Intrinsic::Fdiv(*fsize, *rm),
+ Intrinsic::Fsqrt(*fsize, *rm),
+ ]);
+ }
+ }
+ for dsize in &float_sizes {
+ if fsize != dsize {
+ for rm in RoundMode::all() {
+ if *rm != RoundMode::Dynamic {
+ res.push(Intrinsic::FcvtFToF(*fsize, *dsize, *rm));
+ }
+ }
+ }
+ }
+ for isize in &int_sizes {
+ for rm in RoundMode::all() {
+ res.push(Intrinsic::FcvtFToU(*fsize, *isize, *rm));
+ res.push(Intrinsic::FcvtUToF(*isize, *fsize, *rm));
+ if *rm != RoundMode::Dynamic {
+ res.push(Intrinsic::FcvtFToI(*fsize, *isize, *rm));
+ res.push(Intrinsic::FcvtIToF(*isize, *fsize, *rm));
+ }
+ }
+ }
+ }
+ }
+
+ res.iter().map(|i| (*i).into()).collect()
+ }
+
+ fn intrinsic_from_id(&self, id: u32) -> Option<Self::Intrinsic> {
+ RiscVIntrinsic::from_id(id)
+ }
+
+ fn can_assemble(&self) -> bool {
+ true
+ }
+
+ fn assemble(&self, code: &str, _addr: u64) -> Result<Vec<u8>, String> {
+ // FIXME: This does not support any instructions outside the very basic RV32I/RV64I instruction set.
+ // It is completely undocumented how to tell LLVM to accept the additional extensions, and may
+ // require core changes to enable.
+ let arch_triple = if <D::RegFile as RegFile>::Int::width() == 4 {
+ "riscv32-none-none"
+ } else {
+ "riscv64-none-none"
+ };
+
+ llvm_assemble(
+ code,
+ LlvmServicesDialect::Unspecified,
+ arch_triple,
+ LlvmServicesCodeModel::Default,
+ LlvmServicesRelocMode::Static,
+ )
+ }
+
+ fn is_never_branch_patch_available(&self, data: &[u8], addr: u64) -> bool {
+ let op = match D::decode(addr, data) {
+ Ok(Instr::Rv16(op)) => op,
+ Ok(Instr::Rv32(op)) => op,
+ _ => return false,
+ };
+
+ match op {
+ Op::Beq(_) | Op::Bne(_) | Op::Blt(_) | Op::Bge(_) | Op::BltU(_) | Op::BgeU(_) => true,
+ _ => false,
+ }
+ }
+
+ fn is_always_branch_patch_available(&self, data: &[u8], addr: u64) -> bool {
+ self.is_never_branch_patch_available(data, addr)
+ }
+
+ fn is_invert_branch_patch_available(&self, data: &[u8], addr: u64) -> bool {
+ self.is_never_branch_patch_available(data, addr)
+ }
+
+ fn is_skip_and_return_zero_patch_available(&self, data: &[u8], addr: u64) -> bool {
+ let op = match D::decode(addr, data) {
+ Ok(Instr::Rv16(op)) => op,
+ Ok(Instr::Rv32(op)) => op,
+ _ => return false,
+ };
+
+ match op {
+ Op::Jal(ref j) if j.rd().id() != 0 => true,
+ Op::Jalr(ref j) if j.rd().id() != 0 => true,
+ _ => false,
+ }
+ }
+
+ fn is_skip_and_return_value_patch_available(&self, data: &[u8], addr: u64) -> bool {
+ self.is_skip_and_return_zero_patch_available(data, addr)
+ }
+
+ fn convert_to_nop(&self, mut data: &mut [u8], _addr: u64) -> bool {
+ if data.len() & 1 != 0 {
+ // If not aligned on 16 bit boundary, can't convert to nop
+ return false;
+ }
+
+ while data.len() > 0 {
+ if data.len() >= 4 {
+ // If more than 4 bytes left, use uncompressed nop
+ data[0..4].copy_from_slice(&[0x13, 0x00, 0x00, 0x00]);
+ data = data[4..].as_mut();
+ } else {
+ // If only 2 bytes left, use compressed nop
+ data[0..2].copy_from_slice(&[0x01, 0x00]);
+ data = data[2..].as_mut();
+ }
+ }
+
+ true
+ }
+
+ fn always_branch(&self, data: &mut [u8], addr: u64) -> bool {
+ let op = match D::decode(addr, data) {
+ Ok(Instr::Rv16(_)) => return false,
+ Ok(Instr::Rv32(op)) => op,
+ _ => return false,
+ };
+
+ if data.len() < 4 {
+ return false;
+ }
+
+ match op {
+ Op::Beq(ref b)
+ | Op::Bne(ref b)
+ | Op::Blt(ref b)
+ | Op::Bge(ref b)
+ | Op::BltU(ref b)
+ | Op::BgeU(ref b) => {
+ let offset = b.imm() as u32;
+ let opcode = ((offset >> 20) & 1) << 31
+ | ((offset >> 1) & 0x3ff) << 21
+ | ((offset >> 11) & 1) << 20
+ | ((offset >> 12) & 0xff) << 12
+ | 0b1101111;
+ data[0..4].copy_from_slice(&opcode.to_le_bytes());
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn invert_branch(&self, data: &mut [u8], addr: u64) -> bool {
+ let op = match D::decode(addr, data) {
+ Ok(Instr::Rv16(_)) => return false,
+ Ok(Instr::Rv32(op)) => op,
+ _ => return false,
+ };
+
+ if data.len() < 4 {
+ return false;
+ }
+
+ match op {
+ Op::Beq(_) | Op::Bne(_) | Op::Blt(_) | Op::Bge(_) | Op::BltU(_) | Op::BgeU(_) => {
+ data[1] ^= 0x10;
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn skip_and_return_value(&self, data: &mut [u8], addr: u64, value: u64) -> bool {
+ let (instr_len, op) = match D::decode(addr, data) {
+ Ok(Instr::Rv16(op)) => (2, op),
+ Ok(Instr::Rv32(op)) => (4, op),
+ _ => return false,
+ };
+
+ if data.len() < instr_len {
+ return false;
+ }
+
+ let valid = match op {
+ Op::Jal(ref j) if j.rd().id() != 0 => true,
+ Op::Jalr(ref j) if j.rd().id() != 0 => true,
+ _ => false,
+ };
+ if !valid {
+ return false;
+ }
+
+ let signed_value = if <D::RegFile as RegFile>::Int::width() == 4 {
+ value as i32 as i64
+ } else {
+ value as i64
+ };
+
+ match instr_len {
+ 2 => {
+ if signed_value < -0x20 || signed_value > 0x1f {
+ return false;
+ }
+ let opcode = (0b010 << 13)
+ | (signed_value as u16 & 0x10) << 7
+ | (0b01010 << 7)
+ | (signed_value as u16 & 0xf) << 2
+ | 0b01;
+ data[0..2].copy_from_slice(&opcode.to_le_bytes());
+ }
+ 4 => {
+ if signed_value < -0x800 || signed_value > 0x7ff {
+ return false;
+ }
+ let opcode = (signed_value as u32 & 0xfff) << 20 | 0b00000_000_01010_0010011;
+ data[0..4].copy_from_slice(&opcode.to_le_bytes());
+ }
+ _ => unreachable!(),
+ }
+
+ true
+ }
+
+ fn handle(&self) -> CustomArchitectureHandle<Self> {
+ self.custom_handle
+ }
+}
+
+impl<D: 'static + RiscVDisassembler + Send + Sync> AsRef<CoreArchitecture> for RiscVArch<D> {
+ fn as_ref(&self) -> &CoreArchitecture {
+ &self.handle
+ }
+}
+
+struct RiscVELFRelocationHandler<D: 'static + RiscVDisassembler + Send + Sync> {
+ handle: CoreRelocationHandler,
+ custom_handle: CustomRelocationHandlerHandle<Self>,
+ _dis: PhantomData<D>,
+}
+
+impl<D: 'static + RiscVDisassembler + Send + Sync> RiscVELFRelocationHandler<D> {
+ const R_RISCV_NONE: u64 = 0;
+ const R_RISCV_32: u64 = 1;
+ const R_RISCV_64: u64 = 2;
+ const R_RISCV_RELATIVE: u64 = 3;
+ const R_RISCV_COPY: u64 = 4;
+ const R_RISCV_JUMP_SLOT: u64 = 5;
+}
+
+impl<D: 'static + RiscVDisassembler + Send + Sync> RelocationHandler
+ for RiscVELFRelocationHandler<D>
+{
+ type Handle = CustomRelocationHandlerHandle<Self>;
+
+ fn get_relocation_info(
+ &self,
+ _bv: &BinaryView,
+ _arch: &CoreArchitecture,
+ info: &mut [RelocationInfo],
+ ) -> bool {
+ for reloc in info.iter_mut() {
+ reloc.type_ = RelocationType::StandardRelocationType;
+ match reloc.native_type {
+ Self::R_RISCV_NONE => reloc.type_ = RelocationType::IgnoredRelocation,
+ Self::R_RISCV_32 => {
+ reloc.pc_relative = false;
+ reloc.base_relative = false;
+ reloc.has_sign = false;
+ reloc.size = 4;
+ reloc.truncate_size = 4;
+ }
+ Self::R_RISCV_64 => {
+ reloc.pc_relative = false;
+ reloc.base_relative = false;
+ reloc.has_sign = false;
+ reloc.size = 8;
+ reloc.truncate_size = 8;
+ }
+ Self::R_RISCV_RELATIVE => {
+ reloc.pc_relative = false;
+ reloc.base_relative = true;
+ reloc.has_sign = false;
+ reloc.size = <D::RegFile as RegFile>::Int::width();
+ reloc.truncate_size = <D::RegFile as RegFile>::Int::width();
+ }
+ Self::R_RISCV_COPY => {
+ reloc.type_ = RelocationType::ELFCopyRelocationType;
+ reloc.size = <D::RegFile as RegFile>::Int::width();
+ }
+ Self::R_RISCV_JUMP_SLOT => {
+ reloc.type_ = RelocationType::ELFJumpSlotRelocationType;
+ reloc.size = <D::RegFile as RegFile>::Int::width();
+ }
+ _ => {
+ reloc.type_ = RelocationType::UnhandledRelocation;
+ log::warn!(
+ "Unknown relocation type {:?} at {:x?}",
+ reloc.native_type,
+ reloc.address
+ )
+ }
+ }
+ }
+ true
+ }
+
+ fn handle(&self) -> Self::Handle {
+ self.custom_handle
+ }
+}
+
+impl<D: 'static + RiscVDisassembler + Send + Sync> AsRef<CoreRelocationHandler>
+ for RiscVELFRelocationHandler<D>
+{
+ fn as_ref(&self) -> &CoreRelocationHandler {
+ &self.handle
+ }
+}
+
+struct RiscVCC<D: 'static + RiscVDisassembler + Send + Sync> {
+ _dis: PhantomData<D>,
+}
+
+impl<D: 'static + RiscVDisassembler + Send + Sync> RiscVCC<D> {
+ fn new() -> Self {
+ RiscVCC { _dis: PhantomData }
+ }
+}
+
+impl<D: 'static + RiscVDisassembler + Send + Sync> CallingConventionBase for RiscVCC<D> {
+ type Arch = RiscVArch<D>;
+
+ fn caller_saved_registers(&self) -> Vec<Register<D>> {
+ let mut regs = Vec::with_capacity(36);
+ let int_reg_count = <D::RegFile as RegFile>::int_reg_count();
+
+ for i in &[
+ 1u32, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 28, 29, 30, 31,
+ ] {
+ if i < &int_reg_count {
+ regs.push(Register::new(*i));
+ }
+ }
+
+ if <D::RegFile as RegFile>::Float::present() {
+ for i in &[
+ 0u32, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 28, 29, 30, 31,
+ ] {
+ regs.push(Register::new(*i + int_reg_count));
+ }
+ }
+
+ regs
+ }
+
+ fn callee_saved_registers(&self) -> Vec<Register<D>> {
+ let mut regs = Vec::with_capacity(24);
+ let int_reg_count = <D::RegFile as RegFile>::int_reg_count();
+
+ for i in &[8u32, 9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27] {
+ if i < &int_reg_count {
+ regs.push(Register::new(*i));
+ }
+ }
+
+ if <D::RegFile as RegFile>::Float::present() {
+ for i in &[8u32, 9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27] {
+ regs.push(Register::new(*i + int_reg_count));
+ }
+ }
+
+ regs
+ }
+
+ fn int_arg_registers(&self) -> Vec<Register<D>> {
+ let mut regs = Vec::with_capacity(8);
+ let int_reg_count = <D::RegFile as RegFile>::int_reg_count();
+
+ for i in &[10, 11, 12, 13, 14, 15, 16, 17] {
+ if i < &int_reg_count {
+ regs.push(Register::new(*i));
+ }
+ }
+
+ regs
+ }
+
+ fn float_arg_registers(&self) -> Vec<Register<D>> {
+ let mut regs = Vec::with_capacity(8);
+
+ if <D::RegFile as RegFile>::Float::present() {
+ let int_reg_count = <D::RegFile as RegFile>::int_reg_count();
+ for i in &[10, 11, 12, 13, 14, 15, 16, 17] {
+ regs.push(Register::new(*i + int_reg_count));
+ }
+ }
+
+ regs
+ }
+
+ fn arg_registers_shared_index(&self) -> bool {
+ false
+ }
+
+ fn reserved_stack_space_for_arg_registers(&self) -> bool {
+ false
+ }
+ fn stack_adjusted_on_return(&self) -> bool {
+ false
+ }
+
+ fn is_eligible_for_heuristics(&self) -> bool {
+ true
+ }
+
+ // a0 == x10
+ fn return_int_reg(&self) -> Option<Register<D>> {
+ Some(Register::new(10))
+ }
+ // a1 == x11
+ fn return_hi_int_reg(&self) -> Option<Register<D>> {
+ Some(Register::new(11))
+ }
+
+ fn return_float_reg(&self) -> Option<Register<D>> {
+ if <D::RegFile as RegFile>::Float::present() {
+ let int_reg_count = <D::RegFile as RegFile>::int_reg_count();
+ Some(Register::new(10 + int_reg_count))
+ } else {
+ None
+ }
+ }
+
+ // gp == x3
+ fn global_pointer_reg(&self) -> Option<Register<D>> {
+ Some(Register::new(3))
+ }
+
+ fn implicitly_defined_registers(&self) -> Vec<Register<D>> {
+ Vec::new()
+ }
+ fn are_argument_registers_used_for_var_args(&self) -> bool {
+ true
+ }
+}
+
+struct RiscVELFPLTRecognizer;
+
+impl FunctionRecognizer for RiscVELFPLTRecognizer {
+ fn recognize_low_level_il(
+ &self,
+ bv: &BinaryView,
+ func: &Function,
+ llil: &llil::RegularFunction<CoreArchitecture>,
+ ) -> bool {
+ // Look for the following code pattern:
+ // t3 = plt
+ // t3 = [t3 + pltoffset] || t3 = [t3]
+ // t1 = next instruction
+ // jump(t3)
+
+ if llil.instruction_count() < 4 {
+ return false;
+ }
+
+ // Match instruction that fetches PC-relative PLT address range
+ let auipc = llil.instruction_from_idx(0).info();
+ let (auipc_dest, plt_base) = match auipc {
+ InstrInfo::SetReg(r) => {
+ let value = match r.source_expr().info() {
+ ExprInfo::Const(v) | ExprInfo::ConstPtr(v) => v.value(),
+ _ => return false,
+ };
+ (r.dest_reg(), value)
+ }
+ _ => return false,
+ };
+
+ // Match load instruction that loads the imported address
+ let load = llil.instruction_from_idx(1).info();
+ let (mut entry, target_reg) = match load {
+ InstrInfo::SetReg(r) => match r.source_expr().info() {
+ ExprInfo::Load(l) => {
+ let target_reg = r.dest_reg();
+ let entry = match l.source_mem_expr().info() {
+ ExprInfo::Reg(lr) if lr.source_reg() == auipc_dest => plt_base,
+ ExprInfo::Add(a) => match (a.left().info(), a.right().info()) {
+ (ExprInfo::Reg(a), ExprInfo::Const(b) | ExprInfo::ConstPtr(b))
+ if a.source_reg() == auipc_dest =>
+ {
+ plt_base.wrapping_add(b.value())
+ }
+ (ExprInfo::Const(b) | ExprInfo::ConstPtr(b), ExprInfo::Reg(a))
+ if a.source_reg() == auipc_dest =>
+ {
+ plt_base.wrapping_add(b.value())
+ }
+ _ => return false,
+ },
+ ExprInfo::Sub(a) => match (a.left().info(), a.right().info()) {
+ (ExprInfo::Reg(a), ExprInfo::Const(b) | ExprInfo::ConstPtr(b))
+ if a.source_reg() == auipc_dest =>
+ {
+ plt_base.wrapping_sub(b.value())
+ }
+ _ => return false,
+ },
+ _ => return false,
+ };
+ (entry, target_reg)
+ }
+ _ => return false,
+ },
+ _ => return false,
+ };
+ if func.arch().address_size() == 4 {
+ entry = entry as u32 as u64;
+ }
+
+ // Ensure that load is pointing at an import address
+ let sym = match bv.symbol_by_address(entry) {
+ Ok(sym) => sym,
+ Err(_) => return false,
+ };
+ if sym.sym_type() != SymbolType::ImportAddress {
+ return false;
+ }
+
+ // Match instruction that stores the next instruction address into a register
+ let next_pc_inst = llil.instruction_from_idx(2).info();
+ let (next_pc_dest, next_pc, cur_pc) = match next_pc_inst {
+ InstrInfo::SetReg(r) => {
+ let value = match r.source_expr().info() {
+ ExprInfo::Const(v) | ExprInfo::ConstPtr(v) => v.value(),
+ _ => return false,
+ };
+ (r.dest_reg(), value, r.address())
+ }
+ _ => return false,
+ };
+ if next_pc != cur_pc + 4 || next_pc_dest == target_reg {
+ return false;
+ }
+
+ // Match tail call at the end and make sure it is going to the import
+ let jump = llil.instruction_from_idx(3).info();
+ match jump {
+ InstrInfo::TailCall(j) => {
+ match j.target().info() {
+ ExprInfo::Reg(r) if r.source_reg() == target_reg => (),
+ _ => return false,
+ };
+ }
+ InstrInfo::Jump(j) => {
+ match j.target().info() {
+ ExprInfo::Reg(r) if r.source_reg() == target_reg => (),
+ _ => return false,
+ };
+ }
+ _ => return false,
+ }
+
+ let func_sym =
+ Symbol::imported_function_from_import_address_symbol(sym.as_ref(), func.start());
+ bv.define_auto_symbol(func_sym.as_ref());
+ func.apply_imported_types(func_sym.as_ref(), None);
+ true
+ }
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn CorePluginInit() -> bool {
+ binaryninja::logger::init(log::LevelFilter::Trace).expect("Failed to set up logging");
+
+ use riscv_dis::{RiscVIMACDisassembler, Rv32GRegs, Rv64GRegs};
+ let arch32 =
+ architecture::register_architecture("rv32gc", |custom_handle, core_arch| RiscVArch::<
+ RiscVIMACDisassembler<Rv32GRegs>,
+ > {
+ handle: core_arch,
+ custom_handle,
+ _dis: PhantomData,
+ });
+ let arch64 =
+ architecture::register_architecture("rv64gc", |custom_handle, core_arch| RiscVArch::<
+ RiscVIMACDisassembler<Rv64GRegs>,
+ > {
+ handle: core_arch,
+ custom_handle,
+ _dis: PhantomData,
+ });
+
+ arch32.register_relocation_handler("ELF", |custom_handle, core_handler| {
+ RiscVELFRelocationHandler::<RiscVIMACDisassembler<Rv32GRegs>> {
+ handle: core_handler,
+ custom_handle,
+ _dis: PhantomData,
+ }
+ });
+ arch64.register_relocation_handler("ELF", |custom_handle, core_handler| {
+ RiscVELFRelocationHandler::<RiscVIMACDisassembler<Rv64GRegs>> {
+ handle: core_handler,
+ custom_handle,
+ _dis: PhantomData,
+ }
+ });
+
+ arch32.register_function_recognizer(RiscVELFPLTRecognizer);
+ arch64.register_function_recognizer(RiscVELFPLTRecognizer);
+
+ let cc32 = register_calling_convention(arch32, "default", RiscVCC::new());
+ arch32.set_default_calling_convention(&cc32);
+ let cc64 = register_calling_convention(arch64, "default", RiscVCC::new());
+ arch64.set_default_calling_convention(&cc64);
+
+ if let Ok(bvt) = BinaryViewType::by_name("ELF") {
+ bvt.register_arch(
+ (1 << 16) | 243,
+ binaryninja::Endianness::LittleEndian,
+ arch32,
+ );
+ bvt.register_arch(
+ (2 << 16) | 243,
+ binaryninja::Endianness::LittleEndian,
+ arch64,
+ );
+ }
+
+ let plat32 = arch32.standalone_platform().unwrap();
+ let plat64 = arch64.standalone_platform().unwrap();
+
+ let syscall_cc32 = ConventionBuilder::new(arch32)
+ .caller_saved_registers(&[
+ "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "a0", "a1", "a2", "a3", "a4", "a5",
+ "a6", "a7",
+ ])
+ .int_arg_registers(&["a7", "a0", "a1", "a2", "a3", "a4", "a5", "a6"])
+ .return_int_reg("a0")
+ .return_hi_int_reg("a1")
+ .global_pointer_reg("gp")
+ .implicitly_defined_registers(&["gp", "tp"])
+ .register("syscall");
+ let syscall_cc64 = ConventionBuilder::new(arch64)
+ .caller_saved_registers(&[
+ "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "a0", "a1", "a2", "a3", "a4", "a5",
+ "a6", "a7",
+ ])
+ .int_arg_registers(&["a7", "a0", "a1", "a2", "a3", "a4", "a5", "a6"])
+ .return_int_reg("a0")
+ .return_hi_int_reg("a1")
+ .global_pointer_reg("gp")
+ .implicitly_defined_registers(&["gp", "tp"])
+ .register("syscall");
+
+ plat32.set_syscall_convention(&syscall_cc32);
+ plat64.set_syscall_convention(&syscall_cc64);
+
+ true
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "C" fn CorePluginDependencies() {
+ add_optional_plugin_dependency("view_elf");
+}
diff --git a/arch/riscv/src/liftcheck.rs b/arch/riscv/src/liftcheck.rs
new file mode 100644
index 00000000..f59b25e5
--- /dev/null
+++ b/arch/riscv/src/liftcheck.rs
@@ -0,0 +1,427 @@
+use binaryninja::architecture::{Architecture, CoreArchitecture, Flag, Register, RegisterInfo};
+use binaryninja::binaryview::{BinaryView, BinaryViewExt};
+use binaryninja::command;
+use binaryninja::function::Function;
+use binaryninja::llil;
+use binaryninja::llil::{
+ Expression, Finalized, Instruction, LiftedNonSSA, NonSSA, ValueExpr, VisitorAction,
+};
+
+fn check_expression(expr: &Expression<CoreArchitecture, Finalized, NonSSA<LiftedNonSSA>, ValueExpr>,
+ required_size: Option<usize>)
+{
+ use llil::ExprInfo::*;
+
+ match expr.info() {
+ Reg(ref op) => {
+ let size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != size {
+ error!("LLIL_REG op gives a {} byte value where {} bytes are expected! (addr: {:x} {:?})",
+ size, required_size, op.address(), expr);
+ }
+ }
+
+ if let llil::Register::ArchReg(r) = op.source_reg() {
+ let reg_size = r.info().size();
+
+ if reg_size != size {
+ error!("LLIL_REG attempting to load {} bytes out of a {} byte register! (addr: {:x} {:?})",
+ size, reg_size, op.address(), expr);
+ }
+ }
+ }
+
+ Flag(ref op) => {
+ if let Some(required_size) = required_size {
+ if required_size != 0 {
+ error!("LLIL_FLAG op gives a boolean value where {} bytes are expected! (addr: {:x} {:?})",
+ required_size, op.address(), expr);
+ }
+ }
+ }
+
+ FlagBit(ref op) => {
+ // TODO
+ }
+
+ Load(ref op) => {
+ if let Some(required_size) = required_size {
+ if required_size != op.size() {
+ error!("LLIL_LOAD op gives a {} byte value where {} bytes are expected! (addr: {:x} {:?})",
+ op.size(), required_size, op.address(), expr);
+ }
+ }
+ }
+
+ Pop(ref op) => {
+ if let Some(required_size) = required_size {
+ if required_size != op.size() {
+ error!("LLIL_POP op gives a {} byte value where {} bytes are expected! (addr: {:x} {:?})",
+ op.size(), required_size, op.address(), expr);
+ }
+ }
+ }
+
+ Const(ref op) | ConstPtr(ref op) => {
+ if let Some(required_size) = required_size {
+ if required_size != op.size() {
+ error!("LLIL_CONST op gives a {} byte value where {} bytes are expected! (addr: {:x} {:?})",
+ op.size(), required_size, op.address(), expr);
+ }
+ }
+ }
+
+ FlagCond(ref op) => {
+ if let Some(required_size) = required_size {
+ if required_size != 0 {
+ error!("LLIL_FLAG_COND op gives boolean value where {} bytes are expected! (addr: {:x} {:?})",
+ required_size, op.address(), expr);
+ }
+ }
+ }
+
+ CmpE(ref op) | CmpNe(ref op) |
+ CmpSlt(ref op) | CmpUlt(ref op) |
+ CmpSle(ref op) | CmpUle(ref op) |
+ CmpSge(ref op) | CmpUge(ref op) |
+ CmpSgt(ref op) | CmpUgt(ref op) => {
+ if let Some(required_size) = required_size {
+ if required_size != 0 {
+ error!("LLIL_CMP ops produce a boolean value, and a {} byte value is expected here! (addr: {:x} {:?})",
+ required_size, op.address(), expr);
+ }
+ }
+
+ let cmp_size = op.size();
+ if cmp_size == 0 {
+ error!("compare of zero width detected! {:?}", expr);
+ }
+
+ check_expression(&op.left(), Some(cmp_size));
+ check_expression(&op.right(), Some(cmp_size));
+ }
+
+ Adc(ref op) |
+ Sbb(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL_ADC/SBB producing {} byte value {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ check_expression(&op.left(), Some(op_size));
+ check_expression(&op.right(), Some(op_size));
+ check_expression(&op.carry(), Some(0));
+ }
+
+ Rlc(ref op) |
+ Rrc(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL_RLC/RRC producing {} byte value {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ // rotate amounts just need to be >= 1 byte
+ if let Some(0) = op.right().info().size() {
+ error!("LLIL_RLC/RRC can't rotate by a 0 byte expression! (addr: {:x} {:?})",
+ op.address(), expr);
+ }
+
+ check_expression(&op.left(), Some(op_size));
+ check_expression(&op.right(), None);
+ check_expression(&op.carry(), Some(0));
+ }
+
+ Add(ref op) |
+ Sub(ref op) |
+ And(ref op) |
+ Or (ref op) |
+ Xor(ref op) |
+ Mul(ref op) |
+ Divu(ref op) |
+ Divs(ref op) |
+ Modu(ref op) |
+ Mods(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL binary op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ check_expression(&op.left(), Some(op_size));
+ check_expression(&op.right(), Some(op_size));
+ }
+
+ Lsl(ref op) |
+ Lsr(ref op) |
+ Asr(ref op) |
+ Rol(ref op) |
+ Ror(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL rotate/shift binary op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ // rotate amounts just need to be >= 1 byte
+ if let Some(0) = op.right().info().size() {
+ error!("LLIL shift/rotate ops can't rotate by a 0 byte expression! (addr: {:x} {:?})",
+ op.address(), expr);
+ }
+
+ check_expression(&op.left(), Some(op_size));
+ check_expression(&op.right(), None);
+ }
+
+ MulsDp(ref op) |
+ MuluDp(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size * 2 {
+ error!("LLIL double precision mul op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ check_expression(&op.left(), Some(op_size));
+ check_expression(&op.right(), Some(op_size));
+ }
+
+ DivuDp(ref op) |
+ DivsDp(ref op) |
+ ModuDp(ref op) |
+ ModsDp(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL double precision div op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ // TODO what's the actual right size here?
+ check_expression(&op.high(), Some(op_size));
+ check_expression(&op.low(), Some(op_size));
+ check_expression(&op.right(), Some(op_size));
+ }
+
+ Neg(ref op) |
+ Not(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL unary op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ check_expression(&op.operand(), Some(op_size));
+ }
+
+ Sx(ref op) |
+ Zx(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL extending op producing {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ let operand = op.operand();
+
+ if let Some(actual_size) = operand.info().size() {
+ if actual_size >= op_size {
+ error!("LLIL extending op to {} bytes is invalid; source is already {} bytes (addr: {:x} {:?})",
+ op_size, actual_size, op.address(), expr);
+ }
+ }
+
+ check_expression(&operand, None);
+ }
+
+ LowPart(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL_LOW_PART truncating to {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ let operand = op.operand();
+
+ if let Some(actual_size) = operand.info().size() {
+ if actual_size <= op_size {
+ error!("LLIL truncating op to {} bytes is invalid; source is already {} bytes (addr: {:x} {:?})",
+ op_size, actual_size, op.address(), expr);
+ }
+ }
+
+ check_expression(&operand, None);
+ }
+
+ BoolToInt(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL_BOOL_TO_INT extending to {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ check_expression(&op.operand(), Some(0));
+ }
+
+ UnimplMem(ref op) => {
+ let op_size = op.size();
+
+ if let Some(required_size) = required_size {
+ if required_size != op_size {
+ error!("LLIL_UNIMPL_MEM producing {} byte value where {} byte value is expected! (addr: {:x} {:?})",
+ op_size, required_size, op.address(), expr);
+ }
+ }
+
+ check_expression(&op.mem_expr(), None);
+ }
+
+ Unimpl(_) => {}
+
+ op => {
+ info!(" unhandled expr @ {:x} ... {:?}", op.address(), expr);
+ }
+ }
+}
+
+fn check_instruction(inst: &Instruction<CoreArchitecture, Finalized, NonSSA<LiftedNonSSA>>) {
+ use llil::InstrInfo::*;
+
+ match inst.info() {
+ SetReg(op) => {
+ let required_expr_size = op.size();
+
+ // TODO how to do sanity checking for temp registers?
+ if let llil::Register::ArchReg(r) = op.dest_reg() {
+ let reg_size = r.info().size();
+
+ if reg_size != required_expr_size {
+ error!("LLIL_SET_REG can't set {} byte register to {} byte value! (addr: {:x})",
+ reg_size, required_expr_size, op.address());
+ }
+ }
+
+ check_expression(&op.source_expr(), Some(required_expr_size));
+ }
+ SetRegSplit(op) => {
+ let required_reg_size = op.size();
+
+ if let llil::Register::ArchReg(hi) = op.dest_reg_high() {
+ if hi.info().size() != required_reg_size {
+ error!("LLIL_SET_REG_SPLIT received a register with wrong size (wanted {} bytes)! (addr: {:x})",
+ required_reg_size, op.address());
+ }
+ }
+
+ if let llil::Register::ArchReg(lo) = op.dest_reg_low() {
+ if lo.info().size() != required_reg_size {
+ error!("LLIL_SET_REG_SPLIT received a register with wrong size (wanted {} bytes)! (addr: {:x})",
+ required_reg_size, op.address());
+ }
+ }
+
+ check_expression(&op.source_expr(), Some(required_reg_size * 2));
+ }
+ SetFlag(op) => {
+ check_expression(&op.source_expr(), Some(0));
+ }
+ Store(op) => {
+ let required_expr_size = op.size();
+
+ if required_expr_size == 0 {
+ error!("LLIL_STORE storing a 0 byte value! non-sensical! (addr {:x})", op.address());
+ }
+
+ // TODO make sure size matches arch default addr len?
+ check_expression(&op.dest_mem_expr(), None);
+ check_expression(&op.source_expr(), Some(required_expr_size));
+ }
+ Push(op) => {
+ let required_expr_size = op.size();
+
+ if required_expr_size == 0 {
+ error!("LLIL_PUSH pushing a 0 byte value! non-sensical! (addr {:x})", op.address());
+ }
+
+ check_expression(&op.operand(), Some(required_expr_size));
+ }
+ Jump(op) => {
+ check_expression(&op.target(), None);
+ }
+ JumpTo(op) => {
+ check_expression(&op.target(), None);
+ }
+ Call(op) => {
+ check_expression(&op.target(), None);
+ }
+ Ret(op) => {
+ check_expression(&op.target(), None);
+ }
+ If(op) => {
+ check_expression(&op.condition(), Some(0));
+ }
+ Value(e, _) => {
+ check_expression(&e, None);
+ }
+ _ => {},
+ }
+}
+
+pub fn check_function(func: &Function) {
+ if let Ok(llil) = func.lifted_il() {
+ for block in &llil.basic_blocks() {
+ for inst in &*block {
+ check_instruction(&inst);
+ }
+ }
+ }
+}
+
+use rayon::prelude::*;
+use std::thread;
+use std::time;
+
+pub fn check_all_functions_parallel(bv: &BinaryView) {
+ let bv = bv.to_owned();
+
+ thread::spawn(move || {
+ let start = time::Instant::now();
+ let functions = bv.functions();
+
+ functions.par_iter().for_each(|f| check_function(&f));
+
+ let elapsed = time::Instant::now().duration_since(start);
+ info!("LiftCheck parallel: checked {} functions in {:?} seconds", functions.len(), elapsed);
+ });
+}