Save FPU context on signal stack

This commit is contained in:
Qingsong Chen 2025-07-17 09:14:03 +00:00 committed by Ruihan Li
parent 6cd53fbb8a
commit f1299d4b8d
6 changed files with 209 additions and 119 deletions

View File

@ -4,6 +4,8 @@ use alloc::{format, string::String};
use ostd::{
cpu::context::{CpuExceptionInfo, GeneralRegs, UserContext},
mm::Vaddr,
user::UserContextApi,
Pod,
};
@ -46,47 +48,8 @@ impl LinuxAbi for UserContext {
}
}
/// General-purpose registers.
#[derive(Debug, Clone, Copy, Pod, Default)]
#[repr(C)]
pub struct GpRegs {
pub zero: usize,
pub ra: usize,
pub sp: usize,
pub gp: usize,
pub tp: usize,
pub t0: usize,
pub t1: usize,
pub t2: usize,
pub s0: usize,
pub s1: usize,
pub a0: usize,
pub a1: usize,
pub a2: usize,
pub a3: usize,
pub a4: usize,
pub a5: usize,
pub a6: usize,
pub a7: usize,
pub s2: usize,
pub s3: usize,
pub s4: usize,
pub s5: usize,
pub s6: usize,
pub s7: usize,
pub s8: usize,
pub s9: usize,
pub s10: usize,
pub s11: usize,
pub t3: usize,
pub t4: usize,
pub t5: usize,
pub t6: usize,
}
macro_rules! copy_gp_regs {
($src: ident, $dst: ident) => {
$dst.zero = $src.zero;
$dst.ra = $src.ra;
$dst.sp = $src.sp;
$dst.gp = $src.gp;
@ -121,13 +84,60 @@ macro_rules! copy_gp_regs {
};
}
impl GpRegs {
pub fn copy_to_raw(&self, dst: &mut GeneralRegs) {
copy_gp_regs!(self, dst);
/// Represents the context of a signal handler.
///
/// This contains the context saved before a signal handler is invoked and restored by `sys_rt_sigreturn`.
#[repr(C)]
#[repr(align(16))]
#[derive(Clone, Copy, Debug, Default, Pod)]
pub struct SigContext {
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,
// In RISC-V, the signal stack layout places the FPU context directly
// after the general-purpose registers.
}
impl SigContext {
pub fn copy_user_regs_to(&self, dst: &mut UserContext) {
let gp_regs = dst.general_regs_mut();
copy_gp_regs!(self, gp_regs);
dst.set_instruction_pointer(self.pc);
}
pub fn copy_from_raw(&mut self, src: &GeneralRegs) {
copy_gp_regs!(src, self);
pub fn copy_user_regs_from(&mut self, src: &UserContext) {
let gp_regs = src.general_regs();
copy_gp_regs!(gp_regs, self);
self.pc = src.instruction_pointer();
}
}

View File

@ -7,9 +7,8 @@ use alloc::{
};
use ostd::{
cpu::context::{
cpuid, CpuException, GeneralRegs, PageFaultErrorCode, RawPageFaultInfo, UserContext,
},
cpu::context::{cpuid, CpuException, PageFaultErrorCode, RawPageFaultInfo, UserContext},
mm::Vaddr,
Pod,
};
@ -52,30 +51,41 @@ impl LinuxAbi for UserContext {
}
}
/// General-purpose registers.
#[derive(Debug, Clone, Copy, Pod, Default)]
/// Represents the context of a signal handler.
///
/// This contains the context saved before a signal handler is invoked and restored by `sys_rt_sigreturn`.
#[derive(Clone, Copy, Debug, Default, Pod)]
#[repr(C)]
pub struct GpRegs {
pub rax: usize,
pub rbx: usize,
pub rcx: usize,
pub rdx: usize,
pub rsi: usize,
pub rdi: usize,
pub rbp: usize,
pub rsp: usize,
pub r8: usize,
pub r9: usize,
pub r10: usize,
pub r11: usize,
pub r12: usize,
pub r13: usize,
pub r14: usize,
pub r15: usize,
pub rip: usize,
pub rflags: usize,
pub fsbase: usize,
pub gsbase: usize,
pub struct SigContext {
r8: usize,
r9: usize,
r10: usize,
r11: usize,
r12: usize,
r13: usize,
r14: usize,
r15: usize,
rdi: usize,
rsi: usize,
rbp: usize,
rbx: usize,
rdx: usize,
rax: usize,
rcx: usize,
rsp: usize,
rip: usize,
rflags: usize,
cs: u16,
gs: u16,
fs: u16,
ss: u16,
error_code: usize,
trap_num: usize,
old_mask: u64,
page_fault_addr: usize,
// A stack pointer to FPU context.
fpu_context_addr: Vaddr,
reserved: [u64; 8],
}
macro_rules! copy_gp_regs {
@ -98,18 +108,28 @@ macro_rules! copy_gp_regs {
$dst.r15 = $src.r15;
$dst.rip = $src.rip;
$dst.rflags = $src.rflags;
$dst.fsbase = $src.fsbase;
$dst.gsbase = $src.gsbase;
};
}
impl GpRegs {
pub fn copy_to_raw(&self, dst: &mut GeneralRegs) {
copy_gp_regs!(self, dst);
impl SigContext {
pub fn copy_user_regs_to(&self, dst: &mut UserContext) {
let gp_regs = dst.general_regs_mut();
copy_gp_regs!(self, gp_regs);
}
pub fn copy_from_raw(&mut self, src: &GeneralRegs) {
copy_gp_regs!(src, self);
pub fn copy_user_regs_from(&mut self, src: &UserContext) {
let gp_regs = src.general_regs();
copy_gp_regs!(gp_regs, self);
// TODO: Fill exception information in `SigContext`.
}
pub fn fpu_context_addr(&self) -> Vaddr {
self.fpu_context_addr
}
pub fn set_fpu_context_addr(&mut self, addr: Vaddr) {
self.fpu_context_addr = addr;
}
}

View File

@ -6,10 +6,12 @@
use core::mem::{self, size_of};
use aster_util::read_union_field;
use inherit_methods_macro::inherit_methods;
use ostd::cpu::context::UserContext;
use super::sig_num::SigNum;
use crate::{
arch::cpu::GpRegs,
arch::cpu::SigContext,
prelude::*,
process::{Pid, Uid},
};
@ -157,7 +159,8 @@ union siginfo_addr_bnd_t {
upper: Vaddr, // *const c_void,
}
#[derive(Clone, Copy, Debug, Pod)]
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Copy, Debug, Default, Pod)]
#[repr(C)]
pub struct ucontext_t {
pub uc_flags: u64,
@ -165,18 +168,26 @@ pub struct ucontext_t {
pub uc_stack: stack_t,
pub uc_mcontext: mcontext_t,
pub uc_sigmask: sigset_t,
pub fpregs: [u8; 64 * 8], //fxsave structure
}
#[cfg(target_arch = "riscv64")]
#[derive(Clone, Copy, Debug, Pod)]
#[repr(C)]
pub struct ucontext_t {
pub uc_flags: u64,
pub uc_link: Vaddr, // *mut ucontext_t
pub uc_stack: stack_t,
pub uc_sigmask: sigset_t,
pub __unused: [u8; 120],
pub uc_mcontext: mcontext_t,
}
#[cfg(target_arch = "riscv64")]
impl Default for ucontext_t {
fn default() -> Self {
Self {
uc_flags: Default::default(),
uc_link: Default::default(),
uc_stack: Default::default(),
uc_mcontext: Default::default(),
uc_sigmask: Default::default(),
fpregs: [0u8; 64 * 8],
__unused: [0; 120],
..Default::default()
}
}
}
@ -194,20 +205,17 @@ pub struct sigaltstack_t {
#[derive(Debug, Clone, Copy, Pod, Default)]
#[repr(C)]
pub struct mcontext_t {
pub inner: SignalCpuContext,
// TODO: the fields should be csgsfs, err, trapno, oldmask, and cr2
_unused0: [u64; 5],
// TODO: this field should be `fpregs: fpregset_t,`
_unused1: usize,
_reserved: [u64; 8],
inner: SigContext,
}
#[derive(Debug, Clone, Copy, Pod, Default)]
#[repr(C)]
pub struct SignalCpuContext {
pub gp_regs: GpRegs,
pub fpregs_on_heap: u64,
pub fpregs: Vaddr, // *mut FpRegs,
#[inherit_methods(from = "self.inner")]
impl mcontext_t {
pub fn copy_user_regs_to(&self, context: &mut UserContext);
pub fn copy_user_regs_from(&mut self, context: &UserContext);
#[cfg(target_arch = "x86_64")]
pub fn fpu_context_addr(&self) -> Vaddr;
#[cfg(target_arch = "x86_64")]
pub fn set_fpu_context_addr(&mut self, addr: Vaddr);
}
#[derive(Clone, Copy, Pod)]

View File

@ -19,7 +19,10 @@ use align_ext::AlignExt;
use c_types::{siginfo_t, ucontext_t};
use constants::SIGSEGV;
pub use events::{SigEvents, SigEventsFilter};
use ostd::{cpu::context::UserContext, user::UserContextApi};
use ostd::{
cpu::context::{FpuContext, UserContext},
user::UserContextApi,
};
pub use pause::{with_sigmask_changed, Pause};
pub use poll::{PollAdaptor, PollHandle, Pollable, Pollee, Poller};
use sig_action::{SigAction, SigActionFlags, SigDefaultAction};
@ -197,31 +200,65 @@ pub fn handle_user_signal(
let siginfo_addr = stack_pointer;
// 2. Write ucontext_t.
stack_pointer = alloc_aligned_in_user_stack(stack_pointer, mem::size_of::<ucontext_t>(), 16)?;
let mut ucontext = ucontext_t {
uc_sigmask: mask.into(),
..Default::default()
};
ucontext
.uc_mcontext
.inner
.gp_regs
.copy_from_raw(user_ctx.general_regs());
ucontext.uc_mcontext.copy_user_regs_from(user_ctx);
let sig_context = ctx.thread_local.sig_context().get();
if let Some(sig_context_addr) = sig_context {
ucontext.uc_link = sig_context_addr;
} else {
ucontext.uc_link = 0;
}
// TODO: store fp regs in ucontext
user_space.write_val(stack_pointer as _, &ucontext)?;
let ucontext_addr = stack_pointer;
// Clone and reset the FPU context.
let fpu_context = ctx.thread_local.fpu().clone_context();
let fpu_context_bytes = fpu_context.as_bytes();
ctx.thread_local.fpu().set_context(FpuContext::new());
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
// Align the FPU context address to the 64-byte boundary so that the
// user program can use the XSAVE/XRSTOR instructions at that address,
// if necessary.
let fpu_context_addr =
alloc_aligned_in_user_stack(stack_pointer, fpu_context_bytes.len(), 64)?;
let ucontext_addr = alloc_aligned_in_user_stack(
fpu_context_addr,
size_of::<ucontext_t>(),
align_of::<ucontext_t>(),
)?;
ucontext
.uc_mcontext
.set_fpu_context_addr(fpu_context_addr as _);
const UC_FP_XSTATE: u64 = 1 << 0;
ucontext.uc_flags = UC_FP_XSTATE;
} else if #[cfg(target_arch = "riscv64")] {
let ucontext_addr = alloc_aligned_in_user_stack(
stack_pointer,
size_of::<ucontext_t>() + fpu_context_bytes.len(),
align_of::<ucontext_t>(),
)?;
let fpu_context_addr = (ucontext_addr as usize) + size_of::<ucontext_t>();
} else {
compile_error!("unsupported target");
}
}
let mut fpu_context_reader = VmReader::from(fpu_context.as_bytes());
user_space.write_bytes(fpu_context_addr as _, &mut fpu_context_reader)?;
user_space.write_val(ucontext_addr as _, &ucontext)?;
// Store the ucontext addr in sig context of current thread.
ctx.thread_local
.sig_context()
.set(Some(ucontext_addr as Vaddr));
// 3. Write the address of the restorer code.
stack_pointer = ucontext_addr;
if flags.contains(SigActionFlags::SA_RESTORER) {
// If the SA_RESTORER flag is present, the restorer code address is provided by the user.
stack_pointer = write_u64_to_user_stack(stack_pointer, restorer_addr as u64)?;

View File

@ -2,7 +2,10 @@
use core::sync::atomic::Ordering;
use ostd::{cpu::context::UserContext, user::UserContextApi};
use ostd::{
cpu::context::{FpuContext, UserContext},
user::UserContextApi,
};
use super::SyscallReturn;
use crate::{prelude::*, process::signal::c_types::ucontext_t};
@ -41,11 +44,24 @@ pub fn sys_rt_sigreturn(ctx: &Context, user_ctx: &mut UserContext) -> Result<Sys
} else {
thread_local.sig_context().set(Some(ucontext.uc_link));
};
ucontext
.uc_mcontext
.inner
.gp_regs
.copy_to_raw(user_ctx.general_regs_mut());
ucontext.uc_mcontext.copy_user_regs_to(user_ctx);
// Restore FPU context on stack
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
let fpu_context_addr = ucontext.uc_mcontext.fpu_context_addr();
} else if #[cfg(target_arch = "riscv64")] {
// In RISC-V, FPU context is placed directly after `ucontext_t` on signal stack.
let fpu_context_addr = sig_context_addr + size_of::<ucontext_t>();
} else {
compile_error!("unsupported target");
}
}
let mut fpu_context = FpuContext::new();
let mut fpu_context_writer = VmWriter::from(fpu_context.as_bytes_mut());
ctx.user_space()
.read_bytes(fpu_context_addr, &mut fpu_context_writer)?;
ctx.thread_local.fpu().set_context(fpu_context);
// unblock sig mask
let sig_mask = ucontext.uc_sigmask;

View File

@ -196,10 +196,9 @@ int test_handle_sigfpe()
(void)c;
fxsave(y);
// Asterinas does not save and restore fpregs now, so we emit this check.
// if (memcmp(x, y, 512) != 0) {
// THROW_ERROR("floating point registers are modified");
// }
if (memcmp(x, y, 512) != 0) {
THROW_ERROR("floating point registers are modified");
}
printf("Signal handler successfully jumped over the divide-by-zero instruction\n");
fflush(stdout);
@ -318,7 +317,7 @@ static void handle_sigpipe(int num, siginfo_t *info, void *context)
recursion_level--;
}
#define SIGSTACKSIZE (4 * 4096)
#define SIGSTACKSIZE (8 * 4096)
int test_sigaltstack()
{