Read command line args from userspace programs (#351)

* Read command line args from userspace programs

* Use Stop syscall to debug pointers from userpace

* Fix address translation system

* Update binaries

* Move print to bin

* Add hardcoded alias system

* Strip debug from binaries

* Build userspace binaries before running tests

* Save all the args in args including the command invocation name

* Move sleep command to /bin

* Rebuild binaries
This commit is contained in:
Vincent Ollivier 2022-06-14 22:43:33 +02:00 committed by GitHub
parent 6e3f3afcb0
commit dd5899a74d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 184 additions and 89 deletions

View File

@ -17,4 +17,5 @@ jobs:
- run: sudo apt-get -qqy update
- run: sudo apt-get -qqy install qemu-system-x86
- run: make
- run: make user-rust
- run: make test

View File

@ -28,6 +28,7 @@ user-rust:
-C relocation-model=static
basename -s .rs src/bin/*.rs | xargs -I {} \
cp target/x86_64-moros/release/{} dsk/bin/{}
strip dsk/bin/*
bin = target/x86_64-moros/release/bootimage-moros.bin
img = disk.img

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dsk/bin/print Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,8 +1,8 @@
use crate::api::syscall;
pub fn spawn(path: &str) -> Result<(), ()> {
pub fn spawn(path: &str, args: &[&str]) -> Result<(), ()> {
if syscall::info(path).is_some() {
return syscall::spawn(path);
return syscall::spawn(path, args);
}
Err(())
}

View File

@ -80,10 +80,12 @@ pub fn close(handle: usize) {
unsafe { syscall!(CLOSE, handle as usize) };
}
pub fn spawn(path: &str) -> Result<(), ()> {
let ptr = path.as_ptr() as usize;
let len = path.len() as usize;
let res = unsafe { syscall!(SPAWN, ptr, len) } as isize;
pub fn spawn(path: &str, args: &[&str]) -> Result<(), ()> {
let path_ptr = path.as_ptr() as usize;
let path_len = path.len() as usize;
let args_ptr = args.as_ptr() as usize;
let args_len = args.len() as usize;
let res = unsafe { syscall!(SPAWN, path_ptr, path_len, args_ptr, args_len) } as isize;
if res.is_negative() {
Err(())
} else {
@ -91,12 +93,16 @@ pub fn spawn(path: &str) -> Result<(), ()> {
}
}
pub fn stop(code: usize) {
unsafe { syscall!(STOP, code) };
}
pub fn reboot() {
unsafe { syscall!(STOP, 0xcafe) };
stop(0xcafe);
}
pub fn halt() {
unsafe { syscall!(STOP, 0xdead) };
stop(0xdead);
}
#[test_case]

30
src/bin/print.rs Normal file
View File

@ -0,0 +1,30 @@
#![no_std]
#![no_main]
use moros::api::syscall;
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
syscall::write(1, b"An exception occured!\n");
loop {}
}
#[no_mangle]
pub unsafe extern "sysv64" fn _start(args_ptr: u64, args_len: usize) {
let args = core::slice::from_raw_parts(args_ptr as *const _, args_len);
let code = main(args);
syscall::exit(code);
}
fn main(args: &[&str]) -> usize {
let n = args.len();
for i in 1..n {
syscall::write(1, args[i].as_bytes());
if i < n - 1 {
syscall::write(1, b" ");
}
}
syscall::write(1, b"\n");
0
}

28
src/bin/sleep.rs Normal file
View File

@ -0,0 +1,28 @@
#![no_std]
#![no_main]
use moros::api::syscall;
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
syscall::write(1, b"An exception occured!\n");
loop {}
}
#[no_mangle]
pub unsafe extern "sysv64" fn _start(args_ptr: u64, args_len: usize) {
let args = core::slice::from_raw_parts(args_ptr as *const _, args_len);
let code = main(args);
syscall::exit(code);
}
fn main(args: &[&str]) -> usize {
if args.len() == 2 {
if let Ok(duration) = args[1].parse::<f64>() {
syscall::sleep(duration);
return 0
}
}
1
}

View File

@ -44,16 +44,18 @@ pub fn init_heap(mapper: &mut impl Mapper<Size4KiB>, frame_allocator: &mut impl
Ok(())
}
pub fn alloc_pages(addr: u64, size: u64) {
pub fn alloc_pages(addr: u64, size: usize) {
//debug!("Alloc pages (addr={:#x}, size={})", addr, size);
let mut mapper = unsafe { sys::mem::mapper(VirtAddr::new(sys::mem::PHYS_MEM_OFFSET)) };
let mut frame_allocator = unsafe { sys::mem::BootInfoFrameAllocator::init(sys::mem::MEMORY_MAP.unwrap()) };
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::USER_ACCESSIBLE;
let pages = {
let start_page = Page::containing_address(VirtAddr::new(addr));
let end_page = Page::containing_address(VirtAddr::new(addr + size - 1));
let end_page = Page::containing_address(VirtAddr::new(addr + (size as u64) - 1));
Page::range_inclusive(start_page, end_page)
};
for page in pages {
//debug!("Alloc page {:?}", page);
if let Some(frame) = frame_allocator.allocate_frame() {
unsafe {
if let Ok(mapping) = mapper.map_to(page, frame, flags, &mut frame_allocator) {
@ -71,11 +73,11 @@ pub fn alloc_pages(addr: u64, size: u64) {
use x86_64::structures::paging::page::PageRangeInclusive;
// TODO: Replace `free` by `dealloc`
pub fn free_pages(addr: u64, size: u64) {
pub fn free_pages(addr: u64, size: usize) {
let mut mapper = unsafe { sys::mem::mapper(VirtAddr::new(sys::mem::PHYS_MEM_OFFSET)) };
let pages: PageRangeInclusive<Size4KiB> = {
let start_page = Page::containing_address(VirtAddr::new(addr));
let end_page = Page::containing_address(VirtAddr::new(addr + size - 1));
let end_page = Page::containing_address(VirtAddr::new(addr + (size as u64) - 1));
Page::range_inclusive(start_page, end_page)
};
for page in pages {

View File

@ -180,13 +180,14 @@ extern "sysv64" fn syscall_handler(stack_frame: &mut InterruptStackFrame, regs:
let arg1 = regs.rdi;
let arg2 = regs.rsi;
let arg3 = regs.rdx;
let arg4 = regs.r8;
if n == sys::syscall::number::SPAWN { // Backup CPU context
sys::process::set_stack_frame(**stack_frame);
sys::process::set_registers(*regs);
}
let res = sys::syscall::dispatcher(n, arg1, arg2, arg3);
let res = sys::syscall::dispatcher(n, arg1, arg2, arg3, arg4);
if n == sys::syscall::number::EXIT { // Restore CPU context
let sf = sys::process::stack_frame();

View File

@ -14,6 +14,7 @@ use x86_64::structures::idt::InterruptStackFrameValue;
const MAX_FILE_HANDLES: usize = 64;
const MAX_PROCS: usize = 2; // TODO: Update this when more than one process can run at once
const MAX_PROC_SIZE: usize = 1 << 20; // 1 MB
lazy_static! {
pub static ref PID: AtomicUsize = AtomicUsize::new(0);
@ -145,7 +146,12 @@ pub fn set_code_addr(addr: u64) {
}
pub fn ptr_from_addr(addr: u64) -> *mut u8 {
(code_addr() + addr) as *mut u8
let base = code_addr();
if addr < base {
(base + addr) as *mut u8
} else {
addr as *mut u8
}
}
pub fn registers() -> Registers {
@ -175,7 +181,7 @@ pub fn set_stack_frame(stack_frame: InterruptStackFrameValue) {
pub fn exit() {
let table = PROCESS_TABLE.read();
let proc = &table[id()];
sys::allocator::free_pages(proc.code_addr, proc.code_size);
sys::allocator::free_pages(proc.code_addr, MAX_PROC_SIZE);
MAX_PID.fetch_sub(1, Ordering::SeqCst);
set_id(0); // FIXME: No process manager so we switch back to process 0
}
@ -193,7 +199,6 @@ use core::sync::atomic::AtomicU64;
use x86_64::VirtAddr;
static CODE_ADDR: AtomicU64 = AtomicU64::new((sys::allocator::HEAP_START as u64) + (16 << 20));
const PAGE_SIZE: u64 = 4 * 1024;
#[repr(align(8), C)]
#[derive(Debug, Clone, Copy, Default)]
@ -216,7 +221,7 @@ const BIN_MAGIC: [u8; 4] = [0x7F, b'B', b'I', b'N'];
pub struct Process {
id: usize,
code_addr: u64,
code_size: u64,
stack_addr: u64,
entry_point: u64,
stack_frame: InterruptStackFrameValue,
registers: Registers,
@ -235,7 +240,7 @@ impl Process {
Self {
id,
code_addr: 0,
code_size: 0,
stack_addr: 0,
entry_point: 0,
stack_frame: isf,
registers: Registers::default(),
@ -243,13 +248,13 @@ impl Process {
}
}
pub fn spawn(bin: &[u8]) -> Result<(), ()> {
pub fn spawn(bin: &[u8], args_ptr: usize, args_len: usize) -> Result<(), ()> {
if let Ok(pid) = Self::create(bin) {
let proc = {
let table = PROCESS_TABLE.read();
table[pid].clone()
};
proc.exec();
proc.exec(args_ptr, args_len);
Ok(())
} else {
Err(())
@ -257,35 +262,29 @@ impl Process {
}
fn create(bin: &[u8]) -> Result<usize, ()> {
// Allocate some memory for the code and the stack of the program
let code_size = 1 * PAGE_SIZE;
let code_addr = CODE_ADDR.fetch_add(code_size, Ordering::SeqCst);
let proc_size = MAX_PROC_SIZE as u64;
let code_addr = CODE_ADDR.fetch_add(proc_size, Ordering::SeqCst);
let stack_addr = code_addr + proc_size;
let mut entry_point = 0;
let code_ptr = code_addr as *mut u8;
if bin[0..4] == ELF_MAGIC { // ELF binary
if let Ok(obj) = object::File::parse(bin) {
sys::allocator::alloc_pages(code_addr, code_size);
//sys::allocator::alloc_pages(code_addr, code_size);
entry_point = obj.entry();
for segment in obj.segments() {
let addr = segment.address() as usize;
if let Ok(data) = segment.data() {
for (i, op) in data.iter().enumerate() {
unsafe {
let ptr = code_ptr.add(addr + i);
core::ptr::write(ptr, *op);
}
for (i, b) in data.iter().enumerate() {
unsafe { core::ptr::write(code_ptr.add(addr + i), *b) };
}
}
}
}
} else if bin[0..4] == BIN_MAGIC { // Flat binary
sys::allocator::alloc_pages(code_addr, code_size);
for (i, op) in bin.iter().skip(4).enumerate() {
unsafe {
let ptr = code_ptr.add(i);
core::ptr::write(ptr, *op);
}
//sys::allocator::alloc_pages(code_addr, code_size);
for (i, b) in bin.iter().skip(4).enumerate() {
unsafe { core::ptr::write(code_ptr.add(i), *b) };
}
} else {
return Err(());
@ -299,28 +298,58 @@ impl Process {
let stack_frame = parent.stack_frame;
let id = MAX_PID.fetch_add(1, Ordering::SeqCst);
let proc = Process { id, code_addr, code_size, entry_point, data, stack_frame, registers };
let proc = Process { id, code_addr, stack_addr, entry_point, data, stack_frame, registers };
table[id] = Box::new(proc);
Ok(id)
}
// Switch to user mode and execute the program
fn exec(&self) {
fn exec(&self, args_ptr: usize, args_len: usize) {
let args_ptr = ptr_from_addr(args_ptr as u64) as usize;
let args: &[&str] = unsafe { core::slice::from_raw_parts(args_ptr as *const &str, args_len) };
let heap_addr = self.code_addr + (self.stack_addr - self.code_addr) / 2;
let mut ptr = heap_addr;
let vec: Vec<&str> = args.iter().map(|arg| {
let src_len = arg.len();
let src_ptr = arg.as_ptr();
let dst_ptr = ptr as *mut u8;
//let dst_ptr_translated = ((dst_ptr as u64) - self.code_addr) as *const u8; // Userspace address
ptr = ((dst_ptr as usize) + src_len) as u64;
unsafe {
core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, src_len);
//core::str::from_utf8_unchecked(core::slice::from_raw_parts(dst_ptr_translated, src_len))
core::str::from_utf8_unchecked(core::slice::from_raw_parts(dst_ptr, src_len))
}
}).collect();
let args = vec.as_slice();
let src_len = args.len();
let src_ptr = args.as_ptr();
let dst_ptr = ptr as *mut &str;
let args: &[&str] = unsafe {
core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, src_len);
core::slice::from_raw_parts(dst_ptr, src_len)
};
//let args_ptr = (args.as_ptr() as u64) - self.code_addr; // userspace address
let args_ptr = args.as_ptr() as u64;
set_id(self.id); // Change PID
unsafe {
asm!(
"cli", // Disable interrupts
"push rax", // Stack segment (SS)
"push rsi", // Stack pointer (RSP)
"push {:r}", // Stack segment (SS)
"push {:r}", // Stack pointer (RSP)
"push 0x200", // RFLAGS with interrupts enabled
"push rdx", // Code segment (CS)
"push rdi", // Instruction pointer (RIP)
"push {:r}", // Code segment (CS)
"push {:r}", // Instruction pointer (RIP)
"iretq",
in("rax") GDT.1.user_data.0,
in("rsi") self.code_addr + self.code_size,
in("rdx") GDT.1.user_code.0,
in("rdi") self.code_addr + self.entry_point,
in(reg) GDT.1.user_data.0,
in(reg) self.stack_addr,
in(reg) GDT.1.user_code.0,
in(reg) self.code_addr + self.entry_point,
in("rdi") args_ptr,
in("rsi") args_len,
);
}
}

View File

@ -10,7 +10,7 @@ use core::arch::asm;
* Dispatching system calls
*/
pub fn dispatcher(n: usize, arg1: usize, arg2: usize, arg3: usize) -> usize {
pub fn dispatcher(n: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> usize {
match n {
number::EXIT => {
service::exit(arg1)
@ -50,7 +50,7 @@ pub fn dispatcher(n: usize, arg1: usize, arg2: usize, arg3: usize) -> usize {
let handle = arg1;
let ptr = sys::process::ptr_from_addr(arg2 as u64);
let len = arg3;
let buf = unsafe { core::slice::from_raw_parts_mut(ptr, len) };
let buf = unsafe { core::slice::from_raw_parts_mut(ptr, len) }; // TODO: Remove mut
service::write(handle, buf) as usize
}
number::CLOSE => {
@ -64,10 +64,14 @@ pub fn dispatcher(n: usize, arg1: usize, arg2: usize, arg3: usize) -> usize {
service::dup(old_handle, new_handle) as usize
}
number::SPAWN => {
let ptr = sys::process::ptr_from_addr(arg1 as u64);
let len = arg2;
let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(ptr, len)) };
service::spawn(path) as usize
let path_ptr = sys::process::ptr_from_addr(arg1 as u64);
let path_len = arg2;
let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(path_ptr, path_len)) };
let args_ptr = arg3;
let args_len = arg4;
service::spawn(path, args_ptr, args_len) as usize
}
number::STOP => {
service::stop(arg1)
@ -125,6 +129,17 @@ pub unsafe fn syscall3(n: usize, arg1: usize, arg2: usize, arg3: usize) -> usize
res
}
#[doc(hidden)]
pub unsafe fn syscall4(n: usize, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> usize {
let res: usize;
asm!(
"int 0x80", in("rax") n,
in("rdi") arg1, in("rsi") arg2, in("rdx") arg3, in("r8") arg4,
lateout("rax") res
);
res
}
#[macro_export]
macro_rules! syscall {
($n:expr) => (
@ -139,4 +154,7 @@ macro_rules! syscall {
($n:expr, $a1:expr, $a2:expr, $a3:expr) => (
$crate::sys::syscall::syscall3(
$n as usize, $a1 as usize, $a2 as usize, $a3 as usize));
($n:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => (
$crate::sys::syscall::syscall4(
$n as usize, $a1 as usize, $a2 as usize, $a3 as usize, $a4 as usize));
}

View File

@ -80,7 +80,7 @@ pub fn close(handle: usize) {
sys::process::delete_file_handle(handle);
}
pub fn spawn(path: &str) -> isize {
pub fn spawn(path: &str, args_ptr: usize, args_len: usize) -> isize {
let path = match sys::fs::canonicalize(path) {
Ok(path) => path,
Err(_) => return -1,
@ -89,7 +89,7 @@ pub fn spawn(path: &str) -> isize {
let mut buf = vec![0; file.size()];
if let Ok(bytes) = file.read(&mut buf) {
buf.resize(bytes, 0);
if Process::spawn(&buf).is_ok() {
if Process::spawn(&buf, args_ptr, args_len).is_ok() {
return 0;
}
}
@ -110,7 +110,9 @@ pub fn stop(code: usize) -> usize {
0xdead => { // Halt
sys::acpi::shutdown();
}
_ => {}
_ => {
debug!("STOP SYSCALL: Invalid code '{:#x}' received", code);
}
}
0
}

View File

@ -20,7 +20,9 @@ pub fn copy_files(verbose: bool) {
copy_file("/bin/clear", include_bytes!("../../dsk/bin/clear"), verbose);
copy_file("/bin/halt", include_bytes!("../../dsk/bin/halt"), verbose);
copy_file("/bin/hello", include_bytes!("../../dsk/bin/hello"), verbose);
copy_file("/bin/print", include_bytes!("../../dsk/bin/print"), verbose);
copy_file("/bin/reboot", include_bytes!("../../dsk/bin/reboot"), verbose);
copy_file("/bin/sleep", include_bytes!("../../dsk/bin/sleep"), verbose);
create_dir("/dev/clk", verbose); // Clocks
create_dev("/dev/clk/uptime", DeviceType::Uptime, verbose);

View File

@ -26,11 +26,9 @@ pub mod memory;
pub mod net;
pub mod pci;
pub mod pow;
pub mod print;
pub mod r#move;
pub mod read;
pub mod shell;
pub mod sleep;
pub mod socket;
pub mod tcp;
pub mod time;

View File

@ -1,13 +0,0 @@
use crate::usr;
pub fn main(args: &[&str]) -> usr::shell::ExitCode {
let n = args.len();
for i in 1..n {
print!("{}", args[i]);
if i < n - 1 {
print!(" ");
}
}
println!();
usr::shell::ExitCode::CommandSuccessful
}

View File

@ -12,11 +12,11 @@ use alloc::vec::Vec;
use alloc::string::{String, ToString};
// TODO: Scan /bin
const AUTOCOMPLETE_COMMANDS: [&str; 37] = [
const AUTOCOMPLETE_COMMANDS: [&str; 35] = [
"2048", "base64", "calc", "colors", "copy", "date", "delete", "dhcp", "disk", "edit",
"env", "exit", "geotime", "goto", "help", "hex", "host", "http", "httpd", "install",
"keyboard", "lisp", "list", "memory", "move", "net", "pci", "print", "read",
"shell", "sleep", "socket", "tcp", "time", "user", "vga", "write"
"keyboard", "lisp", "list", "memory", "move", "net", "pci", "read",
"shell", "socket", "tcp", "time", "user", "vga", "write"
];
#[repr(u8)]
@ -276,7 +276,6 @@ pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
"m" | "move" => usr::r#move::main(&args),
"n" => ExitCode::CommandUnknown,
"o" => ExitCode::CommandUnknown,
"p" | "print" => usr::print::main(&args),
"q" | "quit" | "exit" => ExitCode::ShellExit,
"r" | "read" => usr::read::main(&args),
"s" => ExitCode::CommandUnknown,
@ -289,7 +288,6 @@ pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
"z" => ExitCode::CommandUnknown,
"vga" => usr::vga::main(&args),
"sh" | "shell" => usr::shell::main(&args),
"sleep" => usr::sleep::main(&args),
"calc" => usr::calc::main(&args),
"base64" => usr::base64::main(&args),
"date" => usr::date::main(&args),
@ -328,7 +326,7 @@ pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
ExitCode::CommandSuccessful
}
Some(FileType::File) => {
if api::process::spawn(&path).is_ok() {
if api::process::spawn(&path, &args[1..]).is_ok() {
// TODO: get exit code
ExitCode::CommandSuccessful
} else {
@ -337,8 +335,12 @@ pub fn exec(cmd: &str, env: &mut BTreeMap<String, String>) -> ExitCode {
}
}
_ => {
// TODO: add aliases
if api::process::spawn(&format!("/bin/{}", args[0])).is_ok() {
// TODO: add aliases command instead of hardcoding them
let name = match args[0] {
"p" => "print",
arg => arg,
};
if api::process::spawn(&format!("/bin/{}", name), &args).is_ok() {
ExitCode::CommandSuccessful
} else {
error!("Could not execute '{}'", cmd);

View File

@ -1,12 +0,0 @@
use crate::usr;
use crate::api::syscall;
pub fn main(args: &[&str]) -> usr::shell::ExitCode {
if args.len() == 2 {
if let Ok(duration) = args[1].parse::<f64>() {
syscall::sleep(duration);
return usr::shell::ExitCode::CommandSuccessful;
}
}
usr::shell::ExitCode::CommandError
}