diff options
| author | Rusty Wagner <rusty.wagner@gmail.com> | 2023-12-19 22:39:33 -0700 |
|---|---|---|
| committer | Rusty Wagner <rusty.wagner@gmail.com> | 2024-01-04 12:42:11 -0700 |
| commit | a9cfac7ff14d93ff22092366f99eb2dd3213ea7e (patch) | |
| tree | b61163e052a1827846b0c3b8330b0289aac8cabf /arch/riscv/src | |
| parent | a82a2105fd1b1a99bddb835ed1d9dbf2e2a5c56f (diff) | |
Add RISC-V architecture plugin
Diffstat (limited to 'arch/riscv/src')
| -rw-r--r-- | arch/riscv/src/lib.rs | 2570 | ||||
| -rw-r--r-- | arch/riscv/src/liftcheck.rs | 427 |
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(®.name()), + Register, + )); + } + Operand::F(r) => { + let reg = self::Register::from(r); + + res.push(InstructionTextToken::new( + BnString::new(®.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(®.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); + }); +} |
