#[cfg(target_arch = "riscv64")]
use core::mem::{MaybeUninit, align_of, size_of};
use core::{future::poll_fn, task::Poll};
use ax_errno::{AxError, AxResult};
use ax_runtime::hal::cpu::uspace::UserContext;
use ax_task::{
TaskInner, current,
future::{block_on, interruptible},
};
use linux_raw_sys::general::{CLD_CONTINUED, CLD_STOPPED, CLD_TRAPPED};
use starry_process::Pid;
use starry_signal::{SignalInfo, SignalOSAction, SignalSet, Signo};
#[cfg(target_arch = "riscv64")]
use starry_vm::vm_read_slice;
use super::{
AsThread, ProcessData, SYSCALL_INSN_LEN, Thread, do_exit, get_process_data, get_process_group,
get_task, is_zombie_pid,
};
pub struct SyscallRestartInfo {
pub saved_a0: usize,
pub saved_sysno: usize,
}
#[cfg(target_arch = "riscv64")]
#[derive(Clone, Copy)]
struct UserStackFrame {
fp: usize,
ra: usize,
}
#[cfg(target_arch = "riscv64")]
fn read_user_stack_frame(fp: usize) -> Option<UserStackFrame> {
let frame_addr = fp.checked_sub(size_of::<UserStackFrame>())?;
if frame_addr == 0 || !frame_addr.is_multiple_of(align_of::<usize>()) {
return None;
}
let mut words = [MaybeUninit::<usize>::uninit(); 2];
vm_read_slice(frame_addr as *const usize, &mut words).ok()?;
Some(UserStackFrame {
fp: unsafe { words[0].assume_init() },
ra: unsafe { words[1].assume_init() },
})
}
#[cfg(target_arch = "riscv64")]
fn dump_user_backtrace(uctx: &UserContext) {
const MAX_USER_FRAMES: usize = 32;
let mut fp = uctx.regs.s0;
let sp = uctx.regs.sp;
warn!(
"user backtrace:\n #00 pc={:#018x} ra={:#018x} sp={:#018x} fp={:#018x}",
uctx.sepc, uctx.regs.ra, sp, fp
);
for depth in 1..MAX_USER_FRAMES {
let Some(frame) = read_user_stack_frame(fp) else {
warn!(" <unwind stopped: unreadable frame at fp={:#018x}>", fp);
break;
};
if frame.fp == 0 || frame.ra == 0 {
break;
}
if frame.fp <= fp {
warn!(
" <unwind stopped: non-growing fp {:#018x} after {:#018x}>",
frame.fp, fp
);
break;
}
let frame_sp = frame.fp - size_of::<UserStackFrame>();
warn!(
" #{:02} pc={:#018x} sp={:#018x} fp={:#018x}",
depth, frame.ra, frame_sp, frame.fp
);
fp = frame.fp;
}
}
#[cfg(not(target_arch = "riscv64"))]
fn dump_user_backtrace(_uctx: &UserContext) {}
fn dump_user_crash_context(uctx: &UserContext) {
#[cfg(target_arch = "riscv64")]
{
let r = &uctx.regs;
warn!(
"user register dump:\n pc(sepc)={:#018x} ra={:#018x} sp={:#018x}\n gp={:#018x} \
tp={:#018x} s0/fp={:#018x} s1={:#018x}\n a0={:#018x} a1={:#018x} a2={:#018x} \
a3={:#018x}\n a4={:#018x} a5={:#018x} a6={:#018x} a7={:#018x}\n s2={:#018x} \
s3={:#018x} s4={:#018x} s5={:#018x}\n s6={:#018x} s7={:#018x} s8={:#018x} \
s9={:#018x}\n s10={:#018x} s11={:#018x} t3={:#018x} t4={:#018x}\n t5={:#018x} \
t6={:#018x}",
uctx.sepc,
r.ra,
r.sp,
r.gp,
r.tp,
r.s0,
r.s1,
r.a0,
r.a1,
r.a2,
r.a3,
r.a4,
r.a5,
r.a6,
r.a7,
r.s2,
r.s3,
r.s4,
r.s5,
r.s6,
r.s7,
r.s8,
r.s9,
r.s10,
r.s11,
r.t3,
r.t4,
r.t5,
r.t6,
);
}
#[cfg(target_arch = "aarch64")]
{
warn!(
"user register dump:\n pc(elr)={:#018x} spsr={:#018x}\n x0={:#018x} x1={:#018x} \
x2={:#018x} x3={:#018x}\n x29(fp)={:#018x} x30(lr)={:#018x}",
uctx.elr, uctx.spsr, uctx.x[0], uctx.x[1], uctx.x[2], uctx.x[3], uctx.x[29], uctx.x[30],
);
}
#[cfg(target_arch = "x86_64")]
{
warn!(
"user register dump:\n rip={:#018x} rsp={:#018x} rflags={:#018x}\n rax={:#018x} \
rdi={:#018x} rsi={:#018x} rdx={:#018x}",
uctx.rip, uctx.rsp, uctx.rflags, uctx.rax, uctx.rdi, uctx.rsi, uctx.rdx,
);
}
#[cfg(target_arch = "loongarch64")]
{
let r = &uctx.regs;
warn!(
"user register dump:\n era={:#018x} ra={:#018x} sp={:#018x} tp={:#018x}\n \
a0={:#018x} a1={:#018x} a2={:#018x} a3={:#018x}",
uctx.era, r.ra, r.sp, r.tp, r.a0, r.a1, r.a2, r.a3,
);
}
#[cfg(not(any(
target_arch = "riscv64",
target_arch = "aarch64",
target_arch = "x86_64",
target_arch = "loongarch64",
)))]
{
warn!("user register dump: not implemented for this arch");
}
dump_user_backtrace(uctx);
}
pub fn ptrace_stop_current(
thr: &Thread,
signo: Signo,
uctx: &mut UserContext,
) -> Option<Option<Signo>> {
ptrace_stop_current_impl(thr, signo, uctx, false)
}
pub fn ptrace_syscall_stop_current(
thr: &Thread,
signo: Signo,
uctx: &mut UserContext,
) -> Option<Option<Signo>> {
ptrace_stop_current_impl(thr, signo, uctx, true)
}
fn ptrace_stop_current_impl(
thr: &Thread,
signo: Signo,
uctx: &mut UserContext,
is_syscall_stop: bool,
) -> Option<Option<Signo>> {
if !thr.proc_data.is_ptrace_traceme() && !thr.proc_data.is_ptrace_attached() {
return None;
}
#[cfg(target_arch = "riscv64")]
{
thr.proc_data.save_current_fp_for_ptrace();
}
if is_syscall_stop {
thr.proc_data.set_ptrace_syscall_stop(signo, uctx);
} else {
thr.proc_data.set_ptrace_stop(signo, uctx);
}
let waiter_pid = thr
.proc_data
.ptrace_tracer_pid()
.or_else(|| thr.proc_data.proc.parent().map(|parent| parent.pid()));
if let Some(waiter_pid) = waiter_pid
&& let Ok(parent_data) = get_process_data(waiter_pid)
{
let sigchld = SignalInfo::new_sigchld(
thr.proc_data.proc.pid(),
thr.cred().uid,
CLD_TRAPPED as i32,
signo as i32,
);
let _ = send_signal_to_process(waiter_pid, Some(sigchld));
parent_data.child_exit_event.wake();
}
current().clear_interrupt();
let wait_result = block_on(interruptible(poll_fn(|cx| {
if thr.proc_data.ptrace_stop_signo().is_none() {
Poll::Ready(())
} else {
thr.proc_data.register_ptrace_stop_waker(cx.waker());
if thr.proc_data.ptrace_stop_signo().is_none() {
Poll::Ready(())
} else {
Poll::Pending
}
}
})));
if wait_result.is_err() {
thr.proc_data.clear_ptrace_stop();
} else if let Some(resume_uctx) = thr.proc_data.take_ptrace_stop_user_context() {
*uctx = resume_uctx;
thr.proc_data.restore_current_fp_for_ptrace(uctx);
}
Some(thr.proc_data.take_ptrace_resume_signo())
}
pub fn check_signals(
thr: &Thread,
uctx: &mut UserContext,
restore_blocked: Option<SignalSet>,
restart_info: Option<&SyscallRestartInfo>,
) -> bool {
if thr.take_exit_request() {
do_exit(0, false);
return true;
}
let Some((sig, os_action)) =
thr.signal
.check_signals_with(uctx, restore_blocked, |uctx, _sig, restartable| {
if let Some(info) = restart_info
&& (uctx.retval() as isize) == -(ax_errno::LinuxError::EINTR.code() as isize)
&& restartable
{
let new_ip = uctx.ip() - SYSCALL_INSN_LEN;
uctx.set_ip(new_ip);
uctx.set_arg0(info.saved_a0);
#[cfg(target_arch = "x86_64")]
uctx.set_sysno(info.saved_sysno);
#[cfg(not(target_arch = "x86_64"))]
let _ = info.saved_sysno;
}
})
else {
return false;
};
let signo = sig.signo();
if signo != Signo::SIGKILL
&& !thr.proc_data.take_ptrace_resume_signal_bypass(signo)
&& let Some(resume_signo) = ptrace_stop_current(thr, signo, uctx)
{
match resume_signo {
None => return true,
Some(new_signo) if new_signo != signo => {
thr.proc_data.set_ptrace_resume_signal_bypass(new_signo);
let _ = thr.signal.send_signal(SignalInfo::new_kernel(new_signo));
return true;
}
Some(_) => {}
}
}
let dump_on_terminate = thr
.fault_dump_signo
.compare_exchange(
signo as u8,
0,
core::sync::atomic::Ordering::AcqRel,
core::sync::atomic::Ordering::Relaxed,
)
.is_ok();
match os_action {
SignalOSAction::Terminate => {
if dump_on_terminate {
dump_user_crash_context(uctx);
}
do_exit(signo as i32, true);
}
SignalOSAction::CoreDump => {
if dump_on_terminate {
dump_user_crash_context(uctx);
}
do_exit(128 + signo as i32, true);
}
SignalOSAction::Stop => do_job_stop(thr, signo),
SignalOSAction::Continue => {}
SignalOSAction::NoFurtherAction => {}
}
true
}
fn notify_parent_job_change(proc_data: &ProcessData, code: i32, status: i32) {
let proc = &proc_data.proc;
let Some(parent) = proc.parent() else {
return;
};
let child_uid = proc
.threads()
.into_iter()
.next()
.and_then(|tid| get_task(tid).ok())
.map_or(0, |task| task.as_thread().cred().uid);
let sig = SignalInfo::new_sigchld(proc.pid(), child_uid, code, status);
let _ = send_signal_to_process(parent.pid(), Some(sig));
if let Ok(data) = get_process_data(parent.pid()) {
data.child_exit_event.wake();
}
}
fn do_job_stop(thr: &Thread, signo: Signo) {
let proc_data = &thr.proc_data;
let continue_gen = proc_data.continue_generation();
if !proc_data.set_job_stopped(signo, continue_gen) {
return;
}
notify_parent_job_change(proc_data, CLD_STOPPED as i32, signo as i32);
let cont_event = proc_data.cont_event();
block_on(poll_fn(|cx| {
if !proc_data.is_job_stopped() {
return Poll::Ready(());
}
cont_event.register(cx.waker());
if proc_data.is_job_stopped() {
Poll::Pending
} else {
Poll::Ready(())
}
}));
}
pub fn block_next_signal() {
current().as_thread().block_next_signal_check();
}
pub fn unblock_next_signal() -> bool {
current().as_thread().unblock_next_signal_check()
}
pub fn with_blocked_signals<R>(
blocked: Option<SignalSet>,
f: impl FnOnce() -> AxResult<R>,
) -> AxResult<R> {
let curr = current();
let sig = &curr.as_thread().signal;
let old_blocked = blocked.map(|set| sig.set_blocked(set));
f().inspect(|_| {
if let Some(old) = old_blocked {
sig.set_blocked(old);
}
})
}
pub(super) fn send_signal_thread_inner(task: &TaskInner, thr: &Thread, sig: SignalInfo) {
if thr.signal.send_signal(sig) {
task.interrupt();
}
}
pub fn send_signal_to_thread(tgid: Option<Pid>, tid: Pid, sig: Option<SignalInfo>) -> AxResult<()> {
let task = get_task(tid)?;
let thread = task.try_as_thread().ok_or(AxError::OperationNotPermitted)?;
if tgid.is_some_and(|tgid| thread.proc_data.proc.pid() != tgid) {
return Err(AxError::NoSuchProcess);
}
if let Some(sig) = sig {
info!("Send signal {:?} to thread {}", sig.signo(), tid);
if thread.signal.send_signal(sig) {
ax_task::wake_task(&task);
}
}
Ok(())
}
pub fn send_signal_to_process(pid: Pid, sig: Option<SignalInfo>) -> AxResult<()> {
let proc_data = match get_process_data(pid) {
Ok(proc_data) => proc_data,
Err(_) => {
if is_zombie_pid(pid) {
return Ok(());
}
return Err(AxError::NoSuchProcess);
}
};
if let Some(sig) = &sig {
match sig.signo() {
Signo::SIGCONT if proc_data.set_job_continued() => {
notify_parent_job_change(&proc_data, CLD_CONTINUED as i32, Signo::SIGCONT as i32);
}
Signo::SIGKILL => proc_data.clear_job_stop_for_kill(),
_ => {}
}
}
if let Some(sig) = sig {
let signo = sig.signo();
info!("Send signal {signo:?} to process {pid}");
if signo == Signo::SIGKILL && proc_data.ptrace_stop_signo().is_some() {
proc_data.clear_ptrace_stop();
}
if let Some(tid) = proc_data.signal.send_signal(sig) {
if let Ok(task) = get_task(tid) {
ax_task::wake_task(&task);
}
} else {
for tid in proc_data.proc.threads() {
if let Ok(task) = get_task(tid)
&& task
.as_thread()
.signal
.sigwait_set
.lock()
.is_some_and(|s| s.has(signo))
{
ax_task::wake_task(&task);
}
}
}
}
Ok(())
}
pub fn send_signal_to_process_group(pgid: Pid, sig: Option<SignalInfo>) -> AxResult<()> {
let pg = get_process_group(pgid)?;
if let Some(sig) = sig {
info!("Send signal {:?} to process group {}", sig.signo(), pgid);
for proc in pg.processes() {
if let Err(e) = send_signal_to_process(proc.pid(), Some(sig.clone())) {
debug!(
"send_signal_to_process_group: skipped pid {}: {:?}",
proc.pid(),
e
);
}
}
}
Ok(())
}
pub fn raise_signal_fatal(sig: SignalInfo, uctx: &UserContext) -> AxResult<()> {
let curr = current();
let thread = curr.as_thread();
let signo = sig.signo();
info!(
"Synchronous-exception fatal signal {:?} on tid={}",
signo,
thread.proc_data.proc.pid()
);
{
use starry_signal::SignalDisposition;
let actions_arc = thread.proc_data.signal.actions();
let mut actions = actions_arc.lock();
let act = &mut actions[signo];
let force_default = matches!(act.disposition, SignalDisposition::Ignore)
|| (matches!(act.disposition, SignalDisposition::Default)
&& matches!(
signo.default_action(),
starry_signal::DefaultSignalAction::Ignore
));
if force_default {
*act = starry_signal::SignalAction::default();
}
}
let mut mask = thread.signal.blocked();
if mask.has(signo) {
mask.remove(signo);
thread.signal.set_blocked(mask);
}
thread
.fault_dump_signo
.store(signo as u8, core::sync::atomic::Ordering::Release);
if thread.signal.send_signal(sig) {
curr.interrupt();
} else {
thread
.fault_dump_signo
.store(0, core::sync::atomic::Ordering::Release);
dump_user_crash_context(uctx);
do_exit(signo as i32, true);
}
Ok(())
}