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(§ion.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(§ion.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(§ion.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(®ister) != 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}