use memf_core::object_reader::ObjectReader;
use memf_format::PhysicalMemoryProvider;
use crate::{BootTimeEstimate, BootTimeSource, Error, Result};
pub fn extract_boot_time<P: PhysicalMemoryProvider>(
reader: &ObjectReader<P>,
) -> Result<BootTimeEstimate> {
let tk_addr = reader
.symbols()
.symbol_address("tk_core")
.or_else(|| reader.symbols().symbol_address("timekeeper"))
.ok_or_else(|| Error::MissingKernelSymbol {
name: "tk_core".into(),
})?;
let tk_offset = reader
.symbols()
.field_offset("tk_core", "timekeeper")
.unwrap_or(0);
let timekeeper_addr = tk_addr + tk_offset;
let _xtime_sec: i64 = reader.read_field(timekeeper_addr, "timekeeper", "xtime_sec")?;
let w2m_offset = reader
.symbols()
.field_offset("timekeeper", "wall_to_monotonic")
.ok_or_else(|| Error::MissingField {
struct_name: "timekeeper".into(),
field_name: "wall_to_monotonic".into(),
})?;
let w2m_addr = timekeeper_addr + w2m_offset;
let w2m_tv_sec: i64 = reader.read_field(w2m_addr, "timespec64", "tv_sec")?;
let offs_boot_ns: i64 = reader
.read_field(timekeeper_addr, "timekeeper", "offs_boot")
.unwrap_or(0);
let boot_epoch = -w2m_tv_sec - offs_boot_ns / 1_000_000_000;
Ok(BootTimeEstimate {
source: BootTimeSource::Timekeeper,
boot_epoch_secs: boot_epoch,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BootTimeSource;
use memf_core::object_reader::ObjectReader;
use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
use memf_core::vas::{TranslationMode, VirtualAddressSpace};
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
const XTIME_SEC_OFF: usize = 0;
const W2M_OFF: usize = 8;
const OFFS_BOOT_OFF: usize = 24;
fn build_boot_time_reader(
xtime_sec: i64,
w2m_tv_sec: i64,
offs_boot_ns: i64,
) -> ObjectReader<SyntheticPhysMem> {
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let isf = IsfBuilder::new()
.add_struct("tk_core", 128)
.add_field("tk_core", "timekeeper", 0, "timekeeper")
.add_struct("timekeeper", 128)
.add_field("timekeeper", "xtime_sec", 0, "long long")
.add_field("timekeeper", "wall_to_monotonic", 8, "timespec64")
.add_field("timekeeper", "offs_boot", 24, "long long")
.add_struct("timespec64", 16)
.add_field("timespec64", "tv_sec", 0, "long long")
.add_field("timespec64", "tv_nsec", 8, "long long")
.add_symbol("tk_core", vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let mut data = vec![0u8; 4096];
data[XTIME_SEC_OFF..XTIME_SEC_OFF + 8].copy_from_slice(&xtime_sec.to_le_bytes());
data[W2M_OFF..W2M_OFF + 8].copy_from_slice(&w2m_tv_sec.to_le_bytes());
data[OFFS_BOOT_OFF..OFFS_BOOT_OFF + 8].copy_from_slice(&offs_boot_ns.to_le_bytes());
let (cr3, mem) = PageTableBuilder::new()
.map_4k(vaddr, paddr, flags::WRITABLE)
.write_phys(paddr, &data)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn extract_boot_time_no_suspend() {
let reader = build_boot_time_reader(
1_712_100_000, -1_712_000_000, 0, );
let est = extract_boot_time(&reader).unwrap();
assert_eq!(est.source, BootTimeSource::Timekeeper);
assert_eq!(est.boot_epoch_secs, 1_712_000_000);
}
#[test]
fn extract_boot_time_with_suspend() {
let reader = build_boot_time_reader(
1_712_100_000,
-1_712_000_000,
7_200_000_000_000, );
let est = extract_boot_time(&reader).unwrap();
assert_eq!(est.source, BootTimeSource::Timekeeper);
assert_eq!(est.boot_epoch_secs, 1_711_992_800);
}
#[test]
fn extract_boot_time_missing_symbol() {
let isf = IsfBuilder::new()
.add_struct("timekeeper", 64)
.add_field("timekeeper", "xtime_sec", 0, "long long")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new().build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader = ObjectReader::new(vas, Box::new(resolver));
let result = extract_boot_time(&reader);
assert!(
matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "tk_core" || name == "timekeeper"),
"expected MissingKernelSymbol for tk_core/timekeeper, got {result:?}"
);
}
#[test]
fn extract_boot_time_missing_wall_to_monotonic_returns_missing_field() {
let tk_vaddr: u64 = 0xFFFF_8000_0010_0000;
let tk_paddr: u64 = 0x0080_0000;
let mut data = vec![0u8; 4096];
data[0..8].copy_from_slice(&1700000000i64.to_le_bytes());
let isf = IsfBuilder::new()
.add_symbol("tk_core", tk_vaddr)
.add_struct("tk_core", 256)
.add_field("tk_core", "timekeeper", 0, "timekeeper")
.add_struct("timekeeper", 128)
.add_field("timekeeper", "xtime_sec", 0, "long long")
.add_struct("timespec64", 16)
.add_field("timespec64", "tv_sec", 0, "long long")
.add_field("timespec64", "tv_nsec", 8, "long")
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let (cr3, mem) = PageTableBuilder::new()
.map_4k(tk_vaddr, tk_paddr, flags::WRITABLE)
.write_phys(tk_paddr, &data)
.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
let result = extract_boot_time(&reader);
assert!(
matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "timekeeper" && field_name == "wall_to_monotonic"),
"expected MissingField timekeeper.wall_to_monotonic, got {result:?}"
);
}
}