symbolic_cfi/
lib.rs

1//! Handling of Call Frame Information (stack frame info).
2//!
3//! The root type exposed by this crate is [`CfiCache`], which offers a high-level API to extract
4//! CFI from object files and serialize a format that the Breakpad processor can understand.
5//!
6//! # Background
7//!
8//! Call Frame Information (CFI) is used by the [processor] to improve the quality of stacktraces
9//! during stackwalking. When the executable was compiled with frame pointer omission, the call
10//! stack does not contain sufficient information to resolve frames on its own. CFI contains
11//! programs that can calculate the base address of a frame based on register values of the current
12//! frame.
13//!
14//! Without CFI, the stackwalker needs to scan the stack memory for values that look like valid base
15//! addresses. This frequently yields false-positives.
16//!
17//! [processor]: ../processor/index.html
18//! [`CfiCache`]: struct.CfiCache.html
19
20use std::collections::HashMap;
21use std::error::Error;
22use std::fmt;
23use std::io::{self, Write};
24use std::ops::Range;
25
26use thiserror::Error;
27
28use symbolic_common::{Arch, ByteView, CpuFamily, UnknownArchError};
29use symbolic_debuginfo::breakpad::{BreakpadError, BreakpadObject, BreakpadStackRecord};
30use symbolic_debuginfo::dwarf::gimli::{
31    BaseAddresses, CfaRule, CieOrFde, DebugFrame, EhFrame, Error as GimliError,
32    FrameDescriptionEntry, Reader, ReaderOffset, Register, RegisterRule, UnwindContext,
33    UnwindSection,
34};
35use symbolic_debuginfo::dwarf::Dwarf;
36use symbolic_debuginfo::macho::{
37    CompactCfiOp, CompactCfiRegister, CompactUnwindInfoIter, CompactUnwindOp, MachError, MachObject,
38};
39use symbolic_debuginfo::pdb::pdb::{self, FallibleIterator, FrameData, Rva, StringTable};
40use symbolic_debuginfo::pdb::PdbObject;
41use symbolic_debuginfo::pe::{PeObject, RuntimeFunction, StackFrameOffset, UnwindOperation};
42use symbolic_debuginfo::{Object, ObjectError, ObjectLike};
43
44/// The magic file preamble to identify cficache files.
45///
46/// Files with version < 2 do not have the full preamble with magic+version, but rather start
47/// straight away with a `STACK` record.
48/// The magic here is a `u32` corresponding to the big-endian `CFIC`.
49/// It will be written and read using native endianness, so mismatches between writer/reader will
50/// result in a [`CfiErrorKind::BadFileMagic`] error.
51pub const CFICACHE_MAGIC: u32 = u32::from_be_bytes(*b"CFIC");
52
53/// The latest version of the file format.
54pub const CFICACHE_LATEST_VERSION: u32 = 2;
55
56// The preamble are 8 bytes, a 4-byte magic and 4 bytes for the version.
57// The 4-byte magic should be read as little endian to check for endian mismatch.
58
59// Version history:
60//
61// 1: Initial ASCII-only implementation
62// 2: Implementation with a versioned preamble
63
64/// Used to detect empty runtime function entries in PEs.
65const EMPTY_FUNCTION: RuntimeFunction = RuntimeFunction {
66    begin_address: 0,
67    end_address: 0,
68    unwind_info_address: 0,
69};
70
71/// Names for x86 CPU registers by register number.
72static I386: &[&str] = &[
73    "$eax", "$ecx", "$edx", "$ebx", "$esp", "$ebp", "$esi", "$edi", "$eip", "$eflags", "$unused1",
74    "$st0", "$st1", "$st2", "$st3", "$st4", "$st5", "$st6", "$st7", "$unused2", "$unused3",
75    "$xmm0", "$xmm1", "$xmm2", "$xmm3", "$xmm4", "$xmm5", "$xmm6", "$xmm7", "$mm0", "$mm1", "$mm2",
76    "$mm3", "$mm4", "$mm5", "$mm6", "$mm7", "$fcw", "$fsw", "$mxcsr", "$es", "$cs", "$ss", "$ds",
77    "$fs", "$gs", "$unused4", "$unused5", "$tr", "$ldtr",
78];
79
80/// Names for x86_64 CPU registers by register number.
81static X86_64: &[&str] = &[
82    "$rax", "$rdx", "$rcx", "$rbx", "$rsi", "$rdi", "$rbp", "$rsp", "$r8", "$r9", "$r10", "$r11",
83    "$r12", "$r13", "$r14", "$r15", "$rip", "$xmm0", "$xmm1", "$xmm2", "$xmm3", "$xmm4", "$xmm5",
84    "$xmm6", "$xmm7", "$xmm8", "$xmm9", "$xmm10", "$xmm11", "$xmm12", "$xmm13", "$xmm14", "$xmm15",
85    "$st0", "$st1", "$st2", "$st3", "$st4", "$st5", "$st6", "$st7", "$mm0", "$mm1", "$mm2", "$mm3",
86    "$mm4", "$mm5", "$mm6", "$mm7", "$rflags", "$es", "$cs", "$ss", "$ds", "$fs", "$gs",
87    "$unused1", "$unused2", "$fs.base", "$gs.base", "$unused3", "$unused4", "$tr", "$ldtr",
88    "$mxcsr", "$fcw", "$fsw",
89];
90
91/// Names for 32bit ARM CPU registers by register number.
92static ARM: &[&str] = &[
93    "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr",
94    "pc", "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "fps", "cpsr", "", "", "", "", "", "",
95    "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
96    "", "", "", "", "", "", "", "", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9",
97    "s10", "s11", "s12", "s13", "s14", "s15", "s16", "s17", "s18", "s19", "s20", "s21", "s22",
98    "s23", "s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31", "f0", "f1", "f2", "f3", "f4",
99    "f5", "f6", "f7",
100];
101
102/// Names for 64bit ARM CPU registers by register number.
103static ARM64: &[&str] = &[
104    "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14",
105    "x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27",
106    "x28", "x29", "x30", "sp", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
107    "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "v0", "v1", "v2", "v3", "v4", "v5",
108    "v6", "v7", "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", "v19",
109    "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31",
110];
111
112/// Names for MIPS CPU registers by register number.
113static MIPS: &[&str] = &[
114    "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", "$t0", "$t1", "$t2", "$t3", "$t4",
115    "$t5", "$t6", "$t7", "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", "$t8", "$t9",
116    "$k0", "$k1", "$gp", "$sp", "$fp", "$ra", "$lo", "$hi", "$pc", "$f0", "$f2", "$f3", "$f4",
117    "$f5", "$f6", "$f7", "$f8", "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", "$f16",
118    "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23", "$f24", "$f25", "$f26", "$f27", "$f28",
119    "$f29", "$f30", "$f31", "$fcsr", "$fir",
120];
121
122/// The error type for [`CfiError`].
123#[non_exhaustive]
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub enum CfiErrorKind {
126    /// Required debug sections are missing in the `Object` file.
127    MissingDebugInfo,
128
129    /// The debug information in the `Object` file is not supported.
130    UnsupportedDebugFormat,
131
132    /// The debug information in the `Object` file is invalid.
133    BadDebugInfo,
134
135    /// The `Object`s architecture is not supported by symbolic.
136    UnsupportedArch,
137
138    /// CFI for an invalid address outside the mapped range was encountered.
139    InvalidAddress,
140
141    /// Generic error when writing CFI information, likely IO.
142    WriteFailed,
143
144    /// Invalid magic bytes in the cfi cache header.
145    BadFileMagic,
146}
147
148impl fmt::Display for CfiErrorKind {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        match self {
151            Self::MissingDebugInfo => write!(f, "missing cfi debug sections"),
152            Self::UnsupportedDebugFormat => write!(f, "unsupported debug format"),
153            Self::BadDebugInfo => write!(f, "bad debug information"),
154            Self::UnsupportedArch => write!(f, "unsupported architecture"),
155            Self::InvalidAddress => write!(f, "invalid cfi address"),
156            Self::WriteFailed => write!(f, "failed to write cfi"),
157            Self::BadFileMagic => write!(f, "bad cfi cache magic"),
158        }
159    }
160}
161
162/// An error returned by [`AsciiCfiWriter`](struct.AsciiCfiWriter.html).
163#[derive(Debug, Error)]
164#[error("{kind}")]
165pub struct CfiError {
166    kind: CfiErrorKind,
167    #[source]
168    source: Option<Box<dyn Error + Send + Sync + 'static>>,
169}
170
171impl CfiError {
172    /// Creates a new CFI error from a known kind of error as well as an
173    /// arbitrary error payload.
174    fn new<E>(kind: CfiErrorKind, source: E) -> Self
175    where
176        E: Into<Box<dyn Error + Send + Sync>>,
177    {
178        let source = Some(source.into());
179        Self { kind, source }
180    }
181
182    /// Returns the corresponding [`CfiErrorKind`] for this error.
183    pub fn kind(&self) -> CfiErrorKind {
184        self.kind
185    }
186}
187
188impl From<CfiErrorKind> for CfiError {
189    fn from(kind: CfiErrorKind) -> Self {
190        Self { kind, source: None }
191    }
192}
193
194impl From<io::Error> for CfiError {
195    fn from(e: io::Error) -> Self {
196        Self::new(CfiErrorKind::WriteFailed, e)
197    }
198}
199
200impl From<UnknownArchError> for CfiError {
201    fn from(_: UnknownArchError) -> Self {
202        // UnknownArchError does not carry any useful information
203        CfiErrorKind::UnsupportedArch.into()
204    }
205}
206
207impl From<BreakpadError> for CfiError {
208    fn from(e: BreakpadError) -> Self {
209        Self::new(CfiErrorKind::BadDebugInfo, e)
210    }
211}
212
213impl From<ObjectError> for CfiError {
214    fn from(e: ObjectError) -> Self {
215        Self::new(CfiErrorKind::BadDebugInfo, e)
216    }
217}
218
219impl From<pdb::Error> for CfiError {
220    fn from(e: pdb::Error) -> Self {
221        Self::new(CfiErrorKind::BadDebugInfo, e)
222    }
223}
224
225impl From<GimliError> for CfiError {
226    fn from(e: GimliError) -> Self {
227        Self::new(CfiErrorKind::BadDebugInfo, e)
228    }
229}
230
231impl From<MachError> for CfiError {
232    fn from(e: MachError) -> Self {
233        Self::new(CfiErrorKind::BadDebugInfo, e)
234    }
235}
236
237/// Temporary helper trait to set the address size on any unwind section.
238trait UnwindSectionExt<R>: UnwindSection<R>
239where
240    R: Reader,
241{
242    fn set_address_size(&mut self, address_size: u8);
243}
244
245impl<R: Reader> UnwindSectionExt<R> for EhFrame<R> {
246    fn set_address_size(&mut self, address_size: u8) {
247        self.set_address_size(address_size)
248    }
249}
250
251impl<R: Reader> UnwindSectionExt<R> for DebugFrame<R> {
252    fn set_address_size(&mut self, address_size: u8) {
253        self.set_address_size(address_size)
254    }
255}
256
257/// Context information for unwinding.
258struct UnwindInfo<U> {
259    arch: Arch,
260    load_address: u64,
261    section: U,
262    bases: BaseAddresses,
263}
264
265impl<U> UnwindInfo<U> {
266    pub fn new<'d: 'o, 'o, O, R>(object: &O, addr: u64, mut section: U) -> Self
267    where
268        O: ObjectLike<'d, 'o>,
269        R: Reader,
270        U: UnwindSectionExt<R>,
271    {
272        let arch = object.arch();
273        let load_address = object.load_address();
274
275        // CFI can have relative offsets to the virtual address of the respective debug
276        // section (either `.eh_frame` or `.debug_frame`). We need to supply this offset to the
277        // entries iterator before starting to interpret instructions. The other base addresses are
278        // not needed for CFI.
279        let bases = BaseAddresses::default().set_eh_frame(addr);
280
281        // Based on the architecture, pointers inside eh_frame and debug_frame have different sizes.
282        // Configure the section to read them appropriately.
283        if let Some(pointer_size) = arch.cpu_family().pointer_size() {
284            section.set_address_size(pointer_size as u8);
285        }
286
287        UnwindInfo {
288            arch,
289            load_address,
290            section,
291            bases,
292        }
293    }
294}
295
296/// Returns the name of a register in a given architecture used in CFI programs.
297///
298/// Each CPU family specifies its own register sets, wherer the registers are numbered. This
299/// resolves the name of the register for the given family, if defined. Returns `None` if the
300/// CPU family is unknown, or the register is not defined for the family.
301///
302/// **Note**: The CFI register name differs from [`ip_register_name`](CpuFamily::ip_register_name).
303/// For instance, on x86-64
304/// the instruction pointer is returned as `$rip` instead of just `rip`. This differentiation is
305/// made to be compatible with the Google Breakpad library.
306fn cfi_register_name(arch: CpuFamily, register: u16) -> Option<&'static str> {
307    let index = register as usize;
308
309    let opt = match arch {
310        CpuFamily::Intel32 => I386.get(index),
311        CpuFamily::Amd64 => X86_64.get(index),
312        CpuFamily::Arm64 | CpuFamily::Arm64_32 => ARM64.get(index),
313        CpuFamily::Arm32 => ARM.get(index),
314        CpuFamily::Mips32 | CpuFamily::Mips64 => MIPS.get(index),
315        _ => None,
316    };
317
318    opt.copied().filter(|name| !name.is_empty())
319}
320/// A service that converts call frame information (CFI) from an object file to Breakpad ASCII
321/// format and writes it to the given writer.
322///
323/// The default way to use this writer is to create a writer, pass it to the `AsciiCfiWriter` and
324/// then process an object:
325///
326/// ```rust,no_run
327/// use symbolic_common::ByteView;
328/// use symbolic_debuginfo::Object;
329/// use symbolic_cfi::AsciiCfiWriter;
330///
331/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
332/// let view = ByteView::open("/path/to/object")?;
333/// let object = Object::parse(&view)?;
334///
335/// let mut writer = Vec::new();
336/// AsciiCfiWriter::new(&mut writer).process(&object)?;
337/// # Ok(())
338/// # }
339/// ```
340///
341/// For writers that implement `Default`, there is a convenience method that creates an instance and
342/// returns it right away:
343///
344/// ```rust,no_run
345/// use symbolic_common::ByteView;
346/// use symbolic_debuginfo::Object;
347/// use symbolic_cfi::AsciiCfiWriter;
348///
349/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
350/// let view = ByteView::open("/path/to/object")?;
351/// let object = Object::parse(&view)?;
352///
353/// let buffer = AsciiCfiWriter::<Vec<u8>>::transform(&object)?;
354/// # Ok(())
355/// # }
356/// ```
357pub struct AsciiCfiWriter<W: Write> {
358    inner: W,
359}
360
361impl<W: Write> AsciiCfiWriter<W> {
362    /// Creates a new `AsciiCfiWriter` that outputs to a writer.
363    pub fn new(inner: W) -> Self {
364        AsciiCfiWriter { inner }
365    }
366
367    /// Extracts CFI from the given object file.
368    pub fn process(&mut self, object: &Object<'_>) -> Result<(), CfiError> {
369        match object {
370            Object::Breakpad(o) => self.process_breakpad(o),
371            Object::MachO(o) => self.process_macho(o),
372            Object::Elf(o) => self.process_dwarf(o, false),
373            Object::Pdb(o) => self.process_pdb(o),
374            Object::Pe(o) => self.process_pe(o),
375            Object::Wasm(o) => self.process_dwarf(o, false),
376            Object::SourceBundle(_) => Ok(()),
377            Object::PortablePdb(_) => Ok(()),
378        }
379    }
380
381    /// Returns the wrapped writer from this instance.
382    pub fn into_inner(self) -> W {
383        self.inner
384    }
385
386    fn process_breakpad(&mut self, object: &BreakpadObject<'_>) -> Result<(), CfiError> {
387        for record in object.stack_records() {
388            match record? {
389                BreakpadStackRecord::Cfi(r) => {
390                    writeln!(
391                        self.inner,
392                        "STACK CFI INIT {:x} {:x} {}",
393                        r.start, r.size, r.init_rules
394                    )?;
395
396                    for d in r.deltas().flatten() {
397                        writeln!(self.inner, "STACK CFI {:x} {}", d.address, d.rules)?;
398                    }
399
400                    Ok(())
401                }
402                BreakpadStackRecord::Win(r) => writeln!(
403                    self.inner,
404                    "STACK WIN {} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {} {}",
405                    r.ty as usize,
406                    r.code_start,
407                    r.code_size,
408                    r.prolog_size,
409                    r.epilog_size,
410                    r.params_size,
411                    r.saved_regs_size,
412                    r.locals_size,
413                    r.max_stack_size,
414                    if r.program_string.is_some() { "1" } else { "0" },
415                    if let Some(ps) = r.program_string {
416                        ps
417                    } else if r.uses_base_pointer {
418                        "1"
419                    } else {
420                        "0"
421                    }
422                ),
423            }?
424        }
425
426        Ok(())
427    }
428
429    fn process_macho(&mut self, object: &MachObject<'_>) -> Result<(), CfiError> {
430        let compact_unwind_info = object.compact_unwind_info()?;
431
432        // If we have compact_unwind_info, then any important entries in
433        // the eh_frame section will be explicitly requested by the
434        // Compact Unwinding Info. So skip processing that section for now.
435        let should_skip_eh_frame = compact_unwind_info.is_some();
436        let result = self.process_dwarf(object, should_skip_eh_frame);
437
438        if let Some(compact_unwind_info) = compact_unwind_info {
439            let eh_section = object.section("eh_frame");
440            let eh_frame_info = eh_section.as_ref().map(|section| {
441                let endian = object.endianity();
442                let frame = EhFrame::new(&section.data, endian);
443                UnwindInfo::new(object, section.address, frame)
444            });
445            self.read_compact_unwind_info(compact_unwind_info, eh_frame_info.as_ref(), object)?;
446        }
447        result
448    }
449
450    fn process_dwarf<'d: 'o, 'o, O>(
451        &mut self,
452        object: &O,
453        skip_eh_frame: bool,
454    ) -> Result<(), CfiError>
455    where
456        O: ObjectLike<'d, 'o> + Dwarf<'o>,
457    {
458        let endian = object.endianity();
459
460        // First load information from the DWARF debug_frame section. It does not contain any
461        // references to other DWARF sections.
462        // Don't return on error because eh_frame can contain some information
463        let debug_frame_result = if let Some(section) = object.section("debug_frame") {
464            let frame = DebugFrame::new(&section.data, endian);
465            let info = UnwindInfo::new(object, section.address, frame);
466            self.read_cfi(&info)
467        } else {
468            Ok(())
469        };
470
471        if !skip_eh_frame {
472            if let Some(section) = object.section("eh_frame") {
473                // Independently, Linux C++ exception handling information can also provide unwind info.
474                let frame = EhFrame::new(&section.data, endian);
475                let info = UnwindInfo::new(object, section.address, frame);
476                self.read_cfi(&info)?;
477            }
478        }
479
480        debug_frame_result
481    }
482
483    fn read_compact_unwind_info<'d, U, R>(
484        &mut self,
485        mut iter: CompactUnwindInfoIter<'d>,
486        eh_frame_info: Option<&UnwindInfo<U>>,
487        object: &MachObject<'d>,
488    ) -> Result<(), CfiError>
489    where
490        R: Reader + Eq,
491        U: UnwindSection<R>,
492    {
493        fn write_reg_name<W: Write>(
494            writer: &mut W,
495            register: CompactCfiRegister,
496            iter: &CompactUnwindInfoIter,
497            cpu_family: CpuFamily,
498        ) -> Result<(), CfiError> {
499            if register.is_cfa() {
500                write!(writer, ".cfa")?;
501            } else if register == CompactCfiRegister::instruction_pointer() {
502                write!(writer, ".ra")?;
503            } else {
504                // For whatever reason breakpad doesn't prefix registers with $ on ARM.
505                match cpu_family {
506                    CpuFamily::Arm32 | CpuFamily::Arm64 | CpuFamily::Arm64_32 => {
507                        write!(writer, "{}", register.name(iter).unwrap())?;
508                    }
509                    _ => {
510                        write!(writer, "${}", register.name(iter).unwrap())?;
511                    }
512                }
513            }
514            Ok(())
515        }
516        // Preload the symbols as this is expensive to do in the loop.
517        let symbols = object.symbol_map();
518        let cpu_family = object.arch().cpu_family();
519
520        // Initialize an unwind context once and reuse it for the entire section.
521        let mut ctx = UnwindContext::new();
522
523        while let Some(entry) = iter.next()? {
524            if entry.len == 0 {
525                // We saw some duplicate entries (which yield entries with `len == 0`) for example
526                // in `libsystem_kernel.dylib`. In this case just skip the zero-length entry.
527                continue;
528            }
529            match entry.instructions(&iter) {
530                CompactUnwindOp::None => {
531                    // We have seen some of these `CompactUnwindOp::None` correspond to some tiny
532                    // stackless functions, such as `__kill` from `libsystem_kernel.dylib` or similar.
533                    //
534                    // Because they don't have a normal CFI record, we would fall back to frame pointers
535                    // or stack scanning when unwinding, which will cause us to skip the caller
536                    // frame, or fail unwinding completely.
537                    //
538                    // To overcome this problem we will emit a CFI record that basically says that
539                    // the function has no stack space of its own. Since these compact unwind records
540                    // can be the result of merging multiple of these adjacent functions, they can
541                    // span more instructions/bytes than one single symbol.
542                    //
543                    // This can potentially lead to false positives. However in that case, the unwinding
544                    // code will detect the bogus return address and fall back to frame pointers or
545                    // scanning either way.
546
547                    let start_addr = entry.instruction_address;
548                    match cpu_family {
549                        CpuFamily::Amd64 => {
550                            writeln!(
551                                self.inner,
552                                "STACK CFI INIT {:x} {:x} .cfa: $rsp 8 + .ra: .cfa -8 + ^",
553                                start_addr, entry.len
554                            )?;
555                        }
556                        CpuFamily::Arm64 => {
557                            // Assume this is a stackless leaf, return address is in lr (x30).
558                            writeln!(
559                                self.inner,
560                                "STACK CFI INIT {:x} {:x} .cfa: sp .ra: x30",
561                                start_addr, entry.len
562                            )?;
563                        }
564                        _ => {
565                            // Do nothing
566                        }
567                    }
568                }
569                CompactUnwindOp::UseDwarfFde { offset_in_eh_frame } => {
570                    // We need to grab the CFI info from the eh_frame section
571                    if let Some(info) = eh_frame_info {
572                        let offset = U::Offset::from(R::Offset::from_u32(offset_in_eh_frame));
573                        if let Ok(fde) =
574                            info.section
575                                .fde_from_offset(&info.bases, offset, U::cie_from_offset)
576                        {
577                            let start_addr = entry.instruction_address.into();
578                            let sym_name = symbols.lookup(start_addr).and_then(|sym| sym.name());
579
580                            if sym_name == Some("_sigtramp") && cpu_family == CpuFamily::Amd64 {
581                                // This specific function has some hand crafted dwarf expressions.
582                                // They encode how to restore the registers from a machine context accessible via `$rbx`
583                                // See: https://github.com/apple/darwin-libplatform/blob/215b09856ab5765b7462a91be7076183076600df/src/setjmp/x86_64/_sigtramp.s#L198-L258
584
585                                // We currently don't support DWARF expressions
586                                // (`{Register,Cfa}Rule::{Val,}Expression`) at all.
587                                // The register and CFA expressions are simple enough to implement
588                                // in our DWARF to ASCII CFI translator. They look like:
589                                //
590                                // > DW_CFA_expression: RBP DW_OP_breg3 RBX+48, DW_OP_deref, DW_OP_plus_uconst 0x40
591                                // > DW_CFA_def_cfa_expression: DW_OP_breg3 RBX+48, DW_OP_deref, DW_OP_plus_uconst 0x48, DW_OP_deref
592                                //
593                                // However the `.ra`/RIP expression is a lot more complex:
594                                //
595                                // > DW_CFA_val_expression: RIP DW_OP_breg3 RBX+48, DW_OP_deref, DW_OP_dup, DW_OP_plus_uconst 0x90,
596                                // >     DW_OP_deref, DW_OP_swap, DW_OP_plus_uconst 0x0, DW_OP_deref_size 0x4, DW_OP_dup, DW_OP_lit3,
597                                // >     DW_OP_ne, DW_OP_swap, DW_OP_lit4, DW_OP_ne, DW_OP_and, DW_OP_plus
598                                // (the expression just subtracts 1 depending on the exception flag)
599                                //
600                                // The ASCII CFI syntax is not rich enough to express the above
601                                // DWARF expression. DWARF expressions are "in theory" even turing-complete.
602                                //
603                                // As a workaround for this limitation, we generate some hard-coded
604                                // ASCII CFI that is equivalent to the DWARF expressions.
605                                // For simplicity, we only restore
606                                // callee saves / call preserved / non-volatile registers:
607                                // rbx, rsp, rbp, r12, r13, r14, and r15
608
609                                let mc_offset = 48;
610                                let rbx_offset = 24;
611                                let rbp_offset = 64;
612                                let rsp_offset = 72;
613                                let r12_offset = 112;
614                                let r13_offset = 120;
615                                let r14_offset = 128;
616                                let r15_offset = 136;
617                                let rip_offset = 144;
618
619                                write!(
620                                    self.inner,
621                                    "STACK CFI INIT {:x} {:x} ",
622                                    start_addr, entry.len
623                                )?;
624
625                                write!(self.inner, "$rbx: $rbx {mc_offset} + ^ {rbx_offset} + ^ ")?;
626                                write!(self.inner, "$rbp: $rbx {mc_offset} + ^ {rbp_offset} + ^ ")?;
627                                write!(self.inner, "$r12: $rbx {mc_offset} + ^ {r12_offset} + ^ ")?;
628                                write!(self.inner, "$r13: $rbx {mc_offset} + ^ {r13_offset} + ^ ")?;
629                                write!(self.inner, "$r14: $rbx {mc_offset} + ^ {r14_offset} + ^ ")?;
630                                write!(self.inner, "$r15: $rbx {mc_offset} + ^ {r15_offset} + ^ ")?;
631
632                                // rsp aka .cfa
633                                write!(self.inner, ".cfa: $rbx {mc_offset} + ^ {rsp_offset} + ^ ",)?;
634                                // rip aka .ra
635                                writeln!(self.inner, ".ra: $rbx {mc_offset} + ^ {rip_offset} + ^")?;
636                            } else {
637                                self.process_fde(info, &mut ctx, &fde)?;
638                            }
639                        }
640                    }
641                }
642                CompactUnwindOp::CfiOps(ops) => {
643                    // We just need to output a bunch of CFI expressions in a single CFI INIT
644                    let mut line = Vec::new();
645                    let start_addr = entry.instruction_address;
646                    let length = entry.len;
647                    write!(line, "STACK CFI INIT {start_addr:x} {length:x} ")?;
648
649                    for instruction in ops {
650                        // These two operations differ only in whether there should
651                        // be a deref (^) at the end, so we can flatten away their
652                        // differences and merge paths.
653                        let (dest_reg, src_reg, offset, should_deref) = match instruction {
654                            CompactCfiOp::RegisterAt {
655                                dest_reg,
656                                src_reg,
657                                offset_from_src,
658                            } => (dest_reg, src_reg, offset_from_src, true),
659                            CompactCfiOp::RegisterIs {
660                                dest_reg,
661                                src_reg,
662                                offset_from_src,
663                            } => (dest_reg, src_reg, offset_from_src, false),
664                        };
665
666                        write_reg_name(&mut line, dest_reg, &iter, cpu_family)?;
667                        write!(line, ": ")?;
668                        write_reg_name(&mut line, src_reg, &iter, cpu_family)?;
669                        write!(line, " {offset} + ")?;
670                        if should_deref {
671                            write!(line, "^ ")?;
672                        }
673                    }
674
675                    let line = line.strip_suffix(b" ").unwrap_or(&line);
676
677                    self.inner
678                        .write_all(line)
679                        .and_then(|_| writeln!(self.inner))?;
680                }
681            }
682        }
683        Ok(())
684    }
685
686    fn read_cfi<U, R>(&mut self, info: &UnwindInfo<U>) -> Result<(), CfiError>
687    where
688        R: Reader + Eq,
689        U: UnwindSection<R>,
690    {
691        // Initialize an unwind context once and reuse it for the entire section.
692        let mut ctx = UnwindContext::new();
693
694        let mut entries = info.section.entries(&info.bases);
695        while let Some(entry) = entries.next()? {
696            // We skip all Common Information Entries and only process Frame Description Items here.
697            // The iterator yields partial FDEs which need their associated CIE passed in via a
698            // callback. This function is provided by the UnwindSection (frame), which then parses
699            // the CIE and returns it for the FDE.
700            if let CieOrFde::Fde(partial_fde) = entry {
701                if let Ok(fde) = partial_fde.parse(U::cie_from_offset) {
702                    self.process_fde(info, &mut ctx, &fde)?
703                }
704            }
705        }
706
707        Ok(())
708    }
709
710    fn process_fde<R, U>(
711        &mut self,
712        info: &UnwindInfo<U>,
713        ctx: &mut UnwindContext<R::Offset>,
714        fde: &FrameDescriptionEntry<R>,
715    ) -> Result<(), CfiError>
716    where
717        R: Reader + Eq,
718        U: UnwindSection<R>,
719    {
720        // We have seen FDEs with an initial address of `u64::MAX` in user-provided
721        // DWARF files. Such FDEs will invariably fail to process because of either
722        // an address overflow error in `gimli` or an underflow in the `length`
723        // calculation below. Therefore, we skip them immediately so we don't abort
724        // the processing of the entire file.
725        if fde.initial_address() == u64::MAX {
726            return Ok(());
727        }
728
729        // Retrieves the register that specifies the return address. We need to assign a special
730        // format to this register for Breakpad.
731        let ra = fde.cie().return_address_register();
732
733        // Interpret all DWARF instructions of this Frame Description Entry. This gives us an unwind
734        // table that contains rules for retrieving registers at every instruction address. These
735        // rules can directly be transcribed to breakpad STACK CFI records.
736        let mut table = fde.rows(&info.section, &info.bases, ctx)?;
737
738        // Collect all rows first, as we need to know the final end address in order to write the
739        // CFI INIT record describing the extent of the whole unwind table.
740        let mut rows = Vec::new();
741        loop {
742            match table.next_row() {
743                Ok(None) => break,
744                Ok(Some(row)) => rows.push(row.clone()),
745                Err(GimliError::UnknownCallFrameInstruction(_)) => continue,
746                // NOTE: Temporary workaround for https://github.com/gimli-rs/gimli/pull/487
747                Err(GimliError::TooManyRegisterRules) => continue,
748                Err(e) => return Err(e.into()),
749            }
750        }
751
752        if let Some(first_row) = rows.first() {
753            // Calculate the start address and total range covered by the CFI INIT record and its
754            // subsequent CFI records. This information will be written into the CFI INIT record.
755            let start = first_row.start_address();
756            let length = rows.last().unwrap().end_address() - start;
757
758            // Verify that the CFI entry is in range of the mapped module. Zero values are a special
759            // case and seem to indicate that the entry is no longer valid. However, also skip other
760            // entries since the rest of the file may still be valid.
761            if start < info.load_address {
762                return Ok(());
763            }
764
765            // Every register rule in the table will be cached so that it can be compared with
766            // subsequent occurrences. Only registers with changed rules will be written.
767            let mut rule_cache = HashMap::new();
768            let mut cfa_cache = None;
769
770            // Write records for every entry in the unwind table.
771            for row in &rows {
772                let mut written = false;
773                let mut line = Vec::new();
774
775                // Depending on whether this is the first row or any subsequent row, print a INIT or
776                // normal STACK CFI record.
777                if row.start_address() == start {
778                    let start_addr = start - info.load_address;
779                    write!(line, "STACK CFI INIT {start_addr:x} {length:x}")?;
780                } else {
781                    let start_addr = row.start_address() - info.load_address;
782                    write!(line, "STACK CFI {start_addr:x}")?;
783                }
784
785                // Write the mandatory CFA rule for this row, followed by optional register rules.
786                // The actual formatting of the rules depends on their rule type.
787                if cfa_cache != Some(row.cfa()) {
788                    cfa_cache = Some(row.cfa());
789                    written |= Self::write_cfa_rule(&mut line, info.arch, row.cfa())?;
790                }
791
792                // Print only registers that have changed rules to their previous occurrence to
793                // reduce the number of rules per row. Then, cache the new occurrence for the next
794                // row.
795                let mut ra_written = false;
796                for &(register, ref rule) in row.registers() {
797                    if rule_cache.get(&register) != Some(&rule) {
798                        rule_cache.insert(register, rule);
799                        if register == ra {
800                            ra_written = true;
801                        }
802                        written |=
803                            Self::write_register_rule(&mut line, info.arch, register, rule, ra)?;
804                    }
805                }
806                // On MIPS: if no explicit rule was encountered for the return address,
807                // emit a rule stating that the return address should be recovered from the
808                // $ra register.
809                if row.start_address() == start
810                    && !ra_written
811                    && matches!(info.arch, Arch::Mips | Arch::Mips64)
812                {
813                    write!(line, " .ra: $ra")?;
814                }
815
816                if written {
817                    self.inner
818                        .write_all(&line)
819                        .and_then(|_| writeln!(self.inner))?;
820                }
821            }
822        }
823
824        Ok(())
825    }
826
827    fn write_cfa_rule<R: ReaderOffset, T: Write>(
828        mut target: T,
829        arch: Arch,
830        rule: &CfaRule<R>,
831    ) -> Result<bool, CfiError> {
832        let formatted = match rule {
833            CfaRule::RegisterAndOffset { register, offset } => {
834                match cfi_register_name(arch.cpu_family(), register.0) {
835                    Some(register) => format!("{} {} +", register, *offset),
836                    None => return Ok(false),
837                }
838            }
839            CfaRule::Expression(_) => return Ok(false),
840        };
841
842        write!(target, " .cfa: {formatted}")?;
843        Ok(true)
844    }
845
846    fn write_register_rule<R: ReaderOffset, T: Write>(
847        mut target: T,
848        arch: Arch,
849        register: Register,
850        rule: &RegisterRule<R>,
851        ra: Register,
852    ) -> Result<bool, CfiError> {
853        let formatted = match rule {
854            RegisterRule::SameValue => match cfi_register_name(arch.cpu_family(), register.0) {
855                Some(reg) => reg.into(),
856                None => return Ok(false),
857            },
858            RegisterRule::Offset(offset) => format!(".cfa {offset} + ^"),
859            RegisterRule::ValOffset(offset) => format!(".cfa {offset} +"),
860            RegisterRule::Register(register) => {
861                match cfi_register_name(arch.cpu_family(), register.0) {
862                    Some(reg) => reg.into(),
863                    None => return Ok(false),
864                }
865            }
866            _ => return Ok(false),
867        };
868
869        // Breakpad requires an explicit name for the return address register. In all other cases,
870        // we use platform specific names for each register as specified by Breakpad.
871        let register_name = if register == ra {
872            ".ra"
873        } else {
874            match cfi_register_name(arch.cpu_family(), register.0) {
875                Some(reg) => reg,
876                None => return Ok(false),
877            }
878        };
879
880        write!(target, " {register_name}: {formatted}")?;
881        Ok(true)
882    }
883
884    fn process_pdb(&mut self, pdb: &PdbObject<'_>) -> Result<(), CfiError> {
885        let mut pdb = pdb.inner().write();
886        let frame_table = pdb.frame_table()?;
887        let address_map = pdb.address_map()?;
888
889        // See `PdbDebugSession::build`.
890        let string_table = match pdb.string_table() {
891            Ok(string_table) => Some(string_table),
892            Err(pdb::Error::StreamNameNotFound) => None,
893            Err(e) => return Err(e.into()),
894        };
895
896        let mut frames = frame_table.iter();
897        let mut last_frame: Option<FrameData> = None;
898
899        while let Some(frame) = frames.next()? {
900            // Frame data information sometimes contains code_size values close to the maximum `u32`
901            // value, such as `0xffffff6e`. Documentation does not describe the meaning of such
902            // values, but clearly they are not actual code sizes. Since these values also always
903            // occur with a `code_start` close to the end of a function's code range, it seems
904            // likely that these belong to the function epilog and code_size has a different meaning
905            // in this case. Until this value is understood, skip these entries.
906            if frame.code_size > i32::MAX as u32 {
907                continue;
908            }
909
910            // Only print a stack record if information has changed from the last list. It is
911            // surprisingly common (especially in system library PDBs) for DIA to return a series of
912            // identical IDiaFrameData objects. For kernel32.pdb from Windows XP SP2 on x86, this
913            // check reduces the size of the dumped symbol file by a third.
914            if let Some(ref last) = last_frame {
915                if frame.ty == last.ty
916                    && frame.code_start == last.code_start
917                    && frame.code_size == last.code_size
918                    && frame.prolog_size == last.prolog_size
919                {
920                    continue;
921                }
922            }
923
924            // Address ranges need to be translated to the RVA address space. The prolog and the
925            // code portions of the frame have to be treated independently as they may have
926            // independently changed in size, or may even have been split.
927            let prolog_size = u32::from(frame.prolog_size);
928            let prolog_end = frame.code_start + prolog_size;
929            let code_end = frame.code_start + frame.code_size;
930
931            let mut prolog_ranges = address_map
932                .rva_ranges(frame.code_start..prolog_end)
933                .collect::<Vec<_>>();
934
935            let mut code_ranges = address_map
936                .rva_ranges(prolog_end..code_end)
937                .collect::<Vec<_>>();
938
939            // Check if the prolog and code bytes remain contiguous and only output a single record.
940            // This is only done for compactness of the symbol file. Since the majority of PDBs
941            // other than the Kernel do not have translated address spaces, this will be true for
942            // most records.
943            let is_contiguous = prolog_ranges.len() == 1
944                && code_ranges.len() == 1
945                && prolog_ranges[0].end == code_ranges[0].start;
946
947            if is_contiguous {
948                self.write_pdb_stackinfo(
949                    string_table.as_ref(),
950                    &frame,
951                    prolog_ranges[0].start,
952                    code_ranges[0].end,
953                    prolog_ranges[0].end - prolog_ranges[0].start,
954                )?;
955            } else {
956                // Output the prolog first, and then code frames in RVA order.
957                prolog_ranges.sort_unstable_by_key(|range| range.start);
958                code_ranges.sort_unstable_by_key(|range| range.start);
959
960                for Range { start, end } in prolog_ranges {
961                    self.write_pdb_stackinfo(
962                        string_table.as_ref(),
963                        &frame,
964                        start,
965                        end,
966                        end - start,
967                    )?;
968                }
969
970                for Range { start, end } in code_ranges {
971                    self.write_pdb_stackinfo(string_table.as_ref(), &frame, start, end, 0)?;
972                }
973            }
974
975            last_frame = Some(frame);
976        }
977
978        Ok(())
979    }
980
981    fn write_pdb_stackinfo(
982        &mut self,
983        string_table: Option<&StringTable<'_>>,
984        frame: &FrameData,
985        start: Rva,
986        end: Rva,
987        prolog_size: u32,
988    ) -> Result<(), CfiError> {
989        let code_size = end - start;
990        let has_program = frame.program.is_some() && string_table.is_some();
991
992        write!(
993            self.inner,
994            "STACK WIN {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {} ",
995            frame.ty as u8,
996            start.0,
997            code_size,
998            prolog_size,
999            0, // epilog_size
1000            frame.params_size,
1001            frame.saved_regs_size,
1002            frame.locals_size,
1003            frame.max_stack_size.unwrap_or(0),
1004            i32::from(has_program)
1005        )?;
1006
1007        match frame.program {
1008            Some(ref prog_ref) => {
1009                let string_table = match string_table {
1010                    Some(string_table) => string_table,
1011                    None => return Ok(writeln!(self.inner)?),
1012                };
1013
1014                let program_string = prog_ref.to_string_lossy(string_table)?;
1015
1016                writeln!(self.inner, "{}", program_string.trim())?;
1017            }
1018            None => {
1019                writeln!(self.inner, "{}", i32::from(frame.uses_base_pointer))?;
1020            }
1021        }
1022
1023        Ok(())
1024    }
1025
1026    fn process_pe(&mut self, pe: &PeObject<'_>) -> Result<(), CfiError> {
1027        let sections = pe.sections();
1028        let exception_data = match pe.exception_data() {
1029            Some(data) => data,
1030            None => return Ok(()),
1031        };
1032
1033        let mut cfa_reg = Vec::new();
1034        let mut saved_regs = Vec::new();
1035        let mut unwind_codes = Vec::new();
1036
1037        'functions: for function_result in exception_data.functions() {
1038            let function =
1039                function_result.map_err(|e| CfiError::new(CfiErrorKind::BadDebugInfo, e))?;
1040
1041            // Exception directories can contain zeroed out sections which need to be skipped.
1042            // Neither their start/end RVA nor the unwind info RVA is valid.
1043            if function == EMPTY_FUNCTION {
1044                continue;
1045            }
1046
1047            // The minimal stack size is 8 for RIP
1048            // Also, we are using signed math here everywhere, as some unwind operations, such as
1049            // `SaveNonVolatile` can actually point out of the current stack frame, which appears
1050            // to be perfectly fine.
1051            // When this happens, the resulting ASCII CFI can also contain weird things like
1052            // `.cfa -8 - ^` (that means, take the value 8 *above* the stack frame) which is
1053            // perfectly fine as well.
1054            let mut stack_size: i32 = 8;
1055            // Special handling for machine frames
1056            let mut machine_frame_offset = 0;
1057
1058            if function.end_address < function.begin_address {
1059                continue;
1060            }
1061
1062            cfa_reg.clear();
1063            saved_regs.clear();
1064
1065            let mut next_function = Some(function);
1066            while let Some(next) = next_function {
1067                let unwind_info = exception_data
1068                    .get_unwind_info(next, sections)
1069                    .map_err(|e| CfiError::new(CfiErrorKind::BadDebugInfo, e))?;
1070
1071                unwind_codes.clear();
1072                for code_result in unwind_info.unwind_codes() {
1073                    // Due to variable length encoding of operator codes, there is little point in
1074                    // continuing after this. Other functions in this object file can be valid, so
1075                    // swallow the error and continue with the next function.
1076                    let code = match code_result {
1077                        Ok(code) => code,
1078                        Err(_) => {
1079                            continue 'functions;
1080                        }
1081                    };
1082                    unwind_codes.push(code);
1083                }
1084
1085                // The unwind codes are saved in reverse order.
1086                for code in unwind_codes.iter().rev() {
1087                    match code.operation {
1088                        UnwindOperation::SaveNonVolatile(reg, offset) => {
1089                            match offset {
1090                                // If the Frame Register field in the UNWIND_INFO is zero,
1091                                // this offset is from RSP.
1092                                StackFrameOffset::RSP(offset) => {
1093                                    write!(
1094                                        &mut saved_regs,
1095                                        " {}: .cfa {} - ^",
1096                                        reg.name(),
1097                                        stack_size - offset as i32
1098                                    )?;
1099                                }
1100                                // If the Frame Register field is nonzero, this offset is from where
1101                                // RSP was located when the FP register was established.
1102                                // It equals the FP register minus the FP register offset
1103                                // (16 * the scaled frame register offset in the UNWIND_INFO).
1104                                StackFrameOffset::FP(offset) => {
1105                                    write!(
1106                                        &mut saved_regs,
1107                                        " {}: {} {} + ^",
1108                                        reg.name(),
1109                                        unwind_info.frame_register.name(),
1110                                        offset.wrapping_sub(unwind_info.frame_register_offset)
1111                                            as i32
1112                                    )?;
1113                                }
1114                            };
1115                        }
1116                        UnwindOperation::PushNonVolatile(reg) => {
1117                            // $reg = .cfa - current_offset
1118                            stack_size += 8;
1119                            write!(&mut saved_regs, " {}: .cfa {} - ^", reg.name(), stack_size)?;
1120                        }
1121                        UnwindOperation::Alloc(size) => {
1122                            stack_size += size as i32;
1123                        }
1124                        UnwindOperation::SetFPRegister => {
1125                            // Establish the frame pointer register by setting the register to some
1126                            // offset of the current RSP. The offset is equal to the Frame Register
1127                            // offset field in the UNWIND_INFO.
1128                            let offset = stack_size - unwind_info.frame_register_offset as i32;
1129                            // Set the `.cfa = $fp + offset`
1130                            write!(
1131                                &mut cfa_reg,
1132                                ".cfa: {} {} +",
1133                                unwind_info.frame_register.name(),
1134                                offset
1135                            )?;
1136                        }
1137
1138                        UnwindOperation::PushMachineFrame(is_error) => {
1139                            // NOTE:
1140                            // The MachineFrame also contains:
1141                            // * SS (stack segment, we ignore this)
1142                            // * RSP (stack pointer, which we use/restore)
1143                            // * EFLAGS (we ignore this)
1144                            // * CS (code segment, we ignore this)
1145                            // * RIP (return address, which we use/restore)
1146                            // * (optional) Error code (we ignore this)
1147                            let rsp_offset = stack_size + 16;
1148                            let rip_offset = stack_size + 40;
1149                            write!(
1150                                &mut saved_regs,
1151                                " $rsp: .cfa {rsp_offset} - ^ .ra: .cfa {rip_offset} - ^",
1152                            )?;
1153                            stack_size += 40;
1154                            machine_frame_offset = stack_size;
1155                            stack_size += if is_error { 8 } else { 0 };
1156                        }
1157                        _ => {
1158                            // All other codes do not modify RSP
1159                        }
1160                    }
1161                }
1162
1163                next_function = unwind_info.chained_info;
1164            }
1165
1166            if cfa_reg.is_empty() {
1167                write!(&mut cfa_reg, ".cfa: $rsp {stack_size} +")?;
1168            }
1169            if machine_frame_offset == 0 {
1170                write!(&mut saved_regs, " .ra: .cfa 8 - ^")?;
1171            }
1172
1173            write!(
1174                self.inner,
1175                "STACK CFI INIT {:x} {:x} ",
1176                function.begin_address,
1177                function.end_address - function.begin_address,
1178            )?;
1179            self.inner.write_all(&cfa_reg)?;
1180            self.inner.write_all(&saved_regs)?;
1181            writeln!(self.inner)?;
1182        }
1183
1184        Ok(())
1185    }
1186}
1187
1188impl<W: Write + Default> AsciiCfiWriter<W> {
1189    /// Extracts CFI from the given object and pipes it to a new writer instance.
1190    pub fn transform(object: &Object<'_>) -> Result<W, CfiError> {
1191        let mut writer = Default::default();
1192        AsciiCfiWriter::new(&mut writer).process(object)?;
1193        Ok(writer)
1194    }
1195}
1196
1197struct CfiCacheV1<'a> {
1198    byteview: ByteView<'a>,
1199}
1200
1201impl CfiCacheV1<'_> {
1202    pub fn raw(&self) -> &[u8] {
1203        &self.byteview
1204    }
1205}
1206
1207enum CfiCacheInner<'a> {
1208    Unversioned(CfiCacheV1<'a>),
1209    Versioned(u32, CfiCacheV1<'a>),
1210}
1211
1212/// A cache file for call frame information (CFI).
1213///
1214/// The default way to use this cache is to construct it from an `Object` and save it to a file.
1215/// Then, load it from the file and pass it to the minidump processor.
1216///
1217/// ```rust,no_run
1218/// use std::fs::File;
1219/// use symbolic_common::ByteView;
1220/// use symbolic_debuginfo::Object;
1221/// use symbolic_cfi::CfiCache;
1222///
1223/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1224/// let view = ByteView::open("/path/to/object")?;
1225/// let object = Object::parse(&view)?;
1226/// let cache = CfiCache::from_object(&object)?;
1227/// cache.write_to(File::create("my.cficache")?)?;
1228/// # Ok(())
1229/// # }
1230/// ```
1231///
1232/// ```rust,no_run
1233/// use symbolic_common::ByteView;
1234/// use symbolic_cfi::CfiCache;
1235///
1236/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1237/// let view = ByteView::open("my.cficache")?;
1238/// let cache = CfiCache::from_bytes(view)?;
1239/// # Ok(())
1240/// # }
1241/// ```
1242pub struct CfiCache<'a> {
1243    inner: CfiCacheInner<'a>,
1244}
1245
1246impl CfiCache<'static> {
1247    /// Construct a CFI cache from an `Object`.
1248    pub fn from_object(object: &Object<'_>) -> Result<Self, CfiError> {
1249        let mut buffer = vec![];
1250        write_preamble(&mut buffer, CFICACHE_LATEST_VERSION)?;
1251
1252        if let Object::Pe(pe) = object {
1253            let debug_file = pe.debug_file_name();
1254            if let Some(debug_file) = debug_file {
1255                writeln!(
1256                    buffer,
1257                    "MODULE windows {} {} {debug_file}",
1258                    object.arch().name(),
1259                    object.debug_id().breakpad(),
1260                )?;
1261            }
1262        }
1263
1264        AsciiCfiWriter::new(&mut buffer).process(object)?;
1265
1266        let byteview = ByteView::from_vec(buffer);
1267        let inner = CfiCacheInner::Versioned(CFICACHE_LATEST_VERSION, CfiCacheV1 { byteview });
1268        Ok(CfiCache { inner })
1269    }
1270}
1271
1272fn write_preamble<W: Write>(mut writer: W, version: u32) -> Result<(), io::Error> {
1273    writer.write_all(&CFICACHE_MAGIC.to_ne_bytes())?;
1274    writer.write_all(&version.to_ne_bytes())
1275}
1276
1277impl<'a> CfiCache<'a> {
1278    /// Load a symcache from a `ByteView`.
1279    pub fn from_bytes(byteview: ByteView<'a>) -> Result<Self, CfiError> {
1280        if byteview.is_empty() || byteview.starts_with(b"STACK") {
1281            let inner = CfiCacheInner::Unversioned(CfiCacheV1 { byteview });
1282            return Ok(CfiCache { inner });
1283        }
1284
1285        if let Some(preamble) = byteview.get(0..8) {
1286            let magic = u32::from_ne_bytes(preamble[0..4].try_into().unwrap());
1287            if magic == CFICACHE_MAGIC {
1288                let version = u32::from_ne_bytes(preamble[4..8].try_into().unwrap());
1289                let inner = CfiCacheInner::Versioned(version, CfiCacheV1 { byteview });
1290                return Ok(CfiCache { inner });
1291            }
1292        }
1293
1294        Err(CfiErrorKind::BadFileMagic.into())
1295    }
1296
1297    /// Returns the cache file format version.
1298    pub fn version(&self) -> u32 {
1299        match self.inner {
1300            CfiCacheInner::Unversioned(_) => 1,
1301            CfiCacheInner::Versioned(version, _) => version,
1302        }
1303    }
1304
1305    /// Returns whether this cache is up-to-date.
1306    pub fn is_latest(&self) -> bool {
1307        self.version() == CFICACHE_LATEST_VERSION
1308    }
1309
1310    /// Returns the raw buffer of the cache file.
1311    pub fn as_slice(&self) -> &[u8] {
1312        match self.inner {
1313            CfiCacheInner::Unversioned(ref v1) => v1.raw(),
1314            CfiCacheInner::Versioned(_, ref v1) => &v1.raw()[8..],
1315        }
1316    }
1317
1318    /// Writes the cache to the given writer.
1319    pub fn write_to<W: Write>(&self, mut writer: W) -> Result<(), io::Error> {
1320        if let CfiCacheInner::Versioned(version, _) = self.inner {
1321            write_preamble(&mut writer, version)?;
1322        }
1323        io::copy(&mut self.as_slice(), &mut writer)?;
1324        Ok(())
1325    }
1326}
1327
1328#[cfg(test)]
1329mod tests {
1330    use super::*;
1331
1332    #[test]
1333    fn test_cfi_register_name_none() {
1334        assert_eq!(cfi_register_name(CpuFamily::Arm64, 33), None);
1335    }
1336}