-
Notifications
You must be signed in to change notification settings - Fork 44
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
Introduce safe page allocation abstractions #348
Conversation
1f5611a
to
c63aed2
Compare
Is your change compatible with gdbstub? The gdb support requires borrowing the GHCB in order to step in the debugger, but the debugger may be stepping through code that already has the GHCB borrowed, which would cause debugger actions to panic. I'm also concerned about the possibility that somebody tries to add logging to code that has an active GHCB borrow, which would cause a similar panic because the console code will attempt to use the GHCB in order to communicate with the console. While this is safe, it is not exactly desirable to make logging impossible in certain code paths. I started (but did not complete) a nested GHCB borrow design in which GHCB borrowing had a "level" concept, in which each borrow of the GHCB declared an intended nesting level. Three levels were defined: normal, console, and debugger. The premise was that recursive borrows were illegal at a level that was the same or lower than any current borrow, but a recursive borrow was legal at a higher level. The reference tracker (equivalent of As I said, I never finished that work because it seemed like a lower priority than other core logic, but I could pick it up and try to finish it if necessary. But I would be really concerned about any intermediate change that breaks debugger/console functionality in the pursuit of borrow safety. |
I have not tested this, but it looks like it would interfere, yes.
I'm not sure I understand how this would work. If the contents are only copied on drop, then what will we pass to the GHCB method that performs the I would have assumed that the new
I can try to implement something like that if you want. |
It just dawned on me that you perhaps meant that the new ref1 = ghcb_mut(Level::Normal); // Takes snapshot 0
ref2 = ghcb_mut(Level::Debugger); // Takes snapshot 1
ref1.some_field = some_value; // Some modification to the GHCB
drop(ref2); // Restores to snapshot 1
ref1.some_call() // GHCB has unexpected state, ref1 modification is lost I'm not claiming this is necessarily a very common pattern, but one that may happen, making the abstraction somewhat unsafe. |
It would also have some usability issues. If the new let ghcb = ghcb_mut(Level::Normal)?;
let some_ref: &mut GHCB = ghcb.deref_mut();
log::info() {
// call some GHCB method that takes `&mut self`
ghcb_mut(Level::Console)?.some_call();
}
some_ref.some_call()?; On the other hand, if the new type does not implement let ghcb = ghcb_mut(Level::Normal)?;
ghcb.with_mut(|ghcb| ghcb.some_call())?; Which would still not allow using the GHCB while |
For now I'm tempted to drop the last commit on this PR so this can be merged straight away. |
I take your point, and I don't have any alternative suggestions. I suspect this is a case where consumers of the GHCB will almost certainly do the right thing and we can accept the risk, because the pattern you are concerned about is one that looks like it is deliberately trying to do something very strange, and therefore is not one we should ever expect to see. Yes, it's unfortunate if the compiler can't stop this pattern from occurring, but I think we can be OK with that. |
But that's exactly the pattern we need to support: some outer block acquires a mutable reference to the GHCB and some inner block temporarily acquires a nested reference. In your example, the inner block will drop the nested reference before the outer block attempts to reuse its reference, so the nesting should be accomplished successfully - meaning that the inner block will make a temporary copy in its local |
That's very reasonable, but now I want to raise a separate concern. The HV doorbell page contains no mutable data, and thus no mutable reference can ever be acquired (we don't enforce that today, but we should). If no mutable reference can ever be acquired, then there are no constraints on the number of shared references that can be acquired. Since there is no constraint, it seems like we would not want to implement any dynamic logic to enforce borrow checking because it can never fail (by construction) and the borrow checking would be wasted effort. Wouldn't it be better if we just introduced a function that returns |
Not necessarily. This could happen by attaching and detaching with the debugger IIUC.
I think we can enforce this at the compiler level by making any subsequent reference's lifetime depend on the previous one (e.g. something like
It is still technically undefined behavior as far as I understand. I don't think in practice it would cause much of a problem in the current state of the compiler, but then again I'm not a compiler expert and it's still not allowed. The following snippet won't build for example: fn main() {
let mut v = 5u32;
let r1 = &mut v;
*r1 += 1;
{
let r2 = &mut v;
}
println!("{}", *r1);
}
If you try going around the compiler with something like an
Yes, that makes sense, I will change |
Thinking through this further, I cannot prove that the compiler cannot introduce badly timed spills which upset the nesting design. This makes me wonder whether a better approach would be to reimplement the |
This could also be done by replacing GHCB fields with
Yes, or |
We continued the nested reference discussion via email. I think all review comments are addressed, so this should be good to go. |
9ff69ee
to
ea280ad
Compare
This seems to fail booting in debug mode, I'll take a look. Please don't merge yet. |
ada2ee8
to
2dea248
Compare
So this is very messy. As-is, the SVSM works with this PR. The thing is, once I introduce the nested protection the SVSM panics early. This is because the new |
Follow the usual convention and move tests inside a mod test guarded by a cfg item. Signed-off-by: Carlos López <[email protected]>
b5bb268
to
09d0c2c
Compare
Option::map_or() is not available in const contexts, so simply replace it with a match statement. The new function generates the same exact machine code as the old one on the latest compiler. Signed-off-by: Carlos López <[email protected]>
Introduce a new type to manage page-sized allocations using the page allocator directly. The new abstraction works similarly to a regular Box, albeit with just a subset of its methods, easing the memory management for such allocated types. It reduces the amount of unsafe blocks in higher level code and automatically frees memory using the Drop trait. Signed-off-by: Carlos López <[email protected]>
Introduce a new type to abstract away VMSA memory management. The new type is a wrapper around the PageBox type with some extra functionality. Signed-off-by: Carlos López <[email protected]>
Introduce a new type to abstract away the memory management of the GHCB page. The new type is a wrapper around a PageBox with some extra functionality. Signed-off-by: Carlos López <[email protected]>
Introduce a new type to abstract away the memory management of the HV doorbell page and simplify error handling. The new type is a wrapper around a PageBox with some extra functionality. Signed-off-by: Carlos López <[email protected]>
Use the newly introduced abstraction for page allocation, simplifying the logic and reducing the amount of unsafe code. Signed-off-by: Carlos López <[email protected]>
Remove calls to the low-level page allocation APIs (allocate_zeroed_page, free_page) and replace them with uses of the new PageBox type, which simplifies code in several places and reduces the amount of unsafe code. In order to improve readability, move the management of pages in the pagetable into PTPage methods. Signed-off-by: Carlos López <[email protected]>
Added some extra documentation in the latest version of the PR. I will merge this by the end of the day if nobody has any objections. |
PageBox<T>
type. It works likeBox<T>
, releasing memory on drop, but it only has a subset of the functionality and uses memory obtained directly from the page allocator. This is useful for some types that need to reside on a page of their own.VmsaPage
abstraction based on thePageBox
type.HVDoorbellPage
abstraction based on thePageBox
type.GhcbPage
abstraction based on thePageBox
type.PerCpu
code to use thePageBox
type.PageBox
type.