use alloc::{sync::Arc, vec, vec::Vec};
use core::mem::{MaybeUninit, size_of};
#[cfg(target_arch = "riscv64")]
use core::slice;
use ax_errno::{AxError, AxResult, LinuxError};
use ax_memory_addr::{MemoryAddr, VirtAddr};
use ax_runtime::hal::paging::MappingFlags;
use ax_task::current;
use starry_process::Pid;
use starry_signal::Signo;
use starry_vm::{VmMutPtr, VmPtr, vm_read_slice, vm_write_slice};
use crate::{
mm::{AddrSpace, IoVec},
task::{AsThread, ProcessData, get_process_data},
};
const PTRACE_TRACEME: u32 = 0;
const PTRACE_PEEKTEXT: u32 = 1;
const PTRACE_PEEKDATA: u32 = 2;
const PTRACE_POKETEXT: u32 = 4;
const PTRACE_POKEDATA: u32 = 5;
const PTRACE_CONT: u32 = 7;
const PTRACE_KILL: u32 = 8;
const PTRACE_SINGLESTEP: u32 = 9;
const PTRACE_GETREGS: u32 = 12;
const PTRACE_SETREGS: u32 = 13;
const PTRACE_GETFPREGS: u32 = 14;
const PTRACE_SETFPREGS: u32 = 15;
const PTRACE_ATTACH: u32 = 16;
const PTRACE_DETACH: u32 = 17;
const PTRACE_SYSCALL: u32 = 24;
const PTRACE_SETOPTIONS: u32 = 0x4200;
const PTRACE_GETEVENTMSG: u32 = 0x4201;
const PTRACE_GETSIGINFO: u32 = 0x4202;
const PTRACE_SETSIGINFO: u32 = 0x4203;
const PTRACE_GETREGSET: u32 = 0x4204;
const PTRACE_SETREGSET: u32 = 0x4205;
const PTRACE_SEIZE: u32 = 0x4206;
const PTRACE_INTERRUPT: u32 = 0x4207;
const NT_PRSTATUS: usize = 1;
#[cfg(target_arch = "riscv64")]
const NT_FPREGSET: usize = 2;
const PTRACE_O_TRACESYSGOOD: usize = 1;
const PTRACE_O_TRACEFORK: usize = 1 << 1;
const PTRACE_O_TRACEVFORK: usize = 1 << 2;
const PTRACE_O_TRACECLONE: usize = 1 << 3;
const PTRACE_O_TRACEEXEC: usize = 1 << 4;
const PTRACE_O_TRACEVFORKDONE: usize = 1 << 5;
const PTRACE_O_TRACEEXIT: usize = 1 << 6;
pub const PTRACE_EVENT_FORK: u32 = 1;
pub const PTRACE_EVENT_VFORK: u32 = 2;
pub const PTRACE_EVENT_CLONE: u32 = 3;
const PTRACE_EVENT_EXEC: u32 = 4;
pub const PTRACE_EVENT_VFORK_DONE: u32 = 5;
const PTRACE_EVENT_EXIT: u32 = 6;
#[cfg(target_arch = "riscv64")]
const EBREAK_INSN: u16 = 0x9002;
#[cfg(target_arch = "riscv64")]
#[repr(C)]
#[derive(Clone, Copy)]
struct RiscvUserRegs {
pc: usize,
ra: usize,
sp: usize,
gp: usize,
tp: usize,
t0: usize,
t1: usize,
t2: usize,
s0: usize,
s1: usize,
a0: usize,
a1: usize,
a2: usize,
a3: usize,
a4: usize,
a5: usize,
a6: usize,
a7: usize,
s2: usize,
s3: usize,
s4: usize,
s5: usize,
s6: usize,
s7: usize,
s8: usize,
s9: usize,
s10: usize,
s11: usize,
t3: usize,
t4: usize,
t5: usize,
t6: usize,
}
#[cfg(target_arch = "riscv64")]
#[repr(C)]
#[derive(Clone, Copy)]
struct RiscvFpRegs {
f: [u64; 32],
fcsr: usize,
}
pub fn sys_ptrace(request: u32, pid: usize, addr: usize, data: usize) -> AxResult<isize> {
info!("sys_ptrace <= request: {request}, pid: {pid}, addr: {addr:#x}, data: {data:#x}");
match request {
PTRACE_TRACEME => ptrace_traceme(),
PTRACE_PEEKTEXT | PTRACE_PEEKDATA => ptrace_peekdata(pid, addr, data),
PTRACE_POKETEXT | PTRACE_POKEDATA => ptrace_pokedata(pid, addr, data),
PTRACE_CONT => ptrace_cont(pid, data),
PTRACE_KILL => ptrace_kill(pid),
PTRACE_SINGLESTEP => ptrace_singlestep(pid, data),
PTRACE_GETREGS => ptrace_getregs(pid, data),
PTRACE_SETREGS => ptrace_setregs(pid, data),
PTRACE_GETFPREGS => ptrace_getfpregs(pid, data),
PTRACE_SETFPREGS => ptrace_setfpregs(pid, data),
PTRACE_ATTACH => ptrace_attach(pid),
PTRACE_DETACH => ptrace_detach(pid, data),
PTRACE_SYSCALL => ptrace_syscall(pid, data),
PTRACE_SETOPTIONS => ptrace_setoptions(pid, addr),
PTRACE_GETEVENTMSG => ptrace_geteventmsg(pid, data),
PTRACE_GETSIGINFO => ptrace_getsiginfo(pid, data),
PTRACE_SETSIGINFO => ptrace_setsiginfo(pid, data),
PTRACE_GETREGSET => ptrace_getregset(pid, addr, data),
PTRACE_SETREGSET => ptrace_setregset(pid, addr, data),
PTRACE_SEIZE => ptrace_seize(pid, addr),
PTRACE_INTERRUPT => ptrace_interrupt(pid),
_ => Err(AxError::Unsupported),
}
}
fn ptrace_traceme() -> AxResult<isize> {
let curr = current();
let proc_data = &curr.as_thread().proc_data;
if proc_data.proc.parent().is_none()
|| proc_data.is_ptrace_traceme()
|| proc_data.is_ptrace_attached()
|| proc_data.ptrace_tracer_pid().is_some()
{
return Err(AxError::from(LinuxError::EPERM));
}
proc_data.set_ptrace_traceme();
Ok(0)
}
fn ptrace_resume_signo(data: usize) -> AxResult<u32> {
if data == 0 {
return Ok(0);
}
let signo = u8::try_from(data).map_err(|_| AxError::from(LinuxError::EIO))?;
Signo::from_repr(signo).ok_or_else(|| AxError::from(LinuxError::EIO))?;
Ok(signo as u32)
}
fn ptrace_cont(pid: usize, data: usize) -> AxResult<isize> {
let signo = ptrace_resume_signo(data)?;
let tracee = ptrace_stopped_tracee(pid)?;
tracee.set_ptrace_singlestep(false);
tracee.set_ptrace_syscall_trace(false);
tracee.resume_ptrace_stop_with_signal(signo);
Ok(0)
}
fn ptrace_kill(pid: usize) -> AxResult<isize> {
let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
let tracee = get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
if tracee.is_ptrace_traceme() || tracee.is_ptrace_attached() {
tracee.clear_ptrace_stop();
tracee.clear_ptrace_traceme();
tracee.clear_ptrace_attached();
}
use starry_signal::SignalInfo;
use crate::task::send_signal_to_process;
let _ = send_signal_to_process(tracee_pid, Some(SignalInfo::new_kernel(Signo::SIGKILL)));
Ok(0)
}
fn ptrace_singlestep(pid: usize, data: usize) -> AxResult<isize> {
let signo = ptrace_resume_signo(data)?;
let tracee = ptrace_stopped_tracee(pid)?;
tracee.set_ptrace_singlestep(true);
tracee.set_ptrace_syscall_trace(false);
tracee.resume_ptrace_stop_with_signal(signo);
Ok(0)
}
fn ptrace_attach(pid: usize) -> AxResult<isize> {
let tracer_pid = current().as_thread().proc_data.proc.pid();
let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
if tracee_pid == tracer_pid {
return Err(AxError::from(LinuxError::EPERM));
}
let tracee = get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
if tracee.is_ptrace_traceme() || tracee.is_ptrace_attached() {
return Err(AxError::from(LinuxError::EPERM));
}
let is_child = tracee.proc.parent().is_some_and(|p| p.pid() == tracer_pid);
if !is_child {
return Err(AxError::from(LinuxError::EPERM));
}
tracee.set_ptrace_tracer_pid(tracer_pid);
tracee.set_ptrace_attached();
use starry_signal::SignalInfo;
let _ = crate::task::send_signal_to_process(
tracee_pid,
Some(SignalInfo::new_kernel(Signo::SIGSTOP)),
);
Ok(0)
}
fn ptrace_detach(pid: usize, data: usize) -> AxResult<isize> {
let signo = ptrace_resume_signo(data)?;
let tracee = ptrace_stopped_tracee(pid)?;
tracee.clear_ptrace_traceme();
tracee.clear_ptrace_attached();
tracee.clear_ptrace_tracer_pid();
tracee.set_ptrace_singlestep(false);
tracee.set_ptrace_syscall_trace(false);
tracee.set_ptrace_options(0);
tracee.resume_ptrace_stop_with_signal(signo);
Ok(0)
}
fn ptrace_syscall(pid: usize, data: usize) -> AxResult<isize> {
let signo = ptrace_resume_signo(data)?;
let tracee = ptrace_stopped_tracee(pid)?;
tracee.set_ptrace_singlestep(false);
tracee.set_ptrace_syscall_trace(true);
tracee.resume_ptrace_stop_with_signal(signo);
Ok(0)
}
fn ptrace_setoptions(pid: usize, options: usize) -> AxResult<isize> {
let tracee = ptrace_stopped_tracee(pid)?;
let valid_mask = PTRACE_O_TRACESYSGOOD
| PTRACE_O_TRACEFORK
| PTRACE_O_TRACEVFORK
| PTRACE_O_TRACECLONE
| PTRACE_O_TRACEEXEC
| PTRACE_O_TRACEVFORKDONE
| PTRACE_O_TRACEEXIT;
if options & !valid_mask != 0 {
return Err(AxError::InvalidInput);
}
tracee.set_ptrace_options(options);
Ok(0)
}
fn ptrace_geteventmsg(pid: usize, data: usize) -> AxResult<isize> {
let tracee = ptrace_stopped_tracee(pid)?;
let msg = tracee.ptrace_event_msg();
(data as *mut usize).vm_write(msg)?;
Ok(0)
}
fn ptrace_getsiginfo(pid: usize, data: usize) -> AxResult<isize> {
let tracee = ptrace_stopped_tracee(pid)?;
let siginfo = tracee
.ptrace_stop_siginfo()
.ok_or_else(|| AxError::from(LinuxError::ESRCH))?;
#[cfg(target_arch = "riscv64")]
{
let bytes = unsafe {
slice::from_raw_parts(
(&siginfo.0 as *const linux_raw_sys::general::siginfo_t).cast::<u8>(),
size_of::<starry_signal::SignalInfo>(),
)
};
vm_write_slice(data as *mut u8, bytes)?;
Ok(0)
}
#[cfg(not(target_arch = "riscv64"))]
{
let _ = (data, siginfo);
Err(AxError::Unsupported)
}
}
#[cfg(target_arch = "riscv64")]
fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let tracee = ptrace_stopped_tracee(pid)?;
let siginfo = ptrace_read_user_siginfo(data)?;
let signo = ptrace_siginfo_signo(&siginfo)?;
if !tracee.set_ptrace_stop_siginfo(signo, starry_signal::SignalInfo(siginfo)) {
return Err(AxError::from(LinuxError::ESRCH));
}
Ok(0)
}
#[cfg(not(target_arch = "riscv64"))]
fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult<isize> {
let _ = (pid, data);
Err(AxError::Unsupported)
}
fn ptrace_getregset(pid: usize, addr: usize, data: usize) -> AxResult<isize> {
match addr {
NT_PRSTATUS => ptrace_getregset_prstatus(pid, data),
#[cfg(target_arch = "riscv64")]
NT_FPREGSET => ptrace_getregset_fpregset(pid, data),
_ => Err(AxError::Unsupported),
}
}
fn ptrace_setregset(pid: usize, addr: usize, data: usize) -> AxResult<isize> {
match addr {
NT_PRSTATUS => ptrace_setregset_prstatus(pid, data),
#[cfg(target_arch = "riscv64")]
NT_FPREGSET => ptrace_setregset_fpregset(pid, data),
_ => Err(AxError::Unsupported),
}
}
#[cfg(target_arch = "riscv64")]
fn ptrace_getregs(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let regs = ptrace_read_stopped_user_regs(pid)?;
let bytes = unsafe {
slice::from_raw_parts(
(®s as *const RiscvUserRegs).cast::<u8>(),
size_of::<RiscvUserRegs>(),
)
};
vm_write_slice(data as *mut u8, bytes)?;
Ok(0)
}
#[cfg(not(target_arch = "riscv64"))]
fn ptrace_getregs(pid: usize, data: usize) -> AxResult<isize> {
let _ = (pid, data);
Err(AxError::Unsupported)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_setregs(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let regs = ptrace_read_user_regs(data)?;
ptrace_write_stopped_user_regs(pid, regs)
}
#[cfg(not(target_arch = "riscv64"))]
fn ptrace_setregs(pid: usize, data: usize) -> AxResult<isize> {
let _ = (pid, data);
Err(AxError::Unsupported)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_getfpregs(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let regs = ptrace_read_stopped_fp_regs(pid)?;
let bytes = unsafe {
slice::from_raw_parts(
(®s as *const RiscvFpRegs).cast::<u8>(),
size_of::<RiscvFpRegs>(),
)
};
vm_write_slice(data as *mut u8, bytes)?;
Ok(0)
}
#[cfg(not(target_arch = "riscv64"))]
fn ptrace_getfpregs(pid: usize, data: usize) -> AxResult<isize> {
let _ = (pid, data);
Err(AxError::Unsupported)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_setfpregs(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let regs = ptrace_read_user_fpregs(data)?;
ptrace_write_stopped_fp_regs(pid, regs)
}
#[cfg(not(target_arch = "riscv64"))]
fn ptrace_setfpregs(pid: usize, data: usize) -> AxResult<isize> {
let _ = (pid, data);
Err(AxError::Unsupported)
}
fn ptrace_seize(pid: usize, _addr: usize) -> AxResult<isize> {
let tracer_pid = current().as_thread().proc_data.proc.pid();
let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
if tracee_pid == tracer_pid {
return Err(AxError::from(LinuxError::EPERM));
}
let tracee = get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
if tracee.is_ptrace_traceme() || tracee.is_ptrace_attached() {
return Err(AxError::from(LinuxError::EPERM));
}
let is_child = tracee.proc.parent().is_some_and(|p| p.pid() == tracer_pid);
if !is_child {
return Err(AxError::from(LinuxError::EPERM));
}
tracee.set_ptrace_tracer_pid(tracer_pid);
tracee.set_ptrace_attached();
Ok(0)
}
fn ptrace_interrupt(pid: usize) -> AxResult<isize> {
let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
let tracee = get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
if !tracee.is_ptrace_attached() && !tracee.is_ptrace_traceme() {
return Err(AxError::from(LinuxError::ESRCH));
}
if tracee.ptrace_stop_signo().is_some() {
return Ok(0);
}
use starry_signal::SignalInfo;
let _ = crate::task::send_signal_to_process(
tracee_pid,
Some(SignalInfo::new_kernel(Signo::SIGSTOP)),
);
Ok(0)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let regs = ptrace_read_stopped_user_regs(pid)?;
let mut iov = (data as *const IoVec).vm_read()?;
if iov.iov_len < 0 {
return Err(AxError::InvalidInput);
}
let bytes = unsafe {
slice::from_raw_parts(
(®s as *const RiscvUserRegs).cast::<u8>(),
size_of::<RiscvUserRegs>(),
)
};
let copy_len = (iov.iov_len as usize).min(bytes.len());
vm_write_slice(iov.iov_base, &bytes[..copy_len])?;
iov.iov_len = copy_len as isize;
(data as *mut IoVec).vm_write(iov)?;
Ok(0)
}
#[cfg(not(target_arch = "riscv64"))]
fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult<isize> {
let _ = (pid, data);
Err(AxError::Unsupported)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_setregset_prstatus(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let iov = (data as *const IoVec).vm_read()?;
if iov.iov_len < size_of::<RiscvUserRegs>() as isize {
return Err(AxError::InvalidInput);
}
let regs = ptrace_read_user_regs(iov.iov_base as usize)?;
ptrace_write_stopped_user_regs(pid, regs)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_read_stopped_user_regs(pid: usize) -> AxResult<RiscvUserRegs> {
let tracee = ptrace_stopped_tracee(pid)?;
let uctx = tracee
.ptrace_stop_user_context()
.ok_or_else(|| AxError::from(LinuxError::ESRCH))?;
Ok(RiscvUserRegs::from(&uctx))
}
#[cfg(target_arch = "riscv64")]
fn ptrace_read_user_regs(data: usize) -> AxResult<RiscvUserRegs> {
let mut regs = MaybeUninit::<RiscvUserRegs>::uninit();
let bytes = unsafe {
slice::from_raw_parts_mut(
regs.as_mut_ptr().cast::<MaybeUninit<u8>>(),
size_of::<RiscvUserRegs>(),
)
};
starry_vm::vm_read_slice(data as *const u8, bytes)?;
Ok(unsafe { regs.assume_init() })
}
#[cfg(target_arch = "riscv64")]
fn ptrace_write_stopped_user_regs(pid: usize, regs: RiscvUserRegs) -> AxResult<isize> {
let tracee = ptrace_stopped_tracee(pid)?;
let mut uctx = tracee
.ptrace_stop_user_context()
.ok_or_else(|| AxError::from(LinuxError::ESRCH))?;
regs.write_to(&mut uctx);
if !tracee.set_ptrace_stop_user_context(uctx) {
return Err(AxError::from(LinuxError::ESRCH));
}
Ok(0)
}
#[cfg(not(target_arch = "riscv64"))]
fn ptrace_setregset_prstatus(pid: usize, data: usize) -> AxResult<isize> {
let _ = (pid, data);
Err(AxError::Unsupported)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_getregset_fpregset(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let regs = ptrace_read_stopped_fp_regs(pid)?;
let mut iov = (data as *const IoVec).vm_read()?;
if iov.iov_len < size_of::<RiscvFpRegs>() as isize {
return Err(AxError::InvalidInput);
}
let bytes = unsafe {
slice::from_raw_parts(
(®s as *const RiscvFpRegs).cast::<u8>(),
size_of::<RiscvFpRegs>(),
)
};
vm_write_slice(iov.iov_base, bytes)?;
iov.iov_len = bytes.len() as isize;
(data as *mut IoVec).vm_write(iov)?;
Ok(0)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_setregset_fpregset(pid: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let iov = (data as *const IoVec).vm_read()?;
if iov.iov_len < size_of::<RiscvFpRegs>() as isize {
return Err(AxError::InvalidInput);
}
let regs = ptrace_read_user_fpregs(iov.iov_base as usize)?;
ptrace_write_stopped_fp_regs(pid, regs)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_read_stopped_fp_regs(pid: usize) -> AxResult<RiscvFpRegs> {
let tracee = ptrace_stopped_tracee(pid)?;
let fp_data = tracee
.ptrace_stop_fp_data()
.ok_or_else(|| AxError::from(LinuxError::ESRCH))?;
Ok(RiscvFpRegs {
f: fp_data.0,
fcsr: fp_data.1,
})
}
#[cfg(target_arch = "riscv64")]
fn ptrace_read_user_fpregs(data: usize) -> AxResult<RiscvFpRegs> {
let mut regs = MaybeUninit::<RiscvFpRegs>::uninit();
let bytes = unsafe {
slice::from_raw_parts_mut(
regs.as_mut_ptr().cast::<MaybeUninit<u8>>(),
size_of::<RiscvFpRegs>(),
)
};
starry_vm::vm_read_slice(data as *const u8, bytes)?;
Ok(unsafe { regs.assume_init() })
}
#[cfg(target_arch = "riscv64")]
fn ptrace_read_user_siginfo(data: usize) -> AxResult<linux_raw_sys::general::siginfo_t> {
let mut siginfo = MaybeUninit::<linux_raw_sys::general::siginfo_t>::uninit();
let bytes = unsafe {
slice::from_raw_parts_mut(
siginfo.as_mut_ptr().cast::<MaybeUninit<u8>>(),
size_of::<linux_raw_sys::general::siginfo_t>(),
)
};
starry_vm::vm_read_slice(data as *const u8, bytes)?;
Ok(unsafe { siginfo.assume_init() })
}
#[cfg(target_arch = "riscv64")]
fn ptrace_siginfo_signo(siginfo: &linux_raw_sys::general::siginfo_t) -> AxResult<Signo> {
let signo = unsafe { siginfo.__bindgen_anon_1.__bindgen_anon_1.si_signo };
Signo::from_repr(signo as u8).ok_or(AxError::InvalidInput)
}
#[cfg(target_arch = "riscv64")]
fn ptrace_write_stopped_fp_regs(pid: usize, regs: RiscvFpRegs) -> AxResult<isize> {
let tracee = ptrace_stopped_tracee(pid)?;
if !tracee.set_ptrace_stop_fp_data((regs.f, regs.fcsr)) {
return Err(AxError::from(LinuxError::ESRCH));
}
Ok(0)
}
fn ptrace_peekdata(pid: usize, addr: usize, data: usize) -> AxResult<isize> {
if data == 0 {
return Err(AxError::InvalidInput);
}
let tracee = ptrace_stopped_tracee(pid)?;
(data as *mut usize).vm_write(ptrace_read_word(&tracee, addr)?)?;
Ok(0)
}
fn ptrace_pokedata(pid: usize, addr: usize, data: usize) -> AxResult<isize> {
let tracee = ptrace_stopped_tracee(pid)?;
ptrace_write_word(&tracee, addr, data)?;
Ok(0)
}
fn ptrace_read_word(tracee: &ProcessData, addr: usize) -> AxResult<usize> {
let aspace = tracee.aspace();
let mut aspace = aspace.lock();
ptrace_populate_remote_range(&mut aspace, addr, size_of::<usize>(), MappingFlags::READ)?;
let mut bytes = [0u8; size_of::<usize>()];
aspace.read(VirtAddr::from_usize(addr), &mut bytes)?;
Ok(usize::from_ne_bytes(bytes))
}
fn ptrace_write_word(tracee: &ProcessData, addr: usize, data: usize) -> AxResult {
let aspace = tracee.aspace();
let mut aspace = aspace.lock();
ptrace_populate_remote_range(&mut aspace, addr, size_of::<usize>(), MappingFlags::WRITE)?;
aspace.write(VirtAddr::from_usize(addr), &data.to_ne_bytes())?;
ax_runtime::hal::cpu::asm::flush_icache_all();
Ok(())
}
fn ptrace_populate_remote_range(
aspace: &mut AddrSpace,
addr: usize,
len: usize,
access_flags: MappingFlags,
) -> AxResult {
let start = VirtAddr::from_usize(addr);
let end = VirtAddr::from_usize(addr.checked_add(len).ok_or(AxError::BadAddress)?);
let page_start = start.align_down_4k();
let page_end = end.align_up_4k();
aspace.populate_area(page_start, page_end - page_start, access_flags)
}
pub fn sys_process_vm_readv(
pid: usize,
local_iov: *const IoVec,
liovcnt: usize,
remote_iov: *const IoVec,
riovcnt: usize,
flags: usize,
) -> AxResult<isize> {
if flags != 0 {
return Err(AxError::InvalidInput);
}
process_vm_copy(pid, local_iov, liovcnt, remote_iov, riovcnt, false)
}
pub fn sys_process_vm_writev(
pid: usize,
local_iov: *const IoVec,
liovcnt: usize,
remote_iov: *const IoVec,
riovcnt: usize,
flags: usize,
) -> AxResult<isize> {
if flags != 0 {
return Err(AxError::InvalidInput);
}
process_vm_copy(pid, local_iov, liovcnt, remote_iov, riovcnt, true)
}
fn process_vm_copy(
pid: usize,
local_iov: *const IoVec,
liovcnt: usize,
remote_iov: *const IoVec,
riovcnt: usize,
write_remote: bool,
) -> AxResult<isize> {
let tracee = process_vm_tracee(pid)?;
let local = read_iovecs(local_iov, liovcnt)?;
let remote = read_iovecs(remote_iov, riovcnt)?;
let mut local_idx = 0;
let mut remote_idx = 0;
let mut local_off = 0;
let mut remote_off = 0;
let mut copied = 0usize;
while local_idx < local.len() && remote_idx < remote.len() {
skip_empty_iovecs(&local, &mut local_idx, &mut local_off);
skip_empty_iovecs(&remote, &mut remote_idx, &mut remote_off);
if local_idx >= local.len() || remote_idx >= remote.len() {
break;
}
let local_len = local[local_idx].iov_len as usize - local_off;
let remote_len = remote[remote_idx].iov_len as usize - remote_off;
let chunk_len = local_len.min(remote_len);
if chunk_len == 0 {
break;
}
let local_addr = local[local_idx].iov_base.wrapping_add(local_off);
let remote_addr = (remote[remote_idx].iov_base as usize)
.checked_add(remote_off)
.ok_or(AxError::BadAddress)?;
let result: AxResult<()> = if write_remote {
let mut data = vec![0; chunk_len];
let bytes = unsafe {
core::slice::from_raw_parts_mut(
data.as_mut_ptr().cast::<MaybeUninit<u8>>(),
data.len(),
)
};
vm_read_slice(local_addr, bytes)?;
remote_write(&tracee, remote_addr, &data)
} else {
let data = remote_read(&tracee, remote_addr, chunk_len)?;
vm_write_slice(local_addr, &data)?;
Ok(())
};
if let Err(err) = result {
return if copied == 0 {
Err(err)
} else {
Ok(copied as isize)
};
}
copied = copied.checked_add(chunk_len).ok_or(AxError::InvalidInput)?;
local_off += chunk_len;
remote_off += chunk_len;
}
Ok(copied as isize)
}
fn process_vm_tracee(pid: usize) -> AxResult<Arc<ProcessData>> {
let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
let current_pid = current().as_thread().proc_data.proc.pid();
if tracee_pid == current_pid {
get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))
} else {
ptrace_stopped_tracee(pid)
}
}
fn read_iovecs(iov: *const IoVec, iovcnt: usize) -> AxResult<Vec<IoVec>> {
if iovcnt > 1024 {
return Err(AxError::InvalidInput);
}
let mut iovecs = Vec::with_capacity(iovcnt);
let mut total = 0usize;
for idx in 0..iovcnt {
let iov = iov.wrapping_add(idx).vm_read()?;
if iov.iov_len < 0 {
return Err(AxError::InvalidInput);
}
total = total
.checked_add(iov.iov_len as usize)
.filter(|len| *len <= isize::MAX as usize)
.ok_or(AxError::InvalidInput)?;
iovecs.push(iov);
}
Ok(iovecs)
}
fn skip_empty_iovecs(iovecs: &[IoVec], idx: &mut usize, offset: &mut usize) {
while *idx < iovecs.len() && *offset >= iovecs[*idx].iov_len as usize {
*idx += 1;
*offset = 0;
}
}
fn remote_read(tracee: &ProcessData, addr: usize, len: usize) -> AxResult<Vec<u8>> {
let aspace = tracee.aspace();
let mut aspace = aspace.lock();
ptrace_populate_remote_range(&mut aspace, addr, len, MappingFlags::READ)?;
let mut data = vec![0; len];
aspace.read(VirtAddr::from_usize(addr), &mut data)?;
Ok(data)
}
fn remote_write(tracee: &ProcessData, addr: usize, data: &[u8]) -> AxResult {
let aspace = tracee.aspace();
let mut aspace = aspace.lock();
ptrace_populate_remote_range(&mut aspace, addr, data.len(), MappingFlags::WRITE)?;
aspace.write(VirtAddr::from_usize(addr), data)?;
ax_runtime::hal::cpu::asm::flush_icache_all();
Ok(())
}
fn ptrace_stopped_tracee(pid: usize) -> AxResult<Arc<ProcessData>> {
let pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
let tracer_pid = current().as_thread().proc_data.proc.pid();
let tracee = get_process_data(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?;
let is_tracer = (tracee.is_ptrace_traceme() || tracee.is_ptrace_attached())
&& tracee
.ptrace_tracer_pid()
.is_some_and(|pid| pid == tracer_pid);
if !is_tracer || tracee.ptrace_stop_signo().is_none() {
return Err(AxError::from(LinuxError::ESRCH));
}
Ok(tracee)
}
#[cfg(target_arch = "riscv64")]
pub fn ptrace_setup_singlestep(
tracee: &ProcessData,
uctx: &mut ax_runtime::hal::cpu::uspace::UserContext,
) {
let pc = uctx.ip();
let aspace = tracee.aspace();
let mut aspace = aspace.lock();
let saved = tracee.take_ptrace_ss_saved_insn();
if let Some((saved_addr, saved_insn)) = saved {
let _ = ptrace_write_u16_unlocked(&mut aspace, saved_addr, saved_insn as u16);
}
let first_half = match ptrace_read_u16_unlocked(&aspace, pc) {
Ok(half) => half,
Err(_) => {
tracee.set_ptrace_ss_saved_insn(None);
return;
}
};
let insn_len = riscv_insn_len(first_half);
let current_insn = if insn_len == 2 {
first_half as u32
} else {
match ptrace_read_u32_unlocked(&aspace, pc) {
Ok(word) => word,
Err(_) => {
tracee.set_ptrace_ss_saved_insn(None);
return;
}
}
};
let next_insn_addr = riscv_next_pc(current_insn, insn_len, pc, uctx);
if next_insn_addr == pc {
tracee.set_ptrace_ss_saved_insn(None);
return;
}
let orig_insn = match ptrace_read_u16_unlocked(&aspace, next_insn_addr) {
Ok(half) => half,
Err(_) => {
tracee.set_ptrace_ss_saved_insn(None);
return;
}
};
if orig_insn == EBREAK_INSN {
tracee.set_ptrace_ss_saved_insn(None);
ax_runtime::hal::cpu::asm::flush_icache_all();
return;
}
let _ = ptrace_write_u16_unlocked(&mut aspace, next_insn_addr, EBREAK_INSN);
tracee.set_ptrace_ss_saved_insn(Some((next_insn_addr, orig_insn as usize)));
ax_runtime::hal::cpu::asm::flush_icache_all();
}
#[cfg(target_arch = "riscv64")]
fn riscv_insn_len(first_half: u16) -> usize {
if first_half & 0x3 != 0x3 { 2 } else { 4 }
}
#[cfg(target_arch = "riscv64")]
fn riscv_next_pc(
insn: u32,
insn_len: usize,
pc: usize,
uctx: &ax_runtime::hal::cpu::uspace::UserContext,
) -> usize {
if insn_len == 2 {
return riscv_compressed_next_pc(insn as u16, pc, uctx);
}
match insn & 0x7f {
0x63 => {
let rs1 = ((insn >> 15) & 0x1f) as usize;
let rs2 = ((insn >> 20) & 0x1f) as usize;
let lhs = riscv_reg(uctx, rs1);
let rhs = riscv_reg(uctx, rs2);
let take = match (insn >> 12) & 0x7 {
0x0 => lhs == rhs,
0x1 => lhs != rhs,
0x4 => (lhs as isize) < (rhs as isize),
0x5 => (lhs as isize) >= (rhs as isize),
0x6 => lhs < rhs,
0x7 => lhs >= rhs,
_ => false,
};
if take {
riscv_add_pc(
pc,
riscv_sign_extend(
(((insn >> 31) & 0x1) << 12)
| (((insn >> 7) & 0x1) << 11)
| (((insn >> 25) & 0x3f) << 5)
| (((insn >> 8) & 0xf) << 1),
13,
),
)
} else {
pc + 4
}
}
0x67 => {
let rs1 = ((insn >> 15) & 0x1f) as usize;
let imm = riscv_sign_extend((insn >> 20) & 0xfff, 12);
riscv_add_pc(riscv_reg(uctx, rs1), imm) & !0x1
}
0x6f => riscv_add_pc(
pc,
riscv_sign_extend(
(((insn >> 31) & 0x1) << 20)
| (((insn >> 12) & 0xff) << 12)
| (((insn >> 20) & 0x1) << 11)
| (((insn >> 21) & 0x3ff) << 1),
21,
),
),
_ => pc + 4,
}
}
#[cfg(target_arch = "riscv64")]
fn riscv_compressed_next_pc(
insn: u16,
pc: usize,
uctx: &ax_runtime::hal::cpu::uspace::UserContext,
) -> usize {
let quadrant = insn & 0x3;
let funct3 = (insn >> 13) & 0x7;
if quadrant == 0x1 {
match funct3 {
0x1 | 0x5 => {
return riscv_add_pc(pc, riscv_sign_extend(riscv_cj_imm(insn) as u32, 12));
}
0x6 | 0x7 => {
let rs1 = 8 + (((insn >> 7) & 0x7) as usize);
let take = if funct3 == 0x6 {
riscv_reg(uctx, rs1) == 0
} else {
riscv_reg(uctx, rs1) != 0
};
return if take {
riscv_add_pc(pc, riscv_sign_extend(riscv_cb_imm(insn) as u32, 9))
} else {
pc + 2
};
}
_ => {}
}
}
if quadrant == 0x2 && funct3 == 0x4 {
let rs1 = ((insn >> 7) & 0x1f) as usize;
let rs2 = ((insn >> 2) & 0x1f) as usize;
if rs1 != 0 && rs2 == 0 {
return riscv_reg(uctx, rs1);
}
}
pc + 2
}
#[cfg(target_arch = "riscv64")]
fn riscv_cj_imm(insn: u16) -> u16 {
(((insn >> 12) & 0x1) << 11)
| (((insn >> 11) & 0x1) << 4)
| (((insn >> 9) & 0x3) << 8)
| (((insn >> 8) & 0x1) << 10)
| (((insn >> 7) & 0x1) << 6)
| (((insn >> 6) & 0x1) << 7)
| (((insn >> 3) & 0x7) << 1)
| (((insn >> 2) & 0x1) << 5)
}
#[cfg(target_arch = "riscv64")]
fn riscv_cb_imm(insn: u16) -> u16 {
(((insn >> 12) & 0x1) << 8)
| (((insn >> 10) & 0x3) << 3)
| (((insn >> 5) & 0x3) << 6)
| (((insn >> 3) & 0x3) << 1)
| (((insn >> 2) & 0x1) << 5)
}
#[cfg(target_arch = "riscv64")]
fn riscv_sign_extend(value: u32, bits: u32) -> isize {
let shift = usize::BITS - bits;
((value as usize) << shift) as isize >> shift
}
#[cfg(target_arch = "riscv64")]
fn riscv_add_pc(base: usize, offset: isize) -> usize {
if offset >= 0 {
base.wrapping_add(offset as usize)
} else {
base.wrapping_sub((-offset) as usize)
}
}
#[cfg(target_arch = "riscv64")]
fn riscv_reg(uctx: &ax_runtime::hal::cpu::uspace::UserContext, index: usize) -> usize {
match index {
0 => 0,
1 => uctx.regs.ra,
2 => uctx.regs.sp,
3 => uctx.regs.gp,
4 => uctx.regs.tp,
5 => uctx.regs.t0,
6 => uctx.regs.t1,
7 => uctx.regs.t2,
8 => uctx.regs.s0,
9 => uctx.regs.s1,
10 => uctx.regs.a0,
11 => uctx.regs.a1,
12 => uctx.regs.a2,
13 => uctx.regs.a3,
14 => uctx.regs.a4,
15 => uctx.regs.a5,
16 => uctx.regs.a6,
17 => uctx.regs.a7,
18 => uctx.regs.s2,
19 => uctx.regs.s3,
20 => uctx.regs.s4,
21 => uctx.regs.s5,
22 => uctx.regs.s6,
23 => uctx.regs.s7,
24 => uctx.regs.s8,
25 => uctx.regs.s9,
26 => uctx.regs.s10,
27 => uctx.regs.s11,
28 => uctx.regs.t3,
29 => uctx.regs.t4,
30 => uctx.regs.t5,
31 => uctx.regs.t6,
_ => 0,
}
}
#[cfg(target_arch = "riscv64")]
fn ptrace_read_u16_unlocked(aspace: &AddrSpace, addr: usize) -> AxResult<u16> {
let mut bytes = [0u8; size_of::<u16>()];
aspace.read(VirtAddr::from_usize(addr), &mut bytes)?;
Ok(u16::from_ne_bytes(bytes))
}
#[cfg(target_arch = "riscv64")]
fn ptrace_read_u32_unlocked(aspace: &AddrSpace, addr: usize) -> AxResult<u32> {
let mut bytes = [0u8; size_of::<u32>()];
aspace.read(VirtAddr::from_usize(addr), &mut bytes)?;
Ok(u32::from_ne_bytes(bytes))
}
#[cfg(target_arch = "riscv64")]
fn ptrace_write_u16_unlocked(aspace: &mut AddrSpace, addr: usize, data: u16) -> AxResult {
aspace.write(VirtAddr::from_usize(addr), &data.to_ne_bytes())?;
Ok(())
}
pub fn ptrace_notify_clone(parent_pid: Pid, child_pid: Pid, event: u32) -> bool {
let Ok(parent) = get_process_data(parent_pid) else {
return false;
};
if !parent.is_ptrace_traceme() && !parent.is_ptrace_attached() {
return false;
}
let options = parent.ptrace_options();
let option_flag = match event {
PTRACE_EVENT_FORK => PTRACE_O_TRACEFORK,
PTRACE_EVENT_VFORK => PTRACE_O_TRACEVFORK,
PTRACE_EVENT_CLONE => PTRACE_O_TRACECLONE,
_ => return false,
};
if options & option_flag == 0 {
return false;
}
parent.set_ptrace_event_msg(child_pid as usize);
parent.set_ptrace_event(event);
true
}
pub fn ptrace_notify_exec(tracee_pid: Pid) -> bool {
let Ok(tracee) = get_process_data(tracee_pid) else {
return false;
};
let options = tracee.ptrace_options();
if options & PTRACE_O_TRACEEXEC == 0 {
return false;
}
tracee.set_ptrace_event_msg(tracee_pid as usize);
tracee.set_ptrace_event(PTRACE_EVENT_EXEC);
true
}
pub fn ptrace_notify_exit(tracee_pid: Pid, exit_code: i32) -> bool {
let Ok(tracee) = get_process_data(tracee_pid) else {
return false;
};
if !tracee.is_ptrace_traceme() && !tracee.is_ptrace_attached() {
return false;
}
let options = tracee.ptrace_options();
if options & PTRACE_O_TRACEEXIT == 0 {
return false;
}
tracee.set_ptrace_event_msg(exit_code as usize);
tracee.set_ptrace_event(PTRACE_EVENT_EXIT);
true
}
pub fn ptrace_notify_vfork_done(parent_pid: Pid, child_pid: Pid) -> bool {
let Ok(parent) = get_process_data(parent_pid) else {
return false;
};
if !parent.is_ptrace_traceme() && !parent.is_ptrace_attached() {
return false;
}
let options = parent.ptrace_options();
if options & PTRACE_O_TRACEVFORKDONE == 0 {
return false;
}
parent.set_ptrace_event_msg(child_pid as usize);
parent.set_ptrace_event(PTRACE_EVENT_VFORK_DONE);
true
}
#[cfg(target_arch = "riscv64")]
impl From<&ax_runtime::hal::cpu::uspace::UserContext> for RiscvUserRegs {
fn from(uctx: &ax_runtime::hal::cpu::uspace::UserContext) -> Self {
let r = &uctx.regs;
Self {
pc: uctx.sepc,
ra: r.ra,
sp: r.sp,
gp: r.gp,
tp: r.tp,
t0: r.t0,
t1: r.t1,
t2: r.t2,
s0: r.s0,
s1: r.s1,
a0: r.a0,
a1: r.a1,
a2: r.a2,
a3: r.a3,
a4: r.a4,
a5: r.a5,
a6: r.a6,
a7: r.a7,
s2: r.s2,
s3: r.s3,
s4: r.s4,
s5: r.s5,
s6: r.s6,
s7: r.s7,
s8: r.s8,
s9: r.s9,
s10: r.s10,
s11: r.s11,
t3: r.t3,
t4: r.t4,
t5: r.t5,
t6: r.t6,
}
}
}
#[cfg(target_arch = "riscv64")]
impl RiscvUserRegs {
fn write_to(&self, uctx: &mut ax_runtime::hal::cpu::uspace::UserContext) {
uctx.sepc = self.pc;
let r = &mut uctx.regs;
r.ra = self.ra;
r.sp = self.sp;
r.gp = self.gp;
r.tp = self.tp;
r.t0 = self.t0;
r.t1 = self.t1;
r.t2 = self.t2;
r.s0 = self.s0;
r.s1 = self.s1;
r.a0 = self.a0;
r.a1 = self.a1;
r.a2 = self.a2;
r.a3 = self.a3;
r.a4 = self.a4;
r.a5 = self.a5;
r.a6 = self.a6;
r.a7 = self.a7;
r.s2 = self.s2;
r.s3 = self.s3;
r.s4 = self.s4;
r.s5 = self.s5;
r.s6 = self.s6;
r.s7 = self.s7;
r.s8 = self.s8;
r.s9 = self.s9;
r.s10 = self.s10;
r.s11 = self.s11;
r.t3 = self.t3;
r.t4 = self.t4;
r.t5 = self.t5;
r.t6 = self.t6;
}
}