mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 20:48:55 +00:00
hypervisor: tests: add 32-bit pmode segment limit test
BUG=b:354053941 Change-Id: I16008b408379afd59730863587e2ca138c577bdf Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5730916 Reviewed-by: Oystein Eftevaag <oysteine@chromium.org> Reviewed-by: Idan Raiter <idanr@google.com> Commit-Queue: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
parent
44b2688a3c
commit
034f48e1af
1 changed files with 200 additions and 64 deletions
|
@ -298,30 +298,66 @@ global_asm_data!(
|
|||
const GDT_OFFSET: u64 = 0x1500;
|
||||
const IDT_OFFSET: u64 = 0x1528;
|
||||
|
||||
pub fn configure_long_mode_memory(vm: &mut dyn Vm) -> Segment {
|
||||
// Condensed version of the function in x86_64\src\gdt.rs
|
||||
pub fn segment_from_gdt(entry: u64, table_index: u8) -> Segment {
|
||||
let g = ((entry & 0x0080000000000000) >> 55) as u8;
|
||||
let limit =
|
||||
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32;
|
||||
let limit_bytes = if g == 0 { limit } else { (limit * 4096) + 4095 };
|
||||
Segment {
|
||||
base: (((entry) & 0xFF00000000000000) >> 32)
|
||||
| (((entry) & 0x000000FF00000000) >> 16)
|
||||
| (((entry) & 0x00000000FFFF0000) >> 16),
|
||||
limit_bytes,
|
||||
selector: (table_index * 8) as u16,
|
||||
type_: ((entry & 0x00000F0000000000) >> 40) as u8,
|
||||
present: ((entry & 0x0000800000000000) >> 47) as u8,
|
||||
dpl: ((entry & 0x0000600000000000) >> 45) as u8,
|
||||
db: ((entry & 0x0040000000000000) >> 54) as u8,
|
||||
s: ((entry & 0x0000100000000000) >> 44) as u8,
|
||||
l: ((entry & 0x0020000000000000) >> 53) as u8,
|
||||
g,
|
||||
avl: ((entry & 0x0010000000000000) >> 52) as u8,
|
||||
}
|
||||
// Condensed version of the function in x86_64\src\gdt.rs
|
||||
pub fn segment_from_gdt(entry: u64, table_index: u8) -> Segment {
|
||||
let g = ((entry & 0x0080000000000000) >> 55) as u8;
|
||||
let limit = ((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32;
|
||||
let limit_bytes = if g == 0 { limit } else { (limit * 4096) + 4095 };
|
||||
Segment {
|
||||
base: (((entry) & 0xFF00000000000000) >> 32)
|
||||
| (((entry) & 0x000000FF00000000) >> 16)
|
||||
| (((entry) & 0x00000000FFFF0000) >> 16),
|
||||
limit_bytes,
|
||||
selector: (table_index * 8) as u16,
|
||||
type_: ((entry & 0x00000F0000000000) >> 40) as u8,
|
||||
present: ((entry & 0x0000800000000000) >> 47) as u8,
|
||||
dpl: ((entry & 0x0000600000000000) >> 45) as u8,
|
||||
db: ((entry & 0x0040000000000000) >> 54) as u8,
|
||||
s: ((entry & 0x0000100000000000) >> 44) as u8,
|
||||
l: ((entry & 0x0020000000000000) >> 53) as u8,
|
||||
g,
|
||||
avl: ((entry & 0x0010000000000000) >> 52) as u8,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn null_descriptor() -> [u8; 8] {
|
||||
[0u8; 8]
|
||||
}
|
||||
|
||||
const DESC_ACCESS_PRESENT: u8 = 1 << 7;
|
||||
const DESC_ACCESS_NOT_SYS: u8 = 1 << 4;
|
||||
const DESC_ACCESS_EXEC: u8 = 1 << 3;
|
||||
const DESC_ACCESS_RW: u8 = 1 << 1;
|
||||
const DESC_ACCESS_ACCESSED: u8 = 1 << 0;
|
||||
|
||||
const DESC_FLAG_GRAN_4K: u8 = 1 << 7;
|
||||
const DESC_FLAG_DEFAULT_OP_SIZE_32: u8 = 1 << 6;
|
||||
const DESC_FLAG_LONG_MODE: u8 = 1 << 5;
|
||||
|
||||
pub fn segment_descriptor(base: u32, limit: u32, access: u8, flags: u8) -> [u8; 8] {
|
||||
assert!(limit < (1 << 20)); // limit value must fit in 20 bits
|
||||
assert!(flags & 0x0F == 0x00); // flags must be in the high 4 bits only
|
||||
|
||||
[
|
||||
limit as u8, // limit [7:0]
|
||||
(limit >> 8) as u8, // limit [15:8]
|
||||
base as u8, // base [7:0]
|
||||
(base >> 8) as u8, // base [15:8]
|
||||
(base >> 16) as u8, // base [23:16]
|
||||
access, // type + s + dpl + p
|
||||
(limit >> 16) as u8 | flags, // limit [19:16] + flags
|
||||
(base >> 24) as u8, // base [31:24]
|
||||
]
|
||||
}
|
||||
|
||||
pub fn write_gdt(guest_mem: &GuestMemory, gdt: &[u8]) {
|
||||
let gdt_addr = GuestAddress(GDT_OFFSET);
|
||||
guest_mem
|
||||
.write_at_addr(gdt, gdt_addr)
|
||||
.expect("Failed to write GDT entry to guest memory");
|
||||
}
|
||||
|
||||
pub fn configure_long_mode_memory(vm: &mut dyn Vm) -> Segment {
|
||||
let guest_mem = vm.get_memory();
|
||||
|
||||
assert!(
|
||||
|
@ -329,50 +365,25 @@ pub fn configure_long_mode_memory(vm: &mut dyn Vm) -> Segment {
|
|||
"Long-mode setup requires 0x1500-0xc000 to be mapped in the guest."
|
||||
);
|
||||
|
||||
const PRESENT: u8 = 1 << 7;
|
||||
const NOT_SYS: u8 = 1 << 4;
|
||||
const EXEC: u8 = 1 << 3;
|
||||
const RW: u8 = 1 << 1;
|
||||
const ACCESSED: u8 = 1 << 0;
|
||||
|
||||
const GRAN_4K: u8 = 1 << 7;
|
||||
const LONG_MODE: u8 = 1 << 5;
|
||||
|
||||
// Setup GDT
|
||||
let gdt: Vec<u8> = vec![
|
||||
// Null descriptor
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
// Null descriptor
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
// Code segment descriptor
|
||||
0xFF, // Limit & Base (low, bits 0-15)
|
||||
0xFF,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // Base (mid, bits 16-23)
|
||||
PRESENT | NOT_SYS | EXEC | RW | ACCESSED, // Access byte
|
||||
GRAN_4K | LONG_MODE | 0x0F, // Flags & Limit (high)
|
||||
0x00, // Base (high)
|
||||
];
|
||||
let mut gdt = Vec::new();
|
||||
// 0x00
|
||||
gdt.extend_from_slice(&null_descriptor());
|
||||
// 0x08
|
||||
gdt.extend_from_slice(&null_descriptor());
|
||||
// 0x10: code segment descriptor
|
||||
gdt.extend_from_slice(&segment_descriptor(
|
||||
0x0,
|
||||
0xFFFFF,
|
||||
DESC_ACCESS_PRESENT
|
||||
| DESC_ACCESS_NOT_SYS
|
||||
| DESC_ACCESS_EXEC
|
||||
| DESC_ACCESS_RW
|
||||
| DESC_ACCESS_ACCESSED,
|
||||
DESC_FLAG_GRAN_4K | DESC_FLAG_LONG_MODE,
|
||||
));
|
||||
|
||||
let gdt_addr = GuestAddress(GDT_OFFSET);
|
||||
guest_mem
|
||||
.write_at_addr(&gdt, gdt_addr)
|
||||
.expect("Failed to write GDT entry to guest memory");
|
||||
write_gdt(guest_mem, &gdt);
|
||||
|
||||
// Convert the GDT entries to a vector of u64
|
||||
let gdt_entries: Vec<u64> = gdt
|
||||
|
@ -448,6 +459,78 @@ pub fn enter_long_mode(vcpu: &mut dyn VcpuX86_64, vm: &mut dyn Vm) {
|
|||
vcpu.set_sregs(&sregs).expect("failed to set sregs");
|
||||
}
|
||||
|
||||
pub fn configure_flat_protected_mode_memory(vm: &mut dyn Vm) -> Segment {
|
||||
let guest_mem = vm.get_memory();
|
||||
|
||||
assert!(
|
||||
guest_mem.range_overlap(GuestAddress(0x1500), GuestAddress(0xc000)),
|
||||
"Protected-mode setup requires 0x1500-0xc000 to be mapped in the guest."
|
||||
);
|
||||
|
||||
// Setup GDT
|
||||
let mut gdt = Vec::new();
|
||||
|
||||
// 0x00
|
||||
gdt.extend_from_slice(&null_descriptor());
|
||||
// 0x08
|
||||
gdt.extend_from_slice(&null_descriptor());
|
||||
// 0x10: code segment descriptor
|
||||
gdt.extend_from_slice(&segment_descriptor(
|
||||
0x0,
|
||||
0xFFFFF,
|
||||
DESC_ACCESS_PRESENT
|
||||
| DESC_ACCESS_NOT_SYS
|
||||
| DESC_ACCESS_EXEC
|
||||
| DESC_ACCESS_RW
|
||||
| DESC_ACCESS_ACCESSED,
|
||||
DESC_FLAG_GRAN_4K | DESC_FLAG_DEFAULT_OP_SIZE_32,
|
||||
));
|
||||
|
||||
write_gdt(guest_mem, &gdt);
|
||||
|
||||
// Convert the GDT entries to a vector of u64
|
||||
let gdt_entries: Vec<u64> = gdt
|
||||
.chunks(8)
|
||||
.map(|chunk| {
|
||||
let mut array = [0u8; 8];
|
||||
array.copy_from_slice(chunk);
|
||||
u64::from_le_bytes(array)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let code_seg = segment_from_gdt(gdt_entries[2], 2);
|
||||
|
||||
// Setup IDT
|
||||
let idt_addr = GuestAddress(IDT_OFFSET);
|
||||
let idt_entry: u64 = 0; // Empty IDT
|
||||
let idt_entry_bytes = idt_entry.to_le_bytes();
|
||||
guest_mem
|
||||
.write_at_addr(&idt_entry_bytes, idt_addr)
|
||||
.expect("failed to write IDT entry to guest memory");
|
||||
|
||||
code_seg
|
||||
}
|
||||
|
||||
pub fn enter_protected_mode(vcpu: &mut dyn VcpuX86_64, vm: &mut dyn Vm) {
|
||||
let code_seg = configure_flat_protected_mode_memory(vm);
|
||||
|
||||
let mut sregs = vcpu.get_sregs().expect("failed to get sregs");
|
||||
|
||||
sregs.cs = code_seg;
|
||||
|
||||
sregs.gdt.base = GDT_OFFSET;
|
||||
sregs.gdt.limit = 0xFFFF;
|
||||
|
||||
sregs.idt.base = IDT_OFFSET;
|
||||
sregs.idt.limit = 0xFFF;
|
||||
|
||||
// 32-bit protected mode, paging disabled
|
||||
sregs.cr0 |= 0x1; // PE
|
||||
sregs.cr0 &= !0x80000000; // ~PG
|
||||
|
||||
vcpu.set_sregs(&sregs).expect("failed to set sregs");
|
||||
}
|
||||
|
||||
// This runs a minimal program under virtualization.
|
||||
// It should require only the ability to execute instructions under virtualization, physical
|
||||
// memory, the ability to get and set some guest VM registers, and intercepting HLT.
|
||||
|
@ -3656,3 +3739,56 @@ fn test_minimal_exception_injection() {
|
|||
|_, exit, _, _: &mut dyn Vm| matches!(exit, VcpuExit::Hlt)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pmode_segment_limit() {
|
||||
// This test configures 32-bit protected mode and verifies that segment limits are converted
|
||||
// correctly. The test setup configures a segment with the 20-bit limit field set to 0xFFFFF and
|
||||
// the 4096-byte granularity bit set, which should result in a 4 GB limit (0xFFFFFFFF).
|
||||
mod assembly {
|
||||
use super::*;
|
||||
|
||||
global_asm_data!(
|
||||
pub init,
|
||||
".code32",
|
||||
// Load the CS segment limit into EAX.
|
||||
"mov cx, cs",
|
||||
"lsl eax, cx",
|
||||
"hlt",
|
||||
);
|
||||
}
|
||||
|
||||
let mem_size: u64 = 0x20000;
|
||||
|
||||
let setup = TestSetup {
|
||||
initial_regs: Regs {
|
||||
..Default::default()
|
||||
},
|
||||
mem_size,
|
||||
extra_vm_setup: Some(Box::new(|vcpu: &mut dyn VcpuX86_64, vm: &mut dyn Vm| {
|
||||
enter_protected_mode(vcpu, vm);
|
||||
|
||||
let guest_mem = vm.get_memory();
|
||||
|
||||
let mut regs = vcpu.get_regs().expect("Failed to get regs");
|
||||
regs.rax = 12345;
|
||||
regs.rip = 0x1000;
|
||||
vcpu.set_regs(®s).expect("Failed to set regs");
|
||||
|
||||
let init_assembly = assembly::init::data().to_vec();
|
||||
guest_mem
|
||||
.write_at_addr(&init_assembly, GuestAddress(0x1000))
|
||||
.expect("Failed to write init assembly to guest memory");
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
run_tests!(
|
||||
setup,
|
||||
|_, regs, _| {
|
||||
// The output of the LSL instruction should be 4GB - 1.
|
||||
assert_eq!(regs.rax, 0xFFFFFFFF);
|
||||
},
|
||||
|_, exit, _, _: &mut dyn Vm| matches!(exit, VcpuExit::Hlt)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue