1use memf_core::object_reader::ObjectReader;
15use memf_format::PhysicalMemoryProvider;
16
17use crate::{BootTimeEstimate, BootTimeSource, Error, Result};
18
19pub fn extract_boot_time<P: PhysicalMemoryProvider>(
28 reader: &ObjectReader<P>,
29) -> Result<BootTimeEstimate> {
30 let tk_addr = reader
32 .symbols()
33 .symbol_address("tk_core")
34 .or_else(|| reader.symbols().symbol_address("timekeeper"))
35 .ok_or_else(|| Error::MissingKernelSymbol {
36 name: "tk_core".into(),
37 })?;
38
39 let tk_offset = reader
43 .symbols()
44 .field_offset("tk_core", "timekeeper")
45 .unwrap_or(0);
46 let timekeeper_addr = tk_addr + tk_offset;
47
48 let _xtime_sec: i64 = reader.read_field(timekeeper_addr, "timekeeper", "xtime_sec")?;
50
51 let w2m_offset = reader
53 .symbols()
54 .field_offset("timekeeper", "wall_to_monotonic")
55 .ok_or_else(|| Error::MissingField {
56 struct_name: "timekeeper".into(),
57 field_name: "wall_to_monotonic".into(),
58 })?;
59 let w2m_addr = timekeeper_addr + w2m_offset;
60 let w2m_tv_sec: i64 = reader.read_field(w2m_addr, "timespec64", "tv_sec")?;
61
62 let offs_boot_ns: i64 = reader
65 .read_field(timekeeper_addr, "timekeeper", "offs_boot")
66 .unwrap_or(0);
67
68 let boot_epoch = -w2m_tv_sec - offs_boot_ns / 1_000_000_000;
70
71 Ok(BootTimeEstimate {
72 source: BootTimeSource::Timekeeper,
73 boot_epoch_secs: boot_epoch,
74 })
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::BootTimeSource;
81 use memf_core::object_reader::ObjectReader;
82 use memf_core::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
83 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
84 use memf_symbols::isf::IsfResolver;
85 use memf_symbols::test_builders::IsfBuilder;
86
87 const XTIME_SEC_OFF: usize = 0;
98 const W2M_OFF: usize = 8;
99 const OFFS_BOOT_OFF: usize = 24;
100
101 fn build_boot_time_reader(
102 xtime_sec: i64,
103 w2m_tv_sec: i64,
104 offs_boot_ns: i64,
105 ) -> ObjectReader<SyntheticPhysMem> {
106 let vaddr: u64 = 0xFFFF_8000_0010_0000;
107 let paddr: u64 = 0x0080_0000;
108
109 let isf = IsfBuilder::new()
110 .add_struct("tk_core", 128)
111 .add_field("tk_core", "timekeeper", 0, "timekeeper")
112 .add_struct("timekeeper", 128)
113 .add_field("timekeeper", "xtime_sec", 0, "long long")
114 .add_field("timekeeper", "wall_to_monotonic", 8, "timespec64")
115 .add_field("timekeeper", "offs_boot", 24, "long long")
116 .add_struct("timespec64", 16)
117 .add_field("timespec64", "tv_sec", 0, "long long")
118 .add_field("timespec64", "tv_nsec", 8, "long long")
119 .add_symbol("tk_core", vaddr)
120 .build_json();
121 let resolver = IsfResolver::from_value(&isf).unwrap();
122
123 let mut data = vec![0u8; 4096];
124 data[XTIME_SEC_OFF..XTIME_SEC_OFF + 8].copy_from_slice(&xtime_sec.to_le_bytes());
125 data[W2M_OFF..W2M_OFF + 8].copy_from_slice(&w2m_tv_sec.to_le_bytes());
126 data[OFFS_BOOT_OFF..OFFS_BOOT_OFF + 8].copy_from_slice(&offs_boot_ns.to_le_bytes());
128
129 let (cr3, mem) = PageTableBuilder::new()
130 .map_4k(vaddr, paddr, flags::WRITABLE)
131 .write_phys(paddr, &data)
132 .build();
133 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
134 ObjectReader::new(vas, Box::new(resolver))
135 }
136
137 #[test]
142 fn extract_boot_time_no_suspend() {
143 let reader = build_boot_time_reader(
144 1_712_100_000, -1_712_000_000, 0, );
148 let est = extract_boot_time(&reader).unwrap();
149 assert_eq!(est.source, BootTimeSource::Timekeeper);
150 assert_eq!(est.boot_epoch_secs, 1_712_000_000);
151 }
152
153 #[test]
159 fn extract_boot_time_with_suspend() {
160 let reader = build_boot_time_reader(
161 1_712_100_000,
162 -1_712_000_000,
163 7_200_000_000_000, );
165 let est = extract_boot_time(&reader).unwrap();
166 assert_eq!(est.source, BootTimeSource::Timekeeper);
167 assert_eq!(est.boot_epoch_secs, 1_711_992_800);
168 }
169
170 #[test]
172 fn extract_boot_time_missing_symbol() {
173 let isf = IsfBuilder::new()
174 .add_struct("timekeeper", 64)
175 .add_field("timekeeper", "xtime_sec", 0, "long long")
176 .build_json();
177 let resolver = IsfResolver::from_value(&isf).unwrap();
178 let (cr3, mem) = PageTableBuilder::new().build();
179 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
180 let reader = ObjectReader::new(vas, Box::new(resolver));
181
182 let result = extract_boot_time(&reader);
183 assert!(
184 matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "tk_core" || name == "timekeeper"),
185 "expected MissingKernelSymbol for tk_core/timekeeper, got {result:?}"
186 );
187 }
188
189 #[test]
190 fn extract_boot_time_missing_wall_to_monotonic_returns_missing_field() {
191 let tk_vaddr: u64 = 0xFFFF_8000_0010_0000;
193 let tk_paddr: u64 = 0x0080_0000;
194 let mut data = vec![0u8; 4096];
195 data[0..8].copy_from_slice(&1700000000i64.to_le_bytes());
197
198 let isf = IsfBuilder::new()
199 .add_symbol("tk_core", tk_vaddr)
200 .add_struct("tk_core", 256)
201 .add_field("tk_core", "timekeeper", 0, "timekeeper")
202 .add_struct("timekeeper", 128)
203 .add_field("timekeeper", "xtime_sec", 0, "long long")
204 .add_struct("timespec64", 16)
206 .add_field("timespec64", "tv_sec", 0, "long long")
207 .add_field("timespec64", "tv_nsec", 8, "long")
208 .build_json();
209 let resolver = IsfResolver::from_value(&isf).unwrap();
210 let (cr3, mem) = PageTableBuilder::new()
211 .map_4k(tk_vaddr, tk_paddr, flags::WRITABLE)
212 .write_phys(tk_paddr, &data)
213 .build();
214 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
215 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
216 let result = extract_boot_time(&reader);
217 assert!(
218 matches!(result, Err(crate::Error::MissingField { ref struct_name, ref field_name }) if struct_name == "timekeeper" && field_name == "wall_to_monotonic"),
219 "expected MissingField timekeeper.wall_to_monotonic, got {result:?}"
220 );
221 }
222}