udbg 0.3.1

cross-platform library for binary debugging and memory hacking
Documentation
//!
//! Breakpoint types
//!

use std::{
    cell::Cell,
    sync::{Arc, Weak},
};

use crate::{error::*, os::tid_t, register::*, target::UDbgTarget};
use cfg_if::*;

pub type BpID = isize;

#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum HwbpType {
    Execute = 0,
    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
    Write = 1,
    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
    Read = 1,
    #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
    Write = 2,
    Access = 3,
}

#[derive(Copy, Clone, Debug)]
pub enum BpType {
    Soft,
    Table,
    Hwbp(HwbpType, u8),
}

impl BpType {
    #[inline]
    pub fn is_hard(&self) -> bool {
        if let Self::Hwbp(_, _) = self {
            true
        } else {
            false
        }
    }

    #[inline]
    pub fn is_soft(&self) -> bool {
        if let Self::Soft = self {
            true
        } else {
            false
        }
    }

    #[inline]
    pub fn is_table(&self) -> bool {
        if let Self::Table = self {
            true
        } else {
            false
        }
    }
}

impl ToString for BpType {
    fn to_string(&self) -> String {
        match self {
            Self::Soft => "soft".into(),
            Self::Table => "table".into(),
            Self::Hwbp(t, l) => {
                format!(
                    "hwbp:{}{}",
                    match t {
                        HwbpType::Execute => "e",
                        #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
                        HwbpType::Read => "r",
                        HwbpType::Write => "w",
                        HwbpType::Access => "a",
                    },
                    ["1", "2", "8", "4"][*l as usize]
                )
            }
        }
    }
}

impl From<u8> for HwbpType {
    fn from(b: u8) -> Self {
        match b {
            0 => HwbpType::Execute,
            1 => HwbpType::Write,
            3 => HwbpType::Access,
            _ => unreachable!(),
        }
    }
}

impl Into<u8> for HwbpLen {
    fn into(self) -> u8 {
        self as u8
    }
}

#[repr(u8)]
#[derive(Debug, Copy, Clone)]
pub enum HwbpLen {
    L1 = 0,
    L2,
    L4,
    L8,
}

impl HwbpLen {
    pub fn to_int(self) -> u32 {
        match self {
            Self::L1 => 1,
            Self::L2 => 2,
            Self::L4 => 4,
            Self::L8 => 8,
        }
    }

    pub fn encode(self) -> u8 {
        if crate::consts::IS_X86 {
            return match self {
                Self::L1 => 0,
                Self::L2 => 1,
                Self::L4 => 3,
                Self::L8 => 2,
            };
        }

        if crate::consts::IS_ARM {
            return ((1 << self.to_int()) - 1) as _;
        }

        unreachable!()
    }
}

#[derive(Debug)]
pub struct BpOpt {
    pub address: usize,
    pub rw: Option<HwbpType>,
    pub len: Option<HwbpLen>,
    pub table: bool, // table type bp
    pub temp: bool,
    pub enable: bool,
    pub tid: Option<tid_t>,
}

impl From<usize> for BpOpt {
    fn from(address: usize) -> Self {
        Self::int3(address)
    }
}

impl From<(usize, HwbpType)> for BpOpt {
    fn from((address, ty): (usize, HwbpType)) -> Self {
        Self::hwbp(address, ty, None)
    }
}

impl BpOpt {
    pub fn int3(address: usize) -> Self {
        Self {
            address,
            temp: false,
            enable: true,
            tid: None,
            rw: None,
            len: None,
            table: false,
        }
    }

    pub fn hwbp(address: usize, ty: HwbpType, len: Option<HwbpLen>) -> Self {
        Self {
            address,
            temp: false,
            enable: true,
            tid: None,
            rw: ty.into(),
            len,
            table: false,
        }
    }

    pub fn temp(mut self, b: bool) -> Self {
        self.temp = b;
        self
    }

    pub fn enable(mut self, b: bool) -> Self {
        self.enable = b;
        self
    }

    pub fn thread(mut self, tid: tid_t) -> Self {
        self.tid = Some(tid);
        self
    }

    pub fn len(mut self, len: HwbpLen) -> Self {
        self.len = len.into();
        self
    }
}

#[derive(Clone, Copy, Debug)]
pub struct HwbpInfo {
    pub rw: u8,
    pub len: u8,
    pub index: u8,
}

cfg_if! {
    if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
        #[derive(Copy, Clone)]
        pub enum InnerBpType {
            Soft(BpInsn),
            Hard(HwbpInfo),
            Table {index: isize, origin: usize},
        }
        pub type BpInsn = [u8; 1];
        pub const BP_INSN: &BpInsn = &[0xCC];
    } else if #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] {
        #[derive(Copy, Clone)]
        pub enum InnerBpType {
            Soft(BpInsn),
            Hard(HwbpInfo),
            Table {index: isize, origin: usize},
        }
        pub type BpInsn = [u8; 4];
        pub const BP_INSN: &BpInsn = &[0x00, 0x00, 0x3E, 0xD4];
    }
}

