detcore_model/
cpu_profile.rs1use serde::Deserialize;
12use serde::Serialize;
13
14#[derive(
16 Debug,
17 Clone,
18 Copy,
19 Default,
20 PartialEq,
21 Eq,
22 Hash,
23 Serialize,
24 Deserialize,
25 clap::ValueEnum
26)]
27pub enum CpuProfile {
28 #[default]
30 #[value(name = "x86-64-v2")]
31 #[serde(rename = "x86-64-v2")]
32 X86_64V2,
33 #[value(name = "x86-64-v3")]
37 #[serde(rename = "x86-64-v3")]
38 X86_64V3,
39 #[value(name = "host-no-rdrand")]
42 #[serde(rename = "host-no-rdrand")]
43 HostNoRdrand,
44}
45
46impl CpuProfile {
47 pub fn is_default(&self) -> bool {
50 *self == CpuProfile::X86_64V2
51 }
52
53 pub fn name(&self) -> &'static str {
55 match self {
56 CpuProfile::X86_64V2 => "x86-64-v2",
57 CpuProfile::X86_64V3 => "x86-64-v3",
58 CpuProfile::HostNoRdrand => "host-no-rdrand",
59 }
60 }
61}
62
63impl std::fmt::Display for CpuProfile {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 f.write_str(self.name())
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
77pub struct HostXstate {
78 pub vendor: [u32; 3],
80 pub signature: u32,
82 pub xcr0: u64,
84 pub xsave_leaves: Vec<[u32; 5]>,
88 pub xcomp_supp: u64,
91 pub xcomp_perm: u64,
92 pub xcomp_guest_perm: u64,
93 pub xfeature_max: u32,
96 pub probe_cases: u32,
99 pub probe_forced_exit: bool,
100}
101
102impl HostXstate {
103 pub fn xsave_leaf(&self, subleaf: u32) -> Option<[u32; 4]> {
105 self.xsave_leaves
106 .iter()
107 .find(|row| row[0] == subleaf)
108 .map(|row| [row[1], row[2], row[3], row[4]])
109 }
110}
111
112#[macro_export]
115macro_rules! v3_residual {
116 () => {
117 "With --cpu-profile x86-64-v3, Hermit checks at startup that this host does not \
118 change the XSTATE_BV bits a guest reads (with XSAVE, XSAVEC or XSAVEOPT, or in a \
119 signal frame) when the host saves and restores the guest's registers. The check \
120 samples a fixed set of cases and forces only CPUID exits, so a CPU or hypervisor \
121 that behaves differently only on other instruction paths, or on VM exits the guest \
122 cannot see, is not excluded. Firmware (SMM) entries cannot be checked at all. \
123 Default runs use x86-64-v2, which this does not affect."
124 };
125}
126
127pub const V3_RESIDUAL: &str = v3_residual!();
130
131#[macro_export]
133macro_rules! host_no_rdrand_residual {
134 () => {
135 "With --cpu-profile host-no-rdrand, CPUID remains intercepted, but its answers \
136 come from the host CPU with hardware-randomness and unvirtualized timing \
137 features hidden. Results are host-CPU-model specific: cross-host builds need \
138 the same CPU model and compatible kernel/XSTATE settings and CPU topology. \
139 The tracer and guest are pinned to the lowest originally allowed CPU, even \
140 with the timeslice disabled; repeat runs require the same allowed CPU set. Host XSAVE behavior \
141 is retained without the x86-64-v3 XSTATE qualification, so programs inspecting \
142 XSAVE or signal-register metadata have no determinism guarantee. Programs must honor \
143 the advertised feature bits; unconditional RDRAND or RDSEED is not trapped \
144 by this profile."
145 };
146}
147
148pub const HOST_NO_RDRAND_RESIDUAL: &str = host_no_rdrand_residual!();
149
150pub const CPU_PROFILE_LONG_HELP: &str = concat!(
152 "The CPU the guest sees: x86-64-v2 (the default), x86-64-v3, or host-no-rdrand. \
153 host-no-rdrand is available for `hermit run` on the ptrace backend with CPUID \
154 faulting and virtual time; it refuses hosts without CPUID faulting instead of \
155 allowing native CPUID. RDRAND and RDSEED are hidden. Check availability with \
156 `hermit host-capabilities --cpu-profile host-no-rdrand`. Recording and replay \
157 with host-no-rdrand are not supported.\n\n",
158 host_no_rdrand_residual!(),
159 "\n\n",
160 "v3 is offered only on \
161 the ptrace backend, on hosts that pass a startup qualification \
162 (`hermit host-capabilities --cpu-profile x86-64-v3` runs it alone). A recording binds \
163 the host facts its guest's answers come from, and replay refuses a host where any of \
164 them differs (<https://github.com/rrnewton/hermit/issues/4243>).\n\n",
165 v3_residual!()
166);
167
168#[cfg(test)]
169mod tests {
170 use clap::ValueEnum;
171
172 use super::*;
173
174 #[test]
175 fn names_round_trip_on_the_command_line_and_in_serde() {
176 for profile in CpuProfile::value_variants() {
177 assert_eq!(CpuProfile::from_str(profile.name(), false), Ok(*profile));
178 let json = serde_json::to_string(profile).unwrap();
179 assert_eq!(json, format!("\"{}\"", profile.name()));
180 assert_eq!(serde_json::from_str::<CpuProfile>(&json).unwrap(), *profile);
181 }
182 assert_eq!(CpuProfile::default(), CpuProfile::X86_64V2);
183 assert!(CpuProfile::X86_64V2.is_default());
184 assert!(!CpuProfile::X86_64V3.is_default());
185 assert!(!CpuProfile::HostNoRdrand.is_default());
186 }
187}