@@ -1112,6 +1112,7 @@ mod debug {
11121112
11131113#[ cfg( test) ]
11141114#[ cfg( feature = "init-paging" ) ]
1115+ #[ allow( clippy:: needless_range_loop) ]
11151116mod tests {
11161117 use std:: sync:: { Arc , Mutex } ;
11171118
@@ -1122,8 +1123,6 @@ mod tests {
11221123 #[ cfg( kvm) ]
11231124 use crate :: hypervisor:: regs:: FP_CONTROL_WORD_DEFAULT ;
11241125 use crate :: hypervisor:: regs:: { CommonSegmentRegister , CommonTableRegister , MXCSR_DEFAULT } ;
1125- #[ cfg( target_os = "windows" ) ]
1126- use crate :: hypervisor:: wrappers:: HandleWrapper ;
11271126 use crate :: mem:: layout:: SandboxMemoryLayout ;
11281127 use crate :: mem:: memory_region:: { GuestMemoryRegion , MemoryRegionFlags } ;
11291128 use crate :: mem:: mgr:: { GuestPageTableBuffer , SandboxMemoryManager } ;
@@ -1289,24 +1288,108 @@ mod tests {
12891288 }
12901289 }
12911290
1292- /// Build a dirty XSAVE area for testing reset_vcpu.
1293- /// Can't use all 1s because XSAVE has reserved fields that must be zero
1294- /// (header at 512-575, MXCSR reserved bits, etc.)
1295- ///
1296- /// Takes the current xsave state to preserve hypervisor-specific header fields
1297- /// like XCOMP_BV which MSHV/WHP require to be set correctly.
1291+ /// Query CPUID.0DH.n for XSAVE component info.
1292+ /// Returns (size, offset, align_64) for the given component:
1293+ /// - size: CPUID.0DH.n:EAX - size in bytes
1294+ /// - offset: CPUID.0DH.n:EBX - offset from XSAVE base (standard format only)
1295+ /// - align_64: CPUID.0DH.n:ECX bit 1 - true if 64-byte aligned (compacted format)
1296+ fn xsave_component_info ( comp_id : u32 ) -> ( usize , usize , bool ) {
1297+ let result = unsafe { std:: arch:: x86_64:: __cpuid_count ( 0xD , comp_id) } ;
1298+ let size = result. eax as usize ;
1299+ let offset = result. ebx as usize ;
1300+ let align_64 = ( result. ecx & 0b10 ) != 0 ;
1301+ ( size, offset, align_64)
1302+ }
1303+
1304+ /// Query CPUID.0DH.00H for the bitmap of supported user state components.
1305+ /// EDX:EAX forms a 64-bit bitmap where bit i indicates support for component i.
1306+ fn xsave_supported_components ( ) -> u64 {
1307+ let result = unsafe { std:: arch:: x86_64:: __cpuid_count ( 0xD , 0 ) } ;
1308+ ( result. edx as u64 ) << 32 | ( result. eax as u64 )
1309+ }
1310+
1311+ /// Dirty extended state components using compacted XSAVE format (MSHV/WHP).
1312+ /// Components are stored contiguously starting at byte 576, with alignment
1313+ /// requirements from CPUID.0DH.n:ECX[1].
1314+ /// Returns a bitmask of components that were actually dirtied.
1315+ fn dirty_xsave_extended_compacted (
1316+ xsave : & mut [ u32 ] ,
1317+ xcomp_bv : u64 ,
1318+ supported_components : u64 ,
1319+ ) -> u64 {
1320+ let mut dirtied_mask = 0u64 ;
1321+ let mut offset = 576usize ;
1322+
1323+ for comp_id in 2 ..63u32 {
1324+ // Skip if component not supported by CPU or not enabled in XCOMP_BV
1325+ if ( supported_components & ( 1u64 << comp_id) ) == 0 {
1326+ continue ;
1327+ }
1328+ if ( xcomp_bv & ( 1u64 << comp_id) ) == 0 {
1329+ continue ;
1330+ }
1331+
1332+ let ( size, _, align_64) = xsave_component_info ( comp_id) ;
1333+
1334+ // ECX[1]=1 means 64-byte aligned; ECX[1]=0 means immediately after previous
1335+ if align_64 {
1336+ offset = offset. next_multiple_of ( 64 ) ;
1337+ }
1338+
1339+ // Dirty this component's data area (only if it fits in the buffer)
1340+ let start_idx = offset / 4 ;
1341+ let end_idx = ( offset + size) / 4 ;
1342+ if end_idx <= xsave. len ( ) {
1343+ for i in start_idx..end_idx {
1344+ xsave[ i] = 0x12345678 ^ comp_id. wrapping_mul ( 0x11111111 ) ;
1345+ }
1346+ dirtied_mask |= 1u64 << comp_id;
1347+ }
1348+
1349+ offset += size;
1350+ }
1351+
1352+ dirtied_mask
1353+ }
1354+
1355+ /// Dirty extended state components using standard XSAVE format (KVM).
1356+ /// Components are at fixed offsets from CPUID.0DH.n:EBX.
1357+ /// Returns a bitmask of components that were actually dirtied.
1358+ fn dirty_xsave_extended_standard ( xsave : & mut [ u32 ] , supported_components : u64 ) -> u64 {
1359+ let mut dirtied_mask = 0u64 ;
1360+
1361+ for comp_id in 2 ..63u32 {
1362+ // Skip if component not supported by CPU
1363+ if ( supported_components & ( 1u64 << comp_id) ) == 0 {
1364+ continue ;
1365+ }
1366+
1367+ let ( size, fixed_offset, _) = xsave_component_info ( comp_id) ;
1368+
1369+ let start_idx = fixed_offset / 4 ;
1370+ let end_idx = ( fixed_offset + size) / 4 ;
1371+ if end_idx <= xsave. len ( ) {
1372+ for i in start_idx..end_idx {
1373+ xsave[ i] = 0x12345678 ^ comp_id. wrapping_mul ( 0x11111111 ) ;
1374+ }
1375+ dirtied_mask |= 1u64 << comp_id;
1376+ }
1377+ }
1378+
1379+ dirtied_mask
1380+ }
1381+
1382+ /// Dirty the legacy XSAVE region (bytes 0-511) for testing reset_vcpu.
1383+ /// This includes FPU/x87 state, SSE state, and reserved areas.
12981384 ///
12991385 /// Layout (from Intel SDM Table 13-1):
13001386 /// Bytes 0-1: FCW, 2-3: FSW, 4: FTW, 5: reserved, 6-7: FOP
13011387 /// Bytes 8-15: FIP, 16-23: FDP
1302- /// Bytes 24-27: MXCSR, 28-31: MXCSR_MASK (don't touch )
1303- /// Bytes 32-159: ST0-ST7/MM0-MM7 (8 regs × 16 bytes, only 10 bytes used per reg )
1388+ /// Bytes 24-27: MXCSR, 28-31: MXCSR_MASK (preserve - hardware defined )
1389+ /// Bytes 32-159: ST0-ST7/MM0-MM7 (8 regs × 16 bytes)
13041390 /// Bytes 160-415: XMM0-XMM15 (16 regs × 16 bytes)
13051391 /// Bytes 416-511: Reserved
1306- /// Bytes 512-575: XSAVE header (preserve XCOMP_BV at 520-527)
1307- fn dirty_xsave ( current_xsave : & [ u8 ] ) -> [ u32 ; 1024 ] {
1308- let mut xsave = [ 0u32 ; 1024 ] ;
1309-
1392+ fn dirty_xsave_legacy ( xsave : & mut [ u32 ] , current_xsave : & [ u8 ] ) {
13101393 // FCW (bytes 0-1) + FSW (bytes 2-3) - pack into xsave[0]
13111394 // FCW = 0x0F7F (different from default 0x037F), FSW = 0x1234
13121395 xsave[ 0 ] = 0x0F7F | ( 0x1234 << 16 ) ;
@@ -1321,35 +1404,66 @@ mod tests {
13211404 xsave[ 5 ] = 0xBABE0004 ;
13221405 // MXCSR (bytes 24-27) - xsave[6], use valid value different from default
13231406 xsave[ 6 ] = 0x3F80 ;
1324- // xsave[7] is MXCSR_MASK - preserve from current
1407+ // xsave[7] is MXCSR_MASK - preserve from current (hardware defined, read-only)
13251408 if current_xsave. len ( ) >= 32 {
13261409 xsave[ 7 ] = u32:: from_le_bytes ( current_xsave[ 28 ..32 ] . try_into ( ) . unwrap ( ) ) ;
13271410 }
13281411
13291412 // ST0-ST7/MM0-MM7 (bytes 32-159, indices 8-39)
1330- for item in xsave . iter_mut ( ) . take ( 40 ) . skip ( 8 ) {
1331- * item = 0xCAFEBABE ;
1413+ for i in 8 .. 40 {
1414+ xsave [ i ] = 0xCAFEBABE ;
13321415 }
13331416 // XMM0-XMM15 (bytes 160-415, indices 40-103)
1334- for item in xsave . iter_mut ( ) . take ( 104 ) . skip ( 40 ) {
1335- * item = 0xDEADBEEF ;
1417+ for i in 40 .. 104 {
1418+ xsave [ i ] = 0xDEADBEEF ;
13361419 }
13371420
1338- // Preserve entire XSAVE header (bytes 512-575, indices 128-143) from current state
1339- // This includes XSTATE_BV (512-519) and XCOMP_BV (520-527) which
1340- // MSHV/WHP require to be set correctly for compacted format.
1341- // Failure to do this will cause set_xsave to fail on MSHV.
1342- if current_xsave. len ( ) >= 576 {
1343- #[ allow( clippy:: needless_range_loop) ]
1344- for i in 128 ..144 {
1345- let byte_offset = i * 4 ;
1346- xsave[ i] = u32:: from_le_bytes (
1347- current_xsave[ byte_offset..byte_offset + 4 ]
1348- . try_into ( )
1349- . unwrap ( ) ,
1350- ) ;
1351- }
1421+ // Reserved area (bytes 416-511, indices 104-127)
1422+ for i in 104 ..128 {
1423+ xsave[ i] = 0xABCDEF12 ;
1424+ }
1425+ }
1426+
1427+ /// Preserve XSAVE header (bytes 512-575) from current state.
1428+ /// This includes XSTATE_BV and XCOMP_BV which hypervisors require.
1429+ fn preserve_xsave_header ( xsave : & mut [ u32 ] , current_xsave : & [ u8 ] ) {
1430+ for i in 128 ..144 {
1431+ let byte_offset = i * 4 ;
1432+ xsave[ i] = u32:: from_le_bytes (
1433+ current_xsave[ byte_offset..byte_offset + 4 ]
1434+ . try_into ( )
1435+ . unwrap ( ) ,
1436+ ) ;
13521437 }
1438+ }
1439+
1440+ fn dirty_xsave ( current_xsave : & [ u8 ] ) -> Vec < u32 > {
1441+ let mut xsave = vec ! [ 0u32 ; current_xsave. len( ) / 4 ] ;
1442+
1443+ dirty_xsave_legacy ( & mut xsave, current_xsave) ;
1444+ preserve_xsave_header ( & mut xsave, current_xsave) ;
1445+
1446+ let xcomp_bv = u64:: from_le_bytes ( current_xsave[ 520 ..528 ] . try_into ( ) . unwrap ( ) ) ;
1447+ let supported_components = xsave_supported_components ( ) ;
1448+
1449+ // Dirty extended components and get mask of what was actually dirtied
1450+ let extended_mask = if ( xcomp_bv & ( 1u64 << 63 ) ) != 0 {
1451+ // Compacted format (MSHV/WHP)
1452+ dirty_xsave_extended_compacted ( & mut xsave, xcomp_bv, supported_components)
1453+ } else {
1454+ // Standard format (KVM)
1455+ dirty_xsave_extended_standard ( & mut xsave, supported_components)
1456+ } ;
1457+
1458+ // UPDATE XSTATE_BV to indicate dirtied components have valid data.
1459+ // WHP validates consistency between XSTATE_BV and actual data in the buffer.
1460+ // Bits 0,1 = legacy x87/SSE (always set after dirty_xsave_legacy)
1461+ // Bits 2+ = extended components that we actually dirtied
1462+ let xstate_bv = 0x3 | extended_mask;
1463+
1464+ // Write XSTATE_BV to bytes 512-519 (u32 indices 128-129)
1465+ xsave[ 128 ] = ( xstate_bv & 0xFFFFFFFF ) as u32 ;
1466+ xsave[ 129 ] = ( xstate_bv >> 32 ) as u32 ;
13531467
13541468 xsave
13551469 }
0 commit comments