/// Represents a breakpoint
pub trait UDbgBreakpoint: 'static {
    /// Get the ID of this breakpoint
    fn get_id(&self) -> BpID;

    /// Address of this breakpoint
    fn address(&self) -> usize;

    /// Detect if this breakpoint enabled
    fn enabled(&self) -> bool;

    /// Type of this breakpoint
    fn get_type(&self) -> BpType;

    /// Count of this breakpoint hitted
    fn hit_count(&self) -> usize;

    /// Set count of the to be used,
    /// when hit_count() > this count, bp will be delete
    fn set_count(&self, count: usize);

    /// Set the which can hit the bp. if tid == 0, all thread used
    fn set_hit_thread(&self, tid: tid_t);

    /// Current tid setted by set_hit_thread()
    fn hit_tid(&self) -> tid_t;

    /// Original bytes written by software breakpoint
    fn origin_bytes(&self) -> Option<&[u8]>;

    /// Enable or disable this breakpoint
    fn enable(&self, enable: bool) -> UDbgResult<()>;

    /// Remove this breakpoint
    fn remove(&self) -> UDbgResult<()>;
}

#[derive(Clone)]
pub struct Breakpoint {
    pub address: usize,
    pub enabled: Cell<bool>,
    pub temp: Cell<bool>,
    pub bp_type: InnerBpType,
    pub hit_count: Cell<usize>,
    pub hit_tid: Option<tid_t>,

    pub target: Weak<dyn UDbgTarget>,
    pub common: *const crate::os::TargetCommon,
}

impl Breakpoint {
    pub fn get_hwbp_len(&self) -> Option<usize> {
        if let InnerBpType::Hard(info) = self.bp_type {
            Some(match info.len as _ {
                LEN_1 => 1,
                LEN_2 => 2,
                LEN_4 => 4,
                LEN_8 => 8,
                _ => 0,
            })
        } else {
            None
        }
    }

    #[inline]
    pub fn is_hard(&self) -> bool {
        self.get_type().is_hard()
    }

    #[inline]
    pub fn is_soft(&self) -> bool {
        self.get_type().is_soft()
    }

    #[inline]
    pub fn is_table(&self) -> bool {
        self.get_type().is_table()
    }

    #[inline]
    pub fn hard_index(&self) -> Option<usize> {
        if let InnerBpType::Hard(info) = self.bp_type {
            Some(info.index as usize)
        } else {
            None
        }
    }
}

impl UDbgBreakpoint for Breakpoint {
    fn get_id(&self) -> BpID {
        self.address as BpID
    }
    fn address(&self) -> usize {
        self.address
    }
    fn enabled(&self) -> bool {
        self.enabled.get()
    }
    fn get_type(&self) -> BpType {
        match self.bp_type {
            InnerBpType::Soft { .. } => BpType::Soft,
            InnerBpType::Table { .. } => BpType::Table,
            InnerBpType::Hard(info) => BpType::Hwbp(info.rw.into(), info.len),
        }
    }
    /// count of this breakpoint hitted
    fn hit_count(&self) -> usize {
        self.hit_count.get()
    }
    /// set count of the to be used,
    /// when hit_count() > this count, bp will be delete
    fn set_count(&self, count: usize) {}
    /// set the which can hit the bp. if tid == 0, all thread used
    fn set_hit_thread(&self, tid: tid_t) {}
    /// current tid setted by set_hit_thread()
    fn hit_tid(&self) -> tid_t {
        0
    }

    fn origin_bytes<'a>(&'a self) -> Option<&'a [u8]> {
        if let InnerBpType::Soft(raw) = &self.bp_type {
            Some(raw)
        } else {
            None
        }
    }

    fn enable(&self, enable: bool) -> UDbgResult<()> {
        let t = self.target.upgrade().ok_or(UDbgError::NoTarget)?;
        unsafe {
            let common = self.common.as_ref().unwrap();
            common.enable_breadpoint(t.as_ref(), self, enable)?;
            Ok(())
        }
    }

    fn remove(&self) -> UDbgResult<()> {
        let t = self.target.upgrade().ok_or(UDbgError::NoTarget)?;
        unsafe {
            let common = self.common.as_ref().unwrap();
            self.enable(false);
            common.remove_breakpoint(t.as_ref(), self);
            Ok(())
        }
    }
}

pub trait BreakpointManager {
    fn add_breakpoint(&self, opt: BpOpt) -> UDbgResult<Arc<dyn UDbgBreakpoint>> {
        Err(UDbgError::NotSupport)
    }
    fn get_breakpoint(&self, id: BpID) -> Option<Arc<dyn UDbgBreakpoint>> {
        None
    }
    fn get_bp_by_address(&self, a: usize) -> Option<Arc<dyn UDbgBreakpoint>> {
        self.get_breakpoint(a as BpID)
    }

    fn get_breakpoints(&self) -> Vec<Arc<dyn UDbgBreakpoint>> {
        vec![]
    }
}