Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 118 additions & 16 deletions nvx-rs/src/guest/time_probe/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ fn state_name(device: &TickDevice) -> &'static str {
.map_or("unknown", ClockEventState::name)
}

/// Formats C `%#x`, which prefixes only nonzero values with `0x`.
fn alternate_hex(value: u64) -> String {
if value == 0 {
"0".to_owned()
} else {
format!("{value:#x}")
}
}

/// Formats C `%#010x`, including its alternate prefix and width.
fn alternate_hex_width_10(value: u32) -> String {
if value == 0 {
Expand Down Expand Up @@ -298,6 +307,29 @@ struct CpuidSummary {
hypervisor: bool,
}

impl CpuidSummary {
/// Prints the hypervisor's maximum leaf and vendor signature.
fn print_hypervisor_leaf(&self, output: &mut dyn Write) {
let _ = writeln!(
output,
"cpuid.0x40000000: eax.max_leaf={} vendor=\"{}\"",
alternate_hex(u64::from(self.max_leaf)),
self.vendor
);
}

/// Formats the hypervisor fields of the final clock-probe marker.
fn hypervisor_marker_fields(&self) -> String {
format!(
"hv_vendor={} hv_max_leaf={} hv_features_eax={} hv_features_edx={}",
sanitize_token(&self.vendor),
alternate_hex(u64::from(self.max_leaf)),
alternate_hex(u64::from(self.features_eax)),
alternate_hex(u64::from(self.features_edx))
)
}
}

/// Prints architectural and hypervisor CPUID leaves.
fn print_cpuid(output: &mut dyn Write) -> CpuidSummary {
let basic = sys::x86::cpuid(cpuid::LEAF_MAXIMUM, 0).eax;
Expand Down Expand Up @@ -399,11 +431,7 @@ fn print_cpuid(output: &mut dyn Write) -> CpuidSummary {
let value = sys::x86::cpuid(cpuid::LEAF_HYPERVISOR, 0);
summary.max_leaf = value.eax;
summary.vendor = cpuid::signature(value);
let _ = writeln!(
output,
"cpuid.0x40000000: eax.max_leaf={:#x} vendor=\"{}\"",
summary.max_leaf, summary.vendor
);
summary.print_hypervisor_leaf(output);
let last = summary.max_leaf.min(LAST_PRINTED_HYPERVISOR_LEAF);
for leaf in cpuid::LEAF_HYPERVISOR + 1..=last {
let value = sys::x86::cpuid(leaf, 0);
Expand Down Expand Up @@ -435,6 +463,15 @@ fn print_cpuid(output: &mut dyn Write) -> CpuidSummary {
summary
}

/// Prints one successfully read MSR, including its hexadecimal and decimal values.
fn print_msr(output: &mut dyn Write, index: u32, name: &str, value: u64) {
let _ = writeln!(
output,
"msr {index:#010x} {name:<34} = {} ({value})",
alternate_hex(value)
);
}

/// Prints optional CPU-zero MSRs, reporting `EIO` as a trapped `#GP`.
fn print_msrs(output: &mut dyn Write) {
let device = match MsrFile::open(0, MsrAccess::Read) {
Expand All @@ -451,12 +488,7 @@ fn print_msrs(output: &mut dyn Write) {
};
for (index, name) in PRINTED_MSRS {
match device.read(index) {
Ok(value) => {
let _ = writeln!(
output,
"msr {index:#010x} {name:<34} = {value:#x} ({value})"
);
}
Ok(value) => print_msr(output, index, name, value),
Err(error) => {
let reason = if error == Errno::EIO {
"read faulted (#GP)"
Expand Down Expand Up @@ -648,15 +680,12 @@ pub(super) fn run(
let _ = writeln!(
output,
"NVX-TIME-PROBE clock clocksource={} available={available} tsc_hz={} tsc_hz_source={} \
hv_vendor={} hv_max_leaf={:#x} hv_features_eax={:#x} hv_features_edx={:#x} \
{} \
flags={present} missing_flags={absent} tsc_unstable_msgs={} unchecked_msr_msgs={} {}",
String::from_utf8_lossy(&clocksource),
f0(rate.hz),
rate.source,
sanitize_token(&ids.vendor),
ids.max_leaf,
ids.features_eax,
ids.features_edx,
ids.hypervisor_marker_fields(),
time.tsc_unstable,
time.unchecked_msr,
timer_summary
Expand All @@ -668,6 +697,79 @@ pub(super) fn run(
mod tests {
use super::*;

#[test]
fn msr_values_match_c_alternate_hex() {
let mut output = Vec::new();
for value in [0, 1, u64::MAX] {
print_msr(&mut output, msr::HV_VP_INDEX, "HV_X64_MSR_VP_INDEX", value);
}
assert_eq!(
String::from_utf8(output).unwrap(),
concat!(
"msr 0x40000002 HV_X64_MSR_VP_INDEX = 0 (0)\n",
"msr 0x40000002 HV_X64_MSR_VP_INDEX = 0x1 (1)\n",
"msr 0x40000002 HV_X64_MSR_VP_INDEX = 0xffffffffffffffff (18446744073709551615)\n",
)
);
}

#[test]
fn cpuid_marker_without_hypervisor_matches_c() {
// Match print_cpuid's defaults when CPUID reports no hypervisor.
let summary = CpuidSummary {
vendor: "none".to_owned(),
..CpuidSummary::default()
};
assert_eq!(
summary.hypervisor_marker_fields(),
"hv_vendor=none hv_max_leaf=0 hv_features_eax=0 hv_features_edx=0"
);
}

#[test]
fn cpuid_marker_keeps_each_feature_register() {
let summary = CpuidSummary {
vendor: "Microsoft Hv".to_owned(),
max_leaf: 0x4000_000b,
features_eax: 0x2e7f,
features_edx: 0,
hypervisor: true,
};
assert_eq!(
summary.hypervisor_marker_fields(),
"hv_vendor=Microsoft_Hv hv_max_leaf=0x4000000b hv_features_eax=0x2e7f hv_features_edx=0"
);
}

#[test]
fn hypervisor_fields_match_c_alternate_hex() {
for (value, expected) in [
(0, "0"),
(0x4000_0003, "0x40000003"),
(u32::MAX, "0xffffffff"),
] {
let summary = CpuidSummary {
vendor: "Microsoft Hv".to_owned(),
max_leaf: value,
features_eax: value,
features_edx: value,
hypervisor: true,
};
let mut output = Vec::new();
summary.print_hypervisor_leaf(&mut output);
assert_eq!(
String::from_utf8(output).unwrap(),
format!("cpuid.0x40000000: eax.max_leaf={expected} vendor=\"Microsoft Hv\"\n")
);
assert_eq!(
summary.hypervisor_marker_fields(),
format!(
"hv_vendor=Microsoft_Hv hv_max_leaf={expected} hv_features_eax={expected} hv_features_edx={expected}"
)
);
}
}

#[test]
fn token_sanitizing_keeps_only_marker_safe_bytes() {
assert_eq!(sanitize_token("Microsoft Hv/x"), "Microsoft_Hv_x");
Expand Down
Loading