-
Notifications
You must be signed in to change notification settings - Fork 151
feat(mapper): add MappedPageTable::display
#574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Conversation
b340a21 to
3f25b46
Compare
Freax13
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your contribution!
I like the idea of adding a display impl for the page table types to print mapped ranges.
That said, I'm a bit wary of exposing too many implementation details as public APIs. I think there are probably good use-cases for MappedPageTableRangeInclusiveIter, but I'm not too sure about some of the trait impls for the new types.
|
|
||
| #[derive(Debug)] | ||
| struct PageTableWalker<P: PageTableFrameMapping> { | ||
| pub struct PageTableWalker<P: PageTableFrameMapping> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that exposing this could be useful, but I'm not sure we want to expose this API in its current form.
For example, PageTableWalker::next_table should be unsafe (we can't just blindly trust that the PageTableEntry indeed points to a valid page table).
If it's okay with you, I'd like to delay that to another PR, so that we can properly think about the APIs we want to expose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, we can keep it private. 👍
You say that next_tables has safety requirements regarding entry that are not captured by new's undocumented safety requirements, right? That sounds like we should make this unsafe even when kept private to make this sound. Should we do that before this PR, since this PR uses that API, or ignore the issue for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You say that
next_tableshas safety requirements regardingentrythat are not captured bynew's undocumented safety requirements, right?
Yes. This function can be used to create a shared reference to arbitrary memory and that's not sound.
Should we do that before this PR, since this PR uses that API, or ignore the issue for now?
I'm fine with keeping this as is for now, but if you wanted to clean this up a bit, I'd appreciate that.
| type Item = MappedPageItem; | ||
|
|
||
| fn next(&mut self) -> Option<Self::Item> { | ||
| self.next_forward().or_else(|| self.next_forward()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think calling next_forward twice is sufficient.
Consider the following: A page directory which has its first two entries point to two empty completely page tables, followed by a third entry pointing to a page table with present mappings.
PD[0] -> PT[511:0] -> All not present
PD[1] -> PT[511:0] -> All not present
PD[2] -> PT[0] -> 0x1234000.
If I'm understanding next_forward correctly, the first call to next_forward would traverse the page directory to the first page table, fail to find any entries and increment p1 until increment_p1_index returns None. <MappedPageTableIter as Iterator>::next would then call next_forward a second time. This time, next_forward would traverse the page directory to the second page table, again fail to find any entries and return None yet again. At this point, <MappedPageTableIter as Iterator>::next returns None even though the third page directory entry with the page table containing actual mappings hasn't been traversed yet.
I think this can be fixed by calling self.next_forward() until p4_index >= 512.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks! This will be resolved in my next revision. :)
| Ok(page_table) => break page_table, | ||
| Err(PageTableWalkError::NotMapped) => self.increment_p4_index()?, | ||
| Err(PageTableWalkError::MappedToHugePage) => { | ||
| panic!("level 4 entry has huge page bit set") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the code below, we silently ignore bad entries:
// Invalid frame address
self.increment_p1_index()?;Do we want to do the same here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, done in my next revision. 👍
|
|
||
| #[derive(Debug)] | ||
| pub struct MappedPageTableDisplay<'a, P: PageTableFrameMapping + Clone> { | ||
| inner: MappedPageTableRangeInclusiveIter<'a, P>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we change inner's type from MappedPageTableRangeInclusiveIter<'a, P> to MappedPageTable<'_, P>, we could replace the for mapped_page_range in self.inner.clone() { with for mapped_page_range in self.inner.range_iter() {.
If we do that, we don't need P: Clone anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea! Done in my next revision. 👍
| let frame_end = self.inner.frame_range.end.start_address(); | ||
| let flags = self.inner.flags; | ||
| let format_phys = if page_start.as_u64() == frame_start.as_u64() { | ||
| assert_eq!(page_end.as_u64(), frame_end.as_u64()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| assert_eq!(page_end.as_u64(), frame_end.as_u64()); | |
| debug_assert_eq!(page_end.as_u64(), frame_end.as_u64()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have reworked MappedPageRangeInclusive to guarantee the page range and the frame range have the same length instead. :)
| write!( | ||
| f, | ||
| "size: {size}, len: {len:5}, virt: {page_start:18p}..={page_end:18p}, phys: {format_phys}, flags: {flags:?}" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a bit confusing when 4K and 2M mappings are mixed in a page table.
A mapping covering an address range with 4K mappings could look like this:
size: 4KiB, len: 1024, virt: 0x200000..= 0x5FF000, phys: identity mapped, flags: PageTableFlags(PRESENT | WRITABLE | ACCESSED | DIRTY)
and an otherwise identical mapping of the same range with 2M pages could then look like this:
size: 2MiB, len: 2, virt: 0x200000..= 0x400000, phys: identity mapped, flags: PageTableFlags(PRESENT | WRITABLE | ACCESSED | DIRTY)
The end addresses look completely different even though they're really the same.
Have you considered printing the last covered address (inclusive, i.e. 0x5FFFFF in this example) or one past the last covered address (exclusive, i.e. 0x600000) instead of the start address of the end page?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point!
I think using exclusive ranges would look better. That's also how Linux's proc_pid_maps(5) does it.
This is addressed in my next revision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code in this file needs to be commented to explain what's going on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in my next revision. 👍
| impl<S: PageSize> Add<u64> for MappedPage<S> { | ||
| type Output = Self; | ||
|
|
||
| fn add(self, rhs: u64) -> Self::Output { | ||
| Self { | ||
| page: self.page + rhs, | ||
| frame: self.frame + rhs, | ||
| flags: self.flags, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<S: PageSize> Sub<u64> for MappedPage<S> { | ||
| type Output = Self; | ||
|
|
||
| fn sub(self, rhs: u64) -> Self::Output { | ||
| Self { | ||
| page: self.page - rhs, | ||
| frame: self.frame - rhs, | ||
| flags: self.flags, | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the use cases for these Add and Sub impls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use page == end + 1 to determine whether two mapped pages are contiguous.
While Add would suffice, I also added AddAssign, Sub, and SubAssign for completeness.
|
I appreciate the separate commits and the short explanation for each of them! |
|
Thanks for the quick review! :) I'll split off the I remembered one more thing to discuss:
What's your opinion on this? Is this table the single most obvious way of formatting the whole page table as text, or should we stick with the display adapter? If the latter, is |
I think the whole concept of formatting a set of page tables is a bit unusual, so it's probably warranted to use a inherent |
|
Another idea would be to pretty print the table when alternate ( |
|
Alternate formatting also affects the derived |
3f25b46 to
091b45d
Compare
|
After you confirmed this is generally suitable for upstreaming, I have reworked the code to make it upstreamable. All comments from your last review should be addressed. You are welcome to mark any comments as resolved as you see fit. I split the code into different modules to make it easier to understand. I also added doc comments to all items and have any not-straightforward code. Furthermore, I reworked the formatting of the table and have updated the PR description accordingly. Thanks again for your reviews! They are very valuable. :) |
927c9b7 to
37f815a
Compare
37f815a to
fb3610b
Compare
In Hermit, we found it useful to be able to print our page tables for debugging. We've had several iterations of such debugging functions but are now happy with the current compact range-based table formatting introduced in hermit-os/kernel#1818.
This PR upstreams this page table printing functionality. It can be used as follows:
This is how a formatted table looks like:
This is how a table formatted with the alternate (
#) flag looks like:While this PR is technically non-breaking and could target
master, it becomes much easier to use with #576.The first two commits prepare a directory structure for the
mapped_page_tablemodule. The third commit adds three new modules tomapped_page_table:iter,range_iter, anddisplay. The former two are private for now. They allow iterating over page mappings and contiguous ranges of page mappings, respectively. The third module adds the display adapter forMappedPageTable.Thanks as always for your great work! :)