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 alloc::sync::Arc;
10use x86_64::VirtAddr;
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
143        name: "test-user-ring3",
144        process: alloc::sync::Arc::new(crate::process::process::Process::new(pid, user_as)),
145        pending_signals: super::signal::SignalSet::new(),
146
147        blocked_signals: super::signal::SignalSet::new(),
148        irq_signal_delivery_blocked: core::sync::atomic::AtomicBool::new(false),
149        signal_stack: SyncUnsafeCell::new(None),
150        itimers: super::timer::ITimers::new(),
151        wake_pending: core::sync::atomic::AtomicBool::new(false),
152        wake_deadline_ns: core::sync::atomic::AtomicU64::new(0),
153        trampoline_entry: core::sync::atomic::AtomicU64::new(0),
154        trampoline_stack_top: core::sync::atomic::AtomicU64::new(0),
155        trampoline_arg0: core::sync::atomic::AtomicU64::new(0),
156        ticks: core::sync::atomic::AtomicU64::new(0),
157        sched_policy: crate::process::task::SyncUnsafeCell::new(Task::default_sched_policy(
158            TaskPriority::Normal,
159        )),
160        home_cpu: core::sync::atomic::AtomicUsize::new(usize::MAX),
161        vruntime: core::sync::atomic::AtomicU64::new(0),
162        fair_rq_generation: core::sync::atomic::AtomicU64::new(0),
163        fair_on_rq: core::sync::atomic::AtomicBool::new(false),
164        clear_child_tid: core::sync::atomic::AtomicU64::new(0),
165        robust_list_head: core::sync::atomic::AtomicU64::new(0),
166        robust_list_len: core::sync::atomic::AtomicUsize::new(0),
167        user_fs_base: core::sync::atomic::AtomicU64::new(0),
168        fpu_state: crate::process::task::SyncUnsafeCell::new(fpu_state),
169        xcr0_mask: core::sync::atomic::AtomicU64::new(xcr0_mask),
170        rt_link: intrusive_collections::LinkedListLink::new(),
171    });
172
173    task.seed_interrupt_frame(crate::syscall::SyscallFrame {
174        r15: 0,
175        r14: 0,
176        r13: 0,
177        r12: 0,
178        rbp: 0,
179        rbx: 0,
180        r11: 0x202,
181        r10: 0,
182        r9: 0,
183        r8: 0,
184        rsi: 0,
185        rdi: 0,
186        rdx: 0,
187        rcx: USER_CODE_ADDR,
188        rax: 0,
189        iret_rip: USER_CODE_ADDR,
190        iret_cs: crate::arch::x86_64::gdt::user_code_selector().0 as u64,
191        iret_rflags: 0x202,
192        iret_rsp: USER_STACK_TOP,
193        iret_ss: crate::arch::x86_64::gdt::user_data_selector().0 as u64,
194    });
195
196    crate::process::add_task(task);
197    log::info!(
198        "Ring 3 test task created: code@{:#x}, stack@{:#x}",
199        USER_CODE_ADDR,
200        USER_STACK_TOP,
201    );
202}
203
204/// Static storage for the user address space (accessed by the trampoline).
205static mut USER_TASK_AS: Option<Arc<AddressSpace>> = None;
206
207/// Trampoline that switches to user address space and does IRETQ to Ring 3.
208///
209/// This runs as a kernel task entry point. It:
210/// 1. Switches CR3 to the user address space
211/// 2. Pushes an IRET frame (SS, RSP, RFLAGS, CS, RIP)
212/// 3. Executes IRETQ to jump to Ring 3 code
213extern "C" fn ring3_trampoline() -> ! {
214    use crate::arch::x86_64::gdt;
215
216    // Switch to user address space
217    // SAFETY: The user AS was set up with the kernel half cloned.
218    unsafe {
219        let user_as = &*(&raw const USER_TASK_AS);
220        if let Some(ref as_ref) = user_as {
221            as_ref.switch_to();
222
223            // Diagnostic: verify the code page is mapped before IRETQ
224            let phys = as_ref.translate(x86_64::VirtAddr::new(USER_CODE_ADDR));
225            crate::serial_println!(
226                "[ring3-tramp] CR3={:#x}, translate({:#x})={:?}",
227                as_ref.cr3().as_u64(),
228                USER_CODE_ADDR,
229                phys,
230            );
231
232            // Also verify via a direct CR3 read + manual walk
233            let (cr3_frame, _) = x86_64::registers::control::Cr3::read();
234            let cr3_phys = cr3_frame.start_address().as_u64();
235            let hhdm = crate::memory::hhdm_offset();
236            crate::serial_println!(
237                "[ring3-tramp] Active CR3={:#x} (expected {:#x})",
238                cr3_phys,
239                as_ref.cr3().as_u64(),
240            );
241
242            // Manual PML4[0] check
243            let l4_ptr = (cr3_phys + hhdm) as *const u64;
244            let l4_entry = *l4_ptr; // PML4[0]
245            crate::serial_println!(
246                "[ring3-tramp] PML4[0]={:#x} (present={})",
247                l4_entry,
248                l4_entry & 1,
249            );
250        } else {
251            crate::serial_println!("[ring3-tramp] ERROR: USER_TASK_AS is None!");
252        }
253    }
254
255    let user_cs = gdt::user_code_selector().0 as u64;
256    let user_ss = gdt::user_data_selector().0 as u64;
257    let user_rip = USER_CODE_ADDR;
258    let user_rsp = USER_STACK_TOP;
259    let user_rflags: u64 = 0x202; // IF=1, reserved bit 1 = 1
260
261    crate::serial_println!(
262        "[ring3-tramp] IRETQ: CS={:#x} RIP={:#x} SS={:#x} RSP={:#x} RFLAGS={:#x}",
263        user_cs,
264        user_rip,
265        user_ss,
266        user_rsp,
267        user_rflags,
268    );
269
270    // SAFETY: We've set up valid user mappings. IRETQ will switch to Ring 3.
271    unsafe {
272        core::arch::asm!(
273            "push {ss}",       // SS
274            "push {rsp}",      // RSP
275            "push {rflags}",   // RFLAGS
276            "push {cs}",       // CS
277            "push {rip}",      // RIP
278            "swapgs",
279            "iretq",
280            ss = in(reg) user_ss,
281            rsp = in(reg) user_rsp,
282            rflags = in(reg) user_rflags,
283            cs = in(reg) user_cs,
284            rip = in(reg) user_rip,
285            options(noreturn),
286        );
287    }
288}
289
290/// Write the user-mode test program into the code page.
291///
292/// The program:
293/// ```asm
294/// ; SYS_DEBUG_LOG(buf_ptr, buf_len)
295/// mov rax, 600           ; SYS_DEBUG_LOG
296/// lea rdi, [rip + msg]   ; buffer pointer (rdi = arg1)
297/// mov rsi, 13            ; length (rsi = arg2)
298/// syscall
299///
300/// ; SYS_PROC_EXIT(0)
301/// mov rax, 300           ; SYS_PROC_EXIT
302/// xor rdi, rdi           ; exit code 0
303/// syscall
304///
305/// ; Safety: should never reach here
306/// hlt
307/// jmp $-1
308///
309/// msg: db "Hello Ring 3!"
310/// ```
311fn write_user_code(dest: *mut u8) {
312    // Hand-assembled x86_64 machine code
313    let code: &[u8] = &[
314        // mov rax, 600 (SYS_DEBUG_LOG)
315        0x48, 0xC7, 0xC0, 0x58, 0x02, 0x00, 0x00, // mov rax, 0x258 (600)
316        // lea rdi, [rip + offset_to_msg]
317        // This instruction is at offset 7, length 7, so next IP at offset 14.
318        // msg is at offset 38 (after hlt+jmp = 35+1+2 = 38).
319        // disp32 = 38 - 14 = 24 = 0x18
320        0x48, 0x8D, 0x3D, 0x18, 0x00, 0x00, 0x00, // lea rdi, [rip+0x18]
321        // mov rsi, 13
322        0x48, 0xC7, 0xC6, 0x0D, 0x00, 0x00, 0x00, // mov rsi, 13
323        // syscall
324        0x0F, 0x05, // syscall
325        // mov rax, 300 (SYS_PROC_EXIT)
326        0x48, 0xC7, 0xC0, 0x2C, 0x01, 0x00, 0x00, // mov rax, 0x12C (300)
327        // xor rdi, rdi
328        0x48, 0x31, 0xFF, // xor rdi, rdi
329        // syscall
330        0x0F, 0x05, // syscall
331        // hlt + infinite loop (safety)
332        0xF4, // hlt
333        0xEB, 0xFD, // jmp $-1 (back to hlt)
334        // "Hello Ring 3!" (13 bytes)
335        b'H', b'e', b'l', b'l', b'o', b' ', b'R', b'i', b'n', b'g', b' ', b'3', b'!',
336    ];
337
338    // SAFETY: dest points to a freshly allocated and zeroed page.
339    unsafe {
340        core::ptr::copy_nonoverlapping(code.as_ptr(), dest, code.len());
341    }
342}