use crate::arch;
use crate::reg::Register;
use crate::DynResult;
use gdbstub::common::Signal;
use gdbstub::target;
use gdbstub::target::ext::breakpoints::WatchKind;
use gdbstub::target::{TargetError, TargetResult};
use singlyton::{Singleton, SingletonOption};
use std::collections::HashMap;
use std::convert::TryFrom;
use unicorn_engine::unicorn_const::{uc_error, HookType, MemType, Mode, Query};
use unicorn_engine::{UcHookId, Unicorn};
static STEP_STATE: Singleton<bool> = Singleton::new(false);
static STEP_HOOK: SingletonOption<UcHookId> = SingletonOption::new();
static WATCH_ADDR: SingletonOption<u64> = SingletonOption::new();
fn copy_to_buf(data: &[u8], buf: &mut [u8]) -> usize {
let len = data.len();
let buf = &mut buf[..len];
buf.copy_from_slice(data);
len
}
fn copy_range_to_buf(data: &[u8], offset: u64, length: usize, buf: &mut [u8]) -> usize {
let offset = match usize::try_from(offset) {
Ok(v) => v,
Err(_) => return 0,
};
let len = data.len();
let data = &data[len.min(offset)..len.min(offset + length)];
copy_to_buf(data, buf)
}
fn step_cb<T: 'static>(uc: &mut Unicorn<T>, _addr: u64, _size: u32) {
if *STEP_STATE.get() {
STEP_STATE.replace(false);
return;
}
if let Some(step_hook) = STEP_HOOK.take() {
uc.remove_hook(step_hook).expect("Failed to remove step hook");
}
crate::udbserver_resume::<T>(WATCH_ADDR.take()).expect("Failed to resume udbserver");
}
fn watch_cb<T: 'static>(uc: &mut Unicorn<T>, _mem_type: MemType, addr: u64, _size: usize, _value: i64) -> bool {
if WATCH_ADDR.is_none() {
WATCH_ADDR.replace(addr);
if STEP_HOOK.is_none() {
STEP_HOOK.replace(uc.add_code_hook(1, 0, step_cb::<T>).expect("Failed to add code hook"));
}
}
true
}
pub struct Emu<T: 'static> {
uc: &'static mut Unicorn<'static, T>,
reg: Register,
code_hook: UcHookId,
mem_hook: UcHookId,
bp_sw_hooks: HashMap<u64, UcHookId>,
bp_hw_hooks: HashMap<u64, UcHookId>,
wp_r_hooks: HashMap<u64, HashMap<u64, UcHookId>>,
wp_w_hooks: HashMap<u64, HashMap<u64, UcHookId>>,
wp_rw_hooks: HashMap<u64, HashMap<u64, UcHookId>>,
}
impl<T: 'static> Emu<T> {
pub fn new(uc: &'static mut Unicorn<'static, T>, code_hook: UcHookId, mem_hook: UcHookId) -> DynResult<Emu<T>> {
let arch = uc.get_arch();
let query_mode = uc.query(Query::MODE).expect("Failed to query mode");
let mode = Mode::try_from(query_mode as i32).unwrap();
let reg = Register::new(arch, mode);
Ok(Emu {
uc,
reg,
code_hook,
mem_hook,
bp_sw_hooks: HashMap::new(),
bp_hw_hooks: HashMap::new(),
wp_r_hooks: HashMap::new(),
wp_w_hooks: HashMap::new(),
wp_rw_hooks: HashMap::new(),
})
}
}
impl<T: 'static> Drop for Emu<T> {
fn drop(&mut self) {
self.uc.remove_hook(self.code_hook).expect("Failed to remove empty code hook");
self.uc.remove_hook(self.mem_hook).expect("Failed to remove empty mem hook");
}
}
impl<T: 'static> target::Target for Emu<T> {
type Arch = arch::GenericArch;
type Error = &'static str;
#[inline(always)]
fn base_ops(&mut self) -> target::ext::base::BaseOps<'_, Self::Arch, Self::Error> {
target::ext::base::BaseOps::SingleThread(self)
}
#[inline(always)]
fn support_breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<'_, Self>> {
Some(self)
}
#[inline(always)]
fn support_target_description_xml_override(&mut self) -> Option<target::ext::target_description_xml_override::TargetDescriptionXmlOverrideOps<'_, Self>> {
Some(self)
}
}
impl<T: 'static> target::ext::base::singlethread::SingleThreadBase for Emu<T> {
fn read_registers(&mut self, regs: &mut arch::GenericRegs) -> TargetResult<(), Self> {
regs.buf = Vec::new();
for reg in self.reg.list() {
let val = match reg.0 {
Some(regid) => self.uc.reg_read(regid).map_err(|_| ())?,
None => 0,
};
regs.buf.extend(self.reg.write_u64(val, reg.1));
}
Ok(())
}
fn write_registers(&mut self, regs: &arch::GenericRegs) -> TargetResult<(), Self> {
let mut i = 0;
for reg in self.reg.list() {
let part = ®s.buf[i..i + reg.1];
let val = self.reg.read_u64(part);
i += reg.1;
if let Some(regid) = reg.0 {
self.uc.reg_write(regid, val).map_err(|_| ())?
}
}
Ok(())
}
#[inline(always)]
fn support_single_register_access(&mut self) -> Option<target::ext::base::single_register_access::SingleRegisterAccessOps<'_, (), Self>> {
Some(self)
}
fn read_addrs(&mut self, start_addr: u64, data: &mut [u8]) -> TargetResult<usize, Self> {
match self.uc.mem_read(start_addr, data) {
Ok(_) => Ok(data.len()),
Err(uc_error::READ_UNMAPPED) => Err(TargetError::Errno(1)),
Err(_) => Err(TargetError::Fatal("Failed to read addr")),
}
}
fn write_addrs(&mut self, start_addr: u64, data: &[u8]) -> TargetResult<(), Self> {
match self.uc.mem_write(start_addr, data) {
Ok(_) => Ok(()),
Err(uc_error::WRITE_UNMAPPED) => Err(TargetError::Errno(1)),
Err(_) => Err(TargetError::Fatal("Failed to write addr")),
}
}
#[inline(always)]
fn support_resume(&mut self) -> Option<target::ext::base::singlethread::SingleThreadResumeOps<'_, Self>> {
Some(self)
}
}
impl<T: 'static> target::ext::base::singlethread::SingleThreadResume for Emu<T> {
fn resume(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
Ok(())
}
#[inline(always)]
fn support_single_step(&mut self) -> Option<target::ext::base::singlethread::SingleThreadSingleStepOps<'_, Self>> {
Some(self)
}
}
impl<T: 'static> target::ext::base::singlethread::SingleThreadSingleStep for Emu<T> {
fn step(&mut self, signal: Option<Signal>) -> Result<(), Self::Error> {
if signal.is_some() {
return Err("no support for stepping with signal");
}
STEP_STATE.replace(true);
STEP_HOOK.replace(self.uc.add_code_hook(1, 0, step_cb::<T>).map_err(|_| "Failed to add code hook")?);
Ok(())
}
}
impl<T: 'static> target::ext::breakpoints::Breakpoints for Emu<T> {
#[inline(always)]
fn support_sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<'_, Self>> {
Some(self)
}
#[inline(always)]
fn support_hw_breakpoint(&mut self) -> Option<target::ext::breakpoints::HwBreakpointOps<'_, Self>> {
Some(self)
}
#[inline(always)]
fn support_hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<'_, Self>> {
Some(self)
}
}
macro_rules! add_breakpoint {
( $self:ident, $addr:ident, $hook_map:ident ) => {{
let hook = match $self.uc.add_code_hook($addr.into(), $addr.into(), step_cb::<T>) {
Ok(h) => h,
Err(_) => return Ok(false),
};
$self.$hook_map.insert($addr.into(), hook);
Ok(true)
}};
( $self:ident, $mem_type:ident, $addr:ident, $len:ident, $hook_map:ident ) => {{
let hook = match $self
.uc
.add_mem_hook(HookType::$mem_type, $addr.into(), ($addr + $len - 1).into(), watch_cb::<T>)
{
Ok(h) => h,
Err(_) => return Ok(false),
};
$self.$hook_map.entry($len).or_insert(HashMap::new()).insert($addr.into(), hook);
Ok(true)
}};
}
macro_rules! remove_breakpoint {
( $self:ident, $addr:ident, $hook_map:ident ) => {{
let hook = match $self.$hook_map.remove(&$addr.into()) {
Some(h) => h,
None => return Ok(false),
};
match $self.uc.remove_hook(hook) {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}};
( $self:ident, $addr:ident, $len:ident, $hook_map:ident ) => {{
let map = match $self.$hook_map.get_mut(&$len) {
Some(h) => h,
None => return Ok(false),
};
let hook = match map.remove(&$addr.into()) {
Some(h) => h,
None => return Ok(false),
};
match $self.uc.remove_hook(hook) {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}};
}
impl<T: 'static> target::ext::breakpoints::SwBreakpoint for Emu<T> {
fn add_sw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult<bool, Self> {
add_breakpoint!(self, addr, bp_sw_hooks)
}
fn remove_sw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult<bool, Self> {
remove_breakpoint!(self, addr, bp_sw_hooks)
}
}
impl<T: 'static> target::ext::breakpoints::HwBreakpoint for Emu<T> {
fn add_hw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult<bool, Self> {
add_breakpoint!(self, addr, bp_hw_hooks)
}
fn remove_hw_breakpoint(&mut self, addr: u64, _kind: usize) -> TargetResult<bool, Self> {
remove_breakpoint!(self, addr, bp_hw_hooks)
}
}
impl<T: 'static> target::ext::breakpoints::HwWatchpoint for Emu<T> {
fn add_hw_watchpoint(&mut self, addr: u64, len: u64, kind: WatchKind) -> TargetResult<bool, Self> {
match kind {
WatchKind::Read => add_breakpoint!(self, MEM_READ, addr, len, wp_r_hooks),
WatchKind::Write => add_breakpoint!(self, MEM_WRITE, addr, len, wp_w_hooks),
WatchKind::ReadWrite => add_breakpoint!(self, MEM_VALID, addr, len, wp_rw_hooks),
}
}
fn remove_hw_watchpoint(&mut self, addr: u64, len: u64, kind: WatchKind) -> TargetResult<bool, Self> {
match kind {
WatchKind::Read => remove_breakpoint!(self, addr, len, wp_r_hooks),
WatchKind::Write => remove_breakpoint!(self, addr, len, wp_w_hooks),
WatchKind::ReadWrite => remove_breakpoint!(self, addr, len, wp_rw_hooks),
}
}
}
impl<T: 'static> target::ext::base::single_register_access::SingleRegisterAccess<()> for Emu<T> {
fn read_register(&mut self, _tid: (), reg_id: arch::GenericRegId, buf: &mut [u8]) -> TargetResult<usize, Self> {
let reg = self.reg.get(reg_id.0)?;
if reg.1 <= 8 {
let val = match reg.0 {
Some(regid) => self.uc.reg_read(regid).map_err(|_| ())?,
None => 0,
};
Ok(copy_to_buf(&self.reg.write_u64(val, reg.1), buf))
} else if let Some(regid) = reg.0 {
let data = &self.uc.reg_read_long(regid).map_err(|_| ())?;
Ok(copy_to_buf(data, buf))
} else {
Ok(0)
}
}
fn write_register(&mut self, _tid: (), reg_id: arch::GenericRegId, val: &[u8]) -> TargetResult<(), Self> {
let reg = self.reg.get(reg_id.0)?;
assert!(reg.1 == val.len(), "Length mismatch when write register {}", reg.0.unwrap());
if let Some(regid) = reg.0 {
if reg.1 <= 8 {
let v = self.reg.read_u64(val);
self.uc.reg_write(regid, v).map_err(|_| ())?;
} else {
self.uc.reg_write_long(regid, val).map_err(|_| ())?;
}
}
Ok(())
}
}
impl<T: 'static> target::ext::target_description_xml_override::TargetDescriptionXmlOverride for Emu<T> {
fn target_description_xml(&self, _annex: &[u8], offset: u64, length: usize, buf: &mut [u8]) -> TargetResult<usize, Self> {
let xml = self.reg.description_xml().as_bytes();
Ok(copy_range_to_buf(xml, offset, length, buf))
}
}