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
35pub const DEFAULT_LOGS_PER_PERIOD: u32 = 150;
37
38const ALIGNMENT: usize = 8;
40
41const ALIGNMENT_MASK: usize = ALIGNMENT - 1;
43
44pub const MAX_LOG_BUFFER_SIZE: u32 = 0x400000; pub const MAX_MESSAGE_LENGTH: u16 = 0x1000; const SUPPRESS_LOGS: [&str; 3] = [
55 "WARNING: There is mismatch of supported HashMask (0x2 - 0x7) between modules",
56 "that are linking different HashInstanceLib instances!",
57 "ConvertPages: failed to find range",
58];
59
60#[derive(Debug, Clone)]
62pub struct EfiDiagnosticsLog<'a> {
63 pub debug_level: u32,
65 pub ticks: u64,
67 pub phase: u16,
69 pub message: &'a str,
71}
72
73fn debug_level_to_string(debug_level: u32) -> Cow<'static, str> {
75 if debug_level.count_ones() == 1 {
77 if let Some(&(_, name)) = DEBUG_FLAG_NAMES
78 .iter()
79 .find(|&&(flag, _)| flag == debug_level)
80 {
81 return Cow::Borrowed(name);
82 }
83 }
84
85 let flags: Vec<&str> = DEBUG_FLAG_NAMES
87 .iter()
88 .filter(|&&(flag, _)| debug_level & flag != 0)
89 .map(|&(_, name)| name)
90 .collect();
91
92 if flags.is_empty() {
93 Cow::Borrowed("UNKNOWN")
94 } else {
95 Cow::Owned(flags.join("+"))
96 }
97}
98
99fn phase_to_string(phase: u16) -> &'static str {
101 PHASE_NAMES
102 .iter()
103 .find(|&&(phase_raw, _)| phase_raw == phase)
104 .map(|&(_, name)| name)
105 .unwrap_or("UNKNOWN")
106}
107
108pub fn log_diagnostic_ratelimited(log: EfiDiagnosticsLog<'_>, limit: u32) {
110 let debug_level_str = debug_level_to_string(log.debug_level);
111 let phase_str = phase_to_string(log.phase);
112
113 match log.debug_level {
114 DEBUG_WARN => tracelimit::warn_ratelimited!(
115 limit: limit,
116 debug_level = %debug_level_str,
117 ticks = log.ticks,
118 phase = %phase_str,
119 log_message = log.message,
120 "EFI log entry"
121 ),
122 DEBUG_ERROR => tracelimit::error_ratelimited!(
123 limit: limit,
124 debug_level = %debug_level_str,
125 ticks = log.ticks,
126 phase = %phase_str,
127 log_message = log.message,
128 "EFI log entry"
129 ),
130 _ => tracelimit::info_ratelimited!(
131 limit: limit,
132 debug_level = %debug_level_str,
133 ticks = log.ticks,
134 phase = %phase_str,
135 log_message = log.message,
136 "EFI log entry"
137 ),
138 }
139}
140
141pub fn log_diagnostic_unrestricted(log: EfiDiagnosticsLog<'_>) {
143 let debug_level_str = debug_level_to_string(log.debug_level);
144 let phase_str = phase_to_string(log.phase);
145
146 match log.debug_level {
147 DEBUG_WARN => tracing::warn!(
148 debug_level = %debug_level_str,
149 ticks = log.ticks,
150 phase = %phase_str,
151 log_message = log.message,
152 "EFI log entry"
153 ),
154 DEBUG_ERROR => tracing::error!(
155 debug_level = %debug_level_str,
156 ticks = log.ticks,
157 phase = %phase_str,
158 log_message = log.message,
159 "EFI log entry"
160 ),
161 _ => tracing::info!(
162 debug_level = %debug_level_str,
163 ticks = log.ticks,
164 phase = %phase_str,
165 log_message = log.message,
166 "EFI log entry"
167 ),
168 }
169}
170
171#[derive(Debug, Error)]
173#[allow(missing_docs)]
174pub enum EntryParseError {
175 #[error("Expected: {0:#x}, got: {1:#x}")]
176 SignatureMismatch(u32, u32),
177 #[error("Expected message length < {0:#x}, got: {1:#x}")]
178 MessageLength(u16, u16),
179 #[error("Failed to read from buffer slice")]
180 SliceRead,
181 #[error("Arithmetic overflow in {0}")]
182 Overflow(&'static str),
183 #[error("Failed to read UTF-8 string: {0}")]
184 Utf8Error(#[from] std::str::Utf8Error),
185 #[error("message_end ({0:#x}) exceeds buffer slice length ({1:#x})")]
186 BadMessageEnd(usize, usize),
187}
188
189struct EntryData<'a> {
191 debug_level: u32,
193 time_stamp: u64,
195 phase: u16,
197 message: &'a str,
199 entry_size: usize,
201}
202
203fn parse_entry(buffer_slice: &[u8]) -> Result<EntryData<'_>, EntryParseError> {
205 let (entry, _) = AdvancedLoggerMessageEntryV2::read_from_prefix(buffer_slice)
207 .map_err(|_| EntryParseError::SliceRead)?;
208
209 let signature = entry.signature;
210 if signature != u32::from_le_bytes(SIG_ENTRY) {
211 return Err(EntryParseError::SignatureMismatch(
212 u32::from_le_bytes(SIG_ENTRY),
213 signature,
214 ));
215 }
216
217 if entry.message_len > MAX_MESSAGE_LENGTH {
218 return Err(EntryParseError::MessageLength(
219 MAX_MESSAGE_LENGTH,
220 entry.message_len,
221 ));
222 }
223
224 let message_offset = entry.message_offset;
225 let message_len = entry.message_len;
226
227 let message_start = message_offset as usize;
229 let message_end = message_start
230 .checked_add(message_len as usize)
231 .ok_or(EntryParseError::Overflow("message_end"))?;
232
233 if message_end > buffer_slice.len() {
234 return Err(EntryParseError::BadMessageEnd(
235 message_end,
236 buffer_slice.len(),
237 ));
238 }
239
240 let message = std::str::from_utf8(&buffer_slice[message_start..message_end])?;
241
242 let base_offset = size_of::<AdvancedLoggerMessageEntryV2>()
244 .checked_add(message_len as usize)
245 .ok_or(EntryParseError::Overflow("base_offset"))?;
246
247 let aligned_offset = base_offset
249 .checked_add(ALIGNMENT_MASK)
250 .ok_or(EntryParseError::Overflow("aligned_offset"))?;
251 let entry_size = aligned_offset & !ALIGNMENT_MASK;
252
253 Ok(EntryData {
254 debug_level: entry.debug_level,
255 time_stamp: entry.time_stamp,
256 phase: entry.phase,
257 message,
258 entry_size,
259 })
260}
261
262#[derive(Debug, Error)]
264#[allow(missing_docs)]
265pub enum DiagnosticsError {
266 #[error("Failed to parse entry: {0}")]
267 EntryParse(#[from] EntryParseError),
268 #[error("Expected: {0:#x}, got: {1:#x}")]
269 HeaderSignatureMismatch(u32, u32),
270 #[error("Expected log buffer size < {0:#x}, got: {1:#x}")]
271 HeaderBufferSize(u32, u32),
272 #[error("Bad GPA value: {0:#x}")]
273 BadGpa(u32),
274 #[error("No GPA set")]
275 NoGpa,
276 #[error("Failed to read from guest memory: {0}")]
277 GuestMemoryRead(#[from] GuestMemoryError),
278 #[error("Arithmetic overflow in {0}")]
279 Overflow(&'static str),
280 #[error("Expected used log buffer size < {0:#x}, got: {1:#x}")]
281 BadUsedBufferSize(u32, u32),
282 #[error("Expected accumulated message length < {0:#x}, got: {1:#x}")]
283 BadAccumulatedMessageLength(u16, u16),
284}
285
286#[derive(Inspect)]
288pub struct DiagnosticsServices {
289 gpa: Option<u32>,
291 has_guest_processed_before: bool,
293}
294
295impl DiagnosticsServices {
296 pub fn new() -> DiagnosticsServices {
298 DiagnosticsServices {
299 gpa: None,
300 has_guest_processed_before: false,
301 }
302 }
303
304 pub fn reset(&mut self) {
306 self.gpa = None;
307 self.has_guest_processed_before = false;
308 }
309
310 pub fn set_gpa(&mut self, gpa: u32) {
312 self.gpa = match gpa {
313 0 => None,
314 _ => Some(gpa),
315 }
316 }
317
318 fn process_diagnostics<F>(
325 &mut self,
326 allow_reprocess: bool,
327 gm: &GuestMemory,
328 mut log_handler: F,
329 ) -> Result<(), DiagnosticsError>
330 where
331 F: FnMut(EfiDiagnosticsLog<'_>),
332 {
333 if !allow_reprocess {
336 if self.has_guest_processed_before {
337 tracelimit::warn_ratelimited!("Already processed diagnostics, skipping");
338 return Ok(());
339 }
340 self.has_guest_processed_before = true;
341 }
342
343 let gpa = match self.gpa {
345 Some(gpa) if gpa != 0 && gpa != u32::MAX => gpa,
346 Some(invalid_gpa) => return Err(DiagnosticsError::BadGpa(invalid_gpa)),
347 None => return Err(DiagnosticsError::NoGpa),
348 };
349
350 let header: AdvancedLoggerInfo = gm.read_plain(gpa as u64)?;
352
353 let signature = header.signature;
354 if signature != u32::from_le_bytes(SIG_HEADER) {
355 return Err(DiagnosticsError::HeaderSignatureMismatch(
356 u32::from_le_bytes(SIG_HEADER),
357 signature,
358 ));
359 }
360
361 if header.log_buffer_size > MAX_LOG_BUFFER_SIZE {
362 return Err(DiagnosticsError::HeaderBufferSize(
363 MAX_LOG_BUFFER_SIZE,
364 header.log_buffer_size,
365 ));
366 }
367
368 let used_log_buffer_size = header
370 .log_current_offset
371 .checked_sub(header.log_buffer_offset)
372 .ok_or_else(|| DiagnosticsError::Overflow("used_log_buffer_size"))?;
373
374 if used_log_buffer_size == 0 {
376 tracelimit::info_ratelimited!(
377 "EFI diagnostics' used log buffer size is 0, ending processing"
378 );
379 return Ok(());
380 }
381
382 if used_log_buffer_size > header.log_buffer_size
383 || used_log_buffer_size > MAX_LOG_BUFFER_SIZE
384 {
385 return Err(DiagnosticsError::BadUsedBufferSize(
386 MAX_LOG_BUFFER_SIZE,
387 used_log_buffer_size,
388 ));
389 }
390
391 let buffer_start_addr = gpa
393 .checked_add(header.log_buffer_offset)
394 .ok_or_else(|| DiagnosticsError::Overflow("buffer_start_addr"))?;
395
396 let mut buffer_data = vec![0u8; used_log_buffer_size as usize];
398 gm.read_at(buffer_start_addr as u64, &mut buffer_data)?;
399
400 let mut buffer_slice = &buffer_data[..];
402
403 let mut accumulated_message = String::with_capacity(MAX_MESSAGE_LENGTH as usize);
405 let mut debug_level = 0;
406 let mut time_stamp = 0;
407 let mut phase = 0;
408 let mut is_accumulating = false;
409
410 let mut bytes_read: usize = 0;
412 let mut entries_processed: usize = 0;
413
414 let mut suppressed_logs = BTreeMap::new();
415
416 while !buffer_slice.is_empty() {
418 let entry = parse_entry(buffer_slice)?;
419
420 if !is_accumulating {
422 debug_level = entry.debug_level;
423 time_stamp = entry.time_stamp;
424 phase = entry.phase;
425 accumulated_message.clear();
426 is_accumulating = true;
427 }
428
429 accumulated_message.push_str(entry.message);
430 if accumulated_message.len() > MAX_MESSAGE_LENGTH as usize {
431 return Err(DiagnosticsError::BadAccumulatedMessageLength(
432 MAX_MESSAGE_LENGTH,
433 accumulated_message.len() as u16,
434 ));
435 }
436
437 if !entry.message.is_empty() && entry.message.ends_with('\n') {
439 let mut suppress = false;
440 for log in SUPPRESS_LOGS {
441 if accumulated_message.contains(log) {
442 suppressed_logs
443 .entry(log)
444 .and_modify(|c| *c += 1)
445 .or_insert(1);
446 suppress = true;
447 }
448 }
449 if !suppress {
450 log_handler(EfiDiagnosticsLog {
451 debug_level,
452 ticks: time_stamp,
453 phase,
454 message: accumulated_message.trim_end_matches(&['\r', '\n'][..]),
455 });
456 }
457 is_accumulating = false;
458 entries_processed += 1;
459 }
460
461 bytes_read = bytes_read
463 .checked_add(entry.entry_size)
464 .ok_or_else(|| DiagnosticsError::Overflow("bytes_read"))?;
465
466 if entry.entry_size >= buffer_slice.len() {
467 break; } else {
469 buffer_slice = &buffer_slice[entry.entry_size..];
470 }
471 }
472
473 if is_accumulating && !accumulated_message.is_empty() {
475 log_handler(EfiDiagnosticsLog {
476 debug_level,
477 ticks: time_stamp,
478 phase,
479 message: accumulated_message.trim_end_matches(&['\r', '\n'][..]),
480 });
481 entries_processed += 1;
482 }
483
484 for (substring, count) in suppressed_logs {
485 tracelimit::warn_ratelimited!(substring, count, "suppressed logs")
486 }
487
488 tracelimit::info_ratelimited!(entries_processed, bytes_read, "processed EFI log entries");
490
491 Ok(())
492 }
493}
494
495impl UefiDevice {
496 pub(crate) fn process_diagnostics(&mut self, allow_reprocess: bool, limit: Option<u32>) {
505 if let Err(error) =
506 self.service
507 .diagnostics
508 .process_diagnostics(allow_reprocess, &self.gm, |log| match limit {
509 Some(limit) => log_diagnostic_ratelimited(log, limit),
510 None => log_diagnostic_unrestricted(log),
511 })
512 {
513 tracelimit::error_ratelimited!(
514 error = &error as &dyn std::error::Error,
515 "failed to process diagnostics buffer"
516 );
517 }
518 }
519}
520
521mod save_restore {
522 use super::*;
523 use vmcore::save_restore::RestoreError;
524 use vmcore::save_restore::SaveError;
525 use vmcore::save_restore::SaveRestore;
526
527 mod state {
528 use mesh::payload::Protobuf;
529 use vmcore::save_restore::SavedStateRoot;
530
531 #[derive(Protobuf, SavedStateRoot)]
532 #[mesh(package = "firmware.uefi.diagnostics")]
533 pub struct SavedState {
534 #[mesh(1)]
535 pub gpa: Option<u32>,
536 #[mesh(2)]
537 pub did_flush: bool,
538 }
539 }
540
541 impl SaveRestore for DiagnosticsServices {
542 type SavedState = state::SavedState;
543
544 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
545 Ok(state::SavedState {
546 gpa: self.gpa,
547 did_flush: self.has_guest_processed_before,
548 })
549 }
550
551 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
552 let state::SavedState { gpa, did_flush } = state;
553 self.gpa = gpa;
554 self.has_guest_processed_before = did_flush;
555 Ok(())
556 }
557 }
558}