Skip to main content

intel_crashlog/
crashlog.rs

1// Copyright (C) 2025 Intel Corporation
2// SPDX-License-Identifier: MIT
3
4use crate::Error;
5use crate::bert::{Berr, Bert};
6#[cfg(feature = "collateral_manager")]
7use crate::collateral::{CollateralManager, CollateralTree};
8use crate::cper::{Cper, CperSectionBody};
9use crate::metadata::Metadata;
10use crate::node::Node;
11use crate::region::Region;
12#[cfg(not(feature = "std"))]
13use alloc::{collections::VecDeque, vec, vec::Vec};
14#[cfg(feature = "std")]
15use std::collections::VecDeque;
16
17use crate::header::record_types;
18
19/// Set of all the Crash Log records captured on a platform.
20#[derive(Default)]
21pub struct CrashLog {
22    /// Crash Log regions captured on the platform.
23    pub regions: Vec<Region>,
24    /// Extra information extracted alongside the Crash Log records
25    pub metadata: Metadata,
26}
27
28impl CrashLog {
29    pub(crate) fn from_regions(regions: Vec<Region>) -> Result<Self, Error> {
30        let mut queue = VecDeque::from(regions);
31        let mut regions = Vec::new();
32
33        while let Some(region) = queue.pop_front() {
34            for record in region.records.iter() {
35                let errata = record.header.version.into_errata();
36                let is_box = record.header.version.record_type == record_types::BOX
37                    || errata.type0_legacy_server_box;
38
39                if !is_box {
40                    continue;
41                }
42
43                let Some(payload) = record.data.get(record.header.header_size()..) else {
44                    log::error!("The Box record has an empty payload");
45                    continue;
46                };
47
48                match Region::from_slice(payload) {
49                    Ok(mut region) => {
50                        region.set_child_context(&record.header);
51                        queue.push_front(region)
52                    }
53                    Err(err) => log::warn!("Invalid region in Box record: {err}"),
54                }
55            }
56
57            regions.push(region)
58        }
59
60        if regions.is_empty() {
61            return Err(Error::InvalidCrashLog);
62        }
63
64        Ok(CrashLog {
65            regions,
66            ..CrashLog::default()
67        })
68    }
69
70    /// Extracts the Crash Log records from [Berr].
71    pub(crate) fn from_berr(berr: Berr) -> Result<Self, Error> {
72        let regions = berr
73            .entries
74            .iter()
75            .filter_map(|entry| Region::from_cper_section(&entry.cper_section))
76            .collect();
77        CrashLog::from_regions(regions)
78    }
79
80    #[cfg(any(all(target_os = "windows", feature = "extraction"), doc))]
81    /// Searches for any Intel Crash Log logged in the Windows event logs.
82    ///
83    /// The Windows event logs typically captures the records reported through ACPI.
84    pub fn from_windows_event_logs(path: Option<&std::path::Path>) -> Result<Vec<Self>, Error> {
85        Self::from_event_logs(path).map_err(|err| {
86            log::error!("Error while accessing windows event logs: {err}");
87            Error::InternalError
88        })
89    }
90
91    /// Extracts the Crash Log records from [Cper] record.
92    pub(crate) fn from_cper(cper: Cper) -> Result<Self, Error> {
93        let mut regions: Vec<Region> = Vec::new();
94        let mut extra_cper_sections: Vec<CperSectionBody> = Vec::new();
95
96        for section in cper.sections {
97            if let Some(region) = Region::from_cper_section(&section.body) {
98                regions.push(region);
99            } else {
100                extra_cper_sections.push(section.body);
101            }
102        }
103
104        if regions.is_empty() {
105            return Err(Error::NoCrashLogFound);
106        }
107
108        let mut crashlog = CrashLog::from_regions(regions)?;
109        crashlog.metadata.extra_cper_sections = extra_cper_sections;
110        Ok(crashlog)
111    }
112
113    /// Decodes a raw Crash Log binary.
114    pub fn from_slice(s: &[u8]) -> Result<Self, Error> {
115        if let Some(berr) = Berr::from_bert_file(s) {
116            CrashLog::from_berr(berr)
117        } else if let Some(cper) = Cper::from_slice(s) {
118            CrashLog::from_cper(cper)
119        } else {
120            // Input file is a single Crash Log region
121            CrashLog::from_regions(vec![Region::from_slice(s)?])
122        }
123    }
124
125    /// Exports the [CrashLog] as a BERT file.
126    pub fn to_bert(&self) -> Vec<u8> {
127        let mut berr = Berr::from_crashlog(self).to_bytes();
128        let bert = Bert {
129            region: 0,
130            region_length: berr.len() as u32,
131            ..Bert::dummy()
132        };
133
134        let mut bytes = bert.to_bytes();
135        bytes.append(&mut berr);
136        bytes
137    }
138
139    /// Exports the [CrashLog] as a CPER file that wraps the Crash Log regions into Firmware Error
140    /// Record sections.
141    pub fn to_bytes(&self) -> Vec<u8> {
142        Cper::from_raw_crashlog(self).to_bytes()
143    }
144
145    /// Returns the register tree representation of the Crash Log record headers.
146    pub fn decode_without_cm(&self) -> Node {
147        let mut root = Node::root();
148        for region in self.regions.iter() {
149            for record in region.records.iter() {
150                root.merge(record.decode_without_cm())
151            }
152        }
153        root
154    }
155
156    /// Returns the register tree representation of the Crash Log record content.
157    #[cfg(feature = "collateral_manager")]
158    pub fn decode<T: CollateralTree>(&self, cm: &mut CollateralManager<T>) -> Node {
159        let mut root = Node::root();
160        for region in self.regions.iter() {
161            for record in region.records.iter() {
162                root.merge(record.decode(cm))
163            }
164        }
165        root
166    }
167}