Skip to content

Commit b4b6663

Browse files
committed
fix Page Step impl on 32-bit platforms
Previously, we scaled count as a usize, not a u64. This is bad because stepping forward by 0x10_0000 is perfectly fine, yet we were always rejecting this because 0x10_0000 * 0x1000 doesn't fit in usize on 32-bit platforms. Similarly, we would fail to return a step count above or equal to 0x10_0000 because the call to <VirtAddr as Step>::steps_between would fail. Never scale usize values, instead, always scale u64 values. To make this easier to work with, this patch adds variants of the Step functions to VirtAddr that take and return u64 instead of usize. This patch also adds some regression tests.
1 parent 8fb0668 commit b4b6663

File tree

2 files changed

+189
-38
lines changed

2 files changed

+189
-38
lines changed

src/addr.rs

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -240,30 +240,43 @@ impl VirtAddr {
240240
}
241241

242242
// FIXME: Move this into the `Step` impl, once `Step` is stabilized.
243-
#[cfg(any(feature = "instructions", feature = "step_trait"))]
243+
#[cfg(feature = "step_trait")]
244244
pub(crate) fn steps_between_impl(start: &Self, end: &Self) -> (usize, Option<usize>) {
245-
let mut steps = if let Some(steps) = end.0.checked_sub(start.0) {
246-
steps
245+
if let Some(steps) = Self::steps_between_u64(start, end) {
246+
let steps = usize::try_from(steps).ok();
247+
(steps.unwrap_or(usize::MAX), steps)
247248
} else {
248-
return (0, None);
249-
};
249+
(0, None)
250+
}
251+
}
252+
253+
/// An implementation of steps_between that returns u64. Note that this
254+
/// function always returns the exact bound, so it doesn't need to return a
255+
/// lower and upper bound like steps_between does.
256+
#[cfg(any(feature = "instructions", feature = "step_trait"))]
257+
pub(crate) fn steps_between_u64(start: &Self, end: &Self) -> Option<u64> {
258+
let mut steps = end.0.checked_sub(start.0)?;
250259

251260
// Mask away extra bits that appear while jumping the gap.
252261
steps &= 0xffff_ffff_ffff;
253262

254-
let steps = usize::try_from(steps).ok();
255-
(steps.unwrap_or(usize::MAX), steps)
263+
Some(steps)
256264
}
257265

