use std::collections::{hash_map::Entry, HashMap};
use std::sync::{Arc, RwLock};
use applevisor as av;
use bitfield::bitfield;
use crate::backtrace::*;
use crate::caches::*;
use crate::coverage::*;
use crate::crash::*;
use crate::error::*;
use crate::memory::*;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
enum HookType {
HandlerStage1,
HandlerStage2,
Exit,
Unknown(u16),
}
impl From<u16> for HookType {
fn from(val: u16) -> Self {
match val {
0 => Self::HandlerStage1,
1 => Self::HandlerStage2,
0xffff => Self::Exit,
u => Self::Unknown(u),
}
}
}
pub struct HookArgs<'a, LD, GD> {
pub addr: u64,
pub insn: &'a [u8],
pub insn_int: u32,
pub vcpu: &'a mut av::Vcpu,
pub vma: &'a mut VirtMemAllocator,
pub vma_snapshot: &'a VirtMemAllocator,
pub ldata: &'a mut LD,
pub gdata: &'a RwLock<GD>,
pub bdata: &'a mut Backtrace,
pub cdata: &'a mut Coverage,
}
pub type HookFn<LD, GD> = fn(&mut HookArgs<LD, GD>) -> Result<ExitKind>;
#[derive(Clone)]
pub struct ExitHook {
insn: [u8; 4],
applied: bool,
}
impl ExitHook {
fn new() -> Self {
Self {
insn: [0; 4],
applied: false,
}
}
}
impl std::default::Default for ExitHook {
fn default() -> Self {
ExitHook::new()
}
}
#[derive(Clone)]
struct Hook<LD, GD> {
insn: [u8; 4],
next_insn: Option<[u8; 4]>,
custom_handler: Option<HookFn<LD, GD>>,
coverage_handler: Option<HookFn<LD, GD>>,
backtrace_handler: Option<HookFn<LD, GD>>,
tracer_handler: Option<HookFn<LD, GD>>,
applied: bool,
}
impl<LD, GD> Hook<LD, GD> {
fn with_custom(handler: HookFn<LD, GD>) -> Self {
Self {
insn: [0; 4],
next_insn: None,
custom_handler: Some(handler),
coverage_handler: None,
backtrace_handler: None,
tracer_handler: None,
applied: false,
}
}
fn with_coverage(handler: HookFn<LD, GD>) -> Self {
Self {
insn: [0; 4],
next_insn: None,
custom_handler: None,
coverage_handler: Some(handler),
backtrace_handler: None,
tracer_handler: None,
applied: false,
}
}
fn with_backtrace(handler: HookFn<LD, GD>) -> Self {
Self {
insn: [0; 4],
next_insn: None,
custom_handler: None,
coverage_handler: None,
backtrace_handler: Some(handler),
tracer_handler: None,
applied: false,
}
}
fn with_tracer(handler: HookFn<LD, GD>) -> Self {
Self {
insn: [0; 4],
next_insn: None,
custom_handler: None,
coverage_handler: None,
backtrace_handler: None,
tracer_handler: Some(handler),
applied: false,
}
}
}
#[derive(Clone)]
pub struct Hooks<LD, GD> {
hooks: HashMap<u64, Hook<LD, GD>>,
exit_hooks: HashMap<u64, ExitHook>,
}
impl<LD: Clone, GD: Clone> Hooks<LD, GD> {
const BRK_STAGE_1: u32 = 0xd4200000;
const BRK_STAGE_2: u32 = 0xd4200020;
const BRK_EXIT: u32 = 0xd43fffe0;
pub fn new() -> Self {
Self {
hooks: HashMap::new(),
exit_hooks: HashMap::new(),
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn handle(
&mut self,
vcpu: &mut av::Vcpu,
vma: &mut VirtMemAllocator,
vma_snapshot: &VirtMemAllocator,
ldata: &mut LD,
gdata: &Arc<RwLock<GD>>,
cdata: &mut Coverage,
bdata: &mut Backtrace,
) -> Result<ExitKind> {
let exit = vcpu.get_exit_info();
match HookType::from(exit.exception.syndrome as u16) {
HookType::HandlerStage1 => {
self.hook_stage1(vcpu, vma, vma_snapshot, ldata, gdata, cdata, bdata)
}
HookType::HandlerStage2 => self.hook_stage2(vcpu, vma),
HookType::Exit => Ok(ExitKind::Exit),
HookType::Unknown(u) => Err(HookError::InvalidHookType(u))?,
}
}
pub fn add_exit_hook(&mut self, addr: u64) {
if let Entry::Vacant(e) = self.exit_hooks.entry(addr) {
e.insert(ExitHook::new());
};
}
pub fn remove_exit_hook(&mut self, addr: u64) {
self.exit_hooks.remove(&addr);
}
pub fn add_custom_hook(&mut self, addr: u64, handler: HookFn<LD, GD>) {
match self.hooks.entry(addr) {
Entry::Vacant(e) => {
e.insert(Hook::with_custom(handler));
}
Entry::Occupied(mut e) => {
let _ = e.get_mut().custom_handler.insert(handler);
}
};
}
pub fn remove_custom_hook(&mut self, addr: u64) -> bool {
if let Some(hook) = self.hooks.get_mut(&addr) {
if hook.coverage_handler.is_some()
|| hook.tracer_handler.is_some()
|| hook.backtrace_handler.is_some()
{
hook.custom_handler = None;
false
} else {
self.hooks.remove(&addr);
true
}
} else {
true
}
}
pub fn add_coverage_hook(&mut self, addr: u64, handler: HookFn<LD, GD>) {
match self.hooks.entry(addr) {
Entry::Vacant(e) => {
e.insert(Hook::with_coverage(handler));
}
Entry::Occupied(mut e) => {
let _ = e.get_mut().coverage_handler.insert(handler);
}
};
}
pub fn remove_coverage_hook(&mut self, addr: u64) -> bool {
if let Some(hook) = self.hooks.get_mut(&addr) {
if hook.custom_handler.is_some()
|| hook.tracer_handler.is_some()
|| hook.backtrace_handler.is_some()
{
hook.coverage_handler = None;
false
} else {
self.hooks.remove(&addr);
true
}
} else {
true
}
}
pub fn add_backtrace_hook(&mut self, addr: u64, handler: HookFn<LD, GD>) {
match self.hooks.entry(addr) {
Entry::Vacant(e) => {
e.insert(Hook::with_backtrace(handler));
}
Entry::Occupied(mut e) => {
let _ = e.get_mut().backtrace_handler.insert(handler);
}
};
}
pub fn remove_backtrace_hook(&mut self, addr: u64) -> bool {
if let Some(hook) = self.hooks.get_mut(&addr) {
if hook.custom_handler.is_some()
|| hook.tracer_handler.is_some()
|| hook.coverage_handler.is_some()
{
hook.backtrace_handler = None;
false
} else {
self.hooks.remove(&addr);
true
}
} else {
true
}
}
pub fn add_tracer_hook(&mut self, addr: u64, handler: HookFn<LD, GD>) {
match self.hooks.entry(addr) {
Entry::Vacant(e) => {
e.insert(Hook::with_tracer(handler));
}
Entry::Occupied(mut e) => {
let _ = e.get_mut().tracer_handler.insert(handler);
}
};
}
pub fn remove_tracer_hook(&mut self, addr: u64) -> bool {
if let Some(hook) = self.hooks.get_mut(&addr) {
if hook.custom_handler.is_some()
|| hook.coverage_handler.is_some()
|| hook.backtrace_handler.is_some()
{
hook.tracer_handler = None;
false
} else {
self.hooks.remove(&addr);
true
}
} else {
true
}
}
pub fn apply(&mut self, vma: &mut VirtMemAllocator) -> Result<()> {
self.apply_inner(vma, true)
}
pub fn fill_instructions(&mut self, vma: &mut VirtMemAllocator) -> Result<()> {
self.apply_inner(vma, false)
}
pub fn apply_inner(&mut self, vma: &mut VirtMemAllocator, apply: bool) -> Result<()> {
for (&addr, hook) in self.hooks.iter_mut() {
if !hook.applied {
vma.read(addr, &mut hook.insn)?;
if apply {
vma.write_dword(addr, Self::BRK_STAGE_1)?;
hook.applied = true;
}
}
}
for (&addr, hook) in self.exit_hooks.iter_mut() {
if !hook.applied {
vma.read(addr, &mut hook.insn)?;
if apply {
vma.write_dword(addr, Self::BRK_EXIT)?;
hook.applied = true;
}
}
}
Ok(())
}
#[inline]
pub fn revert_coverage_hooks(
&mut self,
addr: u64,
vma: &mut VirtMemAllocator,
vma_snapshot: &mut VirtMemAllocator,
) -> Result<()> {
if let Some(hook) = self.hooks.get_mut(&addr) {
if hook.custom_handler.is_some()
|| hook.tracer_handler.is_some()
|| hook.backtrace_handler.is_some()
{
hook.coverage_handler = None;
} else {
vma.write(addr, &hook.insn)?;
vma_snapshot.write(addr, &hook.insn)?;
if let Some(next_insn) = hook.next_insn {
if let Some(next_hook) = self.hooks.get(&(addr + 4)) {
vma.write(addr + 4, &next_hook.insn)?;
vma_snapshot.write(addr + 4, &next_hook.insn)?;
} else {
vma.write(addr + 4, &next_insn)?;
vma_snapshot.write(addr + 4, &next_insn)?;
}
}
}
}
Ok(())
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn hook_stage1(
&mut self,
vcpu: &mut av::Vcpu,
vma: &mut VirtMemAllocator,
vma_snapshot: &VirtMemAllocator,
ldata: &mut LD,
gdata: &Arc<RwLock<GD>>,
cdata: &mut Coverage,
bdata: &mut Backtrace,
) -> Result<ExitKind> {
let addr = vcpu.get_reg(av::Reg::PC)?;
let hook = self
.hooks
.get_mut(&addr)
.ok_or(HookError::UnknownHook(addr))?;
let insn = u32::from_le_bytes(hook.insn);
let mut args = HookArgs {
addr,
insn: &hook.insn,
insn_int: insn,
vcpu,
vma,
vma_snapshot,
ldata,
gdata,
bdata,
cdata,
};
let tracer_res = if let Some(tracer_handler) = hook.tracer_handler {
tracer_handler(&mut args)?
} else {
ExitKind::Continue
};
let custom_res = if let Some(custom_handler) = hook.custom_handler {
custom_handler(&mut args)?
} else {
ExitKind::Continue
};
let coverage_res = if let Some(coverage_handler) = hook.coverage_handler {
coverage_handler(&mut args)?
} else {
ExitKind::Continue
};
let backtrace_res = if let Some(backtrace_handler) = hook.backtrace_handler {
backtrace_handler(&mut args)?
} else {
ExitKind::Continue
};
match (custom_res.clone(), coverage_res, tracer_res, backtrace_res) {
(x, _, _, _) | (_, x, _, _) | (_, _, x, _) | (_, _, _, x)
if x != ExitKind::Continue =>
{
match x {
ExitKind::Crash(_) | ExitKind::Timeout | ExitKind::Exit => return Ok(x),
_ => {}
}
}
_ => {}
}
if let ExitKind::EarlyFunctionReturn = custom_res {
bdata.backtrace.pop();
}
let new_addr = vcpu.get_reg(av::Reg::PC)?;
if addr != new_addr {
return Ok(ExitKind::Continue);
}
let emu_res = Emulator::emulate(insn, vcpu)?;
match emu_res {
EmulationResult::BranchRel(offset) => {
let addr = if offset >= 0 {
addr + offset as u64
} else {
(addr as i64 + offset as i64) as u64
};
vcpu.set_reg(av::Reg::PC, addr)?;
}
EmulationResult::BranchAbs(addr) => vcpu.set_reg(av::Reg::PC, addr)?,
EmulationResult::Other => {
let mut next_insn = [0; 4];
vma.read(addr + 4, &mut next_insn)?;
hook.next_insn = Some(next_insn);
vma.write_dword(addr + 4, Self::BRK_STAGE_2)?;
vma.write(addr, &hook.insn)?;
vcpu.set_reg(av::Reg::PC, addr)?;
Caches::ic_ivau(vcpu, vma)?;
}
};
Ok(ExitKind::Continue)
}
#[inline]
fn hook_stage2(&mut self, vcpu: &mut av::Vcpu, vma: &mut VirtMemAllocator) -> Result<ExitKind> {
let addr = vcpu.get_reg(av::Reg::PC)?;
let stage1_addr = addr - 4; let hook = self
.hooks
.get(&stage1_addr)
.ok_or(HookError::UnknownHook(stage1_addr))?;
vma.write(addr, &hook.next_insn.unwrap())?;
if hook.tracer_handler.is_some() || hook.coverage_handler.is_some() {
vma.write_dword(stage1_addr, Self::BRK_STAGE_1)?;
}
Caches::ic_ivau(vcpu, vma)?;
Ok(ExitKind::Continue)
}
}
impl<LD: Clone, GD: Clone> Default for Hooks<LD, GD> {
fn default() -> Self {
Self::new()
}
}
bitfield! {
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
struct Cpsr(u32);
impl Debug;
get_m, set_m: 3, 0;
get_f, set_f: 6;
get_i, set_i: 7;
get_a, set_a: 8;
get_e, set_e: 9;
get_ge, set_ge: 19, 16;
get_dit, set_dit: 21;
get_pan, set_pan: 22;
get_ssbs, set_ssbs: 23;
get_q, set_q: 27;
get_v, set_v: 28;
get_c, set_c: 29;
get_z, set_z: 30;
get_n, set_n: 31;
}
#[derive(Debug)]
enum EmulationResult {
BranchRel(i32),
BranchAbs(u64),
Other,
}
struct Emulator;
impl Emulator {
#[inline]
fn emulate(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
match insn {
i if (i >> 24) == 0b01010100 => Self::b_cond(i, vcpu),
i if (i >> 24) == 0b10110101 || (i >> 24) == 0b00110101 => Self::cbnz(i, vcpu),
i if (i >> 24) == 0b10110100 || (i >> 24) == 0b00110100 => Self::cbz(i, vcpu),
i if (i >> 24) == 0b10110111 || (i >> 24) == 0b00110111 => Self::tbnz(i, vcpu),
i if (i >> 24) == 0b10110110 || (i >> 24) == 0b00110110 => Self::tbz(i, vcpu),
i if (i >> 26) == 0b000101 => Self::b(i),
i if (i >> 26) == 0b100101 => Self::bl(i, vcpu),
i if (i >> 10) == 0b1101011000111111000000 && i & 0x1f == 0 => Self::blr(i, vcpu),
i if (i >> 10) == 0b1101011000011111000000 && i & 0x1f == 0 => Self::br(i, vcpu),
i if (i >> 10) == 0b1101011001011111000000 && i & 0x1f == 0 => Self::ret(i, vcpu),
_ => Ok(EmulationResult::Other),
}
}
#[inline]
fn evaluate_condition(cond: u32, cpsr: Cpsr) -> bool {
let ret = match cond >> 1 {
0b000 => cpsr.get_z(),
0b001 => cpsr.get_c(),
0b010 => cpsr.get_n(),
0b011 => cpsr.get_v(),
0b100 => cpsr.get_c() && !cpsr.get_z(),
0b101 => cpsr.get_n() == cpsr.get_v(),
0b110 => cpsr.get_n() == cpsr.get_v() && !cpsr.get_z(),
0b111 => true,
_ => unreachable!("invalid instruction condition"),
};
if cond & 1 == 1 && cond != 0b1111 {
!ret
} else {
ret
}
}
#[inline]
fn sign_extend32(data: u32, size: u32) -> i32 {
assert!(size > 0 && size <= 32);
((data << (32 - size)) as i32) >> (32 - size)
}
#[inline]
fn get_operand(vcpu: &av::Vcpu, rd: u32) -> Result<u64> {
match rd {
0 => Ok(vcpu.get_reg(av::Reg::X0)?),
1 => Ok(vcpu.get_reg(av::Reg::X1)?),
2 => Ok(vcpu.get_reg(av::Reg::X2)?),
3 => Ok(vcpu.get_reg(av::Reg::X3)?),
4 => Ok(vcpu.get_reg(av::Reg::X4)?),
5 => Ok(vcpu.get_reg(av::Reg::X5)?),
6 => Ok(vcpu.get_reg(av::Reg::X6)?),
7 => Ok(vcpu.get_reg(av::Reg::X7)?),
8 => Ok(vcpu.get_reg(av::Reg::X8)?),
9 => Ok(vcpu.get_reg(av::Reg::X9)?),
10 => Ok(vcpu.get_reg(av::Reg::X10)?),
11 => Ok(vcpu.get_reg(av::Reg::X11)?),
12 => Ok(vcpu.get_reg(av::Reg::X12)?),
13 => Ok(vcpu.get_reg(av::Reg::X13)?),
14 => Ok(vcpu.get_reg(av::Reg::X14)?),
15 => Ok(vcpu.get_reg(av::Reg::X15)?),
16 => Ok(vcpu.get_reg(av::Reg::X16)?),
17 => Ok(vcpu.get_reg(av::Reg::X17)?),
18 => Ok(vcpu.get_reg(av::Reg::X18)?),
19 => Ok(vcpu.get_reg(av::Reg::X19)?),
20 => Ok(vcpu.get_reg(av::Reg::X20)?),
21 => Ok(vcpu.get_reg(av::Reg::X21)?),
22 => Ok(vcpu.get_reg(av::Reg::X22)?),
23 => Ok(vcpu.get_reg(av::Reg::X23)?),
24 => Ok(vcpu.get_reg(av::Reg::X24)?),
25 => Ok(vcpu.get_reg(av::Reg::X25)?),
26 => Ok(vcpu.get_reg(av::Reg::X26)?),
27 => Ok(vcpu.get_reg(av::Reg::X27)?),
28 => Ok(vcpu.get_reg(av::Reg::X28)?),
29 => Ok(vcpu.get_reg(av::Reg::X29)?),
30 => Ok(vcpu.get_reg(av::Reg::LR)?),
31 => Ok(vcpu.get_reg(av::Reg::PC)?),
_ => unreachable!("invalid operand"),
}
}
#[inline]
fn b_cond(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
let cpsr = Cpsr(vcpu.get_reg(av::Reg::CPSR)? as u32);
let cond = insn & 0xf;
if Self::evaluate_condition(cond, cpsr) {
Ok(EmulationResult::BranchRel(
Self::sign_extend32((insn >> 5) & 0x3ffff, 18) * 4,
))
} else {
Ok(EmulationResult::BranchRel(4))
}
}
#[inline]
fn cbnz(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
let rd = insn & 0x1f;
let op = if insn >> 31 == 1 {
Self::get_operand(vcpu, rd)?
} else {
Self::get_operand(vcpu, rd)? as u32 as u64
};
if op != 0 {
Ok(EmulationResult::BranchRel(
Self::sign_extend32((insn >> 5) & 0x3ffff, 18) * 4,
))
} else {
Ok(EmulationResult::BranchRel(4))
}
}
#[inline]
fn cbz(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
let rd = insn & 0x1f;
let op = if insn >> 31 == 1 {
Self::get_operand(vcpu, rd)?
} else {
Self::get_operand(vcpu, rd)? as u32 as u64
};
if op == 0 {
Ok(EmulationResult::BranchRel(
Self::sign_extend32((insn >> 5) & 0x3ffff, 18) * 4,
))
} else {
Ok(EmulationResult::BranchRel(4))
}
}
#[inline]
fn tbnz(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
let rd = insn & 0x1f;
let bit_pos = ((insn >> 31) << 5) | ((insn >> 19) & 0x1f);
let op = if insn >> 31 == 1 {
Self::get_operand(vcpu, rd)?
} else {
Self::get_operand(vcpu, rd)? as u32 as u64
};
if (op >> bit_pos) & 1 != 0 {
Ok(EmulationResult::BranchRel(
Self::sign_extend32((insn >> 5) & 0x3fff, 14) * 4,
))
} else {
Ok(EmulationResult::BranchRel(4))
}
}
#[inline]
fn tbz(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
let rd = insn & 0x1f;
let bit_pos = ((insn >> 31) << 5) | ((insn >> 19) & 0x1f);
let op = if insn >> 31 == 1 {
Self::get_operand(vcpu, rd)?
} else {
Self::get_operand(vcpu, rd)? as u32 as u64
};
if (op >> bit_pos) & 1 == 0 {
Ok(EmulationResult::BranchRel(
Self::sign_extend32((insn >> 5) & 0x3fff, 14) * 4,
))
} else {
Ok(EmulationResult::BranchRel(4))
}
}
#[inline]
fn b(insn: u32) -> Result<EmulationResult> {
Ok(EmulationResult::BranchRel(
Self::sign_extend32(insn & 0x3ffffff, 26) * 4,
))
}
#[inline]
fn bl(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
vcpu.set_reg(av::Reg::LR, vcpu.get_reg(av::Reg::PC)? + 4)?;
Self::b(insn)
}
#[inline]
fn br(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
let rd = insn >> 5 & 0x1f;
let target = Self::get_operand(vcpu, rd)?;
Ok(EmulationResult::BranchAbs(target))
}
#[inline]
fn blr(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
vcpu.set_reg(av::Reg::LR, vcpu.get_reg(av::Reg::PC)? + 4)?;
Self::br(insn, vcpu)
}
#[inline]
fn ret(insn: u32, vcpu: &av::Vcpu) -> Result<EmulationResult> {
Self::br(insn, vcpu)
}
}