diff --git a/app/src/extract.rs b/app/src/extract.rs index 45e153c..5a5bb7b 100644 --- a/app/src/extract.rs +++ b/app/src/extract.rs @@ -15,7 +15,16 @@ pub fn extract(output_path: Option<&Path>) { } #[cfg(target_os = "linux")] { - result = CrashLog::from_linux_sysfs().map(|crashlog| Vec::from([crashlog])); + let crashlogs: Vec = [CrashLog::from_acpi_sysfs(), CrashLog::from_pmt_sysfs()] + .into_iter() + .filter_map(|crashlog| crashlog.ok()) + .collect(); + + result = if crashlogs.is_empty() { + Err(Error::NoCrashLogFound) + } else { + Ok(crashlogs) + }; } match result { diff --git a/lib/src/crashlog.rs b/lib/src/crashlog.rs index 06e0662..094e5f8 100644 --- a/lib/src/crashlog.rs +++ b/lib/src/crashlog.rs @@ -6,19 +6,13 @@ use crate::bert::{Berr, Bert}; #[cfg(feature = "collateral_manager")] use crate::collateral::{CollateralManager, CollateralTree}; use crate::cper::Cper; -#[cfg(feature = "extraction")] -use crate::extract; use crate::metadata::Metadata; use crate::node::Node; use crate::region::Region; #[cfg(not(feature = "std"))] use alloc::{collections::VecDeque, vec, vec::Vec}; -#[cfg(target_os = "uefi")] -use core::ptr::NonNull; #[cfg(feature = "std")] use std::collections::VecDeque; -#[cfg(target_os = "uefi")] -use uefi_raw::table::system::SystemTable; use crate::header::HeaderType; use crate::header::record_types; @@ -33,7 +27,7 @@ pub struct CrashLog { } impl CrashLog { - fn from_regions(regions: Vec) -> Result { + pub(crate) fn from_regions(regions: Vec) -> Result { let mut queue = VecDeque::from(regions); let mut regions = Vec::new(); @@ -85,27 +79,17 @@ impl CrashLog { CrashLog::from_regions(regions) } - #[cfg(all(target_os = "uefi", feature = "extraction"))] - /// Reads the Crash Log records from the EFI System Table. - pub fn from_system_table(system_table: Option>) -> Result { - extract::efi::get_crashlog_from_system_table(system_table) - } - #[cfg(any(all(target_os = "windows", feature = "extraction"), doc))] - /// Searches for any Intel Crash Log logged in the Windows event log + /// Searches for any Intel Crash Log logged in the Windows event logs. + /// + /// The Windows event logs typically captures the records reported through ACPI. pub fn from_windows_event_logs(path: Option<&std::path::Path>) -> Result, Error> { - extract::event_log::get_crashlogs_from_event_logs(path).map_err(|err| { + Self::from_event_logs(path).map_err(|err| { log::error!("Error while accessing windows event logs: {err}"); Error::InternalError }) } - #[cfg(any(all(target_os = "linux", feature = "extraction"), doc))] - /// Reads the Crash Log reported through ACPI from the linux sysfs - pub fn from_linux_sysfs() -> Result { - extract::sysfs::read_berr_from_sysfs().and_then(CrashLog::from_berr) - } - /// Extracts the Crash Log records from [Cper] record. pub(crate) fn from_cper(cper: Cper) -> Result { let regions: Vec = cper diff --git a/lib/src/extract/efi.rs b/lib/src/extract/efi.rs index e9d5d8b..9d28528 100644 --- a/lib/src/extract/efi.rs +++ b/lib/src/extract/efi.rs @@ -73,32 +73,33 @@ pub fn find_bert() -> Result { }) } -pub(crate) fn get_crashlog_from_system_table( - system_table: Option>, -) -> Result { - if let Some(system_table) = system_table { - unsafe { uefi::table::set_system_table(system_table.as_ptr()) } - } - - let mut crashlog = find_bert() - .and_then(|bert| { - unsafe { bert.berr_from_phys_mem() }.ok_or(Error::InvalidBootErrorRecordRegion) - }) - .and_then(CrashLog::from_berr)?; +impl CrashLog { + /// Reads the Crash Log records from the EFI System Table. + pub fn from_system_table(system_table: Option>) -> Result { + if let Some(system_table) = system_table { + unsafe { uefi::table::set_system_table(system_table.as_ptr()) } + } - crashlog.metadata = metadata::Metadata { - computer: Some("efi".to_string()), - time: uefi::runtime::get_time() - .map(|time| metadata::Time { - year: time.year(), - month: time.month(), - day: time.day(), - hour: time.hour(), - minute: time.minute(), + let mut crashlog = find_bert() + .and_then(|bert| { + unsafe { bert.berr_from_phys_mem() }.ok_or(Error::InvalidBootErrorRecordRegion) }) - .inspect_err(|err| log::warn!("Cannot get time: {err}")) - .ok(), - }; + .and_then(CrashLog::from_berr)?; + + crashlog.metadata = metadata::Metadata { + computer: Some("efi".to_string()), + time: uefi::runtime::get_time() + .map(|time| metadata::Time { + year: time.year(), + month: time.month(), + day: time.day(), + hour: time.hour(), + minute: time.minute(), + }) + .inspect_err(|err| log::warn!("Cannot get time: {err}")) + .ok(), + }; - Ok(crashlog) + Ok(crashlog) + } } diff --git a/lib/src/extract/event_log.rs b/lib/src/extract/event_log.rs index 1f1ca5e..89c066d 100644 --- a/lib/src/extract/event_log.rs +++ b/lib/src/extract/event_log.rs @@ -209,37 +209,39 @@ fn query_crashlogs(path: PCWSTR, query: PCWSTR, query_flags: u32) -> Result) -> Result> { - let evtx_path_hstring = path.map(HSTRING::from); - let evtx_path = evtx_path_hstring - .as_ref() - .map(|hstring| PCWSTR(hstring.as_ptr())); - let query_flags = if path.is_some() { - EvtQueryFilePath.0 - } else { - EvtQueryChannelPath.0 - }; +impl CrashLog { + pub(crate) fn from_event_logs(path: Option<&Path>) -> Result> { + let evtx_path_hstring = path.map(HSTRING::from); + let evtx_path = evtx_path_hstring + .as_ref() + .map(|hstring| PCWSTR(hstring.as_ptr())); + let query_flags = if path.is_some() { + EvtQueryFilePath.0 + } else { + EvtQueryChannelPath.0 + }; - let mut crashlogs = query_crashlogs( - evtx_path.unwrap_or(w!("Microsoft-Windows-Kernel-WHEA/Errors")), - w!("*[System[Provider[@Name=\"Microsoft-Windows-Kernel-WHEA\"]]]"), - query_flags, - )?; - log::info!( - "Extracted {} Crash Logs from Application Event Logs", - crashlogs.len() - ); - - let mut system_crashlogs = query_crashlogs( - evtx_path.unwrap_or(w!("System")), - w!("*[System[Provider[@Name=\"Microsoft-Windows-WHEA-Logger\"]]]"), - query_flags, - )?; - log::info!( - "Extracted {} Crash Logs from Windows Event Logs", - system_crashlogs.len() - ); - - crashlogs.append(&mut system_crashlogs); - Ok(crashlogs) + let mut crashlogs = query_crashlogs( + evtx_path.unwrap_or(w!("Microsoft-Windows-Kernel-WHEA/Errors")), + w!("*[System[Provider[@Name=\"Microsoft-Windows-Kernel-WHEA\"]]]"), + query_flags, + )?; + log::info!( + "Extracted {} Crash Logs from Application Event Logs", + crashlogs.len() + ); + + let mut system_crashlogs = query_crashlogs( + evtx_path.unwrap_or(w!("System")), + w!("*[System[Provider[@Name=\"Microsoft-Windows-WHEA-Logger\"]]]"), + query_flags, + )?; + log::info!( + "Extracted {} Crash Logs from Windows Event Logs", + system_crashlogs.len() + ); + + crashlogs.append(&mut system_crashlogs); + Ok(crashlogs) + } } diff --git a/lib/src/extract/sysfs.rs b/lib/src/extract/sysfs.rs index b805845..18c4c37 100644 --- a/lib/src/extract/sysfs.rs +++ b/lib/src/extract/sysfs.rs @@ -1,16 +1,76 @@ // Copyright (C) 2025 Intel Corporation // SPDX-License-Identifier: MIT +use crate::CrashLog; use crate::bert::Berr; use crate::error::Error; +use crate::region::Region; const BERR_PATH: &str = "/sys/firmware/acpi/tables/data/BERT"; +const PMT_PATH: &str = "/sys/class/intel_pmt"; -pub(crate) fn read_berr_from_sysfs() -> Result { - std::fs::read(BERR_PATH) - .map_err(|err| match err.kind() { - std::io::ErrorKind::NotFound => Error::NoCrashLogFound, - _ => Error::from(err), - }) - .and_then(|berr| Berr::from_slice(&berr).ok_or(Error::InvalidBootErrorRecordRegion)) +impl CrashLog { + /// Reads the Crash Log reported through ACPI from the linux sysfs + pub fn from_acpi_sysfs() -> Result { + let berr = std::fs::read(BERR_PATH) + .map_err(|err| { + log::warn!("Cannot read {BERR_PATH}: {err}"); + match err.kind() { + std::io::ErrorKind::NotFound => Error::NoCrashLogFound, + _ => Error::from(err), + } + }) + .and_then(|berr| { + log::info!("Found ACPI boot error record in sysfs"); + Berr::from_slice(&berr).ok_or(Error::InvalidBootErrorRecordRegion) + })?; + + Self::from_berr(berr) + } + + /// Reads the Crash Log reported through Intel PMT from the linux sysfs + pub fn from_pmt_sysfs() -> Result { + let regions: Vec = std::fs::read_dir(PMT_PATH) + .map_err(|err| { + log::warn!("Cannot read {PMT_PATH}: {err}"); + match err.kind() { + std::io::ErrorKind::NotFound => Error::NoCrashLogFound, + _ => Error::from(err), + } + })? + .filter_map(|entry| { + entry + .inspect_err(|err| log::error!("Cannot access directory entry: {err}")) + .ok() + }) + .filter(|entry| { + let is_dir = entry + .file_type() + .map(|file_type| file_type.is_dir()) + .unwrap_or(false); + let is_crashlog_dir = entry + .file_name() + .to_str() + .map(|name| name.starts_with("crashlog")) + .unwrap_or(false); + + is_dir && is_crashlog_dir + }) + .filter_map(|entry| { + let mut path = entry.path(); + + log::info!("Found Crash Log entry in PMT sysfs: {}", path.display()); + path.push("crashlog"); + + std::fs::read(&path) + .map_err(Error::IOError) + .and_then(|region| Region::from_slice(®ion)) + .inspect(|_| log::info!("Extracted valid record from {}", path.display())) + .inspect_err(|err| log::error!("{}: {err}", path.display())) + .ok() + }) + .collect(); + + Self::from_regions(regions) + } } diff --git a/lib/src/ffi.rs b/lib/src/ffi.rs index 69a3f1c..3a63eaa 100644 --- a/lib/src/ffi.rs +++ b/lib/src/ffi.rs @@ -169,15 +169,28 @@ pub extern "C" fn crashlog_read_from_windows_event_logs( .unwrap_or(ptr::null_mut()) } -/// Reads the Crash Log reported through ACPI from the linux sysfs. +/// Reads the Crash Log reported through ACPI from the linux sysfs /// /// # Errors /// -/// Returns a `NULL` pointer if the Crash Log records cannot be found. +/// Returns a `NULL` pointer if the Crash Log record cannot be found. +#[cfg(any(all(target_os = "linux", feature = "extraction"), doc))] +#[unsafe(no_mangle)] +pub extern "C" fn crashlog_read_from_acpi_sysfs(context: *mut CrashLogContext) -> *mut CrashLog { + CrashLog::from_acpi_sysfs() + .map(alloc) + .unwrap_or(ptr::null_mut()) +} + +/// Reads the Crash Log reported through Intel PMT from the linux sysfs +/// +/// # Errors +/// +/// Returns a `NULL` pointer if the Crash Log record cannot be found. #[cfg(any(all(target_os = "linux", feature = "extraction"), doc))] #[unsafe(no_mangle)] -pub extern "C" fn crashlog_read_from_linux_sysfs(context: *mut CrashLogContext) -> *mut CrashLog { - CrashLog::from_linux_sysfs() +pub extern "C" fn crashlog_read_from_pmt_sysfs(context: *mut CrashLogContext) -> *mut CrashLog { + CrashLog::from_pmt_sysfs() .map(alloc) .unwrap_or(ptr::null_mut()) }