258266
// FIXME: Move this into the `Step` impl, once `Step` is stabilized.
259267
#[inline]
260268
pub(crate) fn forward_checked_impl(start: Self, count: usize) -> Option<Self> {
261-
let offset = u64::try_from(count).ok()?;
262-
if offset > ADDRESS_SPACE_SIZE {
269+
Self::forward_checked_u64(start, u64::try_from(count).ok()?)
270+
}
271+
272+
/// An implementation of forward_checked that takes u64 instead of usize.
273+
#[inline]
274+
pub(crate) fn forward_checked_u64(start: Self, count: u64) -> Option<Self> {
275+
if count > ADDRESS_SPACE_SIZE {
263276
return None;
264277
}
265278

266-
let mut addr = start.0.checked_add(offset)?;
279+
let mut addr = start.0.checked_add(count)?;
267280

268281
match addr.get_bits(47..) {
269282
0x1 => {
@@ -279,6 +292,31 @@ impl VirtAddr {
279292

280293
Some(unsafe { Self::new_unsafe(addr) })
281294
}
295+
296+
/// An implementation of backward_checked that takes u64 instead of usize.
297+
#[cfg(feature = "step_trait")]
298+
#[inline]
299+
pub(crate) fn backward_checked_u64(start: Self, count: u64) -> Option<Self> {
300+
if count > ADDRESS_SPACE_SIZE {
301+
return None;
302+
}
303+
304+
let mut addr = start.0.checked_sub(count)?;
305+
306+
match addr.get_bits(47..) {
307+
0x1fffe => {
308+
// Jump the gap by sign extending the 47th bit.
309+
addr.set_bits(47.., 0);
310+
}
311+
0x1fffd => {
312+
// Address underflow
313+
return None;
314+
}
315+
_ => {}
316+
}
317+
318+
Some(unsafe { Self::new_unsafe(addr) })
319+
}
282320
}
283321

284322
impl fmt::Debug for VirtAddr {
@@ -376,26 +414,7 @@ impl Step for VirtAddr {
376414

377415
#[inline]
378416
fn backward_checked(start: Self, count: usize) -> Option<Self> {
379-
let offset = u64::try_from(count).ok()?;
380-
if offset > ADDRESS_SPACE_SIZE {
381-
return None;
382-
}
383-
384-
let mut addr = start.0.checked_sub(offset)?;
385-
386-
match addr.get_bits(47..) {
387-
0x1fffe => {
388-
// Jump the gap by sign extending the 47th bit.
389-
addr.set_bits(47.., 0);
390-
}
391-
0x1fffd => {
392-
// Address underflow
393-
return None;
394-
}
395-
_ => {}
396-
}
397-
398-
Some(unsafe { Self::new_unsafe(addr) })
417+
Self::backward_checked_u64(start, u64::try_from(count).ok()?)
399418
}
400419
}
401420

@@ -779,6 +798,21 @@ mod tests {
779798
),
780799
(0, None)
781800
);
801+
// Make sure that we handle `steps > u32::MAX` correctly on 32-bit
802+
// targets. On 64-bit targets, `0x1_0000_0000` fits into `usize`, so we
803+
// can return exact lower and upper bounds. On 32-bit targets,
804+
// `0x1_0000_0000` doesn't fit into `usize`, so we only return an lower
805+
// bound of `usize::MAX` and don't return an upper bound.
806+
#[cfg(target_pointer_width = "64")]
807+
assert_eq!(
808+
Step::steps_between(&VirtAddr(0), &VirtAddr(0x1_0000_0000)),
809+
(0x1_0000_0000, Some(0x1_0000_0000))
810+
);
811+
#[cfg(not(target_pointer_width = "64"))]
812+
assert_eq!(
813+
Step::steps_between(&VirtAddr(0), &VirtAddr(0x1_0000_0000)),
814+
(usize::MAX, None)
815+
);
782816
}
783817

784818
#[test]

src/structures/paging/page.rs

Lines changed: 125 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,26 @@ impl<S: PageSize> Page<S> {
161161
// FIXME: Move this into the `Step` impl, once `Step` is stabilized.
162162
#[cfg(any(feature = "instructions", feature = "step_trait"))]
163163
pub(crate) fn steps_between_impl(start: &Self, end: &Self) -> (usize, Option<usize>) {
164-
let (lower, upper) = VirtAddr::steps_between_impl(&start.start_address, &end.start_address);
165-
let lower = lower / S::SIZE as usize;
166-
let upper = upper.map(|steps| steps / S::SIZE as usize);
167-
(lower, upper)
164+
use core::convert::TryFrom;
165+
166+
if let Some(steps) =
167+
VirtAddr::steps_between_u64(&start.start_address(), &end.start_address())
168+
{
169+
let steps = steps / S::SIZE;
170+
let steps = usize::try_from(steps).ok();
171+
(steps.unwrap_or(usize::MAX), steps)
172+
} else {
173+
(0, None)
174+
}
168175
}
169176

170177
// FIXME: Move this into the `Step` impl, once `Step` is stabilized.
171178
#[cfg(any(feature = "instructions", feature = "step_trait"))]
172179
pub(crate) fn forward_checked_impl(start: Self, count: usize) -> Option<Self> {
173-
let count = count.checked_mul(S::SIZE as usize)?;
174-
let start_address = VirtAddr::forward_checked_impl(start.start_address, count)?;
180+
use core::convert::TryFrom;
181+
182+
let count = u64::try_from(count).ok()?.checked_mul(S::SIZE)?;
183+
let start_address = VirtAddr::forward_checked_u64(start.start_address, count)?;
175184
Some(Self {
176185
start_address,
177186
size: PhantomData,
@@ -304,8 +313,10 @@ impl<S: PageSize> Step for Page<S> {
304313
}
305314

306315
fn backward_checked(start: Self, count: usize) -> Option<Self> {
307-
let count = count.checked_mul(S::SIZE as usize)?;
308-
let start_address = Step::backward_checked(start.start_address, count)?;
316+
use core::convert::TryFrom;
317+
318+
let count = u64::try_from(count).ok()?.checked_mul(S::SIZE)?;
319+
let start_address = VirtAddr::backward_checked_u64(start.start_address, count)?;
309320
Some(Self {
310321
start_address,
311322
size: PhantomData,
@@ -531,4 +542,110 @@ mod tests {
531542
let range_inclusive = PageRangeInclusive { start, end };
532543
assert_eq!(range_inclusive.len(), 51);
533544
}
545+
546+
#[test]
547+
#[cfg(feature = "step_trait")]
548+
fn page_step_forward() {
549+
let test_cases = [
550+
(0, 0, Some(0)),
551+
(0, 1, Some(0x1000)),
552+
(0x1000, 1, Some(0x2000)),
553+
(0x7fff_ffff_f000, 1, Some(0xffff_8000_0000_0000)),
554+
(0xffff_8000_0000_0000, 1, Some(0xffff_8000_0000_1000)),
555+
(0xffff_ffff_ffff_f000, 1, None),
556+
#[cfg(target_pointer_width = "64")]
557+
(0x7fff_ffff_f000, 0x1_2345_6789, Some(0xffff_9234_5678_8000)),
558+
#[cfg(target_pointer_width = "64")]
559+
(0x7fff_ffff_f000, 0x8_0000_0000, Some(0xffff_ffff_ffff_f000)),
560+
#[cfg(target_pointer_width = "64")]
561+
(0x7fff_fff0_0000, 0x8_0000_00ff, Some(0xffff_ffff_ffff_f000)),
562+
#[cfg(target_pointer_width = "64")]
563+
(0x7fff_fff0_0000, 0x8_0000_0100, None),
564+
#[cfg(target_pointer_width = "64")]
565+
(0x7fff_ffff_f000, 0x8_0000_0001, None),
566+
// Make sure that we handle `steps * PAGE_SIZE > u32::MAX`
567+
// correctly on 32-bit targets.
568+
(0, 0x10_0000, Some(0x1_0000_0000)),
569+
];
570+
for (start, count, result) in test_cases {
571+
let start = Page::<Size4KiB>::from_start_address(VirtAddr::new(start)).unwrap();
572+
let result = result
573+
.map(|result| Page::<Size4KiB>::from_start_address(VirtAddr::new(result)).unwrap());
574+
assert_eq!(Step::forward_checked(start, count), result);
575+
}
576+
}
577+
578+
#[test]
579+
#[cfg(feature = "step_trait")]
580+
fn page_step_backwards() {
581+
let test_cases = [
582+
(0, 0, Some(0)),
583+
(0, 1, None),
584+
(0x1000, 1, Some(0)),
585+
(0xffff_8000_0000_0000, 1, Some(0x7fff_ffff_f000)),
586+
(0xffff_8000_0000_1000, 1, Some(0xffff_8000_0000_0000)),
587+
#[cfg(target_pointer_width = "64")]
588+
(0xffff_9234_5678_8000, 0x1_2345_6789, Some(0x7fff_ffff_f000)),
589+
#[cfg(target_pointer_width = "64")]
590+
(0xffff_8000_0000_0000, 0x8_0000_0000, Some(0)),
591+
#[cfg(target_pointer_width = "64")]
592+
(0xffff_8000_0000_0000, 0x7_ffff_ff01, Some(0xff000)),
593+
#[cfg(target_pointer_width = "64")]
594+
(0xffff_8000_0000_0000, 0x8_0000_0001, None),
595+
// Make sure that we handle `steps * PAGE_SIZE > u32::MAX`
596+
// correctly on 32-bit targets.
597+
(0x1_0000_0000, 0x10_0000, Some(0)),
598+
];
599+
for (start, count, result) in test_cases {
600+
let start = Page::<Size4KiB>::from_start_address(VirtAddr::new(start)).unwrap();
601+
let result = result
602+
.map(|result| Page::<Size4KiB>::from_start_address(VirtAddr::new(result)).unwrap());
603+
assert_eq!(Step::backward_checked(start, count), result);
604+
}
605+
}
606+
607+
#[test]
608+
#[cfg(feature = "step_trait")]
609+
fn page_steps_between() {
610+
let test_cases = [
611+
(0, 0, 0, Some(0)),
612+
(0, 0x1000, 1, Some(1)),
613+
(0x1000, 0, 0, None),
614+
(0x1000, 0x1000, 0, Some(0)),
615+
(0x7fff_ffff_f000, 0xffff_8000_0000_0000, 1, Some(1)),
616+
(0xffff_8000_0000_0000, 0x7fff_ffff_f000, 0, None),
617+
(0xffff_8000_0000_0000, 0xffff_8000_0000_0000, 0, Some(0)),
618+
(0xffff_8000_0000_0000, 0xffff_8000_0000_1000, 1, Some(1)),
619+
(0xffff_8000_0000_1000, 0xffff_8000_0000_0000, 0, None),
620+
(0xffff_8000_0000_1000, 0xffff_8000_0000_1000, 0, Some(0)),
621+
// Make sure that we handle `steps * PAGE_SIZE > u32::MAX` correctly on 32-bit
622+
// targets.
623+
(
624+
0x0000_0000_0000,
625+
0x0001_0000_0000,
626+
0x10_0000,
627+
Some(0x10_0000),
628+
),
629+
// The returned bounds are different when `steps` doesn't fit in
630+
// into `usize`. On 64-bit targets, `0x1_0000_0000` fits into
631+
// `usize`, so we can return exact lower and upper bounds. On
632+
// 32-bit targets, `0x1_0000_0000` doesn't fit into `usize`, so we
633+
// only return an lower bound of `usize::MAX` and don't return an
634+
// upper bound.
635+
#[cfg(target_pointer_width = "64")]
636+
(
637+
0x0000_0000_0000,
638+
0x1000_0000_0000,
639+
0x1_0000_0000,
640+
Some(0x1_0000_0000),
641+
),
642+
#[cfg(not(target_pointer_width = "64"))]
643+
(0x0000_0000_0000, 0x1000_0000_0000, usize::MAX, None),
644+
];
645+
for (start, end, lower, upper) in test_cases {
646+
let start = Page::<Size4KiB>::from_start_address(VirtAddr::new(start)).unwrap();
647+
let end = Page::from_start_address(VirtAddr::new(end)).unwrap();
648+
assert_eq!(Step::steps_between(&start, &end), (lower, upper));
649+
}
650+
}
534651
}

0 commit comments

Comments
 (0)