1#![warn(missing_docs)]
15
16use crate::UefiDevice;
17use guestmem::GuestMemory;
18use guestmem::GuestMemoryError;
19use inspect::Inspect;
20use std::borrow::Cow;
21use std::collections::BTreeMap;
22use std::fmt::Debug;
23use std::mem::size_of;
24use thiserror::Error;
25use uefi_specs::hyperv::advanced_logger::AdvancedLoggerInfo;
26use uefi_specs::hyperv::advanced_logger::AdvancedLoggerMessageEntryV2;
27use uefi_specs::hyperv::advanced_logger::PHASE_NAMES;
28use uefi_specs::hyperv::advanced_logger::SIG_ENTRY;
29use uefi_specs::hyperv::advanced_logger::SIG_HEADER;
30use uefi_specs::hyperv::debug_level::DEBUG_ERROR;
31use uefi_specs::hyperv::debug_level::DEBUG_FLAG_NAMES;
32use uefi_specs::hyperv::debug_level::DEBUG_WARN;
33use zerocopy::FromBytes;
34
35const ALIGNMENT: usize = 8;
37
38const ALIGNMENT_MASK: usize = ALIGNMENT - 1;
40
41pub const MAX_LOG_BUFFER_SIZE: u32 = 0x400000; pub const MAX_MESSAGE_LENGTH: u16 = 0x1000; const SUPPRESS_LOGS: [&str; 3] = [
52 "WARNING: There is mismatch of supported HashMask (0x2 - 0x7) between modules",
53 "that are linking different HashInstanceLib instances!",
54 "ConvertPages: failed to find range",
55];
56
57#[derive(Debug, Clone)]
59pub struct EfiDiagnosticsLog<'a> {
60 pub debug_level: u32,
62 pub ticks: u64,
64 pub phase: u16,
66 pub message: &'a str,
68}
69
70fn debug_level_to_string(debug_level: u32) -> Cow<'static, str> {
72 if debug_level.count_ones() == 1 {
74 if let Some(&(_, name)) = DEBUG_FLAG_NAMES
75 .iter()
76 .find(|&&(flag, _)| flag == debug_level)
77 {
78 return Cow::Borrowed(name);
79 }
80 }
81
82 let flags: Vec<&str> = DEBUG_FLAG_NAMES
84 .iter()
85 .filter(|&&(flag, _)| debug_level & flag != 0)
86 .map(|&(_, name)| name)
87 .collect();
88
89 if flags.is_empty() {
90 Cow::Borrowed("UNKNOWN")
91 } else {
92 Cow::Owned(flags.join("+"))
93 }
94}
95
96fn phase_to_string(phase: u16) -> &'static str {
98 PHASE_NAMES
99 .iter()
100 .find(|&&(phase_raw, _)| phase_raw == phase)
101 .map(|&(_, name)| name)
102 .unwrap_or("UNKNOWN")
103}
104
105#[derive(Debug, Error)]
107#[allow(missing_docs)]
108pub enum EntryParseError {
109 #[error("Expected: {0:#x}, got: {1:#x}")]
110 SignatureMismatch(u32, u32),
111 #[error("Expected non-zero timestamp, got: {0:#x}")]
112 Timestamp(u64),
113 #[error("Expected message length < {0:#x}, got: {1:#x}")]
114 MessageLength(u16, u16),
115 #[error("Failed to read from buffer slice")]
116 SliceRead,
117 #[error("Arithmetic overflow in {0}")]
118 Overflow(&'static str),
119 #[error("Failed to read UTF-8 string: {0}")]
120 Utf8Error(#[from] std::str::Utf8Error),
121 #[error("message_end ({0:#x}) exceeds buffer slice length ({1:#x})")]
122 BadMessageEnd(usize, usize),
123}
124
125struct EntryData<'a> {
127 debug_level: u32,
129 time_stamp: u64,
131 phase: u16,
133 message: &'a str,
135 entry_size: usize,
137}
138
139fn parse_entry(buffer_slice: &[u8]) -> Result<EntryData<'_>, EntryParseError> {
141 let (entry, _) = AdvancedLoggerMessageEntryV2::read_from_prefix(buffer_slice)
143 .map_err(|_| EntryParseError::SliceRead)?;
144
145 let signature = entry.signature;
146 if signature != u32::from_le_bytes(SIG_ENTRY) {
147 return Err(EntryParseError::SignatureMismatch(
148 u32::from_le_bytes(SIG_ENTRY),
149 signature,
150 ));
151 }
152
153 if entry.message_len > MAX_MESSAGE_LENGTH {
154 return Err(EntryParseError::MessageLength(
155 MAX_MESSAGE_LENGTH,
156 entry.message_len,
157 ));
158 }
159
160 let message_offset = entry.message_offset;
161 let message_len = entry.message_len;
162
163 let message_start = message_offset as usize;
165 let message_end = message_start
166 .checked_add(message_len as usize)
167 .ok_or(EntryParseError::Overflow("message_end"))?;
168
169 if message_end > buffer_slice.len() {
170 return Err(EntryParseError::BadMessageEnd(
171 message_end,
172 buffer_slice.len(),
173 ));
174 }
175
176 let message = std::str::from_utf8(&buffer_slice[message_start..message_end])?;
177
178 let base_offset = size_of::<AdvancedLoggerMessageEntryV2>()
180 .checked_add(message_len as usize)
181 .ok_or(EntryParseError::Overflow("base_offset"))?;
182
183 let aligned_offset = base_offset
185 .checked_add(ALIGNMENT_MASK)
186 .ok_or(EntryParseError::Overflow("aligned_offset"))?;
187 let entry_size = aligned_offset & !ALIGNMENT_MASK;
188
189 Ok(EntryData {
190 debug_level: entry.debug_level,
191 time_stamp: entry.time_stamp,
192 phase: entry.phase,
193 message,
194 entry_size,
195 })
196}
197
198#[derive(Debug, Error)]
200#[allow(missing_docs)]
201pub enum DiagnosticsError {
202 #[error("Failed to parse entry: {0}")]
203 EntryParse(#[from] EntryParseError),
204 #[error("Expected: {0:#x}, got: {1:#x}")]
205 HeaderSignatureMismatch(u32, u32),
206 #[error("Expected log buffer size < {0:#x}, got: {1:#x}")]
207 HeaderBufferSize(u32, u32),
208 #[error("Bad GPA value: {0:#x}")]
209 BadGpa(u32),
210 #[error("No GPA set")]
211 NoGpa,
212 #[error("Failed to read from guest memory: {0}")]
213 GuestMemoryRead(#[from] GuestMemoryError),
214 #[error("Arithmetic overflow in {0}")]
215 Overflow(&'static str),
216 #[error("Expected used log buffer size < {0:#x}, got: {1:#x}")]
217 BadUsedBufferSize(u32, u32),
218 #[error("Expected accumulated message length < {0:#x}, got: {1:#x}")]
219 BadAccumulatedMessageLength(u16, u16),
220}
221
222#[derive(Inspect)]
224pub struct DiagnosticsServices {
225 gpa: Option<u32>,
227 did_process: bool,
229}
230
231impl DiagnosticsServices {
232 pub fn new() -> DiagnosticsServices {
234 DiagnosticsServices {
235 gpa: None,
236 did_process: false,
237 }
238 }
239
240 pub fn reset(&mut self) {
242 self.gpa = None;
243 self.did_process = false;
244 }
245
246 pub fn set_gpa(&mut self, gpa: u32) {
248 self.gpa = match gpa {
249 0 => None,
250 _ => Some(gpa),
251 }
252 }
253
254 pub fn process_diagnostics<F>(
256 &mut self,
257 gm: &GuestMemory,
258 mut log_handler: F,
259 ) -> Result<(), DiagnosticsError>
260 where
261 F: FnMut(EfiDiagnosticsLog<'_>),
262 {
263 let gpa = match self.gpa {
265 Some(gpa) if gpa != 0 && gpa != u32::MAX => gpa,
266 Some(invalid_gpa) => return Err(DiagnosticsError::BadGpa(invalid_gpa)),
267 None => return Err(DiagnosticsError::NoGpa),
268 };
269
270 let header: AdvancedLoggerInfo = gm.read_plain(gpa as u64)?;
272
273 let signature = header.signature;
274 if signature != u32::from_le_bytes(SIG_HEADER) {
275 return Err(DiagnosticsError::HeaderSignatureMismatch(
276 u32::from_le_bytes(SIG_HEADER),
277 signature,
278 ));
279 }
280
281 if header.log_buffer_size > MAX_LOG_BUFFER_SIZE {
282 return Err(DiagnosticsError::HeaderBufferSize(
283 MAX_LOG_BUFFER_SIZE,
284 header.log_buffer_size,
285 ));
286 }
287
288 let used_log_buffer_size = header
290 .log_current_offset
291 .checked_sub(header.log_buffer_offset)
292 .ok_or_else(|| DiagnosticsError::Overflow("used_log_buffer_size"))?;
293
294 if used_log_buffer_size == 0 {
296 tracelimit::info_ratelimited!(
297 "EFI diagnostics' used log buffer size is 0, ending processing"
298 );
299 return Ok(());
300 }
301
302 if used_log_buffer_size > header.log_buffer_size
303 || used_log_buffer_size > MAX_LOG_BUFFER_SIZE
304 {
305 return Err(DiagnosticsError::BadUsedBufferSize(
306 MAX_LOG_BUFFER_SIZE,
307 used_log_buffer_size,
308 ));
309 }
310
311 let buffer_start_addr = gpa
313 .checked_add(header.log_buffer_offset)
314 .ok_or_else(|| DiagnosticsError::Overflow("buffer_start_addr"))?;
315
316 let mut buffer_data = vec![0u8; used_log_buffer_size as usize];
318 gm.read_at(buffer_start_addr as u64, &mut buffer_data)?;
319
320 let mut buffer_slice = &buffer_data[..];
322
323 let mut accumulated_message = String::with_capacity(MAX_MESSAGE_LENGTH as usize);
325 let mut debug_level = 0;
326 let mut time_stamp = 0;
327 let mut phase = 0;
328 let mut is_accumulating = false;
329
330 let mut bytes_read: usize = 0;
332 let mut entries_processed: usize = 0;
333
334 let mut suppressed_logs = BTreeMap::new();
335
336 while !buffer_slice.is_empty() {
338 let entry = parse_entry(buffer_slice)?;
339
340 if !is_accumulating {
342 debug_level = entry.debug_level;
343 time_stamp = entry.time_stamp;
344 phase = entry.phase;
345 accumulated_message.clear();
346 is_accumulating = true;
347 }
348
349 accumulated_message.push_str(entry.message);
350 if accumulated_message.len() > MAX_MESSAGE_LENGTH as usize {
351 return Err(DiagnosticsError::BadAccumulatedMessageLength(
352 MAX_MESSAGE_LENGTH,
353 accumulated_message.len() as u16,
354 ));
355 }
356
357 if !entry.message.is_empty() && entry.message.ends_with('\n') {
359 let mut suppress = false;
360 for log in SUPPRESS_LOGS {
361 if accumulated_message.contains(log) {
362 suppressed_logs
363 .entry(log)
364 .and_modify(|c| *c += 1)
365 .or_insert(1);
366 suppress = true;
367 }
368 }
369 if !suppress {
370 log_handler(EfiDiagnosticsLog {
371 debug_level,
372 ticks: time_stamp,
373 phase,
374 message: accumulated_message.trim_end_matches(&['\r', '\n'][..]),
375 });
376 }
377 is_accumulating = false;
378 entries_processed += 1;
379 }
380
381 bytes_read = bytes_read
383 .checked_add(entry.entry_size)
384 .ok_or_else(|| DiagnosticsError::Overflow("bytes_read"))?;
385
386 if entry.entry_size >= buffer_slice.len() {
387 break; } else {
389 buffer_slice = &buffer_slice[entry.entry_size..];
390 }
391 }
392
393 if is_accumulating && !accumulated_message.is_empty() {
395 log_handler(EfiDiagnosticsLog {
396 debug_level,
397 ticks: time_stamp,
398 phase,
399 message: accumulated_message.trim_end_matches(&['\r', '\n'][..]),
400 });
401 entries_processed += 1;
402 }
403
404 for (substring, count) in suppressed_logs {
405 tracelimit::warn_ratelimited!(substring, count, "suppressed logs")
406 }
407
408 tracelimit::info_ratelimited!(entries_processed, bytes_read, "processed EFI log entries");
410
411 Ok(())
412 }
413}
414
415impl UefiDevice {
416 pub(crate) fn process_diagnostics(&mut self) {
418 if self.service.diagnostics.did_process {
420 tracelimit::warn_ratelimited!("Already processed diagnostics, skipping");
421 return;
422 }
423 self.service.diagnostics.did_process = true;
424
425 match self
427 .service
428 .diagnostics
429 .process_diagnostics(&self.gm, |log| {
430 let debug_level_str = debug_level_to_string(log.debug_level);
431 let phase_str = phase_to_string(log.phase);
432
433 match log.debug_level {
434 DEBUG_WARN => tracing::warn!(
435 debug_level = %debug_level_str,
436 ticks = log.ticks,
437 phase = %phase_str,
438 log_message = log.message,
439 "EFI log entry"
440 ),
441 DEBUG_ERROR => tracing::error!(
442 debug_level = %debug_level_str,
443 ticks = log.ticks,
444 phase = %phase_str,
445 log_message = log.message,
446 "EFI log entry"
447 ),
448 _ => tracing::info!(
449 debug_level = %debug_level_str,
450 ticks = log.ticks,
451 phase = %phase_str,
452 log_message = log.message,
453 "EFI log entry"
454 ),
455 }
456 }) {
457 Ok(_) => {}
458 Err(error) => {
459 tracelimit::error_ratelimited!(
460 error = &error as &dyn std::error::Error,
461 "Failed to process diagnostics buffer"
462 );
463 }
464 }
465 }
466}
467
468mod save_restore {
469 use super::*;
470 use vmcore::save_restore::RestoreError;
471 use vmcore::save_restore::SaveError;
472 use vmcore::save_restore::SaveRestore;
473
474 mod state {
475 use mesh::payload::Protobuf;
476 use vmcore::save_restore::SavedStateRoot;
477
478 #[derive(Protobuf, SavedStateRoot)]
479 #[mesh(package = "firmware.uefi.diagnostics")]
480 pub struct SavedState {
481 #[mesh(1)]
482 pub gpa: Option<u32>,
483 #[mesh(2)]
484 pub did_flush: bool,
485 }
486 }
487
488 impl SaveRestore for DiagnosticsServices {
489 type SavedState = state::SavedState;
490
491 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
492 Ok(state::SavedState {
493 gpa: self.gpa,
494 did_flush: self.did_process,
495 })
496 }
497
498 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
499 let state::SavedState { gpa, did_flush } = state;
500 self.gpa = gpa;
501 self.did_process = did_flush;
502 Ok(())
503 }
504 }
505}