Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions lib/propolis/src/util/aspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ impl<T> ASpace<T> {
}
}

// Compute the end of the inclusive range beginning at `start` and covering
// `len` address space.
fn safe_end(start: usize, len: usize) -> Option<usize> {
if len == 0 {
None
Expand All @@ -181,6 +183,25 @@ fn safe_end(start: usize, len: usize) -> Option<usize> {
}
}

#[test]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file has a test submodule, shouldn't this be in there?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was on the fence about that, usually i think about mod test { ... } as more comprehensive tests for either a composition of items in super or at least pulling in "a bunch" of stuff. this is just "the function directly above this works in these ways" so moving it feels like it's easier to miss the examples?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷‍♀️ fair enough, normally i feel like i expect to see every test in mod test but i don't actually care about this

fn safe_end_bounds() {
// An inclusive region of size zero is nonsense.
assert_eq!(safe_end(0, 0), None);
assert_eq!(safe_end(1, 0), None);

// An inclusive region of size one can exist anywhere.
assert_eq!(safe_end(0, 1), Some(0));
assert_eq!(safe_end(16, 1), Some(16));
assert_eq!(safe_end(usize::MAX, 1), Some(usize::MAX));

// Given `[0, usize::MAX]` as possible addresses, the size of that set is
// actually `usize::MAX + 1`. This means:
// * there is no `len` that covers the entire possible span
// * start=0, len=usize::MAX is a span that ends at usize::MAX-1
assert_eq!(safe_end(0, usize::MAX), Some(usize::MAX - 1));
assert_eq!(safe_end(1, usize::MAX), Some(usize::MAX));
}

// Flatten the K/V nested tuple
fn kv_flatten<'a, T>(i: (&'a usize, &'a (usize, T))) -> SpaceItem<'a, T> {
let start = *i.0;
Expand Down
40 changes: 39 additions & 1 deletion lib/propolis/src/vmm/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1167,8 +1167,14 @@ impl MemCtx {
len: usize,
) -> Option<(SubMapping<'_>, SubMapping<'_>)> {
let start = addr.0 as usize;
let end = start + len;
let Some(end) = start.checked_add(len) else {
// The mappings in `self.map` do not wrap, so no mapping can match
// with a region wrapping at the end of the address space.
return None;
};
if let Ok((addr, rlen, ent)) = self.map.region_at(start) {
// Unlike start+len before, we know `addr + rlen` cannot overflow:
// if it would, ASpace::register would have rejected this region.
if addr + rlen < end {
return None;
}
Expand Down Expand Up @@ -1437,4 +1443,36 @@ pub mod test {
assert!(sub_write.write_bytes(&buf).is_ok());
assert!(sub_write.read_bytes(&mut buf).is_err());
}

// Tests above cover lookups inside one mapping, but Propolis uses memory
// through a `MemCtx` including an `ASpace<MapEnt>` covering all mappings
// for the VM.
#[test]
fn region_lookup() {
let hdl = VmmHdl::new_test(TEST_LEN)
.expect("create tempfile backed test hdl");
let hdl = Arc::new(hdl);

let mut phys = PhysMap::new(TEST_LEN, hdl);
phys.add_mem("test dram".to_string(), 0, PAGE_SIZE)
.expect("can add test DRAM");
let mem = phys.finalize();

let acc_mem = mem.access().expect("can access memory");

// We can get a readable region covering all added memory.
let region =
acc_mem.readable_region(&GuestRegion(GuestAddr(0), PAGE_SIZE));
assert!(region.is_some());

// But not a region extending past memory in the VM.
let region =
acc_mem.readable_region(&GuestRegion(GuestAddr(0), 2 * PAGE_SIZE));
assert!(region.is_none());

// And not a region that would wrap into VM memory.
let region = acc_mem
.readable_region(&GuestRegion(GuestAddr(u64::MAX), PAGE_SIZE));
assert!(region.is_none());
}
}
Loading