-
Notifications
You must be signed in to change notification settings - Fork 35
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
Add #VC handler #55
Add #VC handler #55
Conversation
I'd say you at least need to set up a GDT with a CPL0 code segment. |
GDT is set and loaded in src/boot_stage2.rs * Load a GDT. Despite the naming, it contains valid
* entries for both, "legacy" 32bit and long mode each.
*/
movl $gdt64_desc, %eax
lgdt (%eax) So I guess no need to set and load it again, right? Just ensure that the IDT fields match the correct GDT entries |
The only GDT entry that is referenced from the IDT is the TSS, which contains the IST stacks for #DF handling, as far as I can tell. This is not set up until |
That was my conclusion too, that's why I was struggling finding what could possibly go wrong.
I didn't, but I just tried and it worked :) VC handler gets called by |
src/cpu/idt_generic_handler.rs
Outdated
// | ||
// Copyright (c) 2022-2023 SUSE LLC | ||
// | ||
// Author: Thomas Leroy <tleroy@suse.de> |
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.
When you move code to a new file, please also add the original authors to the file header.
In general this looks good to me. The question is how we organize the code sharing of the IDT handlers between stage2 and SVSM kernel. The SVSM needs more complicated handlers for some things (e.g. #PF) than stage2, and we want separate handlers for both. We can divide that by introducing features, or just include different sub-modules for SVSM and stage2. Other suggestions are also welcome. |
Exception handling needs a GDT to be set up, because raising the exception will reference the GDT to load the handlers code segment, and the IRET also references the GDT to jump back to the original code segment. |
I think the feeature approach will only work if we separate things into different crates. As far as I know, there is no way to do conditional compilation depending on the binary being built (e.g. stage2 or SVSM kernel). I'd say the best way is to separate both into different sub-modules and include them based on what's needed, as you suggest. Maybe something like |
If needed we can emulate that by introducing features which are only set per-target, like idt_stage2 and idt_svsm.
Yes, that sounds worth a try. Thanks! |
This looks good to me too |
Does it make sense to have two different IDT handler arrays, one for stage 2 handlers and one for SVSM? Or would it be better to just refill |
c1b1589
to
f38ab74
Compare
f38ab74
to
dd7bd2b
Compare
dd7bd2b
to
196bf9b
Compare
bad3123
to
164f547
Compare
PR is almost ready to be reviewed. I left some TODOs were I asked opened questions to me and reviewers, but here is a new one: do we need to already check CPL before the #VC handling (especially for IOIO) or should we wait for CPL-3 support to be working? Maybe @joergroedel |
164f547
to
dd306f6
Compare
I think there is no need to check CPL right now. For CPUID it doesn't matter and CPL-3 will not be able to do IOIO directly. For that an IO permission bitmap is needed which we are not planning to implement. |
dd306f6
to
fc416a7
Compare
98fbbed
to
3d3018d
Compare
Adds tests that make sure a #VC exception is raised while executing various instructions and that the #VC handler properly handles them via the ghcb. Adds tests for cpuid, msrs, port IO, MMIO, wbinvd, rdtsc, dr7, and vmmcall. Relies on being able to run tests inside the guest VM, e.g. with PR coconut-svsm#120. The #VC handler is not yet implemented, so these tests are all marked with #[should_panic]. See PR coconut-svsm#55 for progress on its implementation. TODO: Remove added dependencies Move the tests to vc.rs or a new file Clean up comments, etc. Move assembly wrapper helper functions to more sensible places Implement page state change (PSC) test
Adds tests that make sure a #VC exception is raised while executing various instructions and that the #VC handler properly handles them via the ghcb. Adds tests for cpuid, msrs, port IO, MMIO, wbinvd, rdtsc, dr7, and vmmcall. Relies on being able to run tests inside the guest VM, e.g. with PR coconut-svsm#120. The #VC handler is not yet implemented, so these tests are all marked with #[should_panic]. See PR coconut-svsm#55 for progress on its implementation. TODO: Remove added dependencies Move the tests to vc.rs or a new file Clean up comments, etc. Move assembly wrapper helper functions to more sensible places Implement page state change (PSC) test Signed-off-by: Adam Dunlap <acdunlap@google.com>
Adds tests that make sure a #VC exception is raised while executing various instructions and that the #VC handler properly handles them via the ghcb. Adds tests for cpuid, msrs, port IO, MMIO, wbinvd, rdtsc, dr7, and vmmcall. Relies on being able to run tests inside the guest VM, e.g. with PR coconut-svsm#120. The #VC handler is not yet implemented, so these tests are all marked with #[should_panic]. See PR coconut-svsm#55 for progress on its implementation. TODO: Remove added dependencies Move the tests to vc.rs or a new file Clean up comments, etc. Move assembly wrapper helper functions to more sensible places Implement page state change (PSC) test Signed-off-by: Adam Dunlap <acdunlap@google.com>
3d3018d
to
d1a69b0
Compare
1dd1f3d
to
506f4f3
Compare
I tried the new version and the previously-passing tests still pass (also IOIO for 32-bit operands, which was failing before due a bug in the test). I don't have them running in stage 2 yet, though. There's also some IO tests that are still failing when the port is encoded in the instruction, e.g:
it looks like this happens when the opcode is 0xe4, 0xe5, 0xe6, or 0xe7. I'm not sure if you care about supporting this case, but I thought I'd mention it. |
As we said, I don't expect different behavior in stage 2 and SVSM kernel, but if it's possible for you to test in stage 2, that would be safer. Let me know if there's anything I can do :)
Indeed, port specified in an imm8 is currently not supported. We'll add support later if needed. |
I added testing support to stage2 and the cpuid tests pass there as well. |
Great. I'll remove the Draft tag of my PR then, and make it open for review. Your testing code looks good to me :) |
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.
Added some comments below. Overall error handling could be cleaned up a bit. There are also are some safety concerns I have that I noted below.
Okay, so there have been some suggestions included by me on-top. Can you nuke this commit and rebase the base-PR to latest main branch, please? Please also address Carlos' comments when updating. |
ee760f2
to
7fde4c7
Compare
Rebased on main, and added Carlos' proposals |
By the way your commit messages seem to be strangely line wrapped. I suggest wrapping lines at 70 or 72 columns. |
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.
A few more extra comments
#[derive(Default, Debug, Copy, Clone)] | ||
pub struct Instruction { | ||
pub prefixes: InstructionField, | ||
pub insn_bytes: [u8; MAX_INSN_SIZE], | ||
pub length: usize, | ||
pub opcode: InstructionField, | ||
pub opnd_bytes: usize, | ||
} | ||
|
||
#[derive(Default, Debug, Copy, Clone)] | ||
pub struct InstructionField { | ||
pub bytes: [u8; MAX_INSN_FIELD_SIZE], | ||
pub nb_bytes: usize, | ||
} |
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.
There are several arrays with a field to track length, so how about the following to reduce code duplication:
#[derive(Debug, Copy, Clone, Default)]
pub(super) struct InsBuffer<const N: usize>
where
[u8; N]: Default,
{
buf: [u8; N],
len: usize,
}
#[derive(Debug, Copy, Clone, Default)]
pub(super) struct Instruction {
pub prefixes: InsBuffer<MAX_INSN_FIELD_SIZE>,
pub bytes: InsBuffer<MAX_INSN_SIZE>,
pub opcode: InsBuffer<MAX_INSN_FIELD_SIZE>,
}
This would also make Instruction::new()
a bit more clear. This could also be done in a separate PR if it's too much work to refactor.
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, that's code inspired from C without stepping back enough.
I'll start working on a refactoring but that indeed could be in another PR.
The current InstructionField.nb_bytes
is not correlated to InstructionField.bytes.len()
and is used differently, so I'll keep the nb_bytes
field name or something similar and add a comment why this field
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.
Yes, please do that in an additional PR. It makes sense to properly abstract the instruction buffer, but it can be done on-top of this one.
Stage2 and SVSM kernel have to handle interrupts in different ways. This creates 2 different IDT handler arrays: one calling stage2 handlers, and one calling SVSM handlers. cpu/idt/stage2.rs and cpu/idt/svsm.rs expose the same functions, callable from the appropriate context (stage2 or SVSM). Signed-off-by: Thomas Leroy <thomas.leroy@suse.com>
7fde4c7
to
6478545
Compare
To handle #VC IOIO and CPUID exceptions, we need to decode those two instructions. Since we start by handling IOIO instructions using registers and CPUID, with both fixed size, we don't need a full x86 decoder. Signed-off-by: Thomas Leroy <thomas.leroy@suse.com>
There would be 2 ways to handle CPUID #VC exception: using the CPUID table provided by the firmware (already mapped in memory in stage 2), and forwarding the CPUID instruction to the hypervisor through a VMEXIT. Since the hyervisor is not trusted, we only handle CPUID with the firmware table. With this, since CPUID handling doesn't require the GHCB, stage 2 and SVSM kernel have the same #VC handler. Signed-off-by: Thomas Leroy <thomas.leroy@suse.com>
Handle the IN and OUT instructions by calling the hypervisor through the existing GHCB ioio methods. At this point, IN and OUT instructions with the port given as immediate value is not supported yet. INS and OUTS are also not supported yet. Signed-off-by: Thomas Leroy <thomas.leroy@suse.com>
Just replace an if/else chain logic with a match in SVSM and stage 2 generic IDT handlers. Signed-off-by: Thomas Leroy <thomas.leroy@suse.com>
In stage 2, we need to handle some #VC exceptions with GHCB. To do that, we need to init and load IDT again, after the GHCB is set, with new handlers using GHCB (eg. IOIO handler). Signed-off-by: Thomas Leroy <thomas.leroy@suse.com>
Cleaner error handling with the use of `expect()` and a more precise `VcError`. Signed-off-by: Thomas Leroy <thomas.leroy@suse.com>
6478545
to
e6263e8
Compare
This is a draft for the implementation of #14, which is implementing a #VC handler to handle NAE events.
NAE events have to be handled early, so we also set and load the IDT in stage 2.
At this point, interrupt handling in stage 2 doesn't work. I mostly moved and debug code to try to call the VC handler from a stage 2
cpuid
, which fails. The IDT setup in this PR still handles interrupts in the SVSM kernel, but not in stage 2. My guess goes that IDT entries should be different because stage2 is a different binary (with different GDT entries?), but I'm still debugging. That's where my lack of system programming background hits.