Skip to main content

rustubs/proc/
exec.rs

1//! exec a user program by loading the binary and replace the current address
2//! space
3//! TODO rework this code, this is only POC
4use crate::arch::x86_64::gdt;
5use crate::arch::x86_64::paging::{get_root, map_range, PTEFlags};
6use crate::defs::{HWDefs, Mem};
7use crate::fs;
8use crate::fs::*;
9use crate::proc::loader::load;
10use crate::proc::task::Task;
11use core::arch::asm;
12
13/// 1. loads the program into current address space
14/// 2. create and map a user stack
15/// 3. set TSS RSP0 so interrupts from user mode land on the kernel stack
16/// 4. switch to user mode via iretq (point of no return)
17pub fn exec(file_name: &str) {
18	let task = Task::current().unwrap();
19	let mm = &mut task.mm;
20	// wipe user vmas;
21	// NOTE this doesn't wipe the pagetables
22	mm.vmas
23		.retain(|vma| vma.vm_range.start >= Mem::ID_MAP_START);
24
25	let archive = get_archive();
26	let file = fs::iter(archive).find(|f| f.hdr.name() == file_name);
27	if file.is_none() {
28		println!("error: no such file {}", file_name);
29		return;
30	}
31	let f = file.unwrap();
32	let entry = load(&f);
33	if entry.is_none() {
34		println!("failed to load elf");
35		return;
36	}
37	let entry = entry.unwrap();
38	println!("exec entry: {:#X}", entry);
39
40	// map the top of the user stack so the first pushes don't fault.
41	// USER_STACK_START is the bottom (low addr); the stack grows down from
42	// USER_STACK_START + USER_STACK_SIZE.
43	let stack_top = Mem::USER_STACK_START + Mem::USER_STACK_SIZE;
44	let map_start = stack_top - Mem::PAGE_SIZE * 4;
45	let flags = PTEFlags::PRESENT | PTEFlags::WRITABLE | PTEFlags::USER;
46	let pt_root = get_root();
47	let range = core::ops::Range { start: map_start, end: stack_top };
48	if !map_range(pt_root, &range, flags) {
49		println!("failed to map user stack");
50		return;
51	}
52
53	// set the TSS privilege stack table entry 0 (RSP0) so that any interrupt
54	// from ring 3 switches to this task's kernel stack.
55	let ksp = task.kernel_stack + Mem::KERNEL_STACK_SIZE;
56	unsafe { gdt::set_tss_ksp(ksp) };
57
58	// align user RSP to 16 bytes and leave a small gap below it.
59	let user_sp = stack_top & !0xf;
60
61	// point of no return: drop into ring 3.
62	unsafe { enter_usermode(entry, user_sp) };
63}
64
65/// Build an iretq frame on the current (kernel) stack and iretq into ring 3.
66/// We set CS/SS to the user segments with RPL=3, and RFLAGS with IF set so the
67/// user program runs with interrupts enabled.
68#[inline(never)]
69unsafe fn enter_usermode(entry: u64, user_sp: u64) -> ! {
70	// RFLAGS: set IF (bit 9) to allow irq in ring3
71	let rflags: u64 = 1 << 9;
72	// segment selectors with RPL == 3
73	let udata: u16 = HWDefs::SEG_USER_DATA | 3;
74	let ucode: u16 = HWDefs::SEG_USER_CODE | 3;
75	unsafe {
76		asm!(
77			// load user data segment into all data segments
78			"mov ds, {udata:x}",
79			"mov es, {udata:x}",
80			"mov fs, {udata:x}",
81			"mov gs, {udata:x}",
82			// push the iretq frame: SS, RSP, RFLAGS, CS, RIP
83			"push {udata_q}",	// <- ds
84			"push {usp}",		// <- sp
85			"push {rflags}",	// <- rf
86			"push {ucode_q}",	// <- cs
87			"push {entry}",		// <- ip
88			"xor rax, rax",		// clean rax for syscall convention
89			"iretq",
90			udata = in(reg) udata,
91			udata_q = in(reg) udata as u64,
92			ucode_q = in(reg) ucode as u64,
93			usp = in(reg) user_sp,
94			rflags = in(reg) rflags,
95			entry = in(reg) entry,
96			options(noreturn),
97		);
98	}
99}