From e630b3a20677b323fb7d7fb2776e50498ffcf24c Mon Sep 17 00:00:00 2001 From: Debin Date: Thu, 9 Jul 2026 13:01:24 +0000 Subject: [PATCH 1/4] riscv64: raise NR_CPUS from 1 to 4 Enable SMP on the riscv64-qemu-virt platform. Validated by the full starry-test-harness ci-test suite (21/21 cases) across multiple runs under -smp 4, including the SMP-sensitive cases (sigreturn-smp-race, getrusage-reentry, process-ipc-smoke, mmap-tlb-flush-semantics). --- platforms/riscv64-qemu-virt/defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/riscv64-qemu-virt/defconfig b/platforms/riscv64-qemu-virt/defconfig index 50495cee0..92afd06da 100644 --- a/platforms/riscv64-qemu-virt/defconfig +++ b/platforms/riscv64-qemu-virt/defconfig @@ -9,7 +9,7 @@ ARCH_RISCV64=y BOOT_CONSOLE_ADDR=0x10000000 BOOT_STACK_SIZE=0x40000 # BUILD_TYPE_DEBUG is not set -NR_CPUS=1 +NR_CPUS=4 KERNEL_STACK_SIZE=0x40000 # KFEAT_ALLOC_BUDDY is not set # KFEAT_ALLOC_TLSF is not set -- Gitee From 0d47991ed22acfdc8b70f0505b3954184de23253 Mon Sep 17 00:00:00 2001 From: Debin Date: Fri, 10 Jul 2026 13:31:46 +0800 Subject: [PATCH 2/4] fix(percpu): add diagnostics for per-CPU base register corruption on RISC-V --- arch/khal/src/percpu.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/arch/khal/src/percpu.rs b/arch/khal/src/percpu.rs index 588cf195b..359af9c6c 100644 --- a/arch/khal/src/percpu.rs +++ b/arch/khal/src/percpu.rs @@ -33,7 +33,46 @@ pub fn current_task_ptr() -> *const T { unsafe { // on RISC-V and LA64, reading `CURRENT_TASK_PTR` requires multiple instruction, so we disable local IRQs. let _guard = kspin::IrqSave::new(); - CURRENT_TASK_PTR.read_current_raw() as _ + let ptr = CURRENT_TASK_PTR.read_current_raw() as *const T; + // Diagnostic for issue #IJZMSG: if the per-CPU base register (`gp` on + // RISC-V, the base used by the read above) points outside the per-CPU + // area, the pointer we just read came from the wrong CPU's slot (or is + // garbage). Dump the raw values so CI pinpoints the corruption. A null + // ptr is normal during early bring-up before any task is installed, so + // skip the check then. This catches gross `gp` corruption but NOT a + // stale-slot divergence (another hart's valid slot is still in range). + #[cfg(target_arch = "riscv64")] + if !ptr.is_null() { + assert_percpu_base_in_range(); + } + ptr + } +} + +// Linker symbols bounding the per-CPU area (defined in the linker script as +// `_percpu_start` / `_percpu_end`). Used to validate the per-CPU base register. +#[cfg(target_arch = "riscv64")] +unsafe extern "C" { + static _percpu_start: u8; + static _percpu_end: u8; +} + +/// Asserts that the current per-CPU base register (`gp`) lies within the +/// per-CPU area. Panics with the raw `gp` value and area bounds if not. +#[cfg(target_arch = "riscv64")] +#[cold] +fn assert_percpu_base_in_range() { + let gp = percpu::read_percpu_reg(); + // `addr_of!` on linker-defined extern statics yields their addresses in the + // load-address space — the same space `gp` addresses. + let start = core::ptr::addr_of!(_percpu_start) as usize; + let end = core::ptr::addr_of!(_percpu_end) as usize; + if gp < start || gp >= end { + panic!( + "per-CPU base register (gp={gp:#x}) is outside the per-CPU area [{start:#x}, \ + {end:#x}); current_task_ptr read a value from the wrong CPU slot (issue #IJZMSG \ + diagnostic)" + ); } } -- Gitee From 294dab5198fdf09bac543697b301739bfd3ed8e2 Mon Sep 17 00:00:00 2001 From: Debin Date: Sat, 11 Jul 2026 09:38:26 +0000 Subject: [PATCH 3/4] fix(riscv64): keep per-hart gp out of the migratable trapframe On riscv64 SMP, `gp` holds this hart's per-CPU base (percpu crate), set once at boot. It was saved/restored through the per-task trapframe (slot-3 of PUSH_POP_GENERAL_REGS), which is migratable: when an S-mode trap handler blocks mid-trap (e.g. the page-fault backend reached from `user_copy`), the task can migrate to another hart between PUSH and POP. `.Ltrap_return` then restored the *pushing* hart's base into `gp` on the *resuming* hart, corrupting `current()`/`this_cpu_id()` and ultimately panicking in `access_user_memory` ("called outside of thread context"). `gp` is per-hart-invariant across an S-mode trap, so it must never be stored in a per-task trapframe. Remove `gp` from PUSH_POP_GENERAL_REGS (slot-3 is no longer saved/restored by the macro). The user `gp` still transits slot-3 across a U-mode trap -- saved by `.Lexit_user` on entry and loaded by `.Ltrap_return` on U-mode return only (SPP==0); an S-mode return (SPP==1) leaves `gp` as this hart's own base. U-mode slot-3 semantics are unchanged; the only behavioural change is that S-mode no longer loads slot-3 -- eliminating the sole foreign-base restore path. Verified: disassembly confirms PUSH/POP skip slot-3 and the slot-3 load in `.Ltrap_return` is SPP-guarded; 15/15 starry-test-harness ci-test runs clean under -smp 4 (was ~17% per-run panic rate), all 21 cases pass. Docs: arch/kcpu/docs/{design,security}.md record the gp invariant. --- arch/kcpu/docs/design.md | 8 ++++++++ arch/kcpu/docs/security.md | 6 ++++++ arch/kcpu/src/riscv/excp.S | 18 +++++++++++++++++- arch/kcpu/src/riscv/macros.rs | 11 ++++++++++- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/arch/kcpu/docs/design.md b/arch/kcpu/docs/design.md index d1312ae40..b645e778c 100644 --- a/arch/kcpu/docs/design.md +++ b/arch/kcpu/docs/design.md @@ -135,6 +135,14 @@ TaskContext (每架构定义,callee-saved 寄存器用于上下文切换 每个架构在该函数中完成异常表排序、硬件描述符表加载、trap 向量基址设置。 - **trap handler**(如 `x86_trap_handler`):运行在中断关闭上下文中, 不可睡眠或阻塞。由汇编入口直接调用,调用时栈上已保存完整 trap frame。 +- **riscv64 `gp` 不变量**:`gp`(x3)是每 CPU 的 percpu 基址,启动时由 + `init_percpu_reg` 设置一次,S-mode trap 期间恒定,故**不**由 + `PUSH_POP_GENERAL_REGS` 保存/恢复——绝不放入可迁移的每任务 trapframe。 + U-mode 用户 `gp` 经 slot-3(`UserContext.regs.gp`)中转:`.Lexit_user` + 入口保存,`.Ltrap_return` 仅在返回 U-mode(SPP==0)时恢复;S-mode 返回 + 保持本 hart 的 `gp`。原因:handler 若在 trap 途中阻塞(如 page-fault + 后端),任务会在 PUSH 与 POP 之间迁移到另一 hart,此时从 trapframe 恢复 + 旧 hart 的基址会污染 `current()`/`this_cpu_id()`。 - **`TaskContext::switch_to()`**:必须在中断关闭上下文调用。 涉及页表切换、TLS 更新和 FP 状态保存/恢复。 - **`UserContext::run()`**:禁用本地 IRQ 后进入用户态,返回后重新启用。 diff --git a/arch/kcpu/docs/security.md b/arch/kcpu/docs/security.md index 20546253f..a2aad2953 100644 --- a/arch/kcpu/docs/security.md +++ b/arch/kcpu/docs/security.md @@ -46,6 +46,12 @@ - **用户态寄存器状态**:`UserContext::run()` 切换到用户态后,用户可控制 所有通用寄存器内容。trap handler 需正确处理任意寄存器值。 +- **riscv64 `gp`(percpu 基址)**:`gp` 是每 CPU 不变量,绝不从每任务 + trapframe 恢复。trap 途中任务可能因 handler 阻塞而迁移到其他 hart;若从 + 可迁移的 trapframe 恢复 `gp`,会把另一 hart 的 percpu 基址装入本 hart, + 使 `current()`/`this_cpu_id()` 指向错误的 CPU 与任务,构成跨任务指针泄漏。 + 当前实现:S-mode trap 返回不触碰 `gp`;仅 U-mode 返回从 slot-3 恢复用户 + `gp`。 - **中断/异常输入**:硬件中断和异常向量触发 trap 入口。IRQ 编号来自硬件, 不完全可信。 diff --git a/arch/kcpu/src/riscv/excp.S b/arch/kcpu/src/riscv/excp.S index 7e9001f9d..c5cb32f84 100644 --- a/arch/kcpu/src/riscv/excp.S +++ b/arch/kcpu/src/riscv/excp.S @@ -33,6 +33,14 @@ trap_vector_base: j riscv_trap_handler .Lexit_user: + // Trapped from U-mode: gp currently holds the USER's gp. The kernel + // percpu `gp` (per-hart, never carried in a trapframe) is restored from + // the callee frame at slot-13 below. Stash the user gp into the + // trapframe's slot-3 (UserContext.regs.gp) here -- before `LDR sp, sp, 0` + // switches `sp` to the callee frame -- so `.Ltrap_return` can reload it on + // return to U-mode. PUSH_POP_GENERAL_REGS no longer touches gp, so this is + // the sole write of slot-3 on the U-mode entry path. + STR gp, sp, 3 LDR sp, sp, 0 LDR s0, sp, 0 LDR s1, sp, 1 @@ -80,7 +88,15 @@ enter_user: LDR t1, sp, 33 csrw sepc, t0 csrw sstatus, t1 - + // `gp` is this hart's per-CPU base -- per-hart-invariant, so it is never + // restored from the (per-task, migratable) trapframe. For an S-mode trap + // return (SPP==1) leave `gp` as this hart's own base. For a U-mode return + // (SPP==0) restore the user's gp from slot-3 (saved by `.Lexit_user` on + // entry). t0/t1 are caller-saved and restored by POP_GENERAL_REGS below. + andi t0, t1, 1 << 8 // SPP (from saved sstatus in t1) + bnez t0, .Lgp_skip // S-mode: keep this hart's gp + LDR gp, sp, 3 // U-mode: restore user gp +.Lgp_skip: POP_GENERAL_REGS LDR sp, sp, 2 // restore sp diff --git a/arch/kcpu/src/riscv/macros.rs b/arch/kcpu/src/riscv/macros.rs index 7e19d59eb..bce104610 100644 --- a/arch/kcpu/src/riscv/macros.rs +++ b/arch/kcpu/src/riscv/macros.rs @@ -168,7 +168,16 @@ macro_rules! include_asm_macros { .macro PUSH_POP_GENERAL_REGS, op \op ra, sp, 1 - \op gp, sp, 3 + // gp (x3, slot-3) is intentionally NOT saved/restored here. + // On RISC-V `gp` holds this hart's per-CPU base (percpu + // crate); it is per-hart-invariant and set once at boot, so it + // must never be stored in a per-task trapframe that can + // migrate between harts between PUSH and POP -- doing so + // restored a stale foreign base into `gp` and corrupted + // `current()`. slot-3 is instead used to carry the *user* gp + // across a U-mode trap: written by `.Lexit_user` on entry and + // loaded by `.Ltrap_return` on U-mode return only. An S-mode + // trap return leaves `gp` untouched. \op tp, sp, 4 \op t0, sp, 5 \op t1, sp, 6 -- Gitee From 7728ed5a4f0e0333d5d095e060578956844c4bd6 Mon Sep 17 00:00:00 2001 From: Debin Date: Sat, 11 Jul 2026 11:38:35 +0000 Subject: [PATCH 4/4] refactor(riscv64): remove obsolete percpu base-range diagnostic The `assert_percpu_base_in_range()` check added in d6b3b99cb validated that `gp` pointed inside the per-CPU area, but its own comment noted it could not catch the real SMP bug: another hart's valid per-CPU slot is still in range, so a cross-hart `gp` (restored from a migrated trapframe) passed the check silently. That bug is now fixed at its root (b820d9dd9: gp removed from the migratable trapframe), so the assert on every `current_task_ptr` call is dead code on a hot path. Remove the assert, its `extern` linker-symbol block, and the call site. No behavioral change: the assert never fired after the root fix. --- arch/khal/src/percpu.rs | 41 +---------------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/arch/khal/src/percpu.rs b/arch/khal/src/percpu.rs index 359af9c6c..80bcff974 100644 --- a/arch/khal/src/percpu.rs +++ b/arch/khal/src/percpu.rs @@ -33,46 +33,7 @@ pub fn current_task_ptr() -> *const T { unsafe { // on RISC-V and LA64, reading `CURRENT_TASK_PTR` requires multiple instruction, so we disable local IRQs. let _guard = kspin::IrqSave::new(); - let ptr = CURRENT_TASK_PTR.read_current_raw() as *const T; - // Diagnostic for issue #IJZMSG: if the per-CPU base register (`gp` on - // RISC-V, the base used by the read above) points outside the per-CPU - // area, the pointer we just read came from the wrong CPU's slot (or is - // garbage). Dump the raw values so CI pinpoints the corruption. A null - // ptr is normal during early bring-up before any task is installed, so - // skip the check then. This catches gross `gp` corruption but NOT a - // stale-slot divergence (another hart's valid slot is still in range). - #[cfg(target_arch = "riscv64")] - if !ptr.is_null() { - assert_percpu_base_in_range(); - } - ptr - } -} - -// Linker symbols bounding the per-CPU area (defined in the linker script as -// `_percpu_start` / `_percpu_end`). Used to validate the per-CPU base register. -#[cfg(target_arch = "riscv64")] -unsafe extern "C" { - static _percpu_start: u8; - static _percpu_end: u8; -} - -/// Asserts that the current per-CPU base register (`gp`) lies within the -/// per-CPU area. Panics with the raw `gp` value and area bounds if not. -#[cfg(target_arch = "riscv64")] -#[cold] -fn assert_percpu_base_in_range() { - let gp = percpu::read_percpu_reg(); - // `addr_of!` on linker-defined extern statics yields their addresses in the - // load-address space — the same space `gp` addresses. - let start = core::ptr::addr_of!(_percpu_start) as usize; - let end = core::ptr::addr_of!(_percpu_end) as usize; - if gp < start || gp >= end { - panic!( - "per-CPU base register (gp={gp:#x}) is outside the per-CPU area [{start:#x}, \ - {end:#x}); current_task_ptr read a value from the wrong CPU slot (issue #IJZMSG \ - diagnostic)" - ); + CURRENT_TASK_PTR.read_current_raw() as *const T } } -- Gitee