diff --git a/Cargo.lock b/Cargo.lock index e30b2261..65a5c306 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -340,6 +340,10 @@ version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7" +[[package]] +name = "libuser" +version = "0.1.0" + [[package]] name = "lock_api" version = "0.4.7" @@ -496,6 +500,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec7a6dc0b0bb96a4d23271864a45c0d24dcd9dde2a1b630a35f79fa29c588bf" +[[package]] +name = "riscv_elf" +version = "0.1.0" +dependencies = [ + "arrayvec", +] + [[package]] name = "riscv_page_tables" version = "0.1.0" @@ -565,6 +576,7 @@ dependencies = [ "memoffset", "page_tracking", "rice", + "riscv_elf", "riscv_page_tables", "riscv_pages", "riscv_regs", @@ -758,6 +770,13 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +[[package]] +name = "umode" +version = "0.1.0" +dependencies = [ + "libuser", +] + [[package]] name = "unicode-ident" version = "1.0.3" diff --git a/Cargo.toml b/Cargo.toml index 95f7c936..2350881e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,9 +34,12 @@ s_mode_utils = { path = "./s-mode-utils" } sbi = { path = "./sbi" } spin = { version = "*", default-features = false } sha2 = {version = "0.10", default-features = false } +riscv_elf = { path = "./riscv-elf" } [workspace] members = [ + "libuser", + "umode", "test-workloads", ] diff --git a/Makefile b/Makefile index 156dec96..5c714715 100644 --- a/Makefile +++ b/Makefile @@ -65,21 +65,23 @@ check: --target $(HOST_TRIPLET) \ --workspace \ --exclude test_workloads \ + --exclude libuser \ --lib cargo test \ --target $(HOST_TRIPLET) \ --workspace \ --exclude test_workloads \ + --exclude libuser \ --doc CARGO_FLAGS := .PHONY: salus -salus: +salus: umode cargo build $(CARGO_FLAGS) --release --bin salus .PHONY: salus_debug -salus_debug: +salus_debug: umode cargo build $(CARGO_FLAGS) --bin salus tellus_bin: tellus @@ -94,6 +96,10 @@ guestvm: tellus: guestvm cargo build $(CARGO_FLAGS) --package test_workloads --bin tellus --release +.PHONY: umode +umode: + RUSTFLAGS='-Clink-arg=-Tlds/umode.lds' cargo build --release --package umode + # Runnable targets: # # run_tellus_gdb: Run Tellus as the host VM with GDB debugging enabled. diff --git a/lds/umode.lds b/lds/umode.lds new file mode 100644 index 00000000..5f8e1742 --- /dev/null +++ b/lds/umode.lds @@ -0,0 +1,47 @@ +OUTPUT_ARCH( "riscv" ) + +ENTRY( _start ) + +PHDRS +{ + text PT_LOAD; + rodata PT_LOAD; + data PT_LOAD; + stack PT_LOAD; +} + +SECTIONS +{ + . = 0xffffffff00000000; + + .text ALIGN(4096) : { + *(.text.start) + *(.text.init) *(.text .text.*) + } :text + + .rodata ALIGN(4096) : { + *(.rodata .rodata.*) + } :rodata + + .data ALIGN(4096) : { + *(.data .data.*) + + . = ALIGN(8); + PROVIDE(__global_pointer$ = .); + *(.sdata .sdata.*) + + *(.sbss .sbss.*) *(.bss .bss.*) + } :data + + . += 4096; + + .stack ALIGN(4096) (NOLOAD) : { + PROVIDE(_stack_start = .); + . += 4096; + PROVIDE(_stack_end = .); + } :stack + + /DISCARD/ : { + *(.eh_frame) + } +} diff --git a/libuser/Cargo.toml b/libuser/Cargo.toml new file mode 100644 index 00000000..60c0a14e --- /dev/null +++ b/libuser/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "libuser" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/libuser/src/lib.rs b/libuser/src/lib.rs new file mode 100644 index 00000000..7028b74a --- /dev/null +++ b/libuser/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright (c) 2022 by Rivos Inc. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +use core::arch::{asm, global_asm}; + +global_asm!(include_str!("task_start.S")); + +// Loop making ecalls as the kernel will kill the task on an ecall (the only syscall supported is +// `exit`). +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // Safe to make an ecall that won't return. + unsafe { + loop { + asm!("ecall"); + } + } +} diff --git a/libuser/src/task_start.S b/libuser/src/task_start.S new file mode 100644 index 00000000..9a04159e --- /dev/null +++ b/libuser/src/task_start.S @@ -0,0 +1,23 @@ +// Copyright (c) 2021 by Rivos Inc. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +.option norvc + +.section .text.start + +// The entry point for a task. +.global _start +_start: + +.option push +.option norelax + la gp, __global_pointer$ +.option pop + la sp, _stack_end + + call task_main + + // ecall to exit + ecall + diff --git a/riscv-elf/Cargo.toml b/riscv-elf/Cargo.toml new file mode 100644 index 00000000..2d55aa10 --- /dev/null +++ b/riscv-elf/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "riscv_elf" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies] +arrayvec = { version = "0.7.2", default-features = false } diff --git a/riscv-elf/src/lib.rs b/riscv-elf/src/lib.rs new file mode 100644 index 00000000..65a0f1a9 --- /dev/null +++ b/riscv-elf/src/lib.rs @@ -0,0 +1,570 @@ +// Copyright (c) 2022 by Rivos Inc. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +//! RiscV ELF loader library for salus + +// For testing use the std crate. +#[cfg(test)] +#[macro_use] +extern crate std; + +use arrayvec::ArrayVec; +use core::{fmt, result}; + +// Maximum size of Program Headers supported by the loader. +const ELF_SEGMENTS_MAX: usize = 8; + +/// Elf Offset Helper +/// +/// An Elf Offset. A separate type to be sure to never used it +/// directly, but only through `slice_*` functions. +#[repr(packed, C)] +#[derive(Copy, Clone)] +pub struct ElfOffset64 { + inner: u64, +} + +impl ElfOffset64 { + fn as_usize(&self) -> usize { + // We're 64-bit. u64 fits in a usize. + self.inner as usize + } + + fn usize_add(self, other: usize) -> Option { + let inner = self.inner.checked_add(other as u64)?; + Some(Self { inner }) + } +} + +impl From for ElfOffset64 { + fn from(val: usize) -> Self { + Self { inner: val as u64 } + } +} + +fn slice_check_offset(bytes: &[u8], offset: ElfOffset64) -> bool { + bytes.len() > offset.as_usize() +} + +fn slice_check_range(bytes: &[u8], offset: ElfOffset64, size: usize) -> bool { + if size < 1 { + return false; + } + + if let Some(last) = offset.usize_add(size - 1) { + slice_check_offset(bytes, last) + } else { + false + } +} + +fn slice_get_range(bytes: &[u8], offset: ElfOffset64, len: usize) -> Option<&[u8]> { + if slice_check_range(bytes, offset, len) { + let start = offset.as_usize(); + Some(&bytes[start..start + len]) + } else { + None + } +} + +/// ELF64 Program Header Table Entry +#[repr(packed, C)] +#[derive(Copy, Clone)] +pub struct ElfProgramHeader64 { + p_type: u32, + p_flags: u32, + p_offset: ElfOffset64, + p_vaddr: u64, + p_paddr: u64, + p_filesz: u64, + p_memsz: u64, + p_align: u64, +} + +// ELF Segment Types +// The array element specifies a loadable segment +const PT_LOAD: u32 = 1; + +// Elf Segment Permission +// Execute +const PF_X: u32 = 0x1; +// Write +const PF_W: u32 = 0x2; +// Read +const PF_R: u32 = 0x4; + +/// ELF64 Header +#[repr(packed, C)] +#[derive(Copy, Clone)] +pub struct ElfHeader64 { + ei_magic: [u8; 4], + ei_class: u8, + ei_data: u8, + ei_version: u8, + ei_osabi: u8, + ei_abiversion: u8, + ei_pad: [u8; 7], + e_type: u16, + e_machine: u16, + e_version: u32, + e_entry: u64, + e_phoff: ElfOffset64, + e_shoff: ElfOffset64, + e_flags: u32, + e_ehsize: u16, + e_phentsize: u16, + e_phnum: u16, + e_shentsize: u16, + e_shnum: u16, + e_shstrndx: u16, +} + +const EI_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F']; +const EI_CLASS_64: u8 = 2; +const EI_DATA_LE: u8 = 1; +const EI_VERSION_1: u8 = 1; +const E_TYPE_EXEC: u16 = 2; +const E_MACHINE_RISCV: u16 = 0xf3; +const E_VERSION_1: u32 = 1; +const E_EHSIZE: u16 = 0x40; + +/// ELF Loader Errors. +#[derive(Debug)] +pub enum Error { + /// Requested to read after EOF. + BadOffset, + /// The ELF magic number is wrong. + InvalidMagicNumber, + /// Unexpected ELF Class + InvalidClass, + /// Unsupported Endiannes + InvalidEndianness, + /// ELF is not RISC V. + NotRiscV, + /// Unexpected ELF version. + BadElfVersion, + /// Unexpected ELF object type. + BadElfType, + /// Unexpected ELF header size. + BadElfHeaderSize, + /// Unexpected ELF PH Entry size. + BadEntrySize, + /// No Program Header table. + NoProgramHeader, + /// Malformed Program Header. + ProgramHeaderMalformed, + /// Segment Permissions Unsupported + UnsupportedProgramHeaderFlags(u32), +} + +#[derive(Debug)] +/// Mapping Permissions of an ELF Segment +pub enum ElfSegmentPerms { + /// Read-Only + ReadOnly, + /// Read-Write + ReadWrite, + /// Executable Page (Read Only) + ReadOnlyExecute, +} + +impl fmt::Display for ElfSegmentPerms { + fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { + match &self { + Self::ReadOnly => write!(f, "RO"), + Self::ReadWrite => write!(f, "RW"), + Self::ReadOnlyExecute => write!(f, "RX"), + } + } +} + +/// A structure representing a segment. +#[derive(Debug)] +pub struct ElfSegment<'elf> { + data: Option<&'elf [u8]>, + vaddr: u64, + size: usize, + perms: ElfSegmentPerms, +} + +impl<'elf> ElfSegment<'elf> { + fn new( + data: Option<&'elf [u8]>, + vaddr: u64, + size: usize, + flags: u32, + ) -> Result, Error> { + let perms = if flags == PF_R { + Ok(ElfSegmentPerms::ReadOnly) + } else if flags == PF_R | PF_W { + Ok(ElfSegmentPerms::ReadWrite) + } else if flags == PF_R | PF_X { + Ok(ElfSegmentPerms::ReadOnlyExecute) + } else { + Err(Error::UnsupportedProgramHeaderFlags(flags)) + }?; + // Check size is valid + vaddr + .checked_add(size as u64) + .ok_or(Error::ProgramHeaderMalformed)?; + Ok(ElfSegment { + data, + vaddr, + size, + perms, + }) + } + + /// Returns a reference to the data that must be populated at the beginning of the segment. + pub fn data(&self) -> Option<&'elf [u8]> { + self.data + } + + /// Returns the Virtual Address of the start of the segment. + pub fn vaddr(&self) -> u64 { + self.vaddr + } + + /// Return the size of the Virtual Address area. + pub fn size(&self) -> usize { + self.size + } + + /// Return the mapping permissions of the segment. + pub fn perms(&self) -> &ElfSegmentPerms { + &self.perms + } +} + +/// A structure that checks and prepares and ELF for loading into memory. +pub struct ElfMap<'elf> { + segments: ArrayVec, ELF_SEGMENTS_MAX>, +} + +impl<'elf> ElfMap<'elf> { + /// Create a new ElfMap from a slice containing an ELF file. + pub fn new(bytes: &'elf [u8]) -> Result, Error> { + // Chek ELF Header + let hbytes = slice_get_range(bytes, 0.into(), core::mem::size_of::()) + .ok_or(Error::BadOffset)?; + // Safe because we are sure that the size of the slice is the same size as ElfHeader64. + let header: &'elf ElfHeader64 = unsafe { &*(hbytes.as_ptr() as *const ElfHeader64) }; + // Check magic number + if header.ei_magic != EI_MAGIC { + return Err(Error::InvalidMagicNumber); + } + // Check is 64bit ELF. + if header.ei_class != EI_CLASS_64 { + return Err(Error::InvalidClass); + } + // Check is Little-Endian + if header.ei_data != EI_DATA_LE { + return Err(Error::InvalidEndianness); + } + // Check ELF versions. + if header.ei_version != EI_VERSION_1 || header.e_version != E_VERSION_1 { + return Err(Error::BadElfVersion); + } + if header.e_type != E_TYPE_EXEC { + return Err(Error::BadElfType); + } + // Check is RISC-V. + if header.e_machine != E_MACHINE_RISCV { + return Err(Error::NotRiscV); + } + // Check EH size. + if header.e_ehsize != E_EHSIZE { + return Err(Error::BadElfHeaderSize); + } + + // Check Program Header Table + let phnum = header.e_phnum as usize; + // e_phoff is invalid if zero + if phnum == 0 || header.e_phoff.as_usize() == 0 { + return Err(Error::NoProgramHeader); + } + let phentsize = header.e_phentsize as usize; + // Check that e_phentsize is >= of size of ElfProgramHeader64 + if core::mem::size_of::() > phentsize { + return Err(Error::BadEntrySize); + } + // Check that we can read the program header table. + let program_headers = + slice_get_range(bytes, header.e_phoff, phnum * phentsize).ok_or(Error::BadOffset)?; + + // Load segments + let mut segments = ArrayVec::::new(); + let num_segs = core::cmp::min(phnum, ELF_SEGMENTS_MAX); + for i in 0..num_segs { + // Find the i-th ELF Program Header. + let phbytes = slice_get_range(program_headers, (i * phentsize).into(), phentsize) + .ok_or(Error::BadOffset)?; + // Safe because we are sure that the size of the slice is at least as big as ElfProgramHeader64 + let ph: &'elf ElfProgramHeader64 = + unsafe { &*(phbytes.as_ptr() as *const ElfProgramHeader64) }; + + // Ignore if not a load segment. + if ph.p_type != PT_LOAD { + continue; + } + // Create a segment from the PH. + let data_size = ph.p_filesz as usize; + let data = if data_size > 0 { + Some(slice_get_range(bytes, ph.p_offset, data_size).ok_or(Error::BadOffset)?) + } else { + None + }; + let vaddr = ph.p_vaddr; + let size = ph.p_memsz as usize; + let flags = ph.p_flags; + let segment = ElfSegment::new(data, vaddr, size, flags)?; + segments.push(segment); + } + Ok(Self { segments }) + } + + /// Return an iterator containings loadable segments of this ELF file. + pub fn segments(&self) -> impl Iterator> { + self.segments.iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn build_header() -> ElfHeader64 { + ElfHeader64 { + ei_magic: EI_MAGIC, + ei_class: EI_CLASS_64, + ei_data: EI_DATA_LE, + ei_version: EI_VERSION_1, + ei_osabi: 0, // Not used. + ei_abiversion: 0, // Not used. + ei_pad: [0u8; 7], + e_type: E_TYPE_EXEC, + e_machine: E_MACHINE_RISCV, + e_version: E_VERSION_1, + e_entry: 0, // Not used at this time. + e_phoff: ElfOffset64::from(0), // Do not add a program header table. + e_shoff: ElfOffset64::from(0), // Do not add a section header table. + e_flags: 0, // Not used. + e_ehsize: E_EHSIZE, + e_phentsize: core::mem::size_of::() as u16, + e_phnum: 0, // No PHs + e_shentsize: 0, // Not used. + e_shnum: 0, // Not used. + e_shstrndx: 0, // Not used. + } + } + + fn build_ph(p_type: u32, p_flags: u32, p_offset: usize, p_filesz: u64) -> ElfProgramHeader64 { + ElfProgramHeader64 { + p_type, + p_flags, + p_offset: ElfOffset64::from(p_offset), + p_vaddr: 0, + p_paddr: 0, + p_filesz, + p_memsz: 0, + p_align: 0, + } + } + + fn set_header(bytes: &mut [u8], header: &ElfHeader64) { + let ptr = bytes.as_ptr() as *mut ElfHeader64; + // Safe because bytes >= Elf header size. + unsafe { *ptr = *header }; + } + + fn set_ph(bytes: &mut [u8], off: usize, ph: &ElfProgramHeader64) { + assert!(off + core::mem::size_of::() <= bytes.len()); + let off: isize = off.try_into().unwrap(); + // Safe because we can fit `ElfProgramHeader64` at offset `off` `in bytes`. + unsafe { + let ptr = bytes.as_ptr().offset(off) as *mut ElfProgramHeader64; + *ptr = *ph + }; + } + + #[test] + fn header_test() { + const HEADER_SIZE: usize = core::mem::size_of::(); + const PH_SIZE: usize = core::mem::size_of::(); + let mut bytes = [0u8; HEADER_SIZE + PH_SIZE * 2 + 20]; + { + // Simple ELF program header. No PHs. + let header = build_header(); + set_header(&mut bytes, &header); + let rc = ElfMap::new(&bytes); + // Should fail as we require PHs. + assert!(rc.is_err()); + } + { + // Lie about the number of PHs. + let mut header = build_header(); + header.e_phnum = 42; + set_header(&mut bytes, &header); + let rc = ElfMap::new(&bytes); + // Should fail as we require PHs. + assert!(rc.is_err()); + } + { + // Create an elf with a non-loadable PH. + let ph = build_ph(0 /* PT_NULL */, 0, HEADER_SIZE + PH_SIZE, 10); + let mut header = build_header(); + // add a PH, right after header. + header.e_phnum = 1; + header.e_phoff = ElfOffset64::from(HEADER_SIZE); + set_header(&mut bytes, &header); + set_ph(&mut bytes, HEADER_SIZE, &ph); + let rc = ElfMap::new(&bytes); + // This should succeed and the segments should be empty. + assert!(rc.is_ok()); + let map = rc.unwrap(); + assert!(map.segments().next().is_none()); + } + { + // Create an elf with a PH with no data. + let ph = build_ph(PT_LOAD, PF_R, HEADER_SIZE + PH_SIZE, 0); + let mut header = build_header(); + // add a PH, right after header. + header.e_phnum = 1; + header.e_phoff = ElfOffset64::from(HEADER_SIZE); + set_header(&mut bytes, &header); + set_ph(&mut bytes, HEADER_SIZE, &ph); + let rc = ElfMap::new(&bytes); + // This should succeed and there should be a segment with no data. + assert!(rc.is_ok()); + let map = rc.unwrap(); + let mut segs = map.segments(); + let s = segs.next(); + assert!(s.is_some()); + assert!(s.unwrap().data().is_none()); + } + { + // Create an elf with one non-loadable PH and one loadable. + let ph1 = build_ph(0 /* PT_NULL */, 0, HEADER_SIZE + PH_SIZE * 2, 9); + let ph2 = build_ph(PT_LOAD, PF_R, HEADER_SIZE + PH_SIZE * 2 + 9, 11); + let mut header = build_header(); + // add PHs, right after header. + header.e_phnum = 2; + header.e_phoff = ElfOffset64::from(HEADER_SIZE); + set_header(&mut bytes, &header); + set_ph(&mut bytes, HEADER_SIZE, &ph1); + set_ph(&mut bytes, HEADER_SIZE + PH_SIZE, &ph2); + let rc = ElfMap::new(&bytes); + // This should succeed and there should be one segment only with 11 bytes. + assert!(rc.is_ok()); + let map = rc.unwrap(); + let mut segs = map.segments(); + let s = segs.next(); + assert!(s.is_some()); + assert_eq!(s.unwrap().data().unwrap().len(), 11); + assert!(segs.next().is_none()); + } + { + // Create an elf with two loadable segments. + let ph1 = build_ph(PT_LOAD, PF_R, HEADER_SIZE + PH_SIZE * 2, 9); + let ph2 = build_ph(PT_LOAD, PF_R, HEADER_SIZE + PH_SIZE * 2 + 9, 11); + let mut header = build_header(); + // add PHs, right after header. + header.e_phnum = 2; + header.e_phoff = ElfOffset64::from(HEADER_SIZE); + set_header(&mut bytes, &header); + set_ph(&mut bytes, HEADER_SIZE, &ph1); + set_ph(&mut bytes, HEADER_SIZE + PH_SIZE, &ph2); + let rc = ElfMap::new(&bytes); + // This should succeed and there should be one segment only with 11 bytes. + assert!(rc.is_ok()); + let map = rc.unwrap(); + let mut segs = map.segments(); + let s = segs.next(); + assert!(s.is_some()); + assert_eq!(s.unwrap().data().unwrap().len(), 9); + let s = segs.next(); + assert!(s.is_some()); + assert_eq!(s.unwrap().data().unwrap().len(), 11); + assert!(segs.next().is_none()); + } + { + // Create an elf with a single PH with an offset outside the file. + let ph = build_ph(PT_LOAD, PF_R, bytes.len(), 10); + let mut header = build_header(); + // add a PH, right after header. + header.e_phnum = 1; + header.e_phoff = ElfOffset64::from(HEADER_SIZE); + set_header(&mut bytes, &header); + set_ph(&mut bytes, HEADER_SIZE, &ph); + let rc = ElfMap::new(&bytes); + // This should fail. + assert!(rc.is_err()); + } + { + // Create an elf with a single PH with a size that goes over the end of file. + let ph = build_ph(PT_LOAD, PF_R, HEADER_SIZE + PH_SIZE, bytes.len() as u64); + let mut header = build_header(); + // add a PH, right after header. + header.e_phnum = 1; + header.e_phoff = ElfOffset64::from(HEADER_SIZE); + set_header(&mut bytes, &header); + set_ph(&mut bytes, HEADER_SIZE, &ph); + let rc = ElfMap::new(&bytes); + // This should fail. + assert!(rc.is_err()); + } + } + + #[test] + fn offset_test() { + let bytes1 = [0u8; 5]; + let bytes2 = [0u8; 6]; + let off1 = ElfOffset64 { inner: 3 }; + let off2 = ElfOffset64 { inner: 5 }; + let off3 = ElfOffset64 { inner: 6 }; + + let r = slice_check_offset(&bytes1, off1); + assert_eq!(r, true); + let r = slice_check_offset(&bytes1, off2); + assert_eq!(r, false); + let r = slice_check_offset(&bytes1, off3); + assert_eq!(r, false); + + let r = slice_check_offset(&bytes2, off1); + assert_eq!(r, true); + let r = slice_check_offset(&bytes2, off2); + assert_eq!(r, true); + let r = slice_check_offset(&bytes2, off3); + assert_eq!(r, false); + + let r = slice_check_range(&bytes1, 0.into(), 0); + assert_eq!(r, false); + let r = slice_get_range(&bytes1, 0.into(), 0); + assert!(r.is_none()); + + let r = slice_check_range(&bytes1, 0.into(), 5); + assert_eq!(r, true); + let r = slice_get_range(&bytes1, 0.into(), 5); + assert!(r.is_some()); + assert_eq!(r.unwrap().len(), 5); + + let r = slice_check_range(&bytes1, 0.into(), 6); + assert_eq!(r, false); + let r = slice_get_range(&bytes1, 0.into(), 6); + assert!(r.is_none()); + + let r = slice_check_range(&bytes1, 4.into(), 1); + assert_eq!(r, true); + let r = slice_get_range(&bytes1, 4.into(), 1); + assert!(r.is_some()); + assert_eq!(r.unwrap().len(), 1); + + let r = slice_check_range(&bytes1, 5.into(), 1); + assert_eq!(r, false); + let r = slice_get_range(&bytes1, 5.into(), 1); + assert!(r.is_none()); + } +} diff --git a/riscv-page-tables/src/pte.rs b/riscv-page-tables/src/pte.rs index 3b5b474b..b83548c6 100644 --- a/riscv-page-tables/src/pte.rs +++ b/riscv-page-tables/src/pte.rs @@ -68,6 +68,14 @@ pub enum PteLeafPerms { /// Read/Write/Execute RWX = (PteFieldBit::Read.mask() | PteFieldBit::Write.mask() | PteFieldBit::Execute.mask()) as isize, + /// User Read/Execute + URX = (PteFieldBit::User.mask() | PteFieldBit::Read.mask() | PteFieldBit::Execute.mask()) + as isize, + /// User Read Only + UR = (PteFieldBit::User.mask() | PteFieldBit::Read.mask()) as isize, + /// User Read/Write + URW = + (PteFieldBit::User.mask() | PteFieldBit::Read.mask() | PteFieldBit::Write.mask()) as isize, } const MASK_RWX: u64 = (1 << PteFieldBit::Read.shift()) diff --git a/src/hyp_map.rs b/src/hyp_map.rs index 33b64888..079e34d1 100644 --- a/src/hyp_map.rs +++ b/src/hyp_map.rs @@ -4,26 +4,29 @@ use arrayvec::ArrayVec; use page_tracking::{HwMemMap, HwMemRegion, HwMemRegionType, HwReservedMemType, HypPageAlloc}; +use riscv_elf::{ElfMap, ElfSegment, ElfSegmentPerms}; use riscv_page_tables::{FirstStagePageTable, PteFieldBits, PteLeafPerms, Sv48}; use riscv_pages::{ InternalClean, Page, PageAddr, PageSize, RawAddr, SupervisorPhys, SupervisorVirt, }; use riscv_regs::{satp, LocalRegisterCopy, SatpHelpers}; -/// Maximum number of regions that will be mapped in the hypervisor. -const MAX_HYPMAP_REGIONS: usize = 32; +/// Maximum number of supervisor regions. +const MAX_HYPMAP_SUPERVISOR_REGIONS: usize = 32; +/// Maximum number of user regions. +const MAX_HYPMAP_USER_REGIONS: usize = 32; -/// Represents a region of the virtual address space of the hypervisor. -pub struct HypMapRegion { +/// Represents a virtual address region of the hypervisor with a fixed VA->PA Mapping. +pub struct HypMapFixedRegion { vaddr: PageAddr, paddr: PageAddr, page_count: usize, pte_fields: PteFieldBits, } -impl HypMapRegion { - /// Create an HypMapRegion from a Hw Memory Map entry. - pub fn from_hw_mem_region(r: &HwMemRegion) -> Option { +impl HypMapFixedRegion { + // Create a supervisor VA region from a Hw Memory Map entry. + fn from_hw_mem_region(r: &HwMemRegion) -> Option { let perms = match r.region_type() { HwMemRegionType::Available => { // map available memory as rw - unsure what it'll be used for. @@ -49,7 +52,7 @@ impl HypMapRegion { if let Some(pte_perms) = perms { let paddr = r.base(); // vaddr == paddr in mapping HW memory map. - // Unwrap okay. `paddr` is a page addr so it is aligned to the page. + // Unwrap okay. `r.base()` is a page addr so it is aligned to the page. let vaddr = PageAddr::new(RawAddr::supervisor_virt(r.base().bits())).unwrap(); let page_count = PageSize::num_4k_pages(r.size()) as usize; let pte_fields = PteFieldBits::leaf_with_perms(pte_perms); @@ -64,7 +67,7 @@ impl HypMapRegion { } } - /// Map this region into a page table. + // Map this region into a page table. fn map( &self, sv48: &FirstStagePageTable, @@ -93,6 +96,84 @@ impl HypMapRegion { } } +/// Represents a virtual address region that must be allocated and populated to be mapped. +struct HypMapPopulatedRegion { + // The address space where this region starts. + vaddr: PageAddr, + // Number of bytes of the VA area + size: usize, + // PTE bits for the mappings. + pte_fields: PteFieldBits, + // Data to be populated at the beginning of the VA area + data: Option<&'static [u8]>, +} + +impl HypMapPopulatedRegion { + // Creates an user space virtual address region from a ELF segment. + fn from_user_elf_segment(seg: &ElfSegment<'static>) -> Option { + // Sanity Check for segment alignments. + // + // In general ELF might have segments overlapping in the same page, possibly with different + // permissions. In order to maintain separation and expected permissions on every page, the + // linker script for user ELF creates different segments at different pages. Failure to do so + // would make `map_range()` in `map()` fail. + // + // The following check enforces that the segment starts at a 4k page aligned address. Unless + // the linking is completely corrupt, this also means that it starts at a different page. + // Assert is okay. This is a build error. + assert!(PageSize::Size4k.is_aligned(seg.vaddr())); + + let pte_perms = match seg.perms() { + ElfSegmentPerms::ReadOnly => PteLeafPerms::UR, + ElfSegmentPerms::ReadWrite => PteLeafPerms::URW, + ElfSegmentPerms::ReadOnlyExecute => PteLeafPerms::URX, + }; + // Unwrap okay. `seg.vaddr()` has been checked to be 4k aligned. + let vaddr = PageAddr::new(RawAddr::supervisor_virt(seg.vaddr())).unwrap(); + let pte_fields = PteFieldBits::leaf_with_perms(pte_perms); + Some(HypMapPopulatedRegion { + vaddr, + size: seg.size(), + pte_fields, + data: seg.data(), + }) + } + + // Map this region into a page table. + fn map(&self, sv48: &FirstStagePageTable, hyp_mem: &mut HypPageAlloc) { + // Allocate and populate first. + let page_count = PageSize::num_4k_pages(self.size as u64); + let pages = hyp_mem.take_pages_for_hyp_state(page_count as usize); + // Copy data if present. + if let Some(data) = self.data { + let dest = pages.base().bits() as *mut u8; + let len = core::cmp::min(data.len(), self.size); + // Safe because we copy the minimum between the data size and the VA size. + unsafe { + core::ptr::copy(data.as_ptr(), dest, len); + } + } + // Map the populated pages in the page table. + let mapper = sv48 + .map_range(self.vaddr, PageSize::Size4k, page_count, &mut || { + hyp_mem.take_pages_for_hyp_state(1).into_iter().next() + }) + .unwrap(); + for (virt, phys) in self + .vaddr + .iter_from() + .zip(pages.base().iter_from()) + .take(page_count as usize) + { + // Safe because these pages are mapped into user mode and will not be accessed in + // supervisor mode. + unsafe { + mapper.map_addr(virt, phys, self.pte_fields).unwrap(); + } + } + } +} + /// A page table that contains hypervisor mappings. pub struct HypPageTable { inner: FirstStagePageTable, @@ -109,17 +190,27 @@ impl HypPageTable { /// A set of global mappings of the hypervisor that can be used to create page tables. pub struct HypMap { - regions: ArrayVec, + supervisor_regions: ArrayVec, + user_regions: ArrayVec, } impl HypMap { /// Create a new hypervisor map from a hardware memory mem map. - pub fn new(mem_map: HwMemMap) -> HypMap { - let regions = mem_map + pub fn new(mem_map: HwMemMap, user_map: ElfMap<'static>) -> HypMap { + // All supervisor regions comes from the HW memory map. + let supervisor_regions = mem_map .regions() - .filter_map(HypMapRegion::from_hw_mem_region) + .filter_map(HypMapFixedRegion::from_hw_mem_region) .collect(); - HypMap { regions } + // All user regions come from the ELF segment. + let user_regions = user_map + .segments() + .filter_map(HypMapPopulatedRegion::from_user_elf_segment) + .collect(); + HypMap { + supervisor_regions, + user_regions, + } } /// Create a new page table based on this memory map. @@ -134,12 +225,16 @@ impl HypMap { let sv48: FirstStagePageTable = FirstStagePageTable::new(root_page).expect("creating first sv48"); - // Map all the regions in the memory map that the hypervisor could need. - for r in &self.regions { + // Map supervisor regions + for r in &self.supervisor_regions { r.map(&sv48, &mut || { hyp_mem.take_pages_for_hyp_state(1).into_iter().next() }); } + // Map user regions. + for r in &self.user_regions { + r.map(&sv48, hyp_mem); + } HypPageTable { inner: sv48 } } } diff --git a/src/main.rs b/src/main.rs index a8dff397..ec4dd63c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,7 @@ use host_vm_loader::HostVmLoader; use hyp_alloc::HypAlloc; use hyp_map::HypMap; use page_tracking::*; +use riscv_elf::ElfMap; use riscv_page_tables::*; use riscv_pages::*; use riscv_regs::{hedeleg, henvcfg, hideleg, hie, scounteren}; @@ -432,17 +433,6 @@ extern "C" fn kernel_init(hart_id: u64, fdt_addr: u64) { // NOTE: Do not modify the hardware memory map from here on. let mem_map = mem_map; // Remove mutability. - println!("HW memory map:"); - for (i, r) in mem_map.regions().enumerate() { - println!( - "[{}] region: 0x{:x} -> 0x{:x}, {}", - i, - r.base().bits(), - r.end().bits() - 1, - r.region_type() - ); - } - // We start RAM in the host address space at the same location as it is in the supervisor // address space. // @@ -459,8 +449,34 @@ extern "C" fn kernel_init(hart_id: u64, fdt_addr: u64) { let guest_phys_size = mem_map.regions().last().unwrap().end().bits() - mem_map.regions().next().unwrap().base().bits(); + // Parse the user-mode ELF containing the user-mode task. + let user_elf = include_bytes!("../target/riscv64gc-unknown-none-elf/release/umode"); + let user_map = ElfMap::new(user_elf).expect("Cannot load user-mode ELF"); + + println!("HW memory map:"); + for (i, r) in mem_map.regions().enumerate() { + println!( + "[{:02}] region: 0x{:016x} -> 0x{:016x}, {}", + i, + r.base().bits(), + r.end().bits() - 1, + r.region_type() + ); + } + + println!("USER memory map:"); + for (i, s) in user_map.segments().enumerate() { + println!( + "[{:02}] region: 0x{:016x} -> 0x{:016x}, {}", + i, + s.vaddr(), + s.vaddr() + s.size() as u64, + s.perms() + ); + } + // Create the hypervisor mapping starting from the hardware memory map. - let hyp_map = HypMap::new(mem_map); + let hyp_map = HypMap::new(mem_map, user_map); // The hypervisor mapping is complete. Can setup paging structures now. setup_hyp_paging(hyp_map, &mut hyp_mem); diff --git a/umode/.cargo/config b/umode/.cargo/config new file mode 100644 index 00000000..cc2cbfac --- /dev/null +++ b/umode/.cargo/config @@ -0,0 +1,12 @@ +[net] +git-fetch-with-cli = true + +[build] +target = "riscv64gc-unknown-none-elf" + +[target.riscv64gc-unknown-none-elf] +rustflags = [ + '-Clink-arg=-Tlds/salus.lds', + # new in 1.60 and generates false positives + "-Aclippy::only_used_in_recursion", +] diff --git a/umode/Cargo.toml b/umode/Cargo.toml new file mode 100644 index 00000000..455ac77d --- /dev/null +++ b/umode/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "umode" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libuser = { path = "../libuser" } diff --git a/umode/src/main.rs b/umode/src/main.rs new file mode 100644 index 00000000..64e8a168 --- /dev/null +++ b/umode/src/main.rs @@ -0,0 +1,13 @@ +// Copyright (c) 2022 by Rivos Inc. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] + +extern crate libuser; + +#[no_mangle] +extern "C" fn task_main(_data: u64) { + panic!(""); +}