From 3625aef40f011df9531258c20a6a1d60e21f9bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Fri, 25 Sep 2026 11:02:06 +0200 Subject: [PATCH 1/5] Add `checked_add()` and `checked_sub()` to `VirtAddr` and `PhysAddr` Note that this does not add a checked version for subtracting two addresses yet. --- Changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index 22a913b1..232464ec 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ # Unreleased +## New Features + +- [Add `checked_add()` and `checked_sub()` to `VirtAddr` and `PhysAddr`](https://github.com/rust-osdev/x86_64/pull/618) + # 0.16.0-rc.0 – 2026-09-24 ## Breaking changes From 5009ecae901381d605417493dc973a7dfb925320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Fri, 25 Sep 2026 11:13:43 +0200 Subject: [PATCH 2/5] Add `checked_add()` and `checked_sub()` to `Page` and `PhysFrame` Note that this changes the panic behavior, since `rhs * S::SIZE` did not panic before without overflow checks. Note that this does not add a checked version for subtracting two pages or frames yet. --- Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 232464ec..b0717fa2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,7 @@ ## New Features -- [Add `checked_add()` and `checked_sub()` to `VirtAddr` and `PhysAddr`](https://github.com/rust-osdev/x86_64/pull/618) +- [Add `checked_add()` and `checked_sub()` to `VirtAddr`, `PhysAddr`, `Page`, and `PhysFrame`](https://github.com/rust-osdev/x86_64/pull/618) # 0.16.0-rc.0 – 2026-09-24 From 66a12ae8c067501694e0bfc8cfa79ea7a8ee4b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Fri, 25 Sep 2026 11:16:54 +0200 Subject: [PATCH 3/5] Add `#[track_caller]` to `MappedPage::add()` and `MappedPageItem::add()` --- Changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index b0717fa2..bc1351bf 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,10 @@ - [Add `checked_add()` and `checked_sub()` to `VirtAddr`, `PhysAddr`, `Page`, and `PhysFrame`](https://github.com/rust-osdev/x86_64/pull/618) +## Fixes + +- [Add `#[track_caller]` to `MappedPage::add()` and `MappedPageItem::add()`](https://github.com/rust-osdev/x86_64/pull/618) + # 0.16.0-rc.0 – 2026-09-24 ## Breaking changes From ce84e0052bf0ee1761e6eec382409a82d3f5f206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Sun, 11 Oct 2026 15:21:03 +0200 Subject: [PATCH 4/5] Extract `Page::backward_checked_impl()` --- src/structures/paging/page.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/structures/paging/page.rs b/src/structures/paging/page.rs index 67be7942..47820726 100644 --- a/src/structures/paging/page.rs +++ b/src/structures/paging/page.rs @@ -220,6 +220,17 @@ impl Page { size: PhantomData, }) } + + // FIXME: Move this into the `Step` impl, once `Step` is stabilized. + #[cfg(feature = "step_trait")] + pub(crate) fn backward_checked_impl(start: Self, count: usize) -> Option { + let count = u64::try_from(count).ok()?.checked_mul(S::SIZE)?; + let start_address = VirtAddr::backward_checked_u64(start.start_address, count)?; + Some(Self { + start_address, + size: PhantomData, + }) + } } impl Page { @@ -351,14 +362,7 @@ impl Step for Page { } fn backward_checked(start: Self, count: usize) -> Option { - use core::convert::TryFrom; - - let count = u64::try_from(count).ok()?.checked_mul(S::SIZE)?; - let start_address = VirtAddr::backward_checked_u64(start.start_address, count)?; - Some(Self { - start_address, - size: PhantomData, - }) + Self::backward_checked_impl(start, count) } fn forward_overflowing(start: Self, count: usize) -> (Self, bool) { From 62ace8afbd4e9c6d9abf8aa7fae8442143d985f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Fri, 25 Sep 2026 11:59:16 +0200 Subject: [PATCH 5/5] Make ranges jump address space gaps --- Changelog.md | 4 + src/addr.rs | 1 - src/structures/paging/page.rs | 179 ++++++++-------------------------- 3 files changed, 44 insertions(+), 140 deletions(-) diff --git a/Changelog.md b/Changelog.md index bc1351bf..30b36884 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ # Unreleased +## Breaking changes + +- [Range types (`PageRange`, `PhysFrameRange`, `PageRangeInclusive`, and `PhysFrameRangeInclusive`) now jump the address space gap instead of panicking](https://github.com/rust-osdev/x86_64/pull/618) + ## New Features - [Add `checked_add()` and `checked_sub()` to `VirtAddr`, `PhysAddr`, `Page`, and `PhysFrame`](https://github.com/rust-osdev/x86_64/pull/618) diff --git a/src/addr.rs b/src/addr.rs index 9c7efb8f..9a50aaa8 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -352,7 +352,6 @@ impl VirtAddr { } /// An implementation of backward_checked that takes u64 instead of usize. - #[cfg(feature = "step_trait")] #[inline] pub(crate) fn backward_checked_u64(start: Self, count: u64) -> Option { if count > ADDRESS_SPACE_SIZE { diff --git a/src/structures/paging/page.rs b/src/structures/paging/page.rs index 47820726..c4b74ac6 100644 --- a/src/structures/paging/page.rs +++ b/src/structures/paging/page.rs @@ -208,10 +208,6 @@ impl Page { } // FIXME: Move this into the `Step` impl, once `Step` is stabilized. - #[cfg(any( - all(feature = "instructions", target_arch = "x86_64"), - feature = "step_trait" - ))] pub(crate) fn forward_checked_impl(start: Self, count: usize) -> Option { let count = u64::try_from(count).ok()?.checked_mul(S::SIZE)?; let start_address = VirtAddr::forward_checked_u64(start.start_address, count)?; @@ -222,7 +218,6 @@ impl Page { } // FIXME: Move this into the `Step` impl, once `Step` is stabilized. - #[cfg(feature = "step_trait")] pub(crate) fn backward_checked_impl(start: Self, count: usize) -> Option { let count = u64::try_from(count).ok()?.checked_mul(S::SIZE)?; let start_address = VirtAddr::backward_checked_u64(start.start_address, count)?; @@ -434,7 +429,7 @@ impl Iterator for PageRangeIter { fn next(&mut self) -> Option { if self.0.start < self.0.end { let page = self.0.start; - self.0.start += 1; + self.0.start = Page::forward_checked_impl(self.0.start, 1)?; Some(page) } else { None @@ -487,7 +482,7 @@ impl DoubleEndedIterator for PageRangeIter { #[inline] fn next_back(&mut self) -> Option { if self.0.start < self.0.end { - self.0.end -= 1; + self.0.end = Page::backward_checked_impl(self.0.end, 1)?; Some(self.0.end) } else { None @@ -609,9 +604,9 @@ impl Iterator for PageRangeInclusiveIter { // So instead, in that case we decrement end rather than incrementing start. let max_page_addr = VirtAddr::new(u64::MAX) - (S::SIZE - 1); if self.0.start.start_address() < max_page_addr { - self.0.start += 1; + self.0.start = Page::forward_checked_impl(self.0.start, 1)?; } else { - self.0.end -= 1; + self.0.end = Page::backward_checked_impl(self.0.end, 1)?; } Some(page) } else { @@ -669,9 +664,9 @@ impl DoubleEndedIterator for PageRangeInclusiveIter { // If the start of the inclusive range is 0, decrementing end until // it is smaller than the start will cause an integer underflow. - // So instead, in that case we increment start rather than decrementing end. - if self.0.end.start_address().as_u64() != 0 { - self.0.end -= 1; + // In that case we increment start rather than decrementing end. + if let Some(end) = Page::backward_checked_impl(self.0.end, 1) { + self.0.end = end; } else { self.0.start += 1; } @@ -803,79 +798,81 @@ mod tests { } #[test] - #[should_panic = "attempt to add with overflow or resulted in non-canonical virtual address"] - fn test_page_range_next_jumping_gap_panics() { + fn test_page_range_next_jumping_gap() { let start = 0x7fff_ffff_f000; let end = 0xffff_8000_0000_0000; let start = VirtAddr::new(start); let end = VirtAddr::new(end); let start = Page::::from_start_address(start).unwrap(); let end = Page::from_start_address(end).unwrap(); - Page::range(start, end).into_iter().next(); + assert_eq!(Page::range(start, end).into_iter().next(), Some(start)); } - // TODO: This probably shouldn't panic, but we can't fix this without a breaking change. #[test] - #[should_panic = "attempt to subtract with overflow or resulted in non-canonical virtual address"] - fn test_page_range_next_back_jumping_gap_panics() { + fn test_page_range_next_back_jumping_gap() { let start = 0x7fff_ffff_f000; let end = 0xffff_8000_0000_0000; let start = VirtAddr::new(start); let end = VirtAddr::new(end); let start = Page::::from_start_address(start).unwrap(); let end = Page::from_start_address(end).unwrap(); - Page::range(start, end).into_iter().next_back(); + assert_eq!(Page::range(start, end).into_iter().next_back(), Some(start)); } #[test] - #[should_panic = "attempt to add with overflow or resulted in non-canonical virtual address"] - fn test_page_range_inclusive_next_not_jumping_gap_panics() { + fn test_page_range_inclusive_next_not_jumping_gap() { let start = 0x7fff_ffff_f000; let end = 0x7fff_ffff_f000; let start = VirtAddr::new(start); let end = VirtAddr::new(end); let start = Page::::from_start_address(start).unwrap(); let end = Page::from_start_address(end).unwrap(); - Page::range_inclusive(start, end).into_iter().next(); + assert_eq!( + Page::range_inclusive(start, end).into_iter().next(), + Some(start) + ); } #[test] - #[should_panic = "attempt to subtract with overflow or resulted in non-canonical virtual address"] - fn test_page_range_inclusive_next_back_not_jumping_gap_panics() { + fn test_page_range_inclusive_next_back_not_jumping_gap() { let start = 0x7fff_ffff_f000; let end = 0xffff_8000_0000_0000; let start = VirtAddr::new(start); let end = VirtAddr::new(end); let start = Page::::from_start_address(start).unwrap(); let end = Page::from_start_address(end).unwrap(); - Page::range_inclusive(start, end).into_iter().next_back(); + assert_eq!( + Page::range_inclusive(start, end).into_iter().next_back(), + Some(end) + ); } - // TODO: This probably shouldn't panic, but we can't fix this without a breaking change. #[test] - #[should_panic = "attempt to add with overflow or resulted in non-canonical virtual address"] - fn test_page_range_inclusive_next_jumping_gap_panics() { + fn test_page_range_inclusive_next_jumping_gap() { let start = 0x7fff_ffff_f000; let end = 0x7fff_ffff_f000; let start = VirtAddr::new(start); let end = VirtAddr::new(end); let start = Page::::from_start_address(start).unwrap(); let end = Page::from_start_address(end).unwrap(); - Page::range_inclusive(start, end).into_iter().next(); - Page::range_inclusive(start, end).into_iter().next(); + assert_eq!( + Page::range_inclusive(start, end).into_iter().next(), + Some(start) + ); } #[test] - #[should_panic = "attempt to subtract with overflow or resulted in non-canonical virtual address"] - fn test_page_range_inclusive_next_back_jumping_gap_panics() { + fn test_page_range_inclusive_next_back_jumping_gap() { let start = 0xffff_8000_0000_0000; let end = 0xffff_8000_0000_0000; let start = VirtAddr::new(start); let end = VirtAddr::new(end); let start = Page::::from_start_address(start).unwrap(); let end = Page::from_start_address(end).unwrap(); - Page::range_inclusive(start, end).into_iter().next_back(); - Page::range_inclusive(start, end).into_iter().next_back(); + assert_eq!( + Page::range_inclusive(start, end).into_iter().next_back(), + Some(end) + ); } #[test] @@ -1020,25 +1017,12 @@ mod tests { mod proofs { use super::*; - fn page_range_next_harness(should_panic_mode: bool) { + #[kani::proof] + fn page_range_next() { let start = kani::any::>(); let end = kani::any::>(); - // If the code is expected to panic, only run it in `#[should_panic]` - // mode. - let should_panic = start.start_address().as_u64() != 0x7fff_ffff_e000 - || start.start_address().as_u64() != 0x7fff_ffff_f000; - kani::assume(should_panic == should_panic_mode); - - if should_panic { - // Calling `next` should panic. - let mut our_range_iter = Page::range(start, end).into_iter(); - our_range_iter.next(); - our_range_iter.next(); - return; - } - - // Otherwise the results should match what `Range` returns. + // The results should match what `Range` returns. let mut our_range_iter = Page::range(start, end).into_iter(); let mut native_range = start..end; // The first assert checks that we're returning the correct value. @@ -1048,35 +1032,11 @@ mod proofs { } #[kani::proof] - fn page_range_next() { - page_range_next_harness(false); - } - - #[kani::proof] - #[kani::should_panic] - fn page_range_next_panic() { - page_range_next_harness(true); - } - - fn page_range_next_back_harness(should_panic_mode: bool) { + fn page_range_next_back_harness() { let start = kani::any::>(); let end = kani::any::>(); - // If the code is expected to panic, only run it in `#[should_panic]` - // mode. - let should_panic = start.start_address().as_u64() != 0xffff_8000_0000_0000 - || start.start_address().as_u64() != 0xffff_8000_0000_1000; - kani::assume(should_panic == should_panic_mode); - - if should_panic { - // Calling `next_back` should panic. - let mut our_range_iter = Page::range(start, end).into_iter(); - our_range_iter.next_back(); - our_range_iter.next_back(); - return; - } - - // Otherwise the results should match what `Range` returns. + // The results should match what `Range` returns. let mut our_range_iter = Page::range(start, end).into_iter(); let mut native_range = start..end; // The first assert checks that we're returning the correct value. @@ -1086,35 +1046,11 @@ mod proofs { } #[kani::proof] - fn page_range_next_back() { - page_range_next_back_harness(false); - } - - #[kani::proof] - #[kani::should_panic] - fn page_range_next_back_panic() { - page_range_next_back_harness(true); - } - - fn page_range_inclusive_next_harness(should_panic_mode: bool) { + fn page_range_inclusive_next_harness() { let start = kani::any::>(); let end = kani::any::>(); - // If the code is expected to panic, only run it in `#[should_panic]` - // mode. - let should_panic = start.start_address().as_u64() != 0x7fff_ffff_e000 - || start.start_address().as_u64() != 0x7fff_ffff_f000; - kani::assume(should_panic == should_panic_mode); - - if should_panic { - // Calling `next` should panic. - let mut our_range_iter = Page::range_inclusive(start, end).into_iter(); - our_range_iter.next(); - our_range_iter.next(); - return; - } - - // Otherwise the results should match what `Range` returns. + // The results should match what `Range` returns. let mut our_range_iter = Page::range_inclusive(start, end).into_iter(); let mut native_range = start..=end; // The first assert checks that we're returning the correct value. @@ -1124,35 +1060,11 @@ mod proofs { } #[kani::proof] - fn page_range_inclusive_next() { - page_range_inclusive_next_harness(false); - } - - #[kani::proof] - #[kani::should_panic] - fn page_range_inclusive_next_panic() { - page_range_inclusive_next_harness(true); - } - - fn page_range_inclusive_next_back_harness(should_panic_mode: bool) { + fn page_range_inclusive_next_back() { let start = kani::any::>(); let end = kani::any::>(); - // If the code is expected to panic, only run it in `#[should_panic]` - // mode. - let should_panic = start.start_address().as_u64() != 0xffff_8000_0000_0000 - || start.start_address().as_u64() != 0xffff_8000_0000_1000; - kani::assume(should_panic == should_panic_mode); - - if should_panic { - // Calling `next_back` should panic. - let mut our_range_iter = Page::range_inclusive(start, end).into_iter(); - our_range_iter.next_back(); - our_range_iter.next_back(); - return; - } - - // Otherwise the results should match what `Range` returns. + // The results should match what `Range` returns. let mut our_range_iter = Page::range_inclusive(start, end).into_iter(); let mut native_range = start..=end; // The first assert checks that we're returning the correct value. @@ -1161,17 +1073,6 @@ mod proofs { assert_eq!(our_range_iter.next_back(), native_range.next_back()); } - #[kani::proof] - fn page_range_inclusive_next_back() { - page_range_inclusive_next_back_harness(false); - } - - #[kani::proof] - #[kani::should_panic] - fn page_range_inclusive_next_back_panic() { - page_range_inclusive_next_back_harness(true); - } - fn page_range_nth_harness(should_panic_mode: bool) { let start = kani::any::(); let end = kani::any::();