Files
RedBear-OS/src/platform/redox/ptrace.rs
T
2026-07-15 10:30:48 +01:00

291 lines
9.6 KiB
Rust

//! Note: This module is not going to be clean. We're not going to be
//! able to follow the specs 100%. Linux ptrace is very, very,
//! different to Redox. Many people agree that Linux ptrace is bad, so
//! we are NOT going to bend our API for the sake of
//! compatibility. So, this module will be a hellhole.
use super::super::{ERRNO, PalPtrace, Sys, types::*};
#[cfg(target_arch = "x86_64")]
use super::super::{Pal, PalSignal};
#[cfg(target_arch = "x86_64")]
use crate::header::arch_x64_user::user_regs_struct;
use crate::{
c_str::{CStr, CString},
error::Errno,
fs::File,
header::{
errno::{self as errnoh, EIO},
fcntl,
},
io,
raw_cell::RawCell,
sync::Mutex,
};
#[cfg(target_arch = "x86_64")]
use crate::{
header::{signal, sys_ptrace},
io::prelude::*,
};
use alloc::collections::{BTreeMap, btree_map::Entry};
#[cfg(target_arch = "x86_64")]
use core::mem;
#[cfg(target_arch = "x86_64")]
use syscall;
pub struct Session {
pub first: bool,
pub fpregs: File,
pub mem: File,
pub regs: File,
pub tracer: File,
}
pub struct State {
pub sessions: Mutex<BTreeMap<pid_t, Session>>,
}
impl State {
fn new() -> Self {
Self {
sessions: Mutex::new(BTreeMap::new()),
}
}
}
#[thread_local]
static STATE: RawCell<Option<State>> = RawCell::new(None);
pub fn init_state() -> &'static State {
// Safe due to STATE being thread_local (TODO: is it though?)
unsafe {
if STATE.unsafe_ref().is_none() {
STATE.unsafe_set(Some(State::new()));
}
let state_ptr = core::ptr::from_ref::<State>(STATE.unsafe_ref().as_ref().unwrap());
&*state_ptr
}
}
pub fn is_traceme(pid: pid_t) -> bool {
// Skip special PIDs (<=0)
if pid <= 0 {
return false;
}
File::open(
CStr::borrow(&CString::new(format!("/scheme/chan/ptrace-relibc/{}/traceme", pid)).unwrap()),
fcntl::O_PATH,
)
.is_ok()
}
pub fn get_session(
sessions: &mut BTreeMap<pid_t, Session>,
pid: pid_t,
) -> io::Result<&mut Session> {
const NEW_FLAGS: c_int = fcntl::O_RDWR | fcntl::O_CLOEXEC;
match sessions.entry(pid) {
Entry::Vacant(entry) => {
if is_traceme(pid) {
Ok(entry.insert(Session {
first: true,
tracer: File::open(
CStr::borrow(&CString::new(format!("/scheme/proc/{}/trace", pid)).unwrap()),
NEW_FLAGS,
)?,
mem: File::open(
CStr::borrow(&CString::new(format!("/scheme/proc/{}/mem", pid)).unwrap()),
NEW_FLAGS,
)?,
regs: File::open(
CStr::borrow(
&CString::new(format!("/scheme/proc/{}/regs/int", pid)).unwrap(),
),
NEW_FLAGS,
)?,
fpregs: File::open(
CStr::borrow(
&CString::new(format!("/scheme/proc/{}/regs/float", pid)).unwrap(),
),
NEW_FLAGS,
)?,
}))
} else {
ERRNO.set(errnoh::ESRCH);
Err(io::last_os_error())
}
}
Entry::Occupied(entry) => Ok(entry.into_mut()),
}
}
#[cfg(target_arch = "aarch64")]
unsafe fn inner_ptrace(
request: c_int,
pid: pid_t,
addr: *mut c_void,
data: *mut c_void,
) -> io::Result<c_int> {
//TODO: aarch64
unimplemented!("inner_ptrace not implemented on aarch64");
}
#[cfg(target_arch = "x86")]
unsafe fn inner_ptrace(
request: c_int,
pid: pid_t,
addr: *mut c_void,
data: *mut c_void,
) -> io::Result<c_int> {
//TODO: x86
unimplemented!("inner_ptrace not implemented on x86");
}
#[cfg(target_arch = "x86_64")]
unsafe fn inner_ptrace(
request: c_int,
pid: pid_t,
addr: *mut c_void,
data: *mut c_void,
) -> io::Result<c_int> {
let state = init_state();
if request == sys_ptrace::PTRACE_TRACEME {
// Mark this child as traced, parent will check for this marker file
let pid = Sys::getpid();
mem::forget(File::open(
CStr::borrow(
&CString::new(format!("/scheme/chan/ptrace-relibc/{}/traceme", pid)).unwrap(),
),
fcntl::O_CREAT | fcntl::O_PATH | fcntl::O_EXCL,
)?);
return Ok(0);
}
let mut sessions = state.sessions.lock();
let session = get_session(&mut sessions, pid)?;
match request {
sys_ptrace::PTRACE_CONT
| sys_ptrace::PTRACE_SINGLESTEP
| sys_ptrace::PTRACE_SYSCALL
| sys_ptrace::PTRACE_SYSEMU
| sys_ptrace::PTRACE_SYSEMU_SINGLESTEP => {
if let Ok(()) = Sys::kill(pid, signal::SIGCONT as _) {}; // TODO handle error
// TODO: Translate errors
let syscall = syscall::PTRACE_STOP_PRE_SYSCALL | syscall::PTRACE_STOP_POST_SYSCALL;
(&mut &session.tracer).write(&match request {
sys_ptrace::PTRACE_CONT => syscall::PtraceFlags::empty(),
sys_ptrace::PTRACE_SINGLESTEP => syscall::PTRACE_STOP_SINGLESTEP,
// Skip the first post-syscall when connected
sys_ptrace::PTRACE_SYSCALL if session.first => syscall::PTRACE_STOP_PRE_SYSCALL,
sys_ptrace::PTRACE_SYSCALL => syscall,
// Skip the first post-syscall when connected
sys_ptrace::PTRACE_SYSEMU if session.first => {
syscall::PTRACE_FLAG_IGNORE | syscall::PTRACE_STOP_PRE_SYSCALL
}
sys_ptrace::PTRACE_SYSEMU => syscall::PTRACE_FLAG_IGNORE | syscall,
sys_ptrace::PTRACE_SYSEMU_SINGLESTEP => {
syscall::PTRACE_FLAG_IGNORE | syscall::PTRACE_STOP_SINGLESTEP
}
_ => unreachable!("unhandled ptrace request type {}", request),
})?;
session.first = false;
Ok(0)
}
sys_ptrace::PTRACE_GETREGS => {
let c_regs = unsafe { &mut *data.cast::<user_regs_struct>() };
let mut redox_regs = syscall::IntRegisters::default();
(&mut &session.regs).read(&mut redox_regs)?;
*c_regs = user_regs_struct {
r15: redox_regs.r15 as _,
r14: redox_regs.r14 as _,
r13: redox_regs.r13 as _,
r12: redox_regs.r12 as _,
rbp: redox_regs.rbp as _,
rbx: redox_regs.rbx as _,
r11: redox_regs.r11 as _,
r10: redox_regs.r10 as _,
r9: redox_regs.r9 as _,
r8: redox_regs.r8 as _,
rax: redox_regs.rax as _,
rcx: redox_regs.rcx as _,
rdx: redox_regs.rdx as _,
rsi: redox_regs.rsi as _,
rdi: redox_regs.rdi as _,
orig_rax: redox_regs.rax as _, // redox_regs.orig_rax as _,
rip: redox_regs.rip as _,
cs: redox_regs.cs as _,
eflags: redox_regs.rflags as _,
rsp: redox_regs.rsp as _,
ss: redox_regs.ss as _,
fs_base: 0, // fs_base: redox_regs.fs_base as _,
gs_base: 0, // gs_base: redox_regs.gs_base as _,
ds: 0, // ds: redox_regs.ds as _,
es: 0, // es: redox_regs.es as _,
fs: 0, // fs: redox_regs.fs as _,
gs: 0, // gs: redox_regs.gs as _,
};
Ok(0)
}
sys_ptrace::PTRACE_SETREGS => {
let c_regs = unsafe { &*data.cast::<user_regs_struct>() };
let redox_regs = syscall::IntRegisters {
r15: c_regs.r15 as _,
r14: c_regs.r14 as _,
r13: c_regs.r13 as _,
r12: c_regs.r12 as _,
rbp: c_regs.rbp as _,
rbx: c_regs.rbx as _,
r11: c_regs.r11 as _,
r10: c_regs.r10 as _,
r9: c_regs.r9 as _,
r8: c_regs.r8 as _,
rax: c_regs.orig_rax as _, // c_regs.rax as _,
rcx: c_regs.rcx as _,
rdx: c_regs.rdx as _,
rsi: c_regs.rsi as _,
rdi: c_regs.rdi as _,
// orig_rax: c_regs.orig_rax as _,
rip: c_regs.rip as _,
cs: c_regs.cs as _,
rflags: c_regs.eflags as _,
rsp: c_regs.rsp as _,
ss: c_regs.ss as _,
// fs_base: c_regs.fs_base as _,
// gs_base: c_regs.gs_base as _,
// ds: c_regs.ds as _,
// es: c_regs.es as _,
// fs: c_regs.fs as _,
// gs: c_regs.gs as _,
};
(&mut &session.regs).write(&redox_regs)?;
Ok(0)
}
_ => unimplemented!(),
}
}
#[cfg(target_arch = "riscv64")]
fn inner_ptrace(
request: c_int,
pid: pid_t,
addr: *mut c_void,
data: *mut c_void,
) -> io::Result<c_int> {
//TODO: riscv64
unimplemented!("inner_ptrace not implemented on riscv64");
}
impl PalPtrace for Sys {
#[allow(unused_unsafe)] // keeping inner unsafe fails x86_64, removing fails riscv cross-build
unsafe fn ptrace(
request: c_int,
pid: pid_t,
addr: *mut c_void,
data: *mut c_void,
) -> Result<c_int, Errno> {
unsafe { inner_ptrace(request, pid, addr, data) }
.map_err(|err| Errno(err.raw_os_error().unwrap_or(EIO)))
}
}