Skip to main content

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