use super::unix::{udbg::TraceBuf, *};
use crate::prelude::*;
use crate::register::*;
use anyhow::Context;
use libc::{pid_t, *};
use nix::errno::Errno;
use nix::sys::signal::Signal;
use nix::sys::{ptrace, wait::*};
use nix::unistd::Pid;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fs::{read_link, File};
use std::io::Result as IoResult;
use std::path::Path;
use std::sync::Arc;
pub type priority_t = i64;
pub const TRAP_BRKPT: i32 = 1;
pub const TRAP_TRACE: i32 = 2;
pub const TRAP_BRANCH: i32 = 3;
pub const TRAP_HWBKPT: i32 = 4;
pub const TRAP_UNK: i32 = 5;
mod process;
mod udbg;
pub mod util;
pub use self::process::*;
pub use self::udbg::*;
pub struct PidIter(std::fs::ReadDir);
impl Iterator for PidIter {
type Item = pid_t;
fn next(&mut self) -> Option<pid_t> {
while let Some(e) = self.0.next() {
let e = match e {
Ok(e) => e,
Err(_) => continue,
};
if let Ok(pid) = pid_t::from_str_radix(&e.file_name().into_string().unwrap(), 10) {
return Some(pid);
}
}
None
}
}
impl PidIter {
pub fn proc() -> IoResult<Self> {
std::fs::read_dir("/proc").map(PidIter)
}
pub fn proc_task(pid: pid_t) -> IoResult<Self> {
std::fs::read_dir(format!("/proc/{}/task", pid)).map(PidIter)
}
pub fn proc_fd(pid: pid_t) -> IoResult<Self> {
std::fs::read_dir(format!("/proc/{}/fd", pid)).map(PidIter)
}
}
pub fn get_exception_name(code: u32) -> String {
format!(
"{:?}",
match Signal::try_from(code as i32) {
Ok(s) => s,
Err(_) => return String::new(),
}
)
}
pub fn ptrace_read(pid: pid_t, addr: usize, size: usize) -> Result<Vec<u8>, u32> {
let nix_pid = nix::unistd::Pid::from_raw(pid as _);
let mut word_buffer: Vec<u8> = Vec::new();
for n in (addr..addr + size + 16).step_by(std::mem::size_of::<c_long>()) {
if word_buffer.len() > size {
word_buffer.truncate(size);
break;
}
let word: [u8; std::mem::size_of::<c_long>()] = match ptrace::read(nix_pid, n as _) {
Ok(val) => (val as usize).to_ne_bytes(),
Err(_) => break,
};
word_buffer.extend(word.iter().cloned());
}
Ok(word_buffer)
}
pub fn ptrace_write(pid: pid_t, addr: usize, data: &[u8]) -> Result<(), u32> {
let nix_pid = Pid::from_raw(pid);
let mut index = 0;
loop {
if index + (std::mem::size_of::<c_long>() * 2) > data.len() {
let mut store =
ptrace_read(pid, addr + index, std::mem::size_of::<c_long>() * 2).unwrap();
let remaining = data.len() - index;
let left_slice = &data[index..data.len()];
if left_slice.len() > store.len() {
return Err(0);
}
for n in 0..remaining {
store[n] = left_slice[n]
}
let mut dst = [0u8; std::mem::size_of::<c_long>()];
dst.clone_from_slice(&store[0..std::mem::size_of::<c_long>()]);
let store1 = c_long::from_ne_bytes(dst);
dst.clone_from_slice(
&store[std::mem::size_of::<c_long>()..std::mem::size_of::<c_long>() * 2],
);
let store2 = c_long::from_ne_bytes(dst);
unsafe {
ptrace::write(nix_pid, (addr + index) as _, store1 as _).unwrap();
ptrace::write(
nix_pid,
(addr + index + std::mem::size_of::<c_long>()) as _,
store2 as _,
)
.unwrap();
}
break;
}
let mut _word = [0u8; std::mem::size_of::<c_long>()];
_word.clone_from_slice(&data[index..index + std::mem::size_of::<c_long>()]);
let word = c_long::from_ne_bytes(_word);
unsafe { ptrace::write(nix_pid, (addr + index) as _, word as _).unwrap() }
index += std::mem::size_of::<c_long>();
}
Ok(())
}
pub fn ptrace_write0(pid: pid_t, address: usize, data: &[u8]) {
const SSIZE: usize = core::mem::size_of::<usize>();
unsafe {
for i in (0..data.len()).step_by(SSIZE) {
let val = *((data.as_ptr() as usize + i) as *const usize);
ptrace(PTRACE_POKEDATA, pid, address + i, val);
}
let align_len = data.len() - data.len() % SSIZE;
if align_len < data.len() {
let rest = &data[align_len..];
let mut val = ptrace(PTRACE_PEEKDATA, pid, address + align_len, 0).to_ne_bytes();
for i in 0..data.len() % SSIZE {
val[i] = rest[i];
}
ptrace(
PTRACE_POKEDATA,
pid,
address + align_len,
usize::from_ne_bytes(val),
);
}
}
}
pub fn ptrace_attach_wait(tid: pid_t, opt: c_int) -> nix::Result<WaitStatus> {
ptrace::attach(Pid::from_raw(tid))?;
let status = nix::sys::wait::waitpid(
Pid::from_raw(tid),
Some(WaitPidFlag::from_bits_truncate(opt)),
)?;
Ok(status)
}
impl ProcessInfo {
pub fn enumerate() -> IoResult<impl Iterator<Item = Self>> {
Ok(PidIter::proc()?.map(|pid| Self {
pid,
wow64: false,
name: Process::pid_name(pid).unwrap_or_default(),
path: Process::pid_path(pid).unwrap_or_default(),
cmdline: Process::pid_cmdline(pid).join(" "),
}))
}
}
pub fn ptrace_peekuser(pid: i32, offset: usize) -> nix::Result<c_long> {
Errno::result(unsafe { libc::ptrace(PTRACE_PEEKUSER, Pid::from_raw(pid), offset) })
}
pub fn ptrace_pokeuser(pid: i32, offset: usize, val: c_long) -> nix::Result<c_long> {
Errno::result(unsafe { libc::ptrace(PTRACE_POKEUSER, Pid::from_raw(pid), offset, val) })
}
impl TraceBuf<'_> {
pub fn update_siginfo(&mut self, tid: pid_t) {
ptrace::getsiginfo(Pid::from_raw(tid))
.log_error("siginfo")
.map(|si| self.si = si);
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
mod arch_util {
use super::*;
pub type user_regs = libc::user;
pub type user_hwdebug_state = [reg_t; 8];
const OFFSET_DR: usize = memoffset::offset_of!(libc::user, u_debugreg);
const DEBUGREG_PTR: *const reg_t = OFFSET_DR as _;
#[extend::ext]
impl libc::user {
fn peek_dr(&mut self, pid: i32, i: usize) -> nix::Result<c_long> {
ptrace_peekuser(pid, unsafe { DEBUGREG_PTR.add(i) } as usize)
}
fn peek_dregs(&mut self, pid: i32) -> UDbgResult<()> {
for i in 0..self.u_debugreg.len() {
self.u_debugreg[i] = ptrace_peekuser(pid, unsafe { DEBUGREG_PTR.add(i) } as usize)
.with_context(|| format!("peek dr[{i}]"))?
as _;
}
Ok(())
}
fn poke_regs(&self, pid: i32) {
for i in (0..self.u_debugreg.len()).filter(|&i| i < 4 || i > 5) {
ptrace_pokeuser(
pid,
unsafe { DEBUGREG_PTR.add(i) as usize },
self.u_debugreg[i] as _,
)
.log_error_with(|err| format!("poke dr[{i}]: {err:?}"));
}
}
}
impl HWBPRegs for libc::user {
fn eflags(&mut self) -> &mut reg_t {
&mut self.regs.eflags
}
fn dr(&self, i: usize) -> reg_t {
self.u_debugreg[i]
}
fn set_dr(&mut self, i: usize, v: reg_t) {
self.u_debugreg[i] = v;
}
}
#[cfg(target_arch = "x86_64")]
impl AbstractRegs for user_regs_struct {
fn ip(&mut self) -> &mut reg_t {
&mut self.rip
}
fn sp(&mut self) -> &mut reg_t {
&mut self.rsp
}
}
#[cfg(target_arch = "x86")]
impl AbstractRegs for user_regs_struct {
fn ip(&mut self) -> &mut reg_t {
&mut self.eip
}
fn sp(&mut self) -> &mut reg_t {
&mut self.esp
}
}
impl AbstractRegs for libc::user {
fn ip(&mut self) -> &mut Self::REG {
self.regs.ip()
}
fn sp(&mut self) -> &mut Self::REG {
self.regs.sp()
}
}
impl TraceBuf<'_> {
pub fn update_regs(&mut self, tid: pid_t) {
ptrace::getregs(Pid::from_raw(tid))
.log_error("getregs")
.map(|regs| {
self.user.regs = regs;
self.regs_dirty = true;
});
}
pub fn write_regs(&self, tid: tid_t) {
ptrace::setregs(Pid::from_raw(tid), self.user.regs);
}
}
impl TargetCommon {
pub fn enable_hwbp_for_thread(
&self,
tid: tid_t,
_bp: &Breakpoint,
info: HwbpInfo,
enable: bool,
) -> UDbgResult<bool> {
unsafe {
let mut user: libc::user = core::mem::zeroed();
user.peek_dregs(tid)?;
let i = info.index as usize;
if enable {
user.u_debugreg[i] = self.dbg_reg[i].get() as _;
user.set_bp(self.dbg_reg[i].get(), i, info.rw, info.len);
} else {
user.unset_bp(i);
}
user.poke_regs(tid);
}
Ok(true)
}
pub fn get_hwbp(&self, tb: &mut TraceBuf) -> Option<Arc<Breakpoint>> {
let dr6 = tb
.user
.peek_dr(self.base.event_tid.get(), 6)
.log_error("peek dr6")?;
self.get_bp_(if dr6 & 0x01 > 0 {
-1
} else if dr6 & 0x02 > 0 {
-2
} else if dr6 & 0x04 > 0 {
-3
} else if dr6 & 0x08 > 0 {
-4
} else {
return None;
})
}
}
pub fn call_remote(pid: pid_t, fp: usize, ret: usize, args: &[reg_t]) -> anyhow::Result<reg_t> {
unimplemented!();
}
}
#[cfg(any(target_arch = "aarch64"))]
mod arch_util {
use super::*;
use core::mem::*;
pub const NT_PRSTATUS: i32 = 1;
pub const NT_PRFPREG: i32 = 2;
pub const NT_ARM_HW_BREAK: i32 = 0x402;
pub const NT_ARM_HW_WATCH: i32 = 0x403;
pub const PTRACE_GETREGS: i32 = 12;
pub const PTRACE_SETREGS: i32 = 13;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_hwdebug_state {
pub dbg_info: u32,
pub pad: u32,
pub dbg_regs: [user_hwdebug_state__bindgen_ty_1; 16usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_hwdebug_state__bindgen_ty_1 {
pub addr: u64,
pub ctrl: u32,
pub pad: u32,
}
impl user_hwdebug_state {
pub fn watch_len(&self, i: usize) -> usize {
match (self.dbg_regs[i].ctrl >> 5) & 0xff {
0x01 => 1,
0x03 => 2,
0x0f => 4,
0xff => 8,
_ => 0,
}
}
}
#[cfg(target_arch = "aarch64")]
impl AbstractRegs for user_regs_struct {
fn ip(&mut self) -> &mut reg_t {
&mut self.pc
}
fn sp(&mut self) -> &mut reg_t {
&mut self.sp
}
fn lr(&mut self) -> &mut Self::REG {
&mut self.regs[30]
}
}
#[cfg(target_arch = "arm")]
impl AbstractRegs for user_regs_struct {
fn ip(&mut self) -> &mut reg_t {
&mut self.pc
}
fn sp(&mut self) -> &mut reg_t {
&mut self.sp
}
fn lr(&mut self) -> &mut Self::REG {
&mut self.regs[14]
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct user_regs {
pub regs: user_regs_struct,
pub hwdebug: user_hwdebug_state,
}
impl AbstractRegs for user_regs {
fn ip(&mut self) -> &mut reg_t {
&mut self.regs.pc
}
fn sp(&mut self) -> &mut reg_t {
&mut self.regs.sp
}
fn lr(&mut self) -> &mut Self::REG {
&mut self.regs.regs[30]
}
}
impl HWBPRegs for user_regs {
fn cpsr(&mut self) -> &mut reg_t {
&mut self.regs.pstate
}
fn get_ctrl(&mut self, i: usize) -> &mut u32 {
&mut self.hwdebug.dbg_regs[i].ctrl
}
fn get_addr(&mut self, i: usize) -> &mut reg_t {
&mut self.hwdebug.dbg_regs[i].addr
}
}
impl TraceBuf<'_> {
pub fn update_regs(&mut self, tid: pid_t) {
ptrace_getregs(tid, &mut self.user.regs)
.log_error("getregs")
.map(|_| {
self.regs_dirty = true;
});
}
pub fn write_regs(&self, tid: tid_t) {
ptrace_setregs(tid, &self.user.regs);
}
}
impl TargetCommon {
pub fn enable_hwbp_for_thread(
&self,
tid: tid_t,
bp: &Breakpoint,
info: HwbpInfo,
enable: bool,
) -> UDbgResult<bool> {
let mut dreg = self.hwbps();
let i = info.index as usize;
dreg.dbg_regs[i].ctrl =
((info.rw as u32) << 3) | ((info.len as u32) << 5) | (2 << 1) | enable as u32;
dreg.dbg_regs[i].addr = bp.address as _;
dreg.ptrace_set(tid).context("set regset")?;
Ok(true)
}
pub fn get_hwbp(&self, tb: &mut TraceBuf) -> Option<Arc<Breakpoint>> {
if tb.si.si_code != TRAP_HWBKPT {
return None;
}
let addr = unsafe { tb.si.si_addr() as reg_t };
let dreg = self.hwbps();
let max_hwbps = 4;
for i in 0..max_hwbps {
let a = dreg.dbg_regs[i].addr;
let len = dreg.watch_len(i) as reg_t;
if dreg.dbg_regs[i].ctrl & 1 == 1 && addr >= a && addr < a + len {
return self.get_bp_(-(i as isize) - 1);
}
}
None
}
}
pub trait RegSet: Sized {
const NT: i32;
fn ptrace_get(&mut self, tid: pid_t) -> nix::Result<libc::c_long> {
unsafe {
let mut io = iovec {
iov_base: self as *mut _ as _,
iov_len: size_of_val(self),
};
Errno::result(ptrace(PTRACE_GETREGSET, tid, Self::NT, &mut io))
}
}
fn ptrace_set(&self, tid: pid_t) -> nix::Result<libc::c_long> {
unsafe {
let mut io = iovec {
iov_base: transmute(self),
iov_len: size_of_val(self),
};
Errno::result(ptrace(PTRACE_SETREGSET, tid, Self::NT, &mut io))
}
}
}
impl RegSet for user_regs_struct {
const NT: i32 = NT_PRSTATUS;
}
impl RegSet for user_hwdebug_state {
const NT: i32 = NT_ARM_HW_WATCH;
fn ptrace_set(&self, tid: pid_t) -> nix::Result<libc::c_long> {
unsafe {
let mut io = iovec {
iov_base: transmute(self),
iov_len: memoffset::offset_of!(user_hwdebug_state, dbg_regs)
+ size_of_val(&self.dbg_regs[0]) * 4,
};
Errno::result(ptrace(PTRACE_SETREGSET, tid, Self::NT, &mut io))
}
}
}
pub fn ptrace_getregs(tid: pid_t, regs: &mut user_regs_struct) -> nix::Result<libc::c_long> {
unsafe {
let mut io = iovec {
iov_len: size_of_val(regs),
iov_base: transmute(regs as *mut user_regs_struct),
};
Errno::result(ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &mut io))
.or_else(|_| Errno::result(ptrace(PTRACE_GETREGS as _, tid, 0, regs)))
}
}
pub fn ptrace_setregs(tid: pid_t, regs: &user_regs_struct) -> nix::Result<libc::c_long> {
unsafe {
let mut io = iovec {
iov_base: transmute(regs),
iov_len: size_of_val(regs),
};
Errno::result(ptrace(PTRACE_SETREGSET, tid, NT_PRSTATUS, &mut io))
.or_else(|_| Errno::result(ptrace(PTRACE_SETREGS as _, tid, 0, regs)))
}
}
pub fn call_remote(pid: pid_t, fp: usize, ret: usize, args: &[reg_t]) -> anyhow::Result<reg_t> {
#[cfg(target_arch = "arm")]
const REGS_ARG_NUM: usize = 4;
#[cfg(target_arch = "aarch64")]
const REGS_ARG_NUM: usize = 6;
unsafe {
let mut regs: user_regs_struct = core::mem::zeroed();
ptrace_getregs(pid, &mut regs).context("getregs")?;
let bak = regs;
for i in 0..REGS_ARG_NUM.min(args.len()) {
regs.regs[i] = args[i];
}
if args.len() > REGS_ARG_NUM {
let stack_num = args.len() - REGS_ARG_NUM;
*regs.sp() -= (size_of::<reg_t>() * stack_num) as reg_t;
ptrace_write(pid, *regs.sp() as _, args[REGS_ARG_NUM..].as_byte_array());
}
*regs.lr() = ret as reg_t;
*regs.ip() = fp as reg_t;
ptrace_setregs(pid, ®s).context("setregs")?;
ptrace::cont(Pid::from_raw(pid), None);
libc::waitpid(pid, core::ptr::null_mut(), WUNTRACED);
ptrace_getregs(pid, &mut regs);
ptrace_setregs(pid, &bak).context("setregs")?;
Ok(regs.regs[0])
}
}
}
pub use self::arch_util::*;