diff --git a/Cargo.lock b/Cargo.lock index 9c83c2f..1c6f6eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,10 +55,11 @@ dependencies = [ "bitstruct", "cpio_reader", "goblin", + "iced-x86", "miniz_oxide", "seq-macro", "sha2", - "spin", + "spin 0.10.0", "static_assertions", "x86", "xmodem", @@ -197,6 +198,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "iced-x86" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] + [[package]] name = "libc" version = "0.2.171" @@ -294,6 +313,12 @@ dependencies = [ "digest", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spin" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index e9c301a..39f44c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ goblin = { version = "0.9", default-features = false, features = [ "elf32", "alloc", ] } +iced-x86 = { version = "1.21.0", features = ["decoder", "gas", "no_std"], default-features = false } miniz_oxide = "0.8" seq-macro = "0.3" sha2 = { version = "0.10.8", default-features = false, features = ["force-soft"] } diff --git a/README.md b/README.md index 14e07f0..aff3e8c 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,8 @@ Supported commands include: * `mount ` to mount the UFS ramdisk. * `ls ` to list a file or directory on the ramdisk. * `cat ` to display the contents of a file. +* `copy ,` to copy the contents of a + file to a region of memory. * `elfinfo ` to read the contents of the ELF header and segment headers of an ELF file. * `load ` to load the given ELF file and retrieve its @@ -199,6 +201,10 @@ Supported commands include: extended configuration space for the given bus/device/function * `ecamwr ` writes a 32-bit word to PCIe extended configuration space for the given bus/device/function +* `getbits , ` returns the given bit range + from `` +* `setbits , ` sets the given bit + range in `` to `` ## Building bldb diff --git a/src/bldb.rs b/src/bldb.rs index 49fe189..975bc2f 100644 --- a/src/bldb.rs +++ b/src/bldb.rs @@ -292,6 +292,12 @@ fn range_4k(start: mem::V4KA) -> Range { start..end } +pub(crate) fn loader_text() -> Range { + let start = text_addr().addr() as u64; + let end = rodata_addr().addr() as u64; + start..end +} + /// When the loader enters Rust code, we know that we have a /// minimal virtual memory environment where the loader itself /// is mapped rwx, and the UART registers region is mapped diff --git a/src/idt.rs b/src/idt.rs index 583c79a..a048fa8 100644 --- a/src/idt.rs +++ b/src/idt.rs @@ -264,7 +264,32 @@ impl Idt { } } +// Tries to skip over an instruction. +// +// # Safety +// The caller must ensure `rip` points to an instruction +// somewhere in the loader text. +unsafe fn skip_instr(rip: u64) -> u64 { + use iced_x86::{Code, Decoder, DecoderOptions}; + const MAX_INSTR_LEN: usize = 15; + let loader_text = crate::bldb::loader_text(); + let ripmax = rip + MAX_INSTR_LEN as u64; + if !loader_text.contains(&rip) || !loader_text.contains(&ripmax) { + panic!("PC does not point into loader text"); + } + let ptr = core::ptr::with_exposed_provenance(rip as usize); + let bytes = unsafe { core::slice::from_raw_parts(ptr, MAX_INSTR_LEN) }; + let mut decoder = Decoder::with_ip(64, bytes, rip, DecoderOptions::NONE); + let instr = decoder.decode(); + if instr.code() == Code::INVALID { + panic!("Invalid instruction; can't skip: {instr:x?}"); + } else { + rip + instr.len() as u64 + } +} + extern "C" fn trap(frame: &mut TrapFrame) { + const GPF: u64 = 13; println!("Exception:"); println!("{frame:#x?}"); println!("cr0: {:#x}", unsafe { x86::controlregs::cr0() }); @@ -275,10 +300,17 @@ extern "C" fn trap(frame: &mut TrapFrame) { unsafe { backtrace(frame.rbp); } - // Arrange for the exception return to land in a halt loop. - // The seemingly superfluous cast to usize and then again to - // u64 keeps clippy happy. - frame.rip = crate::bldb::dnr as usize as u64; + // If this is a GPF, attempt to recover by skipping to the + // next instruction. Otherwise, arrange for the exception + // return to land in a halt loop. + if frame.vector == GPF { + println!("GPF OK; attempting to resume"); + frame.rip = unsafe { skip_instr(frame.rip) }; + } else { + // The seemingly superfluous cast to usize and then + // again to u64 keeps clippy happy. + frame.rip = crate::bldb::dnr as usize as u64; + } } /// Prints a call backtrace starting from the given frame diff --git a/src/mem.rs b/src/mem.rs index bf2c352..04f1ed4 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -39,7 +39,7 @@ pub const fn is_canonical_range(start: usize, end: usize) -> bool { } // If in the lower portion of the canonical address space, // end is permitted to be exactly one beyond the supremum. - if start < LOW_CANON_SUP && end <= LOW_CANON_SUP { + if start < LOW_CANON_SUP && start <= end && end <= LOW_CANON_SUP { return true; } // Otherwise, the range is valid IFF it is in the upper @@ -213,6 +213,12 @@ impl Attrs { pub(crate) fn set_c(&mut self, c: bool) { self.set_nc(!c); } + + pub(crate) fn permits(self, wants: Attrs) -> bool { + (!wants.r() || self.r()) + && (!wants.w() || self.w()) + && (!wants.nx() || self.nx()) + } } /// A region of virtual memory. @@ -256,3 +262,21 @@ pub fn round_up_4k(va: usize) -> usize { pub fn round_down_4k(va: usize) -> usize { va & !V4KA::MASK } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn attrs_permits() { + let has = Attrs::new_data(); + assert!(has.nx()); + assert!(has.r()); + assert!(has.w()); + let wants = Attrs::new_ro(); + assert!(!wants.nx()); + assert!(!wants.w()); + assert!(wants.r()); + assert!(has.permits(wants)); + } +} diff --git a/src/mmu.rs b/src/mmu.rs index 4a5384e..1a740d4 100644 --- a/src/mmu.rs +++ b/src/mmu.rs @@ -107,6 +107,7 @@ extern crate alloc; use crate::mem; +#[cfg(not(any(test, clippy)))] use crate::println; use crate::result::{Error, Result}; #[cfg(not(any(test, clippy)))] @@ -1193,32 +1194,27 @@ impl PageTable { /// mapped with the given permissions. Supports mapping at the end of the /// address range. fn is_region_mapped(&self, region: &mem::Region) -> bool { - fn permits(eattrs: &mem::Attrs, attrs: &mem::Attrs) -> bool { - (!attrs.r() || eattrs.r()) - && (!attrs.w() || eattrs.w()) - && (!attrs.x() || eattrs.x()) - } let mut start = region.start().addr(); let end = region.end().addr(); if !mem::is_canonical_range(start, end) { return false; } - let attrs = region.attrs(); + let attrs: mem::Attrs = region.attrs(); while start != end { let va = core::ptr::without_provenance(start); let len = match self.pml4.lookup(va) { Some(EntryParts::Entry1G(_, eattrs)) - if permits(&eattrs, &attrs) => + if eattrs.permits(attrs) => { PFN1G::SIZE - (start % PFN1G::SIZE) } Some(EntryParts::Entry2M(_, eattrs)) - if permits(&eattrs, &attrs) => + if eattrs.permits(attrs) => { PFN2M::SIZE - (start % PFN2M::SIZE) } Some(EntryParts::Entry4K(_, eattrs)) - if permits(&eattrs, &attrs) => + if eattrs.permits(attrs) => { PFN4K::SIZE } @@ -1478,6 +1474,9 @@ impl LoaderPageTable { Ok(()) } + /// Maps the given virtual address range to the given physical + /// address with the given attributes, but restricted so that the + /// physical region can only map RAM, not MMIO space. pub(crate) unsafe fn map_ram( &mut self, range: Range, @@ -1500,7 +1499,7 @@ impl LoaderPageTable { unsafe { self.page_table.unmap_range(&range) } } - /// Returns the page table entry entry for the given virtual address, if it is + /// Returns the page table entry for the given virtual address, if it is /// mapped in this address space. pub(crate) fn lookup(&self, va: *const ()) -> Option { self.page_table.lookup(va).map(|entry| match entry { @@ -1527,6 +1526,15 @@ impl LoaderPageTable { self.page_table.is_region_mapped(®ion) } + pub(crate) fn is_region_readable(&self, range: Range) -> bool { + self.is_region_mapped(range, mem::Attrs::new_ro()) + } + + pub(crate) fn is_region_writeable(&self, range: Range) -> bool { + !Self::overlaps(&self.reserved, &range) + && self.is_region_mapped(range, mem::Attrs::new_rw()) + } + /// Returns true iff region `a` overlaps any of the regions /// in `rs`. /// @@ -1556,6 +1564,7 @@ impl LoaderPageTable { /// Dumps the contents of the page table. pub(crate) fn dump(&self) { + println!("Root (PML4): {root:#x}", root = self.phys_addr()); self.page_table.pml4.dump(0); } } @@ -1601,6 +1610,25 @@ mod loader_page_table_tests { .is_err() }); } + + #[test] + fn region_is_readable() { + let page_table = PageTable::new(); + let mut loader_page_table = LoaderPageTable::new(page_table, &[], &[]); + let region = mem::V4KA::new(0x8000)..mem::V4KA::new(0xa000); + assert!(unsafe { + loader_page_table + .map_region( + region, + mem::Attrs::new_text(), + mem::P4KA::new(0x8000), + ) + .is_ok() + }); + let ptr = ptr::without_provenance(0x9001); + let range = mem::page_range_raw(ptr, 20); + assert!(loader_page_table.is_region_readable(range)); + } } mod arena { diff --git a/src/ramdisk.rs b/src/ramdisk.rs index ff7fdc4..4cffca0 100644 --- a/src/ramdisk.rs +++ b/src/ramdisk.rs @@ -79,6 +79,22 @@ pub fn cat( Ok(()) } +pub fn copy( + fs: &ufs::FileSystem<'_>, + path: &str, + dst: &mut [u8], +) -> Result { + let path = path.as_bytes(); + let file = fs.namei(path)?; + if file.file_type() != ufs::FileType::Regular { + println!("copy: not a regular file"); + return Err(Error::BadArgs); + } + let len = core::cmp::min(file.size(), dst.len()); + let nb = file.read(0, &mut dst[..len])?; + Ok(nb) +} + pub fn sha256(fs: &ufs::FileSystem<'_>, path: &str) -> Result<[u8; 32]> { use sha2::{Digest, Sha256}; diff --git a/src/repl/bits.rs b/src/repl/bits.rs new file mode 100644 index 0000000..4c5804e --- /dev/null +++ b/src/repl/bits.rs @@ -0,0 +1,72 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Simple hex dump routine. + +use crate::bldb; +use crate::println; +use crate::repl::{self, Value}; +use crate::result::{Error, Result}; +use alloc::vec::Vec; +use bit_field::BitField; +use core::ops::Range; + +pub fn get(_config: &mut bldb::Config, env: &mut Vec) -> Result { + let usage = |error| { + println!("usage: getbits , "); + error + }; + let (start, end) = + repl::popenv(env).as_pair().and_then(check_bits_pair).map_err(usage)?; + let value = repl::popenv(env).as_num::().map_err(usage)?; + let bits = value.get_bits(start..end); + Ok(Value::Unsigned(bits)) +} +pub fn set(_config: &mut bldb::Config, env: &mut Vec) -> Result { + let usage = |error| { + println!("usage: setbits , "); + error + }; + let (start, end) = + repl::popenv(env).as_pair().and_then(check_bits_pair).map_err(usage)?; + let new_bits = repl::popenv(env).as_num::().map_err(usage)?; + let mut value = repl::popenv(env).as_num::().map_err(usage)?; + if !value_fits(start..end, value) { + return Err(Error::NumRange); + } + value.set_bits(start..end, new_bits); + Ok(Value::Unsigned(value)) +} + +fn check_bits_pair(pair: (u64, usize)) -> Result<(usize, usize)> { + let start = pair.0 as usize; + let end = pair.1; + if start == end || start > 128 || end > 128 { + return Err(Error::NumRange); + } + Ok((start.min(end), end.max(start))) +} + +fn value_fits(bits: Range, value: u128) -> bool { + let nbits = bits.end - bits.start; + assert_ne!(nbits, 0); + value <= (!0u128 >> (128 - nbits)) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn value_does_fit() { + assert!(value_fits(1..2, 1)); + assert!(value_fits(0..128, !0)); + assert!(value_fits(0..4, 0xf)); + } + + #[test] + fn value_doesnt_fit() { + assert!(!value_fits(1..2, 2)); + assert!(!value_fits(0..4, 0xFF)); + } +} diff --git a/src/repl/copy.rs b/src/repl/copy.rs new file mode 100644 index 0000000..9133a30 --- /dev/null +++ b/src/repl/copy.rs @@ -0,0 +1,25 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::bldb; +use crate::println; +use crate::ramdisk; +use crate::repl::{self, Value}; +use crate::result::{Error, Result}; +use alloc::vec::Vec; + +pub fn run(config: &mut bldb::Config, env: &mut Vec) -> Result { + let usage = |error| { + println!("usage: copy ,"); + error + }; + let path = repl::popenv(env).as_string().map_err(usage)?; + let fs = config.ramdisk.as_ref().ok_or(Error::FsNoRoot)?; + let dst = repl::popenv(env) + .as_slice_mut(&config.page_table, 0) + .and_then(|o| o.ok_or(Error::BadArgs)) + .map_err(usage)?; + let len = ramdisk::copy(fs, &path, dst)?; + Ok(Value::Slice(&dst[..len])) +} diff --git a/src/repl/memory.rs b/src/repl/memory.rs index 397b36d..ac74f60 100644 --- a/src/repl/memory.rs +++ b/src/repl/memory.rs @@ -61,24 +61,46 @@ pub fn xd(config: &mut bldb::Config, env: &mut Vec) -> Result { Ok(Value::Nil) } -fn check_pair( - config: &bldb::Config, - ptr: *const u8, - len: usize, -) -> Result<(*const u8, usize)> { +fn check_pair_canon(ptr: *const u8, len: usize) -> Result<(*const u8, usize)> { let addr = ptr.addr(); if !mem::is_canonical_range(addr, addr + len) { return Err(Error::PtrNonCanon); } - if !config.page_table.is_region_mapped( - mem::page_range_raw(ptr.cast(), len), - mem::Attrs::new_ro(), - ) { - return Err(Error::Unmapped); - } Ok((ptr, len)) } +fn check_pair( + config: &bldb::Config, + ptr: *const u8, + len: usize, +) -> Result<(*const u8, usize)> { + check_pair_canon(ptr, len).and_then(|(ptr, len)| { + let range = mem::page_range_raw(ptr.cast(), len); + if config.page_table.is_region_readable(range) { + Ok((ptr, len)) + } else { + Err(Error::Unmapped) + } + }) +} + +fn check_pair_mut( + config: &bldb::Config, + ptr: *mut u8, + len: usize, +) -> Result<(*mut u8, usize)> { + check_pair_canon(ptr.cast_const(), len) + .and_then(|(ptr, len)| { + let range = mem::page_range_raw(ptr.cast(), len); + if config.page_table.is_region_readable(range) { + Ok((ptr, len)) + } else { + Err(Error::Unmapped) + } + }) + .map(|(ptr, len)| (ptr.cast_mut(), len)) +} + fn check_size(size: usize) -> bool { matches!(size, 1 | 2 | 4 | 8 | 16) } @@ -101,13 +123,9 @@ fn parse_peek_poke_pair_mut( ) -> Result<(*mut u8, usize)> { value .as_ptr_len_mut() - .and_then(|(ptr, len)| check_pair(config, ptr.cast_const(), len)) + .and_then(|(ptr, len)| check_pair_mut(config, ptr, len)) .and_then(|(ptr, len)| { - if check_size(len) { - Ok((ptr.cast_mut(), len)) - } else { - Err(Error::BadArgs) - } + if check_size(len) { Ok((ptr, len)) } else { Err(Error::BadArgs) } }) } diff --git a/src/repl/mod.rs b/src/repl/mod.rs index f3817a7..b662aa6 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -14,8 +14,10 @@ use core::fmt; use core::ptr; use core::slice; +mod bits; mod call; mod cat; +mod copy; mod cpuid; mod ecam; mod elfinfo; @@ -81,17 +83,12 @@ impl Value { let (ptr, len) = match self { Value::Nil => return Ok(None), Value::Slice(slice) => return Ok(Some(*slice)), - Value::Pair(addr, len) => { - Ok((unsigned_to_ptr(*addr as u128)?, *len)) - } + Value::Pair(addr, len) => Ok((unsigned_to_ptr(*addr)?, *len)), Value::Unsigned(addr) => Ok((unsigned_to_ptr(*addr)?, deflen)), Value::Pointer(ptr) => Ok((ptr.cast_const(), deflen)), _ => Err(Error::BadArgs), }?; - if page_table.is_region_mapped( - mem::page_range_raw(ptr.cast(), len), - mem::Attrs::new_ro(), - ) { + if page_table.is_region_readable(mem::page_range_raw(ptr.cast(), len)) { Ok(Some(unsafe { slice::from_raw_parts(ptr, len) })) } else { Err(Error::Unmapped) @@ -110,10 +107,8 @@ impl Value { Value::Pointer(ptr) => Ok((*ptr, deflen)), _ => Err(Error::BadArgs), }?; - if page_table.is_region_mapped( - mem::page_range_raw(ptr.cast(), len), - mem::Attrs::new_rw(), - ) { + if page_table.is_region_writeable(mem::page_range_raw(ptr.cast(), len)) + { unsafe { ptr::write_bytes(ptr, 0, len); } @@ -227,10 +222,12 @@ fn evalcmd( match cmd { "call" => call::run(config, env), "cat" => cat::run(config, env), + "copy" => copy::run(config, env), "cpuid" => cpuid::run(config, env), "ecamrd" => ecam::read(config, env), "ecamwr" => ecam::write(config, env), "elfinfo" => elfinfo::run(config, env), + "getbits" => bits::get(config, env), "gpioget" => gpio::get(config, env), "gpioset" => gpio::set(config, env), "hexdump" | "xd" => memory::xd(config, env), @@ -259,6 +256,7 @@ fn evalcmd( "rdsmn" => smn::read(config, env), "rx" => rx::run(config, env), "rz" => rz::run(config, env), + "setbits" => bits::set(config, env), "sha256" => sha::run(config, env), "sha256mem" => sha::mem(config, env), "unmap" => vm::unmap(config, env), @@ -300,10 +298,17 @@ fn eval( reader::Token::Value(v) => env.push(v), } } - if let Some(Value::Cmd(cmd)) = env.pop() { - evalcmd(config, &cmd, env) - } else { - Ok(Value::Nil) + let Some(Value::Cmd(cmd)) = env.pop() else { + return Ok(Value::Nil); + }; + match evalcmd(config, &cmd, env)? { + Value::Nil => Ok(Value::Nil), + v => { + if &cmd != "pop" { + env.push(v.clone()); + } + Ok(v) + } } } } @@ -326,11 +331,7 @@ pub(crate) fn run(config: &mut bldb::Config) { env.clear(); val = Value::Nil; } - Ok(Value::Nil) => val = Value::Nil, - Ok(v) => { - env.push(v.clone()); - val = v; - } + Ok(v) => val = v, } } println!("res: {val:?}"); diff --git a/src/repl/reader.rs b/src/repl/reader.rs index 6746c7a..4bd6cb7 100644 --- a/src/repl/reader.rs +++ b/src/repl/reader.rs @@ -53,6 +53,31 @@ pub(super) fn parse_num>(num: &str) -> Result { T::try_from(num).map_err(|_| Error::NumRange) } +fn parse_len>(mut tok: &str) -> Result { + let mut multiplier: u128 = 1; + while !tok.is_empty() { + if let Some(rest) = tok.strip_suffix(['k', 'K']) { + multiplier *= 1024; + tok = rest; + continue; + } + if let Some(rest) = tok.strip_suffix(['m', 'M']) { + multiplier *= 1024 * 1024; + tok = rest; + continue; + } + if let Some(rest) = tok.strip_suffix(['g', 'G']) { + multiplier *= 1024 * 1024 * 1024; + tok = rest; + continue; + } + break; + } + let num = if tok.is_empty() { 1 } else { parse_num(tok)? }; + let num = multiplier.checked_mul(num).ok_or(Error::NumRange)?; + T::try_from(num).map_err(|_| Error::NumRange) +} + fn split_pair(s: &str, pat: char) -> Result<(&str, Option<&str>)> { let mut it = s.split(pat); let (Some(a), b, None) = (it.next(), it.next(), it.next()) else { @@ -95,7 +120,7 @@ fn parse_value(s: &str) -> Result { Some(c) if c.is_ascii_digit() && !s.contains('/') => { let (a, b) = split_pair(s, ',')?; if let Some(b) = b { - Value::Pair(parse_num(a)?, parse_num(b)?) + Value::Pair(parse_num(a)?, parse_len(b)?) } else { Value::Unsigned(parse_num(a)?) } @@ -165,6 +190,26 @@ pub fn read( Ok(cmds) } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_len_suffix() { + assert_eq!(1024_usize, parse_len("k").unwrap()); + assert_eq!(4096_usize, parse_len("4K").unwrap()); + } + + #[test] + fn parse_value_tests() { + assert!(matches!(parse_value("").unwrap(), Value::Nil)); + assert!(matches!( + parse_value("0x1000,4k").unwrap(), + Value::Pair(0x1000, 4096) + )); + } +} + fn help() { println!( r#" @@ -243,6 +288,8 @@ Supported commands include: * `mount ` to mount the UFS ramdisk * `ls ` to list a file or directory on the ramdisk * `cat ` to display the contents of a file +* `copy ,` to copy the contents of a + file to a region of memory. * `elfinfo ` to read the contents of the ELF header and segment headers of an ELF file * `load ` to load the given ELF file and retrieve its @@ -324,6 +371,10 @@ Supported commands include: extended configuration space for the given bus/device/function * `ecamwr ` writes a 32-bit word to PCIe extended configuration space for the given bus/device/function +* `getbits , ` returns the given bit range + from `` +* `setbits , ` sets the given bit + range in `` to `` "# ); }