Skip to main content

strat9_kernel/process/
usertest.rs

1//! Minimal Ring 3 test task for Strat9-OS.
2//!
3//! Creates a user address space with a tiny machine code blob that:
4//! 1. Calls SYS_DEBUG_LOG to print "Hello Ring 3!" to serial
5//! 2. Calls SYS_PROC_EXIT to cleanly exit
6//!
7//! This tests the full SYSCALL/SYSRET pipeline without needing an ELF loader.
8
9use crate::arch::xshim::VirtAddr;
10use alloc::sync::Arc;
11
12use crate::{
13    memory::address_space::{AddressSpace, VmaFlags, VmaType},
14    process::{
15        task::{CpuContext, KernelStack, SyncUnsafeCell, Task, TaskPriority},
16        TaskState,
17    },
18};
19
20/// User code virtual address (must be in lower half, page-aligned).
21const USER_CODE_ADDR: u64 = 0x40_0000;
22
23/// User stack virtual address (page-aligned).
24const USER_STACK_ADDR: u64 = 0x80_0000;
25
26/// User stack top (stack grows down, so this is base + 4096).
27const USER_STACK_TOP: u64 = USER_STACK_ADDR + 0x1000;
28
29/// Create and schedule a minimal Ring 3 test task.
30///
31/// This function:
32/// 1. Creates a user address space (PML4 with kernel half cloned)
33/// 2. Maps a code page at USER_CODE_ADDR (RX, user-accessible)
34/// 3. Maps a stack page at USER_STACK_ADDR (RW, user-accessible)
35/// 4. Writes a small machine code blob into the code page
36/// 5. Creates a kernel task whose entry point does IRETQ to Ring 3
37pub fn create_user_test_task() {
38    log::info!("Creating Ring 3 test task...");
39
40    // Step 1: Create user address space
41    let user_as = match AddressSpace::new_user() {
42        Ok(a) => Arc::new(a),
43        Err(e) => {
44            log::error!("Failed to create user address space: {}", e);
45            return;
46        }
47    };
48
49    // Step 2: Map code page (USER_ACCESSIBLE | RX)
50    let code_flags = VmaFlags {
51        readable: true,
52        writable: false,
53        executable: true,
54        user_accessible: true,
55    };
56    if let Err(e) = user_as.map_region(
57        USER_CODE_ADDR,
58        1,
59        code_flags,
60        VmaType::Code,
61        crate::memory::address_space::VmaPageSize::Small,
62    ) {
63        log::error!("Failed to map user code page: {}", e);
64        return;
65    }
66
67    // Step 3: Map stack page (USER_ACCESSIBLE | RW)
68    let stack_flags = VmaFlags {
69        readable: true,
70        writable: true,
71        executable: false,
72        user_accessible: true,
73    };
74    if let Err(e) = user_as.map_region(
75        USER_STACK_ADDR,
76        1,
77        stack_flags,
78        VmaType::Stack,
79        crate::memory::address_space::VmaPageSize::Small,
80    ) {
81        log::error!("Failed to map user stack page: {}", e);
82        return;
83    }
84
85    // Step 4: Write machine code into the code page.
86    // We need to write via the HHDM mapping since the user AS isn't active.
87    // Translate user vaddr => phys => HHDM virt for writing.
88    let code_phys = user_as.translate(VirtAddr::new(USER_CODE_ADDR));
89    let code_phys = match code_phys {
90        Some(p) => p,
91        None => {
92            log::error!("Failed to translate user code page");
93            return;
94        }
95    };
96    let code_virt = crate::memory::phys_to_virt(code_phys.as_u64());
97
98    // Write the user code blob
99    write_user_code(code_virt as *mut u8);
100
101    // Step 5: Create a kernel task that will IRETQ to Ring 3.
102    // We store the user_as Arc and entry parameters in statics since the
103    // trampoline is a simple extern "C" fn.
104    // SAFETY: Single-threaded setup, task not yet scheduled.
105    unsafe {
106        let ptr = &raw mut USER_TASK_AS;
107        *ptr = Some(user_as.clone());
108    }
109
110    // Create a kernel task with the IRETQ trampoline as entry point
111    let kernel_stack = match KernelStack::allocate(Task::DEFAULT_STACK_SIZE) {
112        Ok(s) => s,
113        Err(e) => {
114            log::error!("Failed to allocate kernel stack for user task: {}", e);
115            return;
116        }
117    };
118
119    let context = CpuContext::new(ring3_trampoline as *const () as u64, &kernel_stack);
120    let (pid, tid, tgid) = Task::allocate_process_ids();
121    let fpu_state = crate::process::task::ExtendedState::new();
122    let xcr0_mask = fpu_state.xcr0_mask;
123
124    let task = Arc::new(Task {
125        id: crate::process::TaskId::new(),
126        pid,
127        tid,
128        tgid,
129        pgid: core::sync::atomic::AtomicU32::new(pid),
130        sid: core::sync::atomic::AtomicU32::new(pid),
131        uid: core::sync::atomic::AtomicU32::new(0),
132        euid: core::sync::atomic::AtomicU32::new(0),
133        gid: core::sync::atomic::AtomicU32::new(0),
134        egid: core::sync::atomic::AtomicU32::new(0),
135        state: core::sync::atomic::AtomicU8::new(TaskState::Ready as u8),
136        priority: TaskPriority::Normal,
137        context: SyncUnsafeCell::new(context),
138        resume_kind: SyncUnsafeCell::new(crate::process::task::ResumeKind::RetFrame),
139        interrupt_rsp: core::sync::atomic::AtomicU64::new(0),
140        kernel_stack,
141        user_stack: None,
142        stack_canary: core::sync::atomic::AtomicU64::new(0),
143        stack_canary_addr: core::sync::atomic::AtomicU64::new(0),
144        kernel_stack_user: SyncUnsafeCell::new(None),
145
146        name: "test-user-ring3",
147        process: alloc::sync::Arc::new(crate::process::process::Process::new(pid, user_as)),
148        pending_signals: super::signal::SignalSet::new(),
149
150        blocked_signals: super::signal::SignalSet::new(),
151        irq_signal_delivery_blocked: core::sync::atomic::AtomicBool::new(false),
152        signal_stack: SyncUnsafeCell::new(None),
153        itimers: super::timer::ITimers::new(),
154        wake_pending: core::sync::atomic::AtomicBool::new(false),
155        wake_deadline_ns: core::sync::atomic::AtomicU64::new(0),
156        trampoline_entry: core::sync::atomic::AtomicU64::new(0),
157        trampoline_stack_top: core::sync::atomic::AtomicU64::new(0),
158        trampoline_arg0: core::sync::atomic::AtomicU64::new(0),
159        ticks: core::sync::atomic::AtomicU64::new(0),
160        sched_policy: crate::process::task::SyncUnsafeCell::new(Task::default_sched_policy(
161            TaskPriority::Normal,
162        )),
163        home_cpu: core::sync::atomic::AtomicUsize::new(usize::MAX),
164        last_cpu: core::sync::atomic::AtomicUsize::new(usize::MAX),
165        affinity_mask: core::sync::atomic::AtomicU64::new(0),
166        vruntime: core::sync::atomic::AtomicU64::new(0),
167        fair_rq_generation: core::sync::atomic::AtomicU64::new(0),
168        fair_on_rq: core::sync::atomic::AtomicBool::new(false),
169        clear_child_tid: core::sync::atomic::AtomicU64::new(0),
170        robust_list_head: core::sync::atomic::AtomicU64::new(0),
171        robust_list_len: core::sync::atomic::AtomicUsize::new(0),
172        user_fs_base: core::sync::atomic::AtomicU64::new(0),
173        fpu_state: crate::process::task::SyncUnsafeCell::new(fpu_state),
174        xcr0_mask: core::sync::atomic::AtomicU64::new(xcr0_mask),
175        rt_link: intrusive_collections::LinkedListLink::new(),
176        rt_budget_remaining: core::sync::atomic::AtomicU64::new(0),
177        rt_budget_period_start: core::sync::atomic::AtomicU64::new(0),
178        rt_degraded: core::sync::atomic::AtomicBool::new(false),
179        fair_wait_ticks: core::sync::atomic::AtomicU64::new(0),
180    });
181
182    task.seed_interrupt_frame(crate::syscall::SyscallFrame {
183        r15: 0,
184        r14: 0,
185        r13: 0,
186        r12: 0,
187        rbp: 0,
188        rbx: 0,
189        r11: 0x202,
190        r10: 0,
191        r9: 0,
192        r8: 0,
193        rsi: 0,
194        rdi: 0,
195        rdx: 0,
196        rcx: USER_CODE_ADDR,
197        rax: 0,
198        iret_rip: USER_CODE_ADDR,
199        iret_cs: crate::arch::gdt::user_code_selector().0 as u64,
200        iret_rflags: 0x202,
201        iret_rsp: USER_STACK_TOP,
202        iret_ss: crate::arch::gdt::user_data_selector().0 as u64,
203    });
204
205    crate::process::add_task(task);
206    log::info!(
207        "Ring 3 test task created: code@{:#x}, stack@{:#x}",
208        USER_CODE_ADDR,
209        USER_STACK_TOP,
210    );
211}
212
213/// Static storage for the user address space (accessed by the trampoline).
214static mut USER_TASK_AS: Option<Arc<AddressSpace>> = None;
215
216/// Trampoline that switches to user address space and does IRETQ to Ring 3.
217///
218/// This runs as a kernel task entry point. It:
219/// 1. Switches CR3 to the user address space
220/// 2. Pushes an IRET frame (SS, RSP, RFLAGS, CS, RIP)
221/// 3. Executes IRETQ to jump to Ring 3 code
222extern "C" fn ring3_trampoline() -> ! {
223    use crate::arch::gdt;
224
225    // Switch to user address space
226    // SAFETY: The user AS was set up with the kernel half cloned.
227    unsafe {
228        let user_as = &*(&raw const USER_TASK_AS);
229        if let Some(ref as_ref) = user_as {
230            as_ref.switch_to();
231
232            // Diagnostic: verify the code page is mapped before IRETQ
233            let phys = as_ref.translate(crate::arch::xshim::VirtAddr::new(USER_CODE_ADDR));
234            crate::serial_println!(
235                "[ring3-tramp] CR3={:#x}, translate({:#x})={:?}",
236                as_ref.cr3().as_u64(),
237                USER_CODE_ADDR,
238                phys,
239            );
240
241            // Also verify via a direct CR3 read + manual walk
242            let (cr3_frame, _) = crate::x86_crate_shim::registers::control::Cr3::read();
243            let cr3_phys = cr3_frame.start_address().as_u64();
244            let hhdm = crate::memory::hhdm_offset();
245            crate::serial_println!(
246                "[ring3-tramp] Active CR3={:#x} (expected {:#x})",
247                cr3_phys,
248                as_ref.cr3().as_u64(),
249            );
250
251            // Manual PML4[0] check
252            let l4_ptr = (cr3_phys + hhdm) as *const u64;
253            let l4_entry = *l4_ptr; // PML4[0]
254            crate::serial_println!(
255                "[ring3-tramp] PML4[0]={:#x} (present={})",
256                l4_entry,
257                l4_entry & 1,
258            );
259        } else {
260            crate::serial_println!("[ring3-tramp] ERROR: USER_TASK_AS is None!");
261        }
262    }
263
264    let user_cs = gdt::user_code_selector().0 as u64;
265    let user_ss = gdt::user_data_selector().0 as u64;
266    let user_rip = USER_CODE_ADDR;
267    let user_rsp = USER_STACK_TOP;
268    let user_rflags: u64 = 0x202; // IF=1, reserved bit 1 = 1
269
270    crate::serial_println!(
271        "[ring3-tramp] IRETQ: CS={:#x} RIP={:#x} SS={:#x} RSP={:#x} RFLAGS={:#x}",
272        user_cs,
273        user_rip,
274        user_ss,
275        user_rsp,
276        user_rflags,
277    );
278
279    // SAFETY: We've set up valid user mappings. IRETQ will switch to Ring 3.
280    unsafe {
281        core::arch::asm!(
282            "push {ss}",       // SS
283            "push {rsp}",      // RSP
284            "push {rflags}",   // RFLAGS
285            "push {cs}",       // CS
286            "push {rip}",      // RIP
287            "swapgs",
288            "iretq",
289            ss = in(reg) user_ss,
290            rsp = in(reg) user_rsp,
291            rflags = in(reg) user_rflags,
292            cs = in(reg) user_cs,
293            rip = in(reg) user_rip,
294            options(noreturn),
295        );
296    }
297}
298
299/// Write the user-mode test program into the code page.
300///
301/// The program:
302/// ```asm
303/// ; SYS_DEBUG_LOG(buf_ptr, buf_len)
304/// mov rax, 600           ; SYS_DEBUG_LOG
305/// lea rdi, [rip + msg]   ; buffer pointer (rdi = arg1)
306/// mov rsi, 13            ; length (rsi = arg2)
307/// syscall
308///
309/// ; SYS_PROC_EXIT(0)
310/// mov rax, 300           ; SYS_PROC_EXIT
311/// xor rdi, rdi           ; exit code 0
312/// syscall
313///
314/// ; Safety: should never reach here
315/// hlt
316/// jmp $-1
317///
318/// msg: db "Hello Ring 3!"
319/// ```
320fn write_user_code(dest: *mut u8) {
321    // Hand-assembled x86_64 machine code
322    let code: &[u8] = &[
323        // mov rax, 600 (SYS_DEBUG_LOG)
324        0x48, 0xC7, 0xC0, 0x58, 0x02, 0x00, 0x00, // mov rax, 0x258 (600)
325        // lea rdi, [rip + offset_to_msg]
326        // This instruction is at offset 7, length 7, so next IP at offset 14.
327        // msg is at offset 38 (after hlt+jmp = 35+1+2 = 38).
328        // disp32 = 38 - 14 = 24 = 0x18
329        0x48, 0x8D, 0x3D, 0x18, 0x00, 0x00, 0x00, // lea rdi, [rip+0x18]
330        // mov rsi, 13
331        0x48, 0xC7, 0xC6, 0x0D, 0x00, 0x00, 0x00, // mov rsi, 13
332        // syscall
333        0x0F, 0x05, // syscall
334        // mov rax, 300 (SYS_PROC_EXIT)
335        0x48, 0xC7, 0xC0, 0x2C, 0x01, 0x00, 0x00, // mov rax, 0x12C (300)
336        // xor rdi, rdi
337        0x48, 0x31, 0xFF, // xor rdi, rdi
338        // syscall
339        0x0F, 0x05, // syscall
340        // hlt + infinite loop (safety)
341        0xF4, // hlt
342        0xEB, 0xFD, // jmp $-1 (back to hlt)
343        // "Hello Ring 3!" (13 bytes)
344        b'H', b'e', b'l', b'l', b'o', b' ', b'R', b'i', b'n', b'g', b' ', b'3', b'!',
345    ];
346
347    // SAFETY: dest points to a freshly allocated and zeroed page.
348    unsafe {
349        core::ptr::copy_nonoverlapping(code.as_ptr(), dest, code.len());
350    }
351}