use applevisor as av;
use applevisor::Mappable;
use bitfield::bitfield;
use rhexdump as rh;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::{hash_map::Entry, BTreeMap, HashMap, VecDeque};
use std::fmt;
use std::io::prelude::*;
use std::path::Path;
use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex};
use crate::error::*;
use crate::exceptions::*;
use crate::utils::*;
#[macro_export]
macro_rules! round_phys_page {
( $addr: expr ) => {
(($addr as u64) + (av::PAGE_SIZE as u64 - 1)) & !(av::PAGE_SIZE as u64 - 1)
};
}
#[macro_export]
macro_rules! align_phys_page {
( $addr: expr ) => {
$addr & !(av::PAGE_SIZE as u64 - 1)
};
}
#[macro_export]
macro_rules! round_virt_page {
( $addr: expr ) => {
(($addr as u64) + (VIRT_PAGE_SIZE as u64 - 1)) & !(VIRT_PAGE_SIZE as u64 - 1)
};
}
#[macro_export]
macro_rules! align_virt_page {
( $addr: expr ) => {
$addr & !(VIRT_PAGE_SIZE as u64 - 1)
};
}
#[derive(Clone, Debug, PartialEq)]
pub struct PhysMem {
allocator: Option<PhysMemAllocator>,
mem: av::Mapping,
is_free: bool,
}
impl PhysMem {
pub fn new(size: usize) -> Result<Self> {
Ok(Self {
allocator: None,
mem: av::Mapping::new(size)?,
is_free: false,
})
}
pub fn with_pma(allocator: PhysMemAllocator, size: usize) -> Result<Self> {
Ok(Self {
allocator: Some(allocator),
mem: av::Mapping::new(size)?,
is_free: false,
})
}
#[inline]
pub fn map(&mut self, guest_addr: u64, perms: av::MemPerms) -> av::Result<()> {
self.mem.map(guest_addr, perms)
}
#[inline]
pub fn unmap(&mut self) -> av::Result<()> {
self.mem.unmap()
}
#[inline]
pub fn protect(&mut self, perms: av::MemPerms) -> av::Result<()> {
self.mem.protect(perms)
}
#[inline]
pub fn read(&self, guest_addr: u64, data: &mut [u8]) -> av::Result<usize> {
self.mem.read(guest_addr, data)
}
#[inline]
pub fn write(&mut self, guest_addr: u64, data: &[u8]) -> av::Result<usize> {
self.mem.write(guest_addr, data)
}
#[inline]
pub fn get_host_addr(&self) -> *const u8 {
self.mem.get_host_addr()
}
#[inline]
pub fn get_guest_addr(&self) -> Option<u64> {
self.mem.get_guest_addr()
}
#[inline]
pub fn get_size(&self) -> usize {
self.mem.get_size()
}
}
impl std::ops::Drop for PhysMem {
fn drop(&mut self) {
if !self.is_free {
PhysMemAllocator::_free_inner(self).expect("could not free physmem during drop");
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct PhysMemAllocatorInner {
mem_size: usize,
pools: Vec<Vec<u64>>,
allocs: HashMap<u64, usize>,
}
impl PhysMemAllocatorInner {
fn get_order(&self, size: usize) -> Result<usize> {
if size < av::PAGE_SIZE || size > self.mem_size {
Err(MemoryError::InvalidSize(size))?
} else {
Ok(log2(size) - log2(av::PAGE_SIZE))
}
}
fn get_buddy(&self, addr: u64, size: usize) -> Option<u64> {
if size >= self.mem_size {
return None;
}
if addr % (size as u64 * 2) == 0 {
Some(addr + size as u64)
} else {
Some(addr - size as u64)
}
}
}
#[derive(Clone, Debug)]
pub struct PhysMemAllocator {
inner: Arc<Mutex<PhysMemAllocatorInner>>,
}
impl PhysMemAllocator {
pub fn new(mem_size: usize) -> Result<Self> {
if mem_size < av::PAGE_SIZE || mem_size & (mem_size - 1) != 0 {
return Err(MemoryError::InvalidSize(mem_size))?;
}
let mut ba = PhysMemAllocatorInner {
mem_size,
pools: vec![],
allocs: HashMap::new(),
};
let nb_pools = 1 + ba.get_order(mem_size)?;
ba.pools = vec![vec![]; nb_pools];
ba.pools.last_mut().unwrap().push(0);
Ok(Self {
inner: Arc::new(Mutex::new(ba)),
})
}
pub fn alloc(&mut self, size: usize, perms: av::MemPerms) -> Result<PhysMem> {
let mut inner = self.inner.lock().unwrap();
if size & (av::PAGE_SIZE - 1) != 0 {
return Err(MemoryError::UnalignedSize(size))?;
}
let alloc_order = inner.get_order(size)?;
let mut free_order = alloc_order
+ inner
.pools
.iter()
.skip(alloc_order)
.position(|x| !x.is_empty())
.ok_or(MemoryError::OutOfMemory)?;
let alloc_addr = inner.pools[free_order].pop().unwrap();
let mut alloc_size = 1 << (free_order + log2(av::PAGE_SIZE) - 1);
while free_order > alloc_order {
free_order -= 1;
let buddy_addr = alloc_addr + alloc_size;
inner.pools[free_order].push(buddy_addr);
alloc_size >>= 1;
}
inner.allocs.insert(alloc_addr, size);
let mut mapping = PhysMem::with_pma(self.clone(), size)?;
mapping.map(alloc_addr, perms)?;
Ok(mapping)
}
pub fn free(&self, mut mem: PhysMem) -> Result<()> {
Self::_free_inner(&mut mem)
}
fn _free_inner(mem: &mut PhysMem) -> Result<()> {
if mem.allocator.is_none() {
return Ok(());
}
let allocator = mem.allocator.as_ref().unwrap();
let mut inner = allocator.inner.lock().unwrap();
let mut chunk_addr = mem.get_guest_addr().unwrap();
mem.is_free = true;
mem.mem.unmap()?;
if let Some(mut chunk_size) = inner.allocs.remove(&chunk_addr) {
let mut order = inner.get_order(chunk_size)?;
while let Some(buddy_addr) = inner.get_buddy(chunk_addr, chunk_size) {
if inner.allocs.get(&buddy_addr).is_none() {
if let Some(pos) = inner.pools[order].iter().position(|x| *x == buddy_addr) {
inner.pools[order].remove(pos);
} else {
break;
}
chunk_size *= 2;
chunk_addr = std::cmp::min(chunk_addr, buddy_addr);
order += 1;
} else {
break;
}
}
inner.pools[order].push(chunk_addr);
} else {
return Err(MemoryError::UnallocatedMemoryAccess(chunk_addr))?;
}
Ok(())
}
}
impl fmt::Display for PhysMemAllocator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner = self.inner.lock().unwrap();
writeln!(f)?;
writeln!(f, "+-------+-------+")?;
writeln!(f, "| Order | Count |")?;
writeln!(f, "+-------+-------+")?;
for (i, pool) in inner.pools.iter().enumerate() {
writeln!(f, "| {:5} | {:5} |", i, pool.len())?;
}
writeln!(f, "+-------+-------+")
}
}
impl PartialEq for PhysMemAllocator {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SlabAllocator {
pma: PhysMemAllocator,
object_size: usize,
objects_per_slab: usize,
full: VecDeque<SlabReference>,
partial: VecDeque<SlabReference>,
}
impl SlabAllocator {
pub fn new(pma: PhysMemAllocator, object_size: usize) -> Result<Self> {
if object_size > av::PAGE_SIZE {
return Err(MemoryError::InvalidSize(object_size))?;
}
Ok(SlabAllocator {
pma,
object_size,
objects_per_slab: av::PAGE_SIZE / object_size,
full: VecDeque::new(),
partial: VecDeque::new(),
})
}
pub fn alloc(&mut self) -> Result<SlabObject> {
if self.partial.is_empty() {
let mem = self.pma.alloc(av::PAGE_SIZE, av::MemPerms::RWX)?;
let slab = SlabReference::new(mem, self.object_size, self.objects_per_slab);
self.partial.push_back(slab);
}
let slab = self
.partial
.iter_mut()
.next()
.ok_or(MemoryError::CorruptedSlab)?;
let object = slab.alloc().ok_or(MemoryError::CorruptedSlab)?;
if slab.is_full() {
let slab = self.partial.pop_front().unwrap();
self.full.push_back(slab);
}
Ok(object)
}
pub fn free(&mut self, mut object: SlabObject) -> Result<()> {
let parent = object.parent.take();
let mut slab = parent.ok_or(MemoryError::CorruptedSlab)?;
let is_full_slab = slab.is_full();
slab.free(object);
if is_full_slab {
let full_slab_pos = self
.full
.iter()
.position(|x| *x == slab)
.ok_or(MemoryError::CorruptedSlab)?;
let full_slab = self.full.remove(full_slab_pos).unwrap();
self.partial.push_front(full_slab);
}
if slab.is_empty() {
let empty_slab_pos = self
.partial
.iter()
.position(|x| *x == slab)
.ok_or(MemoryError::CorruptedSlab)?;
let _ = self.partial.remove(empty_slab_pos).unwrap();
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Slab {
mem: PhysMem,
objects_per_slab: usize,
freelist: Vec<SlabObject>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SlabReference(Rc<RefCell<Slab>>);
impl SlabReference {
pub fn new(mem: PhysMem, object_size: usize, objects_per_slab: usize) -> Self {
let mut freelist = vec![];
for i in (0..objects_per_slab).rev() {
let object = SlabObject {
host_addr: unsafe { mem.get_host_addr().add(i * object_size) as *const u8 },
guest_addr: mem.get_guest_addr().unwrap() + (i * object_size) as u64,
object_size,
parent: None,
};
freelist.push(object);
}
Self(Rc::new(RefCell::new(Slab {
mem,
objects_per_slab,
freelist,
})))
}
pub fn is_full(&self) -> bool {
self.0.borrow().freelist.is_empty()
}
pub fn is_empty(&self) -> bool {
self.0.borrow().freelist.len() == self.0.borrow().objects_per_slab
}
pub fn alloc(&mut self) -> Option<SlabObject> {
if let Some(mut object) = self.0.borrow_mut().freelist.pop() {
object.parent = Some(self.clone());
unsafe { std::ptr::write_bytes(object.host_addr as *mut u8, 0, object.object_size) };
Some(object)
} else {
None
}
}
pub fn free(&mut self, mut object: SlabObject) {
let _ = object.parent.take();
self.0.borrow_mut().freelist.push(object);
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SlabObject {
host_addr: *const u8,
guest_addr: u64,
object_size: usize,
parent: Option<SlabReference>,
}
impl fmt::Display for SlabObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"Slab object @Host {:#x} - @Guest {:#x}",
self.host_addr as u64, self.guest_addr
)?;
let data: &[u8] = unsafe { std::slice::from_raw_parts(self.host_addr, self.object_size) };
let mut rhx = rhexdump::Rhexdump::default();
rhx.display_duplicate_lines(false);
write!(f, "{}", rhx.hexdump_offset(data, self.host_addr as u32))
}
}
pub const VIRT_PAGE_SIZE: usize = 0x1000;
pub const PAGE_TABLE_NB_ENTRIES: usize = 0x200;
pub const PAGE_TABLE_SIZE: usize = PAGE_TABLE_NB_ENTRIES * std::mem::size_of::<u64>();
bitfield! {
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
pub struct TableDescriptor(u64);
impl Debug;
get_valid, set_valid: 0;
get_type, set_type: 1;
get_addr, set_addr: 47, 12;
get_pxntable, set_pxntable: 59;
get_uxntable, set_uxntable: 60;
get_aptable, set_aptable: 62, 61;
get_nstable, set_nstable: 63;
}
impl TableDescriptor {
pub fn new(addr: u64) -> Self {
let mut descriptor = TableDescriptor(0);
descriptor.set_valid(true);
descriptor.set_type(true);
descriptor.set_addr(addr >> 12);
descriptor.set_pxntable(false);
descriptor.set_uxntable(false);
descriptor.set_aptable(0b00);
descriptor.set_nstable(true);
descriptor
}
}
bitfield! {
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
pub struct PageDescriptor(u64);
impl Debug;
get_valid, set_valid: 0;
get_type, set_type: 1;
get_attrindx, set_attrindx: 4, 2;
get_ns, set_ns: 5;
get_ap, set_ap: 7, 6;
get_sh, set_sh: 9, 8;
get_af, set_af: 10;
get_ng, set_ng: 11;
get_addr, set_addr: 47, 12;
get_dbm, set_dbm: 51;
get_contiguous, set_contiguous: 52;
get_pxn, set_pxn: 53;
get_uxn, set_uxn: 54;
}
impl PageDescriptor {
pub fn new(addr: u64, perms: av::MemPerms, privileged: bool) -> Self {
let mut descriptor = PageDescriptor(0xffff_ffff);
descriptor.set_valid(true);
descriptor.set_type(true);
descriptor.set_attrindx(0b000);
descriptor.set_ns(true);
descriptor.set_ap(match privileged {
true => 0b00,
false => match perms {
av::MemPerms::None | av::MemPerms::W | av::MemPerms::X | av::MemPerms::WX => 0b00,
av::MemPerms::R | av::MemPerms::RX => 0b11,
av::MemPerms::RW | av::MemPerms::RWX => 0b01,
},
});
descriptor.set_sh(0b11);
descriptor.set_af(true);
descriptor.set_ng(false);
descriptor.set_addr(addr >> 12);
descriptor.set_pxn(false);
descriptor.set_uxn(
privileged
| match perms {
av::MemPerms::None | av::MemPerms::W | av::MemPerms::R | av::MemPerms::RW => {
true
}
av::MemPerms::X | av::MemPerms::WX | av::MemPerms::RX | av::MemPerms::RWX => {
false
}
},
);
descriptor
}
pub fn read_only(&self, privileged: bool) -> Self {
let mut descriptor_ro = *self;
let ap0 = self.get_ap() & 1;
let ap1 = (self.get_ap() >> 1) & 1;
let privileged = privileged as u64;
let ap_ro = ((privileged | ap1 | ap0) << 1) | ap0;
descriptor_ro.set_ap(ap_ro);
descriptor_ro
}
}
#[derive(Clone, Debug)]
pub struct PageGlobalDirectory {
entries: SlabObject,
objects: HashMap<usize, PageUpperDirectory>,
}
impl PageGlobalDirectory {
pub fn new(entries: SlabObject) -> Self {
Self {
entries,
objects: HashMap::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct PageUpperDirectory {
descriptor: TableDescriptor,
entries: SlabObject,
objects: HashMap<usize, PageMiddleDirectory>,
}
impl PageUpperDirectory {
pub fn new(entries: SlabObject) -> Self {
Self {
descriptor: TableDescriptor::new(entries.guest_addr as u64),
entries,
objects: HashMap::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct PageMiddleDirectory {
descriptor: TableDescriptor,
entries: SlabObject,
objects: HashMap<usize, Rc<RefCell<PageTable>>>,
}
impl PageMiddleDirectory {
pub fn new(entries: SlabObject) -> Self {
Self {
descriptor: TableDescriptor::new(entries.guest_addr as u64),
entries,
objects: HashMap::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct PageTable {
descriptor: TableDescriptor,
entries: SlabObject,
objects: HashMap<usize, Rc<RefCell<Page>>>,
}
impl PageTable {
pub fn new(entries: SlabObject) -> Self {
Self {
descriptor: TableDescriptor::new(entries.guest_addr as u64),
entries,
objects: HashMap::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct Page {
descriptor: PageDescriptor,
descriptor_in_use: PageDescriptor,
dirty: bool,
perms: av::MemPerms,
privileged: bool,
data: Option<SlabObject>,
parent: Weak<RefCell<PageTable>>,
}
impl Page {
pub fn new(
data: SlabObject,
perms: av::MemPerms,
privileged: bool,
parent: Weak<RefCell<PageTable>>,
) -> Self {
let descriptor = PageDescriptor::new(data.guest_addr as u64, perms, privileged);
Self {
descriptor,
descriptor_in_use: descriptor.read_only(privileged),
perms,
privileged,
dirty: false,
data: Some(data),
parent,
}
}
}
#[derive(Clone, Debug)]
pub struct PageTableManager {
pub(crate) slab: SlabAllocator,
pub(crate) pgd: PageGlobalDirectory,
pub(crate) allocs: BTreeMap<u64, Rc<RefCell<Page>>>,
}
impl PageTableManager {
pub fn new(pma: PhysMemAllocator) -> Result<Self> {
let mut slab = SlabAllocator::new(pma, PAGE_TABLE_SIZE)?;
let pgd = PageGlobalDirectory::new(slab.alloc()?);
Ok(Self {
slab,
pgd,
allocs: BTreeMap::new(),
})
}
pub fn map(
&mut self,
addr: u64,
size: usize,
perms: av::MemPerms,
privileged: bool,
) -> Result<()> {
if addr & (VIRT_PAGE_SIZE as u64 - 1) != 0 {
return Err(MemoryError::UnalignedAddress(addr))?;
}
if size & (VIRT_PAGE_SIZE - 1) != 0 {
return Err(MemoryError::UnalignedSize(size))?;
}
let range_start = addr;
let range_end = round_virt_page!(addr
.checked_add(size as u64)
.ok_or(MemoryError::Overflow(addr, size))?);
for addr in (range_start..range_end).step_by(VIRT_PAGE_SIZE) {
let pud_idx = (addr >> 39 & 0x1ff) as usize;
if let Entry::Vacant(e) = self.pgd.objects.entry(pud_idx) {
let pud = PageUpperDirectory::new(self.slab.alloc()?);
Self::add_entry(pud.descriptor.0, pud_idx, &mut self.pgd.entries)?;
e.insert(pud);
}
let pud = self.pgd.objects.get_mut(&pud_idx).unwrap();
let pmd_idx = (addr >> 30 & 0x1ff) as usize;
if let Entry::Vacant(e) = pud.objects.entry(pmd_idx) {
let pmd = PageMiddleDirectory::new(self.slab.alloc()?);
Self::add_entry(pmd.descriptor.0, pmd_idx, &mut pud.entries)?;
e.insert(pmd);
}
let pmd = pud.objects.get_mut(&pmd_idx).unwrap();
let pt_idx = (addr >> 21 & 0x1ff) as usize;
if let Entry::Vacant(e) = pmd.objects.entry(pt_idx) {
let pt = PageTable::new(self.slab.alloc()?);
Self::add_entry(pt.descriptor.0, pt_idx, &mut pmd.entries)?;
e.insert(Rc::new(RefCell::new(pt)));
}
let pt_cell = pmd.objects.get_mut(&pt_idx).unwrap();
let page_idx = (addr >> 12 & 0x1ff) as usize;
let pt_mut = &mut *pt_cell.borrow_mut();
let pt_entries = &mut pt_mut.entries;
let pt_objects = &mut pt_mut.objects;
if let Entry::Vacant(e) = pt_objects.entry(page_idx) {
let page = Page::new(
self.slab.alloc()?,
perms,
privileged,
Rc::downgrade(pt_cell),
);
Self::add_entry(page.descriptor_in_use.0, page_idx, pt_entries)?;
let page_ref = Rc::new(RefCell::new(page));
e.insert(page_ref.clone());
self.allocs.insert(addr, page_ref);
} else {
return Err(MemoryError::AlreadyMapped(addr))?;
}
}
Ok(())
}
pub fn unmap(&mut self, addr: u64, size: usize) -> Result<()> {
if addr & (VIRT_PAGE_SIZE as u64 - 1) != 0 {
return Err(MemoryError::UnalignedAddress(addr))?;
}
if size & (VIRT_PAGE_SIZE - 1) != 0 {
return Err(MemoryError::UnalignedSize(size))?;
}
let range_start = addr;
let range_end = round_virt_page!(addr
.checked_add(size as u64)
.ok_or(MemoryError::Overflow(addr, size))?);
for addr in (range_start..range_end).step_by(VIRT_PAGE_SIZE) {
let pud_idx = (addr >> 39 & 0x1ff) as usize;
let pud = self
.pgd
.objects
.get_mut(&pud_idx)
.ok_or(MemoryError::UnallocatedMemoryAccess(addr))?;
let pmd_idx = (addr >> 30 & 0x1ff) as usize;
let pmd = pud
.objects
.get_mut(&pmd_idx)
.ok_or(MemoryError::UnallocatedMemoryAccess(addr))?;
let pt_idx = (addr >> 21 & 0x1ff) as usize;
let pt_cell = pmd
.objects
.get_mut(&pt_idx)
.ok_or(MemoryError::UnallocatedMemoryAccess(addr))?;
let page_idx = (addr >> 12 & 0x1ff) as usize;
let mut pt = pt_cell.borrow_mut();
let page = pt
.objects
.remove(&page_idx)
.ok_or(MemoryError::UnallocatedMemoryAccess(addr))?;
self.allocs.remove(&addr);
let mut page_ref = page.borrow_mut();
let page_data = page_ref.data.take().unwrap();
Self::del_entry(page_idx, &mut pt.entries)?;
self.slab.free(page_data)?;
let is_pt_object_empty = pt.objects.is_empty();
drop(pt);
if is_pt_object_empty {
Self::del_entry(pt_idx, &mut pmd.entries)?;
let pt_rc = pmd.objects.remove(&pt_idx).unwrap();
let pt_cell = Rc::try_unwrap(pt_rc).expect("could not unwrap pt_rc");
let pt = pt_cell.into_inner();
self.slab.free(pt.entries)?;
}
if pmd.objects.is_empty() {
Self::del_entry(pmd_idx, &mut pud.entries)?;
let pmd = pud.objects.remove(&pmd_idx).unwrap();
self.slab.free(pmd.entries)?;
}
if pud.objects.is_empty() {
Self::del_entry(pud_idx, &mut self.pgd.entries)?;
let pud = self.pgd.objects.remove(&pud_idx).unwrap();
self.slab.free(pud.entries)?;
}
}
Ok(())
}
pub fn get_page_by_addr(&self, addr: u64) -> Result<Rc<RefCell<Page>>> {
match self.allocs.get(&addr) {
Some(r) => Ok(r.clone()),
None => Err(MemoryError::UnallocatedMemoryAccess(addr))?,
}
}
fn dirty_bit_handler(&mut self, addr: u64) -> Result<bool> {
let addr = align_virt_page!(addr);
let mut page = self
.allocs
.get(&addr)
.ok_or(MemoryError::UnallocatedMemoryAccess(addr))?
.borrow_mut();
let pt_cell = page.parent.upgrade().unwrap();
let mut pt = pt_cell.borrow_mut();
let page_idx = (addr >> 12 & 0x1ff) as usize;
if page.descriptor_in_use != page.descriptor {
Self::add_entry(page.descriptor.0, page_idx, &mut pt.entries)?;
page.descriptor_in_use = page.descriptor;
page.dirty = true;
Ok(true)
} else {
Ok(false)
}
}
#[inline]
fn add_entry(desc: u64, idx: usize, ents: &mut SlabObject) -> Result<()> {
if idx > PAGE_TABLE_NB_ENTRIES {
return Err(MemoryError::InvalidIndex(idx))?;
}
unsafe {
std::ptr::write(ents.host_addr.add(idx * 8) as *mut u64, desc);
};
Ok(())
}
#[inline]
pub fn del_entry(idx: usize, ents: &mut SlabObject) -> Result<()> {
Self::add_entry(0, idx, ents)
}
}
impl fmt::Display for PageTableManager {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\nPGD @{:#x}", self.pgd.entries.host_addr as u64)?;
for (pud_idx, pud) in self.pgd.objects.iter() {
let desc =
unsafe { std::ptr::read(self.pgd.entries.host_addr.add(pud_idx * 8) as *mut u64) };
writeln!(
f,
"+-- PUD #{} @{:#x} ({:x})",
pud_idx, pud.entries.host_addr as u64, desc
)?;
for (pmd_idx, pmd) in pud.objects.iter() {
let desc =
unsafe { std::ptr::read(pud.entries.host_addr.add(pmd_idx * 8) as *mut u64) };
writeln!(
f,
" +-- PMD #{} @{:#x} ({:x})",
pmd_idx, pmd.entries.host_addr as u64, desc
)?;
for (pt_idx, pt_cell) in pmd.objects.iter() {
let desc = unsafe {
std::ptr::read(pmd.entries.host_addr.add(pt_idx * 8) as *mut u64)
};
let pt = pt_cell.borrow();
writeln!(
f,
" +-- PT #{} @{:#x} ({:x})",
pt_idx, pt.entries.host_addr as u64, desc
)?;
for (page_idx, page) in pt.objects.iter() {
let desc = unsafe {
std::ptr::read(pt.entries.host_addr.add(page_idx * 8) as *mut u64)
};
let page = page.borrow();
writeln!(
f,
" +-- PAGE #{} @{:#x} ({:#x})",
page_idx,
page.data.as_ref().unwrap().host_addr as u64,
desc
)?;
}
}
}
}
writeln!(f)
}
}
pub struct VirtMemAllocator {
pub(crate) upper_table: PageTableManager,
pub(crate) lower_table: PageTableManager,
}
impl Clone for VirtMemAllocator {
fn clone(&self) -> Self {
let pma = self.upper_table.slab.pma.clone();
let mut vma =
VirtMemAllocator::new(pma).expect("error occured while cloning VirtMemAllocator");
for (&addr, page) in self.lower_table.allocs.iter() {
let page = page.borrow();
vma.lower_table
.map(addr, VIRT_PAGE_SIZE, page.perms, page.privileged)
.expect("could not clone the lower memory mapping to the new address space");
let data = unsafe {
std::slice::from_raw_parts(page.data.as_ref().unwrap().host_addr, VIRT_PAGE_SIZE)
};
vma.write(addr as u64, data)
.expect("could not copy the memory mapping the new address space");
}
for (&addr, page) in self.upper_table.allocs.iter() {
let page = page.borrow();
vma.upper_table
.map(addr, VIRT_PAGE_SIZE, page.perms, page.privileged)
.expect("could not clone the upper memory mapping to the new address space");
let data = unsafe {
std::slice::from_raw_parts(page.data.as_ref().unwrap().host_addr, VIRT_PAGE_SIZE)
};
vma.write(addr as u64, data)
.expect("could not copy the memory mapping the new address space");
}
vma
}
}
impl VirtMemAllocator {
pub fn new(pma: PhysMemAllocator) -> Result<Self> {
let upper_table = PageTableManager::new(pma.clone())?;
let lower_table = PageTableManager::new(pma)?;
Ok(Self {
upper_table,
lower_table,
})
}
pub fn init(&mut self, vcpu: &mut av::Vcpu, map_exceptions: bool) -> Result<()> {
vcpu.set_sys_reg(av::SysReg::MAIR_EL1, 0xff)?;
vcpu.set_sys_reg(av::SysReg::MAIR_EL1, 0x44)?;
vcpu.set_sys_reg(
av::SysReg::TCR_EL1,
0x10 | (0x10 << 16) | (0b10 << 30) | (1 << 39) | (1 << 40),
)?;
self.set_trans_table_base_registers(vcpu)?;
vcpu.set_sys_reg(av::SysReg::SCTLR_EL1, 0x1005)?;
vcpu.set_sys_reg(av::SysReg::CPACR_EL1, 0x3 << 20)?;
vcpu.set_reg(av::Reg::CPSR, 0x3c0).unwrap();
if map_exceptions {
Exceptions::init(vcpu, self)?;
}
vcpu.set_trap_debug_exceptions(true)?;
vcpu.set_trap_debug_reg_accesses(true)?;
Ok(())
}
pub fn set_trans_table_base_registers(&self, vcpu: &av::Vcpu) -> Result<()> {
vcpu.set_sys_reg(
av::SysReg::TTBR1_EL1,
self.upper_table.pgd.entries.guest_addr as u64,
)?;
vcpu.set_sys_reg(
av::SysReg::TTBR0_EL1,
self.lower_table.pgd.entries.guest_addr as u64,
)?;
Ok(())
}
#[inline]
pub fn map(&mut self, addr: u64, size: usize, perms: av::MemPerms) -> Result<()> {
match addr >> 0x30 {
0x0000 => self.lower_table.map(addr, size, perms, false),
0xffff => self.upper_table.map(addr, size, perms, false),
_ => Err(MemoryError::InvalidAddress(addr))?,
}
}
#[inline]
pub fn map_privileged(&mut self, addr: u64, size: usize, perms: av::MemPerms) -> Result<()> {
match addr >> 0x30 {
0x0000 => self.lower_table.map(addr, size, perms, true),
0xffff => self.upper_table.map(addr, size, perms, true),
_ => Err(MemoryError::InvalidAddress(addr))?,
}
}
#[inline]
pub fn unmap(&mut self, addr: u64, size: usize) -> Result<()> {
match addr >> 0x30 {
0x0000 => self.lower_table.unmap(addr, size),
0xffff => self.upper_table.unmap(addr, size),
_ => Err(MemoryError::InvalidAddress(addr))?,
}
}
pub fn page_fault_dirty_state_handler(&mut self, far: u64) -> Result<bool> {
match far >> 0x30 {
0x0000 => self.lower_table.dirty_bit_handler(far),
0xffff => self.upper_table.dirty_bit_handler(far),
_ => Err(MemoryError::InvalidAddress(far))?,
}
}
pub fn restore_from_snapshot(&mut self, snapshot: &VirtMemAllocator) -> Result<()> {
enum RestoreOperation {
Map(u64, Rc<RefCell<Page>>),
Unmap(u64),
}
let mut operations = vec![];
let mut lower_curr_iter = self.lower_table.allocs.iter();
let mut lower_snap_iter = snapshot.lower_table.allocs.iter();
let mut lower_curr_elem = lower_curr_iter.next();
let mut lower_snap_elem = lower_snap_iter.next();
while lower_curr_elem.is_some() || lower_snap_elem.is_some() {
let (src_addr, src_page) = if let Some(lower_snap_val) = lower_snap_elem.as_mut() {
(*lower_snap_val.0, Some(&lower_snap_val.1))
} else {
(u64::MIN, None)
};
let (dst_addr, dst_page) = if let Some(lower_curr_val) = lower_curr_elem.as_mut() {
(*lower_curr_val.0, Some(&lower_curr_val.1))
} else {
(u64::MAX, None)
};
match dst_addr.cmp(&src_addr) {
Ordering::Less => {
operations.push(RestoreOperation::Unmap(dst_addr));
lower_curr_elem = lower_curr_iter.next();
}
Ordering::Greater => {
operations.push(RestoreOperation::Map(
src_addr,
Rc::clone(src_page.unwrap()),
));
lower_snap_elem = lower_snap_iter.next();
}
Ordering::Equal => {
let mut dst_page = dst_page.unwrap().borrow_mut();
if dst_page.dirty {
let src_page = src_page.unwrap().borrow();
unsafe {
std::ptr::copy(
src_page.data.as_ref().unwrap().host_addr,
dst_page.data.as_ref().unwrap().host_addr as *mut u8,
VIRT_PAGE_SIZE,
)
};
let pt_cell = dst_page.parent.upgrade().unwrap();
let mut pt = pt_cell.borrow_mut();
let page_idx = (src_addr >> 12 & 0x1ff) as usize;
dst_page.descriptor_in_use = src_page.descriptor_in_use;
dst_page.descriptor = src_page.descriptor;
PageTableManager::add_entry(
dst_page.descriptor_in_use.0,
page_idx,
&mut pt.entries,
)?;
dst_page.dirty = false;
}
lower_curr_elem = lower_curr_iter.next();
lower_snap_elem = lower_snap_iter.next();
}
}
}
for op in operations.into_iter() {
match op {
RestoreOperation::Map(src_addr, page) => {
let src_page = page.borrow();
self.lower_table.map(
src_addr,
VIRT_PAGE_SIZE,
src_page.perms,
src_page.privileged,
)?;
let data = unsafe {
std::slice::from_raw_parts(
src_page.data.as_ref().unwrap().host_addr,
VIRT_PAGE_SIZE,
)
};
self.write(src_addr, data)?;
}
RestoreOperation::Unmap(dst_addr) => {
self.lower_table.unmap(dst_addr, VIRT_PAGE_SIZE)?
}
}
}
let mut operations = vec![];
let mut upper_curr_iter = self.upper_table.allocs.iter();
let mut upper_snap_iter = snapshot.upper_table.allocs.iter();
let mut upper_curr_elem = upper_curr_iter.next();
let mut upper_snap_elem = upper_snap_iter.next();
while upper_curr_elem.is_some() || upper_snap_elem.is_some() {
let (src_addr, src_page) = if let Some(upper_snap_val) = upper_snap_elem.as_mut() {
(*upper_snap_val.0, Some(&upper_snap_val.1))
} else {
(u64::MIN, None)
};
let (dst_addr, dst_page) = if let Some(upper_curr_val) = upper_curr_elem.as_mut() {
(*upper_curr_val.0, Some(&upper_curr_val.1))
} else {
(u64::MAX, None)
};
match dst_addr.cmp(&src_addr) {
Ordering::Less => {
operations.push(RestoreOperation::Unmap(dst_addr));
upper_curr_elem = upper_curr_iter.next();
}
Ordering::Greater => {
operations.push(RestoreOperation::Map(
src_addr,
Rc::clone(src_page.unwrap()),
));
upper_snap_elem = upper_snap_iter.next();
}
Ordering::Equal => {
let mut dst_page = dst_page.unwrap().borrow_mut();
if dst_page.dirty {
let src_page = src_page.unwrap().borrow();
unsafe {
std::ptr::copy(
src_page.data.as_ref().unwrap().host_addr,
dst_page.data.as_ref().unwrap().host_addr as *mut u8,
VIRT_PAGE_SIZE,
)
};
let pt_cell = dst_page.parent.upgrade().unwrap();
let mut pt = pt_cell.borrow_mut();
let page_idx = (src_addr >> 12 & 0x1ff) as usize;
dst_page.descriptor_in_use = src_page.descriptor_in_use;
dst_page.descriptor = src_page.descriptor;
PageTableManager::add_entry(
dst_page.descriptor_in_use.0,
page_idx,
&mut pt.entries,
)?;
dst_page.dirty = false;
}
upper_curr_elem = upper_curr_iter.next();
upper_snap_elem = upper_snap_iter.next();
}
}
}
for op in operations.into_iter() {
match op {
RestoreOperation::Map(src_addr, page) => {
let src_page = page.borrow();
self.upper_table.map(
src_addr,
VIRT_PAGE_SIZE,
src_page.perms,
src_page.privileged,
)?;
let data = unsafe {
std::slice::from_raw_parts(
src_page.data.as_ref().unwrap().host_addr,
VIRT_PAGE_SIZE,
)
};
self.write(src_addr, data)?;
}
RestoreOperation::Unmap(dst_addr) => {
self.upper_table.unmap(dst_addr, VIRT_PAGE_SIZE)?
}
}
}
Ok(())
}
pub fn read(&self, addr: u64, buf: &mut [u8]) -> Result<usize> {
let mut read_size = 0;
let mut addr = addr;
let end_addr = addr
.checked_add(buf.len() as u64)
.ok_or(MemoryError::Overflow(addr, buf.len()))?;
loop {
let page_start = align_virt_page!(addr);
let page_end = page_start
.checked_add(VIRT_PAGE_SIZE as u64)
.ok_or(MemoryError::Overflow(page_start, VIRT_PAGE_SIZE))?;
let offset_start = (addr - page_start) as usize;
let offset_end = if page_end >= end_addr {
(end_addr - page_start) as usize
} else {
VIRT_PAGE_SIZE
};
let page = match page_start >> 0x30 {
0x0000 => self.lower_table.get_page_by_addr(page_start),
0xffff => self.upper_table.get_page_by_addr(page_start),
_ => Err(MemoryError::InvalidAddress(addr))?,
}?;
unsafe {
std::ptr::copy(
page.borrow()
.data
.as_ref()
.unwrap()
.host_addr
.add(offset_start),
buf.as_mut_ptr().add(read_size),
offset_end - offset_start,
);
}
read_size += offset_end - offset_start;
addr = page_end;
if page_end >= end_addr {
break;
}
}
Ok(buf.len())
}
#[inline]
pub fn read_byte(&self, addr: u64) -> Result<u8> {
let mut data = [0u8; 1];
self.read(addr, &mut data)?;
Ok(data[0])
}
#[inline]
pub fn read_word(&self, addr: u64) -> Result<u16> {
let mut data = [0u8; 2];
self.read(addr, &mut data)?;
Ok(u16::from_le_bytes(data[..2].try_into().unwrap()))
}
#[inline]
pub fn read_dword(&self, addr: u64) -> Result<u32> {
let mut data = [0u8; 4];
self.read(addr, &mut data)?;
Ok(u32::from_le_bytes(data[..4].try_into().unwrap()))
}
#[inline]
pub fn read_qword(&self, addr: u64) -> Result<u64> {
let mut data = [0u8; 8];
self.read(addr, &mut data)?;
Ok(u64::from_le_bytes(data[..8].try_into().unwrap()))
}
#[inline]
pub fn read_cstring(&self, addr: u64) -> Result<String> {
let mut chars = vec![];
let mut c = self.read_byte(addr)?;
let mut offset = 0;
while c != 0 {
chars.push(c);
offset += 1;
c = self.read_byte(addr + offset)?;
}
Ok(String::from_utf8_lossy(&chars).to_string())
}
fn write_inner(&mut self, addr: u64, buf: &[u8], dirty: bool) -> Result<usize> {
let mut written_size = 0;
let mut addr = addr;
let end_addr = addr
.checked_add(buf.len() as u64)
.ok_or(MemoryError::Overflow(addr, buf.len()))?;
loop {
let page_start = align_virt_page!(addr);
let page_end = page_start
.checked_add(VIRT_PAGE_SIZE as u64)
.ok_or(MemoryError::Overflow(page_start, VIRT_PAGE_SIZE))?;
let offset_start = (addr - page_start) as usize;
let offset_end = if page_end >= end_addr {
(end_addr - page_start) as usize
} else {
VIRT_PAGE_SIZE
};
let page = match page_start >> 0x30 {
0x0000 => self.lower_table.get_page_by_addr(page_start),
0xffff => self.upper_table.get_page_by_addr(page_start),
_ => Err(MemoryError::InvalidAddress(addr))?,
}?;
let mut page_b = page.borrow_mut();
unsafe {
if dirty {
page_b.dirty = true;
}
std::ptr::copy(
buf.as_ptr().add(written_size),
page_b.data.as_ref().unwrap().host_addr.add(offset_start) as *mut u8,
offset_end - offset_start,
);
}
written_size += offset_end - offset_start;
addr = page_end;
if page_end >= end_addr {
break;
}
}
Ok(buf.len())
}
#[inline]
pub fn write(&mut self, addr: u64, buf: &[u8]) -> Result<usize> {
self.write_inner(addr, buf, false)
}
#[inline]
pub fn write_byte(&mut self, addr: u64, data: u8) -> Result<usize> {
self.write(addr, &[data])
}
#[inline]
pub fn write_word(&mut self, addr: u64, data: u16) -> Result<usize> {
self.write(addr, &data.to_le_bytes())
}
#[inline]
pub fn write_dword(&mut self, addr: u64, data: u32) -> Result<usize> {
self.write(addr, &data.to_le_bytes())
}
#[inline]
pub fn write_qword(&mut self, addr: u64, data: u64) -> Result<usize> {
self.write(addr, &data.to_le_bytes())
}
#[inline]
pub fn write_cstring(&mut self, addr: u64, s: &str) -> Result<usize> {
for (i, c) in s.chars().enumerate() {
self.write_byte(addr + i as u64, c as u8)?;
}
self.write_byte(addr + s.len() as u64, 0)?;
Ok(s.len())
}
#[inline]
pub fn write_dirty(&mut self, addr: u64, buf: &[u8]) -> Result<usize> {
self.write_inner(addr, buf, true)
}
#[inline]
pub fn write_byte_dirty(&mut self, addr: u64, data: u8) -> Result<usize> {
self.write_dirty(addr, &[data])
}
#[inline]
pub fn write_word_dirty(&mut self, addr: u64, data: u16) -> Result<usize> {
self.write_dirty(addr, &data.to_le_bytes())
}
#[inline]
pub fn write_dword_dirty(&mut self, addr: u64, data: u32) -> Result<usize> {
self.write_dirty(addr, &data.to_le_bytes())
}
#[inline]
pub fn write_qword_dirty(&mut self, addr: u64, data: u64) -> Result<usize> {
self.write_dirty(addr, &data.to_le_bytes())
}
#[inline]
pub fn write_cstring_dirty(&mut self, addr: u64, s: &str) -> Result<usize> {
for (i, c) in s.chars().enumerate() {
self.write_byte_dirty(addr + i as u64, c as u8)?;
}
self.write_byte_dirty(addr + s.len() as u64, 0)?;
Ok(s.len())
}
pub fn mem_hexdump(&self, outfile: impl AsRef<Path>) -> Result<()> {
let mut f = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(outfile)?;
for (addr, _) in self.lower_table.allocs.iter() {
let mut data = vec![0; VIRT_PAGE_SIZE];
self.read(*addr, &mut data)?;
writeln!(f, "{:#x}", *addr)?;
writeln!(f, "{}", rh::hexdump_offset(&data, *addr as u32))?;
}
for (addr, _) in self.upper_table.allocs.iter() {
let mut data = vec![0; VIRT_PAGE_SIZE];
self.read(*addr, &mut data)?;
writeln!(f, "{:#x}", *addr)?;
writeln!(f, "{}", rh::hexdump_offset(&data, *addr as u32))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn macro_page_round_align() {
assert_eq!(round_phys_page!(0x1234567), 0x1240000);
assert_eq!(align_phys_page!(0x1234567), 0x1230000);
assert_eq!(round_virt_page!(0x1234567), 0x1235000);
assert_eq!(align_virt_page!(0x1234567), 0x1234000);
}
#[test]
fn page_table_table_descriptor() {
let mut td = TableDescriptor(0);
td.set_aptable(2);
assert_eq!(td.0, 2 << 61);
}
#[test]
fn page_table_read_only_descriptor() {
let ro_ap = vec![0, 3, 2, 3, 2, 3, 2, 3];
for (i, expected_ap) in ro_ap.into_iter().enumerate() {
let ap = (i & 3) as u64;
let privileged = if (i >> 2) & 1 == 1 { true } else { false };
let mut d = PageDescriptor(0);
d.set_ap(ap);
let ro = d.read_only(privileged);
assert_eq!(expected_ap, ro.get_ap());
}
}
}