veridian_kernel/process/signal_delivery.rs
1//! Signal delivery to user-space signal handlers
2//!
3//! When a signal becomes pending for a process and the signal has a registered
4//! handler (not SIG_DFL or SIG_IGN), the kernel must arrange for the handler to
5//! execute in user space. This module implements the signal frame construction
6//! and restoration mechanism for all three supported architectures (x86_64,
7//! AArch64, RISC-V):
8//!
9//! 1. **Delivery** (`deliver_signal`): Saves the current thread context into a
10//! signal frame on the user stack, sets up a trampoline return address that
11//! will invoke `sigreturn`, and redirects execution to the signal handler.
12//!
13//! 2. **Restoration** (`restore_signal_frame`): Called from `sys_sigreturn` to
14//! read the saved signal frame from the user stack, restore registers, and
15//! resume execution at the point where the signal interrupted the thread.
16//!
17//! # Signal Nesting
18//!
19//! Nested signals are supported. When a signal is delivered, the delivered
20//! signal number is added to the process's blocked signal mask (see
21//! `deliver_signal_x86_64` which sets `saved_mask | (1 << signum)`). This
22//! prevents the same signal from interrupting its own handler. However,
23//! *different* signals that are not blocked can still be delivered during
24//! handler execution, producing a nested signal frame on the user stack.
25//!
26//! Each signal delivery pushes a new frame (with its own saved context and
27//! signal mask) onto the user stack. When the inner handler returns and
28//! `sigreturn` restores the outer frame, the original signal mask is also
29//! restored, re-enabling the previously blocked signal. This nesting is
30//! bounded only by available user stack space.
31//!
32//! Note: SIGKILL (9) and SIGSTOP (19) can never be blocked, caught, or
33//! ignored -- the mask sanitization enforces this invariant.
34#![allow(dead_code)] // Signal delivery infrastructure -- cross-arch stubs + frame helpers
35
36#[allow(unused_imports)]
37use crate::{error::KernelError, println, process::pcb::Process, process::thread::Thread};
38
39/// Syscall number for SIG_RETURN (must match Syscall::SigReturn = 123).
40const SYS_SIGRETURN: u64 = 123;
41
42/// Signal handler value indicating default action.
43const SIG_DFL: u64 = 0;
44/// Signal handler value indicating the signal should be ignored.
45const SIG_IGN: u64 = 1;
46
47// ============================================================================
48// Signal frame (x86_64)
49// ============================================================================
50
51/// Saved thread context pushed onto the user stack during signal delivery.
52///
53/// This is a C-compatible structure so that the signal handler trampoline can
54/// pass a pointer to it back to `sys_sigreturn`. The layout must remain stable
55/// across kernel versions for ABI compatibility.
56#[repr(C)]
57#[derive(Debug, Clone, Copy)]
58pub struct SignalFrame {
59 // -- Trampoline return address (at lowest address, i.e. where RSP points) --
60 /// Address of the sigreturn trampoline code placed just after this struct.
61 pub trampoline_ret_addr: u64,
62
63 // -- Signal information --
64 /// Signal number that caused this delivery.
65 pub signum: u64,
66
67 // -- Saved signal mask --
68 /// The process signal mask at the time of delivery (restored on sigreturn).
69 pub saved_mask: u64,
70
71 // -- Saved general-purpose registers --
72 pub rax: u64,
73 pub rbx: u64,
74 pub rcx: u64,
75 pub rdx: u64,
76 pub rsi: u64,
77 pub rdi: u64,
78 pub rbp: u64,
79 pub rsp: u64,
80 pub r8: u64,
81 pub r9: u64,
82 pub r10: u64,
83 pub r11: u64,
84 pub r12: u64,
85 pub r13: u64,
86 pub r14: u64,
87 pub r15: u64,
88
89 // -- Saved instruction pointer and flags --
90 pub rip: u64,
91 pub rflags: u64,
92}
93
94/// Size of the signal frame in bytes.
95const SIGNAL_FRAME_SIZE: usize = core::mem::size_of::<SignalFrame>();
96
97/// x86_64 sigreturn trampoline machine code.
98///
99/// This small code sequence is written onto the user stack just above the
100/// signal frame. When the signal handler returns, it executes this trampoline
101/// which calls `syscall(SYS_SIGRETURN, frame_ptr)`.
102///
103/// Assembly:
104/// ```text
105/// lea rdi, [rsp] ; frame_ptr = current RSP (points to SignalFrame)
106/// mov rax, 123 ; SYS_SIGRETURN
107/// syscall
108/// ud2 ; should never reach here
109/// ```
110///
111/// Encoded bytes (15 bytes):
112/// 48 8d 3c 24 lea rdi, [rsp]
113/// 48 c7 c0 7b 00 00 00 mov rax, 123
114/// 0f 05 syscall
115/// 0f 0b ud2
116#[cfg(target_arch = "x86_64")]
117const SIGRETURN_TRAMPOLINE: [u8; 15] = [
118 0x48, 0x8d, 0x3c, 0x24, // lea rdi, [rsp]
119 0x48, 0xc7, 0xc0, 0x7b, 0x00, 0x00, 0x00, // mov rax, 123
120 0x0f, 0x05, // syscall
121 0x0f, 0x0b, // ud2
122];
123
124/// Size of the trampoline code in bytes.
125#[cfg(target_arch = "x86_64")]
126const TRAMPOLINE_SIZE: usize = SIGRETURN_TRAMPOLINE.len();
127
128// ============================================================================
129// Physical memory write/read helpers (same pattern as creation.rs)
130// ============================================================================
131
132/// Write a value to a user-space address via the physical memory window.
133///
134/// # Safety
135///
136/// `vaddr` must be a valid mapped address in the process's VAS with write
137/// permissions. The caller must ensure no concurrent access to this memory.
138#[cfg(feature = "alloc")]
139unsafe fn write_to_user_stack(
140 memory_space: &crate::mm::VirtualAddressSpace,
141 vaddr: usize,
142 value: usize,
143) {
144 use crate::mm::VirtualAddress;
145
146 let pt_root = memory_space.get_page_table();
147 if pt_root == 0 {
148 return;
149 }
150
151 let mapper = unsafe { crate::mm::vas::create_mapper_from_root_pub(pt_root) };
152 if let Ok((frame, _flags)) = mapper.translate_page(VirtualAddress(vaddr as u64)) {
153 let page_offset = vaddr & 0xFFF;
154 let phys_addr = (frame.as_u64() << 12) + page_offset as u64;
155 // SAFETY: phys_addr is converted to a kernel-accessible virtual
156 // address via phys_to_virt_addr (required on x86_64 where physical
157 // memory is mapped at a dynamic offset, not identity-mapped).
158 unsafe {
159 let virt = crate::mm::phys_to_virt_addr(phys_addr);
160 core::ptr::write(virt as *mut usize, value);
161 }
162 }
163}
164
165/// Write a byte slice to a user-space address via the physical memory window.
166///
167/// # Safety
168///
169/// Same requirements as `write_to_user_stack`. The range
170/// `[vaddr, vaddr+data.len())` must be within a single mapped page.
171#[cfg(feature = "alloc")]
172unsafe fn write_bytes_to_user_stack(
173 memory_space: &crate::mm::VirtualAddressSpace,
174 vaddr: usize,
175 data: &[u8],
176) {
177 use crate::mm::VirtualAddress;
178
179 let pt_root = memory_space.get_page_table();
180 if pt_root == 0 {
181 return;
182 }
183
184 let mapper = unsafe { crate::mm::vas::create_mapper_from_root_pub(pt_root) };
185 if let Ok((frame, _flags)) = mapper.translate_page(VirtualAddress(vaddr as u64)) {
186 let page_offset = vaddr & 0xFFF;
187 let phys_addr = (frame.as_u64() << 12) + page_offset as u64;
188 // SAFETY: phys_addr is converted to a kernel-accessible virtual
189 // address via phys_to_virt_addr. The destination has at least
190 // data.len() bytes available within the page.
191 unsafe {
192 let virt = crate::mm::phys_to_virt_addr(phys_addr);
193 core::ptr::copy_nonoverlapping(data.as_ptr(), virt as *mut u8, data.len());
194 }
195 }
196}
197
198/// Read a `usize` value from a user-space address via the physical memory
199/// window.
200///
201/// # Safety
202///
203/// `vaddr` must be a valid mapped address in the process's VAS. The caller
204/// must ensure no concurrent writes to this memory.
205#[cfg(feature = "alloc")]
206unsafe fn read_from_user_stack(
207 memory_space: &crate::mm::VirtualAddressSpace,
208 vaddr: usize,
209) -> Option<usize> {
210 use crate::mm::VirtualAddress;
211
212 let pt_root = memory_space.get_page_table();
213 if pt_root == 0 {
214 return None;
215 }
216
217 let mapper = unsafe { crate::mm::vas::create_mapper_from_root_pub(pt_root) };
218 if let Ok((frame, _flags)) = mapper.translate_page(VirtualAddress(vaddr as u64)) {
219 let page_offset = vaddr & 0xFFF;
220 let phys_addr = (frame.as_u64() << 12) + page_offset as u64;
221 // SAFETY: phys_addr is converted to a kernel-accessible virtual
222 // address via phys_to_virt_addr for reading.
223 Some(unsafe {
224 let virt = crate::mm::phys_to_virt_addr(phys_addr);
225 core::ptr::read(virt as *const usize)
226 })
227 } else {
228 None
229 }
230}
231
232/// Read a byte slice from a user-space address via the physical memory window.
233///
234/// # Safety
235///
236/// `vaddr` must be a valid mapped address. The range `[vaddr, vaddr+len)` must
237/// be within a single mapped page.
238#[cfg(feature = "alloc")]
239unsafe fn read_bytes_from_user_stack(
240 memory_space: &crate::mm::VirtualAddressSpace,
241 vaddr: usize,
242 buf: &mut [u8],
243) -> bool {
244 use crate::mm::VirtualAddress;
245
246 let pt_root = memory_space.get_page_table();
247 if pt_root == 0 {
248 return false;
249 }
250
251 let mapper = unsafe { crate::mm::vas::create_mapper_from_root_pub(pt_root) };
252 if let Ok((frame, _flags)) = mapper.translate_page(VirtualAddress(vaddr as u64)) {
253 let page_offset = vaddr & 0xFFF;
254 let phys_addr = (frame.as_u64() << 12) + page_offset as u64;
255 // SAFETY: phys_addr is converted to a kernel-accessible virtual
256 // address via phys_to_virt_addr. We copy exactly buf.len() bytes.
257 unsafe {
258 let virt = crate::mm::phys_to_virt_addr(phys_addr);
259 core::ptr::copy_nonoverlapping(virt as *const u8, buf.as_mut_ptr(), buf.len());
260 }
261 true
262 } else {
263 false
264 }
265}
266
267// ============================================================================
268// Signal delivery (x86_64)
269// ============================================================================
270
271/// Deliver a signal to a user-space handler by constructing a signal frame on
272/// the user stack.
273///
274/// This function:
275/// 1. Looks up the signal handler for `signum` in the process's handler table.
276/// 2. If the handler is SIG_DFL (0) or SIG_IGN (1), handles the signal
277/// in-kernel (terminate or ignore) and returns without modifying the thread.
278/// 3. For a real handler address, saves the current thread context into a
279/// `SignalFrame` on the user stack.
280/// 4. Writes a sigreturn trampoline just above the frame.
281/// 5. Sets the thread's RIP to the handler, RSP to the signal frame, and RDI to
282/// the signal number (first argument per System V AMD64 ABI).
283///
284/// On success, the next time this thread returns to user space it will execute
285/// the signal handler. When the handler returns, the trampoline calls
286/// `sigreturn` which restores the original context.
287///
288/// # Arguments
289/// - `process`: The process receiving the signal.
290/// - `thread`: The thread whose context will be modified.
291/// - `signum`: Signal number (1-31).
292///
293/// # Returns
294/// - `Ok(true)` if a signal frame was constructed and the handler will run.
295/// - `Ok(false)` if the signal was handled in-kernel (default/ignore).
296/// - `Err(...)` on failure (invalid signal, no mapped stack, etc.).
297#[cfg(feature = "alloc")]
298pub fn deliver_signal(
299 process: &Process,
300 thread: &Thread,
301 signum: usize,
302) -> Result<bool, KernelError> {
303 if signum == 0 || signum > 31 {
304 return Err(KernelError::InvalidArgument {
305 name: "signum",
306 value: "signal number out of range (1-31)",
307 });
308 }
309
310 // Look up the handler for this signal
311 let handler = process.get_signal_handler(signum).unwrap_or(0);
312
313 // Handle SIG_DFL and SIG_IGN in-kernel
314 if handler == SIG_DFL {
315 // Default action: for most signals, terminate the process.
316 // SIGCHLD (17), SIGURG (23), SIGWINCH (28) are ignored by default.
317 // For now, log and return false (caller decides termination).
318 println!(
319 "[SIGNAL] Signal {} for process {}: default action",
320 signum, process.pid.0
321 );
322 return Ok(false);
323 }
324
325 if handler == SIG_IGN {
326 // Explicitly ignored -- clear the pending bit and return.
327 process.clear_pending_signal(signum);
328 return Ok(false);
329 }
330
331 // We have a real handler address -- deliver via signal frame.
332 deliver_signal_to_handler(process, thread, signum, handler)
333}
334
335/// Internal: construct the signal frame for a real handler address.
336#[cfg(feature = "alloc")]
337fn deliver_signal_to_handler(
338 process: &Process,
339 thread: &Thread,
340 signum: usize,
341 handler: u64,
342) -> Result<bool, KernelError> {
343 // Architecture-specific delivery
344 #[cfg(target_arch = "x86_64")]
345 {
346 deliver_signal_x86_64(process, thread, signum, handler)
347 }
348
349 #[cfg(target_arch = "aarch64")]
350 {
351 deliver_signal_aarch64(process, thread, signum, handler)
352 }
353
354 #[cfg(target_arch = "riscv64")]
355 {
356 deliver_signal_riscv(process, thread, signum, handler)
357 }
358}
359
360// ---------------- AArch64 implementation -----------------
361
362/// AArch64 signal delivery implementation.
363///
364/// Constructs an `Aarch64SignalFrame` on the user stack containing all 31
365/// general-purpose registers (x0-x30), SP, PC, and PSTATE. A 4-instruction
366/// trampoline is placed immediately after the frame; when the signal handler
367/// returns, it executes the trampoline which invokes `svc #0` with
368/// `SYS_SIGRETURN` in w8 (the syscall number register on AArch64).
369///
370/// # Stack layout after delivery (growing downward)
371///
372/// ```text
373/// [original SP]
374/// ...
375/// [trampoline: add x0,sp,#0 / mov w8,#SYS_SIGRETURN / svc #0 / brk #0]
376/// [Aarch64SignalFrame] <- new SP
377/// .trampoline_ret = address of trampoline code
378/// .signum
379/// .saved_mask
380/// .regs[0..30] = x0..x30 (x30 = LR)
381/// .sp = original SP
382/// .pc = original PC
383/// .pstate = saved SPSR
384/// ```
385///
386/// The handler is called with x0 = signum, x1 = &frame, and LR pointing
387/// to the trampoline. The stack pointer is 16-byte aligned per AAPCS64.
388#[cfg(all(feature = "alloc", target_arch = "aarch64"))]
389fn deliver_signal_aarch64(
390 process: &Process,
391 thread: &Thread,
392 signum: usize,
393 handler: u64,
394) -> Result<bool, KernelError> {
395 // Build a minimal signal frame: save general-purpose regs + PC/SP + PSTATE
396 #[repr(C)]
397 #[derive(Clone, Copy)]
398 struct Aarch64SignalFrame {
399 trampoline_ret: u64,
400 signum: u64,
401 saved_mask: u64,
402 // GPRs x0-x30 (x30 = LR)
403 regs: [u64; 31],
404 sp: u64,
405 pc: u64,
406 pstate: u64,
407 }
408
409 // Trampoline: add x0, sp, #0 ; mov w8, #SYS_SIGRETURN ; svc #0 ; brk #0
410 const TRAMPOLINE: [u32; 4] = [
411 0x910003e0, // add x0, sp, #0
412 0x52800000, // mov w8,#0 (patched below)
413 0xd4000001, // svc #0
414 0xd4200000, // brk #0
415 ];
416
417 let mut ctx = thread.context.lock();
418 let memory_space = process.memory_space.lock();
419
420 let saved_pc = ctx.pc as usize;
421 let saved_sp = ctx.sp as usize;
422
423 // Allocate space on user stack: frame + trampoline, 16-byte aligned
424 let frame_size = core::mem::size_of::<Aarch64SignalFrame>();
425 let tramp_size = core::mem::size_of_val(&TRAMPOLINE);
426 let total = frame_size + tramp_size;
427 let sp = (saved_sp - total) & !0xF; // maintain 16-byte alignment
428
429 // Write frame
430 let mut regs = [0u64; 31];
431 regs.copy_from_slice(&ctx.x);
432 let frame = Aarch64SignalFrame {
433 trampoline_ret: sp as u64 + frame_size as u64, // trampoline immediately after frame
434 signum: signum as u64,
435 saved_mask: process
436 .signal_mask
437 .load(core::sync::atomic::Ordering::Acquire),
438 regs,
439 sp: saved_sp as u64,
440 pc: saved_pc as u64,
441 pstate: ctx.spsr,
442 };
443
444 // SAFETY: `sp` is derived from the thread's current stack pointer
445 // (saved_sp) minus the frame+trampoline size, 16-byte aligned. The
446 // user stack is mapped in the process's VAS with write permissions.
447 // We write the Aarch64SignalFrame as a raw byte slice via the
448 // physical memory window. The from_raw_parts call is safe because
449 // `frame` is a local repr(C) struct on the kernel stack and we read
450 // exactly its size in bytes.
451 unsafe {
452 write_bytes_to_user_stack(
453 &memory_space,
454 sp,
455 core::slice::from_raw_parts(
456 &frame as *const _ as *const u8,
457 core::mem::size_of::<Aarch64SignalFrame>(),
458 ),
459 );
460 }
461
462 // Patch trampoline SYS_SIGRETURN number
463 let mut tramp = TRAMPOLINE;
464 tramp[1] = 0x52800000 | ((SYS_SIGRETURN as u32) << 5); // mov w8,#SYS_SIGRETURN
465
466 // SAFETY: The trampoline is written immediately after the signal
467 // frame at `sp + frame_size`. This address is within the same user
468 // stack page (or adjacent mapped page). The trampoline is 16 bytes
469 // (4 AArch64 instructions). from_raw_parts is safe because `tramp`
470 // is a local array on the kernel stack.
471 unsafe {
472 write_bytes_to_user_stack(
473 &memory_space,
474 sp + frame_size,
475 core::slice::from_raw_parts(tramp.as_ptr() as *const u8, tramp_size),
476 );
477 }
478
479 // Redirect execution to handler; set x0=signum, x1=&frame
480 ctx.x[0] = signum as u64;
481 ctx.x[1] = sp as u64;
482 ctx.sp = sp as u64;
483 ctx.pc = handler;
484 ctx.elr = handler;
485 ctx.x[30] = frame.trampoline_ret; // LR
486
487 Ok(true)
488}
489
490/// Fallback when not compiling for AArch64 with alloc -- signal delivery
491/// is a no-op since this architecture's handler cannot execute.
492#[cfg(not(all(feature = "alloc", target_arch = "aarch64")))]
493fn deliver_signal_aarch64(
494 _process: &Process,
495 _thread: &Thread,
496 _signum: usize,
497 _handler: u64,
498) -> Result<bool, KernelError> {
499 Ok(false)
500}
501
502// ---------------- RISC-V implementation -----------------
503
504/// RISC-V 64-bit signal delivery implementation.
505///
506/// Constructs an `RiscvSignalFrame` on the user stack containing all 31
507/// general-purpose registers (x1-x31; x0 is hardwired to zero), sepc, and
508/// sstatus. A 4-instruction trampoline is placed immediately after the frame;
509/// when the signal handler returns, it executes the trampoline which invokes
510/// `ecall` with `SYS_SIGRETURN` in a7 (the syscall number register on
511/// RISC-V).
512///
513/// # Stack layout after delivery (growing downward)
514///
515/// ```text
516/// [original SP]
517/// ...
518/// [trampoline: addi a0,sp,0 / addi a7,x0,SYS_SIGRETURN / ecall / ebreak]
519/// [RiscvSignalFrame] <- new SP
520/// .trampoline_ret = address of trampoline code
521/// .signum
522/// .saved_mask
523/// .regs[0..30] = x1..x31 (ra, sp, gp, tp, t0-t6, s0-s11, a0-a7)
524/// .pc = original sepc
525/// .sstatus = saved sstatus
526/// ```
527///
528/// The handler is called with a0 = signum, a1 = &frame, and ra pointing
529/// to the trampoline. The stack pointer is 16-byte aligned per the RISC-V
530/// calling convention.
531#[cfg(all(feature = "alloc", target_arch = "riscv64"))]
532fn deliver_signal_riscv(
533 process: &Process,
534 thread: &Thread,
535 signum: usize,
536 handler: u64,
537) -> Result<bool, KernelError> {
538 // Minimal frame: save x1-x31, pc, sstatus
539 #[repr(C)]
540 #[derive(Clone, Copy)]
541 struct RiscvSignalFrame {
542 trampoline_ret: u64,
543 signum: u64,
544 saved_mask: u64,
545 regs: [u64; 31], // x1..x31 (x0 is zero)
546 pc: u64,
547 sstatus: u64,
548 }
549
550 const TRAMPOLINE: [u32; 4] = [
551 0x00010513, // addi a0, sp, 0 (frame ptr via sp)
552 0x00000893, // addi a7, x0, imm (patched below with SYS_SIGRETURN)
553 0x00000073, // ecall
554 0x00100073, // ebreak
555 ];
556
557 let mut ctx = thread.context.lock();
558 let memory_space = process.memory_space.lock();
559
560 let saved_pc = ctx.pc;
561 let saved_sp = ctx.sp;
562
563 let frame_size = core::mem::size_of::<RiscvSignalFrame>();
564 let tramp_size = core::mem::size_of_val(&TRAMPOLINE);
565 let total = frame_size + tramp_size;
566 let sp = (saved_sp - total) & !0xF;
567
568 let regs = [
569 ctx.ra as u64,
570 ctx.sp as u64,
571 ctx.gp as u64,
572 ctx.tp as u64,
573 ctx.t0 as u64,
574 ctx.t1 as u64,
575 ctx.t2 as u64,
576 ctx.s0 as u64,
577 ctx.s1 as u64,
578 ctx.a0 as u64,
579 ctx.a1 as u64,
580 ctx.a2 as u64,
581 ctx.a3 as u64,
582 ctx.a4 as u64,
583 ctx.a5 as u64,
584 ctx.a6 as u64,
585 ctx.a7 as u64,
586 ctx.s2 as u64,
587 ctx.s3 as u64,
588 ctx.s4 as u64,
589 ctx.s5 as u64,
590 ctx.s6 as u64,
591 ctx.s7 as u64,
592 ctx.s8 as u64,
593 ctx.s9 as u64,
594 ctx.s10 as u64,
595 ctx.s11 as u64,
596 ctx.t3 as u64,
597 ctx.t4 as u64,
598 ctx.t5 as u64,
599 ctx.t6 as u64,
600 ];
601
602 let frame = RiscvSignalFrame {
603 trampoline_ret: sp as u64 + frame_size as u64,
604 signum: signum as u64,
605 saved_mask: process
606 .signal_mask
607 .load(core::sync::atomic::Ordering::Acquire),
608 regs,
609 pc: saved_pc as u64,
610 sstatus: ctx.sstatus as u64,
611 };
612
613 // SAFETY: `sp` is derived from the thread's current stack pointer
614 // (saved_sp) minus the frame+trampoline size, 16-byte aligned. The
615 // user stack is mapped in the process's VAS with write permissions.
616 // We write the RiscvSignalFrame as a raw byte slice via the physical
617 // memory window. The from_raw_parts call is safe because `frame` is
618 // a local repr(C) struct on the kernel stack and we read exactly its
619 // size in bytes.
620 unsafe {
621 write_bytes_to_user_stack(
622 &memory_space,
623 sp,
624 core::slice::from_raw_parts(
625 &frame as *const _ as *const u8,
626 core::mem::size_of::<RiscvSignalFrame>(),
627 ),
628 );
629 }
630
631 let mut tramp = TRAMPOLINE;
632 tramp[1] = 0x00000893 | ((SYS_SIGRETURN as u32) << 20); // addi a7,x0,SYS_SIGRETURN
633
634 // SAFETY: The trampoline is written immediately after the signal
635 // frame at `sp + frame_size`. This address is within the same user
636 // stack page (or adjacent mapped page). The trampoline is 16 bytes
637 // (4 RISC-V instructions). from_raw_parts is safe because `tramp`
638 // is a local array on the kernel stack.
639 unsafe {
640 write_bytes_to_user_stack(
641 &memory_space,
642 sp + frame_size,
643 core::slice::from_raw_parts(tramp.as_ptr() as *const u8, tramp_size),
644 );
645 }
646
647 // Set a0 (arg0) = signum, a1 = &frame
648 ctx.a0 = signum;
649 ctx.a1 = sp;
650 ctx.sp = sp;
651 ctx.pc = handler as usize;
652 ctx.sepc = handler as usize;
653 ctx.ra = frame.trampoline_ret as usize;
654
655 Ok(true)
656}
657
658/// Fallback when not compiling for RISC-V with alloc -- signal delivery
659/// is a no-op since this architecture's handler cannot execute.
660#[cfg(not(all(feature = "alloc", target_arch = "riscv64")))]
661fn deliver_signal_riscv(
662 _process: &Process,
663 _thread: &Thread,
664 _signum: usize,
665 _handler: u64,
666) -> Result<bool, KernelError> {
667 Ok(false)
668}
669
670/// x86_64 signal delivery implementation.
671///
672/// Stack layout after delivery (growing downward):
673///
674/// ```text
675/// [original RSP] <- where the thread was before signal
676/// ...
677/// [trampoline code, 14 bytes] <- trampoline_addr
678/// [padding for 16-byte align]
679/// [SignalFrame] <- new RSP
680/// .trampoline_ret_addr = trampoline_addr
681/// .signum
682/// .saved_mask
683/// .rax..r15
684/// .rip
685/// .rflags
686/// ```
687#[cfg(all(feature = "alloc", target_arch = "x86_64"))]
688fn deliver_signal_x86_64(
689 process: &Process,
690 thread: &Thread,
691 signum: usize,
692 handler: u64,
693) -> Result<bool, KernelError> {
694 use core::sync::atomic::Ordering;
695
696 let memory_space = process.memory_space.lock();
697 let mut ctx = thread.context.lock();
698
699 // Save current register state
700 let saved_rip = ctx.rip;
701 let saved_rsp = ctx.rsp;
702 let saved_rflags = ctx.rflags;
703 let saved_rax = ctx.rax;
704 let saved_rbx = ctx.rbx;
705 let saved_rcx = ctx.rcx;
706 let saved_rdx = ctx.rdx;
707 let saved_rsi = ctx.rsi;
708 let saved_rdi = ctx.rdi;
709 let saved_rbp = ctx.rbp;
710 let saved_r8 = ctx.r8;
711 let saved_r9 = ctx.r9;
712 let saved_r10 = ctx.r10;
713 let saved_r11 = ctx.r11;
714 let saved_r12 = ctx.r12;
715 let saved_r13 = ctx.r13;
716 let saved_r14 = ctx.r14;
717 let saved_r15 = ctx.r15;
718
719 // Save and update signal mask: block the delivered signal during handler
720 let saved_mask = process.signal_mask.load(Ordering::Acquire);
721 let blocked_during_handler = saved_mask | (1u64 << signum);
722 // Cannot mask SIGKILL (9) or SIGSTOP (19)
723 let sanitized = blocked_during_handler & !((1u64 << 9) | (1u64 << 19));
724 process.signal_mask.store(sanitized, Ordering::Release);
725
726 // Clear the pending signal bit (we are delivering it now)
727 process.clear_pending_signal(signum);
728
729 // Calculate stack positions for the signal frame and trampoline.
730 // User stack grows downward from saved_rsp.
731 let mut sp = saved_rsp as usize;
732
733 // 1. Write trampoline code at top (below current SP)
734 sp -= TRAMPOLINE_SIZE;
735 // Align trampoline start to 2-byte boundary (for code fetch efficiency)
736 sp &= !1;
737 let trampoline_addr = sp;
738
739 // SAFETY: sp is within the user stack region and is mapped in the
740 // process's page tables. We write the trampoline machine code bytes.
741 unsafe {
742 write_bytes_to_user_stack(&memory_space, trampoline_addr, &SIGRETURN_TRAMPOLINE);
743 }
744
745 // 2. Allocate space for SignalFrame below the trampoline
746 sp -= SIGNAL_FRAME_SIZE;
747 // Align to 16 bytes (x86_64 ABI requirement for stack alignment)
748 sp &= !0xF;
749 let frame_addr = sp;
750
751 // 3. Build the signal frame
752 let frame = SignalFrame {
753 trampoline_ret_addr: trampoline_addr as u64,
754 signum: signum as u64,
755 saved_mask,
756 rax: saved_rax,
757 rbx: saved_rbx,
758 rcx: saved_rcx,
759 rdx: saved_rdx,
760 rsi: saved_rsi,
761 rdi: saved_rdi,
762 rbp: saved_rbp,
763 rsp: saved_rsp,
764 r8: saved_r8,
765 r9: saved_r9,
766 r10: saved_r10,
767 r11: saved_r11,
768 r12: saved_r12,
769 r13: saved_r13,
770 r14: saved_r14,
771 r15: saved_r15,
772 rip: saved_rip,
773 rflags: saved_rflags,
774 };
775
776 // 4. Write the signal frame to the user stack
777 // SAFETY: frame_addr is within the user stack, aligned to 16 bytes, and
778 // mapped in the process's page tables. We write the entire SignalFrame
779 // as a byte slice.
780 unsafe {
781 let frame_bytes = core::slice::from_raw_parts(
782 &frame as *const SignalFrame as *const u8,
783 SIGNAL_FRAME_SIZE,
784 );
785 write_bytes_to_user_stack(&memory_space, frame_addr, frame_bytes);
786 }
787
788 // 5. Set up the thread context to execute the signal handler.
789 // - RIP = handler address
790 // - RSP = frame_addr (handler's stack; return addr is at [RSP] which is
791 // trampoline_ret_addr in the SignalFrame)
792 // - RDI = signum (first argument, System V AMD64 ABI)
793 ctx.rip = handler;
794 ctx.rsp = frame_addr as u64;
795 ctx.rdi = signum as u64;
796
797 // Clear direction flag and ensure interrupts are enabled in user mode
798 ctx.rflags = (ctx.rflags & !0x400) | 0x200; // clear DF, set IF
799
800 println!(
801 "[SIGNAL] Delivered signal {} to process {} handler {:#x}, frame at {:#x}",
802 signum, process.pid.0, handler, frame_addr
803 );
804
805 Ok(true)
806}
807
808// ============================================================================
809// Signal frame restoration (sigreturn)
810// ============================================================================
811
812/// Restore the original thread context from a signal frame on the user stack.
813///
814/// Called by `sys_sigreturn` after the signal handler returns. Reads the
815/// `SignalFrame` from the user stack and restores all saved registers and the
816/// signal mask.
817///
818/// # Arguments
819/// - `process`: The process whose signal mask will be restored.
820/// - `thread`: The thread whose context will be restored.
821/// - `frame_ptr`: User-space pointer to the `SignalFrame` (passed by the
822/// trampoline via RDI).
823///
824/// # Returns
825/// - `Ok(())` on success (thread context is restored, execution will resume at
826/// the interrupted instruction).
827/// - `Err(...)` if the frame cannot be read.
828#[cfg(feature = "alloc")]
829pub fn restore_signal_frame(
830 process: &Process,
831 thread: &Thread,
832 frame_ptr: usize,
833) -> Result<(), KernelError> {
834 #[cfg(target_arch = "x86_64")]
835 {
836 restore_signal_frame_x86_64(process, thread, frame_ptr)
837 }
838
839 #[cfg(target_arch = "aarch64")]
840 {
841 restore_signal_frame_aarch64(process, thread, frame_ptr)
842 }
843
844 #[cfg(target_arch = "riscv64")]
845 {
846 restore_signal_frame_riscv(process, thread, frame_ptr)
847 }
848}
849
850/// x86_64 signal frame restoration.
851#[cfg(all(feature = "alloc", target_arch = "x86_64"))]
852fn restore_signal_frame_x86_64(
853 process: &Process,
854 thread: &Thread,
855 frame_ptr: usize,
856) -> Result<(), KernelError> {
857 use core::sync::atomic::Ordering;
858
859 let memory_space = process.memory_space.lock();
860
861 // Read the signal frame from the user stack
862 let mut frame_bytes = [0u8; SIGNAL_FRAME_SIZE];
863 // SAFETY: frame_ptr was passed from the trampoline and points to a
864 // SignalFrame we previously wrote. We read it back via the physical
865 // memory window.
866 let ok = unsafe { read_bytes_from_user_stack(&memory_space, frame_ptr, &mut frame_bytes) };
867
868 if !ok {
869 return Err(KernelError::InvalidArgument {
870 name: "frame_ptr",
871 value: "could not read signal frame from user stack",
872 });
873 }
874
875 // SAFETY: frame_bytes contains SIGNAL_FRAME_SIZE bytes that we read from
876 // the user stack. We reinterpret them as a SignalFrame. The struct is
877 // repr(C) and all fields are plain u64 values, so any bit pattern is
878 // valid. We copy the struct by value.
879 let frame: SignalFrame = unsafe { core::ptr::read(frame_bytes.as_ptr() as *const SignalFrame) };
880
881 // Restore the thread context
882 {
883 let mut ctx = thread.context.lock();
884 ctx.rax = frame.rax;
885 ctx.rbx = frame.rbx;
886 ctx.rcx = frame.rcx;
887 ctx.rdx = frame.rdx;
888 ctx.rsi = frame.rsi;
889 ctx.rdi = frame.rdi;
890 ctx.rbp = frame.rbp;
891 ctx.rsp = frame.rsp;
892 ctx.r8 = frame.r8;
893 ctx.r9 = frame.r9;
894 ctx.r10 = frame.r10;
895 ctx.r11 = frame.r11;
896 ctx.r12 = frame.r12;
897 ctx.r13 = frame.r13;
898 ctx.r14 = frame.r14;
899 ctx.r15 = frame.r15;
900 ctx.rip = frame.rip;
901 ctx.rflags = frame.rflags;
902 }
903
904 // Restore the signal mask
905 // Cannot unmask SIGKILL (9) or SIGSTOP (19)
906 let restored_mask = frame.saved_mask & !((1u64 << 9) | (1u64 << 19));
907 process.signal_mask.store(restored_mask, Ordering::Release);
908
909 println!(
910 "[SIGNAL] Restored signal frame for process {}, resuming at {:#x}",
911 process.pid.0, frame.rip
912 );
913
914 Ok(())
915}
916
917/// AArch64 signal frame restoration.
918///
919/// Reads an `Aarch64SignalFrame` from the user stack at `frame_ptr`,
920/// restores all general-purpose registers (x0-x30), SP, PC (ELR), and
921/// PSTATE (SPSR), and restores the process's signal mask from the saved
922/// value. On success, the thread will resume at the original PC when it
923/// next returns to user space.
924#[cfg(all(feature = "alloc", target_arch = "aarch64"))]
925fn restore_signal_frame_aarch64(
926 process: &Process,
927 thread: &Thread,
928 frame_ptr: usize,
929) -> Result<(), KernelError> {
930 use core::sync::atomic::Ordering;
931
932 #[repr(C)]
933 #[derive(Clone, Copy)]
934 struct Aarch64SignalFrame {
935 trampoline_ret: u64,
936 signum: u64,
937 saved_mask: u64,
938 regs: [u64; 31],
939 sp: u64,
940 pc: u64,
941 pstate: u64,
942 }
943
944 let memory_space = process.memory_space.lock();
945 let mut buf = [0u8; core::mem::size_of::<Aarch64SignalFrame>()];
946 // SAFETY: frame_ptr was passed from the sigreturn trampoline and
947 // points to an Aarch64SignalFrame we previously wrote to the user
948 // stack. We read it back via the physical memory window into a
949 // kernel-stack buffer.
950 let ok = unsafe { read_bytes_from_user_stack(&memory_space, frame_ptr, &mut buf) };
951 if !ok {
952 return Err(KernelError::InvalidArgument {
953 name: "frame_ptr",
954 value: "could not read signal frame from user stack",
955 });
956 }
957
958 // SAFETY: buf contains exactly size_of::<Aarch64SignalFrame>() bytes
959 // that we read from the user stack. The struct is repr(C) and all
960 // fields are plain u64 values, so any bit pattern is valid. We copy
961 // the struct by value.
962 let frame: Aarch64SignalFrame =
963 unsafe { core::ptr::read(buf.as_ptr() as *const Aarch64SignalFrame) };
964
965 {
966 let mut ctx = thread.context.lock();
967 ctx.x.copy_from_slice(&frame.regs);
968 ctx.sp = frame.sp;
969 ctx.pc = frame.pc;
970 ctx.elr = frame.pc;
971 ctx.spsr = frame.pstate;
972 }
973
974 // Restore the signal mask, ensuring SIGKILL (9) and SIGSTOP (19)
975 // can never be masked.
976 let restored_mask = frame.saved_mask & !((1u64 << 9) | (1u64 << 19));
977 process.signal_mask.store(restored_mask, Ordering::Release);
978
979 println!(
980 "[SIGNAL] Restored signal frame (aarch64) for process {}, pc={:#x}",
981 process.pid.0, frame.pc
982 );
983
984 Ok(())
985}
986
987/// RISC-V 64-bit signal frame restoration.
988///
989/// Reads a `RiscvSignalFrame` from the user stack at `frame_ptr`,
990/// restores all general-purpose registers (x1-x31 mapped to ra, sp, gp,
991/// tp, t0-t6, s0-s11, a0-a7), sepc, and sstatus, and restores the
992/// process's signal mask from the saved value. On success, the thread
993/// will resume at the original PC when it next returns to user space.
994#[cfg(all(feature = "alloc", target_arch = "riscv64"))]
995fn restore_signal_frame_riscv(
996 process: &Process,
997 thread: &Thread,
998 frame_ptr: usize,
999) -> Result<(), KernelError> {
1000 use core::sync::atomic::Ordering;
1001
1002 #[repr(C)]
1003 #[derive(Clone, Copy)]
1004 struct RiscvSignalFrame {
1005 trampoline_ret: u64,
1006 signum: u64,
1007 saved_mask: u64,
1008 regs: [u64; 31],
1009 pc: u64,
1010 sstatus: u64,
1011 }
1012
1013 let memory_space = process.memory_space.lock();
1014 let mut buf = [0u8; core::mem::size_of::<RiscvSignalFrame>()];
1015 // SAFETY: frame_ptr was passed from the sigreturn trampoline and
1016 // points to a RiscvSignalFrame we previously wrote to the user
1017 // stack. We read it back via the physical memory window into a
1018 // kernel-stack buffer.
1019 let ok = unsafe { read_bytes_from_user_stack(&memory_space, frame_ptr, &mut buf) };
1020 if !ok {
1021 return Err(KernelError::InvalidArgument {
1022 name: "frame_ptr",
1023 value: "could not read signal frame from user stack",
1024 });
1025 }
1026
1027 // SAFETY: buf contains exactly size_of::<RiscvSignalFrame>() bytes
1028 // that we read from the user stack. The struct is repr(C) and all
1029 // fields are plain u64 values, so any bit pattern is valid. We copy
1030 // the struct by value.
1031 let frame: RiscvSignalFrame =
1032 unsafe { core::ptr::read(buf.as_ptr() as *const RiscvSignalFrame) };
1033
1034 {
1035 let mut ctx = thread.context.lock();
1036 let r = frame.regs;
1037 // x1..x31 mapping (see delivery)
1038 ctx.ra = r[0] as usize;
1039 ctx.sp = r[1] as usize;
1040 ctx.gp = r[2] as usize;
1041 ctx.tp = r[3] as usize;
1042 ctx.t0 = r[4] as usize;
1043 ctx.t1 = r[5] as usize;
1044 ctx.t2 = r[6] as usize;
1045 ctx.s0 = r[7] as usize;
1046 ctx.s1 = r[8] as usize;
1047 ctx.a0 = r[9] as usize;
1048 ctx.a1 = r[10] as usize;
1049 ctx.a2 = r[11] as usize;
1050 ctx.a3 = r[12] as usize;
1051 ctx.a4 = r[13] as usize;
1052 ctx.a5 = r[14] as usize;
1053 ctx.a6 = r[15] as usize;
1054 ctx.a7 = r[16] as usize;
1055 ctx.s2 = r[17] as usize;
1056 ctx.s3 = r[18] as usize;
1057 ctx.s4 = r[19] as usize;
1058 ctx.s5 = r[20] as usize;
1059 ctx.s6 = r[21] as usize;
1060 ctx.s7 = r[22] as usize;
1061 ctx.s8 = r[23] as usize;
1062 ctx.s9 = r[24] as usize;
1063 ctx.s10 = r[25] as usize;
1064 ctx.s11 = r[26] as usize;
1065 ctx.t3 = r[27] as usize;
1066 ctx.t4 = r[28] as usize;
1067 ctx.t5 = r[29] as usize;
1068 ctx.t6 = r[30] as usize;
1069 ctx.pc = frame.pc as usize;
1070 ctx.sepc = frame.pc as usize;
1071 ctx.sstatus = frame.sstatus as usize;
1072 }
1073
1074 // Restore the signal mask, ensuring SIGKILL (9) and SIGSTOP (19)
1075 // can never be masked.
1076 let restored_mask = frame.saved_mask & !((1u64 << 9) | (1u64 << 19));
1077 process.signal_mask.store(restored_mask, Ordering::Release);
1078
1079 println!(
1080 "[SIGNAL] Restored signal frame (riscv) for process {}, pc={:#x}",
1081 process.pid.0, frame.pc
1082 );
1083
1084 Ok(())
1085}
1086
1087// ============================================================================
1088// Pending signal check (called from syscall return path)
1089// ============================================================================
1090
1091/// Check for and deliver any pending signals on the current process/thread.
1092///
1093/// This function should be called on the syscall return path (or on return
1094/// from interrupt) to deliver signals at a safe point. It dequeues the
1095/// lowest-numbered pending unblocked signal and, if a user-space handler is
1096/// registered, constructs a signal frame so the handler executes on return
1097/// to user mode.
1098///
1099/// # Returns
1100/// - `Ok(true)` if a signal was delivered (thread context was modified).
1101/// - `Ok(false)` if no deliverable signal was pending.
1102/// - `Err(...)` on failure.
1103#[cfg(feature = "alloc")]
1104pub fn check_pending_signals() -> Result<bool, KernelError> {
1105 let process =
1106 crate::process::current_process().ok_or(KernelError::ProcessNotFound { pid: 0 })?;
1107 let thread = crate::process::current_thread().ok_or(KernelError::ThreadNotFound { tid: 0 })?;
1108
1109 // Get next pending unblocked signal
1110 if let Some(signum) = process.get_next_pending_signal() {
1111 deliver_signal(process, thread, signum)
1112 } else {
1113 Ok(false)
1114 }
1115}
1116
1117#[cfg(not(feature = "alloc"))]
1118pub fn check_pending_signals() -> Result<bool, KernelError> {
1119 Ok(false)
1120}
1121
1122#[cfg(not(feature = "alloc"))]
1123pub fn deliver_signal(
1124 _process: &Process,
1125 _thread: &Thread,
1126 _signum: usize,
1127) -> Result<bool, KernelError> {
1128 Err(KernelError::NotImplemented {
1129 feature: "signal delivery (requires alloc)",
1130 })
1131}
1132
1133#[cfg(not(feature = "alloc"))]
1134pub fn restore_signal_frame(
1135 _process: &Process,
1136 _thread: &Thread,
1137 _frame_ptr: usize,
1138) -> Result<(), KernelError> {
1139 Err(KernelError::NotImplemented {
1140 feature: "signal frame restore (requires alloc)",
1141 })
1142}