⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

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}