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