Skip to main content

simple_frame_rs/
v3.rs

1//! SFrame Version 3 types and implementation.
2//!
3//! Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html>
4
5use std::{cmp::Ordering, fmt::Write};
6
7use bitflags::bitflags;
8use fallible_iterator::FallibleIterator;
9
10use crate::{SFrameError, SFrameResult, read_binary, read_struct};
11
12/// SFrame ABI/arch Identifier
13///
14/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#SFrame-ABI_002farch-Identifier>
15#[derive(Debug, Clone, Copy)]
16pub enum SFrameABI {
17    /// SFRAME_ABI_AARCH64_ENDIAN_BIG
18    AArch64BigEndian,
19    /// SFRAME_ABI_AARCH64_ENDIAN_LITTLE
20    AArch64LittleEndian,
21    /// SFRAME_ABI_AMD64_ENDIAN_LITTLE
22    AMD64LittleEndian,
23    /// SFRAME_ABI_S390X_ENDIAN_BIG
24    S390XBigEndian,
25}
26
27/// s390x-specific constants and helpers from binutils-gdb sframe.h
28///
29/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#s390x>
30pub mod s390x {
31    /// On s390x, the CFA is defined as SP at call site + 160.
32    /// Therefore the SP value offset from CFA is -160.
33    pub const SFRAME_S390X_SP_VAL_OFFSET: i32 = -160;
34
35    /// On s390x, the CFA offset from CFA base register is by definition a minimum
36    /// of 160. Store it adjusted by -160 to enable use of 8-bit SFrame offsets.
37    pub const SFRAME_S390X_CFA_OFFSET_ADJUSTMENT: i32 = SFRAME_S390X_SP_VAL_OFFSET;
38
39    /// Additionally scale by an alignment factor of 8, as the SP and thus CFA
40    /// offset on s390x is always 8-byte aligned.
41    pub const SFRAME_S390X_CFA_OFFSET_ALIGNMENT_FACTOR: i32 = 8;
42
43    /// Invalid RA offset.  Currently used for s390x as padding to represent FP
44    /// without RA saved.
45    pub const SFRAME_FRE_RA_OFFSET_INVALID: i32 = 0;
46
47    /// Decode a CFA offset from the FRE stored value.
48    pub const fn cfa_offset_decode(offset: i32) -> i32 {
49        (offset * SFRAME_S390X_CFA_OFFSET_ALIGNMENT_FACTOR) - SFRAME_S390X_CFA_OFFSET_ADJUSTMENT
50    }
51
52    /// Check if an offset represents a DWARF register number.
53    pub const fn offset_is_regnum(offset: i32) -> bool {
54        (offset & 1) != 0
55    }
56
57    /// Decode a DWARF register number from an offset.
58    pub const fn offset_decode_regnum(offset: i32) -> i32 {
59        offset >> 1
60    }
61}
62
63bitflags! {
64    /// SFrame Flags
65    ///
66    /// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#SFrame-Flags>
67    #[derive(Debug, Clone, Copy)]
68    pub struct SFrameFlags: u8 {
69        /// Function Descriptor Entries are sorted on PC.
70        const SFRAME_F_FDE_SORTED = 0x1;
71        /// All functions in the object file preserve frame pointer.
72        const SFRAME_F_FRAME_POINTER = 0x2;
73        /// The sfde_func_start_address field in the SFrame FDE is an offset in bytes to the function’s start address, from the field itself. If unset, the sfde_func_start_address field in the SFrame FDE is an offset in bytes to the function’s start address, from the start of the SFrame section.
74        const SFRAME_F_FDE_FUNC_START_PCREL = 0x4;
75    }
76}
77
78/// SFrame section
79///
80/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#SFrame-Section>
81#[derive(Debug, Clone, Copy)]
82#[allow(dead_code)]
83pub struct SFrameSection<'a> {
84    data: &'a [u8],
85    section_base: u64,
86    little_endian: bool,
87    flags: SFrameFlags,
88    abi: SFrameABI,
89    cfa_fixed_fp_offset: i8,
90    cfa_fixed_ra_offset: i8,
91    auxhdr_len: u8,
92    num_fdes: u32,
93    num_fres: u32,
94    fre_len: u32,
95    fdeoff: u32,
96    freoff: u32,
97}
98
99/// The magic number for SFrame section: 0xdee2
100const SFRAME_MAGIC: u16 = 0xdee2;
101
102/// Get register name from dwarf register number
103fn get_regname_from_dwarf(regnum: i32, abi: SFrameABI) -> String {
104    match (regnum, abi) {
105        // rbp
106        (6, SFrameABI::AMD64LittleEndian) => "fp".to_string(),
107        // rsp
108        (7, SFrameABI::AMD64LittleEndian) => "sp".to_string(),
109        _ => format!("r{}", regnum),
110    }
111}
112
113/// Print flex recovery rule to string
114fn flex_recovery_rule_to_string(rule: &SFrameFlexRecoveryRule, abi: SFrameABI) -> String {
115    match (
116        rule.control.is_padding(),
117        rule.control.reg_p(),
118        rule.control.deref_p(),
119    ) {
120        (true, _, _) => {
121            // undefined
122            format!("U")
123        }
124        (false, true, true) => {
125            // register deref
126            format!(
127                "({}{:+})",
128                get_regname_from_dwarf(rule.control.regnum(), abi),
129                rule.offset
130            )
131        }
132        (false, true, false) => {
133            // register
134            format!(
135                "{}{:+}",
136                get_regname_from_dwarf(rule.control.regnum(), abi),
137                rule.offset
138            )
139        }
140        (false, false, _) => {
141            // cfa
142            format!("c{:+}", rule.offset)
143        }
144    }
145}
146
147impl<'a> SFrameSection<'a> {
148    /// Parse SFrame section from data
149    pub fn from(data: &'a [u8], section_base: u64) -> SFrameResult<SFrameSection<'a>> {
150        // parse sframe_header
151        if data.len() < core::mem::size_of::<RawSFrameHeader>() {
152            return Err(SFrameError::UnexpectedEndOfData);
153        }
154
155        // probe magic
156        let magic_offset = core::mem::offset_of!(RawSFrameHeader, magic);
157        let mut magic_bytes: [u8; 2] = [0; 2];
158        magic_bytes.copy_from_slice(&data[magic_offset..magic_offset + 2]);
159        let magic_le = u16::from_le_bytes(magic_bytes);
160        let little_endian;
161        if magic_le == SFRAME_MAGIC {
162            little_endian = true;
163        } else {
164            let magic_be = u16::from_be_bytes(magic_bytes);
165            if magic_be == SFRAME_MAGIC {
166                little_endian = false;
167            } else {
168                return Err(SFrameError::InvalidMagic);
169            }
170        }
171
172        // probe version
173        let version_offset = core::mem::offset_of!(RawSFrameHeader, version);
174        let version = data[version_offset];
175        if version != 3 {
176            return Err(SFrameError::UnsupportedVersion);
177        }
178
179        // probe flag
180        let flags_offset = core::mem::offset_of!(RawSFrameHeader, flags);
181        let flags = data[flags_offset];
182        let flags = match SFrameFlags::from_bits(flags) {
183            Some(flags) => flags,
184            None => return Err(SFrameError::UnsupportedFlags),
185        };
186
187        // probe abi
188        let abi_offset = core::mem::offset_of!(RawSFrameHeader, abi_arch);
189        let abi = data[abi_offset];
190        let abi = match abi {
191            1 => SFrameABI::AArch64BigEndian,
192            2 => SFrameABI::AArch64LittleEndian,
193            3 => SFrameABI::AMD64LittleEndian,
194            4 => SFrameABI::S390XBigEndian,
195            _ => return Err(SFrameError::UnsupportedABI),
196        };
197
198        let cfa_fixed_fp_offset =
199            data[core::mem::offset_of!(RawSFrameHeader, cfa_fixed_fp_offset)] as i8;
200        let cfa_fixed_ra_offset =
201            data[core::mem::offset_of!(RawSFrameHeader, cfa_fixed_ra_offset)] as i8;
202        let auxhdr_len = data[core::mem::offset_of!(RawSFrameHeader, auxhdr_len)];
203
204        // initial validation
205        let num_fdes = read_struct!(RawSFrameHeader, data, little_endian, num_fdes, u32);
206        let fdeoff = read_struct!(RawSFrameHeader, data, little_endian, fdeoff, u32);
207        if data.len() - core::mem::size_of::<RawSFrameHeader>() < fdeoff as usize {
208            return Err(SFrameError::UnexpectedEndOfData);
209        } else if (data.len() - core::mem::size_of::<RawSFrameHeader>() - fdeoff as usize)
210            / core::mem::size_of::<RawSFrameFDEIndex>()
211            < num_fdes as usize
212        {
213            return Err(SFrameError::UnexpectedEndOfData);
214        }
215
216        Ok(SFrameSection {
217            data,
218            section_base,
219            little_endian,
220            flags,
221            abi,
222            cfa_fixed_fp_offset,
223            cfa_fixed_ra_offset,
224            auxhdr_len,
225            num_fdes,
226            num_fres: read_struct!(RawSFrameHeader, data, little_endian, num_fres, u32),
227            fre_len: read_struct!(RawSFrameHeader, data, little_endian, fre_len, u32),
228            fdeoff,
229            freoff: read_struct!(RawSFrameHeader, data, little_endian, freoff, u32),
230        })
231    }
232
233    /// Get the count of FDE entries
234    pub fn get_fde_count(&self) -> u32 {
235        self.num_fdes
236    }
237
238    /// Access FDE by index
239    pub fn get_fde(&self, index: u32) -> SFrameResult<Option<SFrameFDE>> {
240        if index >= self.num_fdes {
241            // out of bounds
242            return Ok(None);
243        }
244
245        // The sub-section offsets, namely sfh_fdeoff and sfh_freoff, in the
246        // SFrame header are relative to the end of the SFrame header; they are
247        // each an offset in bytes into the SFrame section where the SFrame FDE
248        // sub-section and the SFrame FRE sub-section respectively start.
249        let offset = self.fdeoff as usize
250            + index as usize * core::mem::size_of::<RawSFrameFDEIndex>()
251            + core::mem::size_of::<RawSFrameHeader>();
252        if offset + core::mem::size_of::<RawSFrameFDEIndex>() > self.data.len() {
253            return Err(SFrameError::UnexpectedEndOfData);
254        }
255
256        // read fde attribute in fre sub-section
257
258        let func_start_fre_off = read_struct!(
259            RawSFrameFDEIndex,
260            &self.data[offset..],
261            self.little_endian,
262            func_start_fre_off,
263            u32
264        );
265
266        let fre_offset = self.freoff as usize
267            + core::mem::size_of::<RawSFrameHeader>()
268            + func_start_fre_off as usize;
269        let func_num_fres = read_struct!(
270            RawSFrameFDEAttr,
271            &self.data[fre_offset..],
272            self.little_endian,
273            func_num_fres,
274            u16
275        );
276        let func_info = read_struct!(
277            RawSFrameFDEAttr,
278            &self.data[fre_offset..],
279            self.little_endian,
280            func_info,
281            u8
282        );
283        let func_info2 = read_struct!(
284            RawSFrameFDEAttr,
285            &self.data[fre_offset..],
286            self.little_endian,
287            func_info2,
288            u8
289        );
290        let func_rep_size = read_struct!(
291            RawSFrameFDEAttr,
292            &self.data[fre_offset..],
293            self.little_endian,
294            func_rep_size,
295            u8
296        );
297
298        Ok(Some(SFrameFDE {
299            offset,
300            func_start_offset: read_struct!(
301                RawSFrameFDEIndex,
302                &self.data[offset..],
303                self.little_endian,
304                func_start_offset,
305                i64
306            ),
307            func_size: read_struct!(
308                RawSFrameFDEIndex,
309                &self.data[offset..],
310                self.little_endian,
311                func_size,
312                u32
313            ),
314            func_start_fre_off,
315            func_num_fres,
316            func_info: SFrameFDEInfo(func_info),
317            func_info2: SFrameFDEInfo2(func_info2),
318            func_rep_size,
319        }))
320    }
321
322    /// Print the section in string in the same way as objdump
323    pub fn to_string(&self) -> SFrameResult<String> {
324        let mut s = String::new();
325        writeln!(&mut s, "Header :")?;
326        writeln!(&mut s)?;
327        writeln!(&mut s, "  Version: SFRAME_VERSION_3")?;
328        writeln!(
329            &mut s,
330            "  Flags: {}",
331            self.flags
332                .iter_names()
333                .map(|(name, _flag)| name)
334                .collect::<Vec<_>>()
335                .join(",\n         ")
336        )?;
337        if self.cfa_fixed_fp_offset != 0 {
338            writeln!(
339                &mut s,
340                "  CFA fixed FP offset: {:?}",
341                self.cfa_fixed_fp_offset
342            )?;
343        }
344        if self.cfa_fixed_ra_offset != 0 {
345            writeln!(
346                &mut s,
347                "  CFA fixed RA offset: {:?}",
348                self.cfa_fixed_ra_offset
349            )?;
350        }
351        writeln!(&mut s, "  Num FDEs: {:?}", self.num_fdes)?;
352        writeln!(&mut s, "  Num FREs: {:?}", self.num_fres)?;
353        writeln!(&mut s)?;
354        writeln!(&mut s, "Function Index :")?;
355        writeln!(&mut s)?;
356        for i in 0..self.num_fdes {
357            let fde = self.get_fde(i)?.unwrap();
358            let pc = fde.get_pc(self);
359            let mut suffix = String::new();
360
361            let mut attrs = "".to_string();
362            if fde.func_info.is_signal_frame()? {
363                attrs.push('S');
364            }
365            if let Ok(SFrameFDEType::Flex) = fde.func_info2.get_fde_type() {
366                attrs.push('F');
367            }
368
369            if !attrs.is_empty() {
370                suffix += &format!(", attr = \"{}\"", attrs);
371            };
372
373            // aarch64 pauth
374            if let SFrameAArch64PAuthKey::KeyB = fde.func_info.get_aarch64_pauth_key()? {
375                suffix += ", pauth = B key";
376            }
377
378            writeln!(
379                &mut s,
380                "  func idx [{i}]: pc = 0x{:x}, size = {} bytes{}",
381                pc, fde.func_size, suffix
382            )?;
383
384            match fde.func_info.get_fde_pctype()? {
385                SFrameFDEPCType::PCInc => {
386                    writeln!(&mut s, "  STARTPC           CFA      FP     RA")?;
387                }
388                SFrameFDEPCType::PCMask => {
389                    writeln!(&mut s, "  STARTPC[m]        CFA      FP     RA")?;
390                }
391            }
392            let mut iter = fde.iter_fre(self);
393            while let Some(fre) = iter.next()? {
394                let start_pc = match fde.func_info.get_fde_pctype()? {
395                    SFrameFDEPCType::PCInc => pc + fre.start_address.get() as u64,
396                    SFrameFDEPCType::PCMask => fre.start_address.get() as u64,
397                };
398
399                match fde.func_info2.get_fde_type() {
400                    Ok(SFrameFDEType::Default) => {
401                        // default fde
402                        if fre.stack_offsets.is_empty() {
403                            // RA undefined: top level function
404                            writeln!(&mut s, "  {:016x}  RA undefined", start_pc)?;
405                        } else {
406                            let base_reg = if fre.info.get_cfa_base_reg_id() == 0 {
407                                "fp"
408                            } else {
409                                "sp"
410                            };
411                            let cfa = format!("{}+{}", base_reg, fre.stack_offsets[0].get());
412                            let fp = match fre.get_fp_offset(self) {
413                                Some(offset) => format!("c{:+}", offset),
414                                None => "u".to_string(), // without offset
415                            };
416                            let mut ra = if self.cfa_fixed_ra_offset != 0 {
417                                "f".to_string() // fixed
418                            } else {
419                                match fre.get_ra_offset(self) {
420                                    Some(offset) => format!("c{:+}", offset),
421                                    None => "u".to_string(), // without offset
422                                }
423                            };
424                            if fre.info.get_mangled_ra_p() {
425                                // ra is mangled with signature
426                                ra.push_str("[s]");
427                            }
428                            let rest = format!("{cfa:8} {fp:6} {ra}");
429                            writeln!(&mut s, "  {:016x}  {}", start_pc, rest)?;
430                        }
431                    }
432                    Ok(SFrameFDEType::Flex) => {
433                        // flex fde
434                        if let Some(rules) = fre.get_flex_recovery_rules() {
435                            let cfa = flex_recovery_rule_to_string(&rules.cfa, self.abi);
436                            let fp = if let Some(fp_rule) = rules.fp {
437                                flex_recovery_rule_to_string(&fp_rule, self.abi)
438                            } else {
439                                "u".to_string()
440                            };
441                            let ra = if let Some(ra_rule) = rules.ra {
442                                flex_recovery_rule_to_string(&ra_rule, self.abi)
443                            } else {
444                                if self.cfa_fixed_ra_offset != 0 {
445                                    "f".to_string()
446                                } else {
447                                    "u".to_string()
448                                }
449                            };
450                            let rest = format!("{cfa:8} {fp:6} {ra}");
451                            writeln!(&mut s, "  {:016x}  {}", start_pc, rest)?;
452                        } else {
453                            // RA undefined: top level function
454                            writeln!(&mut s, "  {:016x}  RA undefined", start_pc)?;
455                        }
456                    }
457                    _ => {}
458                }
459            }
460            writeln!(&mut s,)?;
461        }
462        Ok(s)
463    }
464
465    /// Iterate FDE entries
466    pub fn iter_fde(&self) -> SFrameFDEIterator<'_> {
467        SFrameFDEIterator {
468            section: self,
469            index: 0,
470        }
471    }
472
473    /// Find FDE entry by pc
474    pub fn find_fde(&self, pc: u64) -> SFrameResult<Option<SFrameFDE>> {
475        if self.flags.contains(SFrameFlags::SFRAME_F_FDE_SORTED) {
476            // binary search
477            // mimic binary_search_by impl from rust std
478            let mut size = self.num_fdes;
479            if size == 0 {
480                return Ok(None);
481            }
482            let mut base = 0;
483
484            while size > 1 {
485                let half = size / 2;
486                let mid = base + half;
487
488                let cmp = self.get_fde(mid)?.unwrap().get_pc(self).cmp(&pc);
489                if cmp != Ordering::Greater {
490                    base = mid;
491                }
492                size -= half;
493            }
494
495            let base_fde = self.get_fde(base)?.unwrap();
496            let base_pc = base_fde.get_pc(self);
497            let cmp = base_pc.cmp(&pc);
498            match cmp {
499                Ordering::Equal | Ordering::Less if pc < base_pc + base_fde.func_size as u64 => {
500                    Ok(Some(base_fde))
501                }
502                _ => Ok(None),
503            }
504        } else {
505            // linear scan
506            let mut iter = self.iter_fde();
507            while let Some(fde) = iter.next()? {
508                let start = fde.get_pc(self);
509                if start <= pc && pc - start < fde.func_size as u64 {
510                    return Ok(Some(fde));
511                }
512            }
513            Ok(None)
514        }
515    }
516
517    /// Get SFrame flags
518    pub fn get_flags(&self) -> SFrameFlags {
519        self.flags
520    }
521
522    /// Get SFrame ABI
523    pub fn get_abi(&self) -> SFrameABI {
524        self.abi
525    }
526
527    /// Get SFrame CFA fixed FP offset
528    pub fn get_cfa_fixed_fp_offset(&self) -> i8 {
529        self.cfa_fixed_fp_offset
530    }
531
532    /// Get SFrame CFA fixed RA offset
533    pub fn get_cfa_fixed_ra_offset(&self) -> i8 {
534        self.cfa_fixed_ra_offset
535    }
536}
537
538/// Raw SFrame Header
539///
540/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#SFrame-Header>
541#[repr(C, packed)]
542struct RawSFrameHeader {
543    magic: u16,
544    version: u8,
545    flags: u8,
546    abi_arch: u8,
547    cfa_fixed_fp_offset: i8,
548    cfa_fixed_ra_offset: i8,
549    auxhdr_len: u8,
550    num_fdes: u32,
551    num_fres: u32,
552    fre_len: u32,
553    fdeoff: u32,
554    freoff: u32,
555}
556
557/// Raw SFrame FDE Index
558///
559/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FDE-Index>
560#[repr(C, packed)]
561#[allow(dead_code)]
562struct RawSFrameFDEIndex {
563    func_start_offset: i64,
564    func_size: u32,
565    func_start_fre_off: u32,
566}
567
568/// Raw SFrame FDE Attribute
569///
570/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FDE-Attribute>
571#[repr(C, packed)]
572#[allow(dead_code)]
573struct RawSFrameFDEAttr {
574    func_num_fres: u16,
575    func_info: u8,
576    func_info2: u8,
577    func_rep_size: u8,
578}
579
580/// SFrame FDE Info Byte
581///
582/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FDE-Info-Bytes>
583#[derive(Debug, Clone, Copy)]
584#[repr(transparent)]
585pub struct SFrameFDEInfo(u8);
586
587impl SFrameFDEInfo {
588    /// Get SFrame FRE type
589    pub fn get_fre_type(&self) -> SFrameResult<SFrameFREType> {
590        let fretype = self.0 & 0b1111;
591        match fretype {
592            0 => Ok(SFrameFREType::Addr1),
593            1 => Ok(SFrameFREType::Addr2),
594            2 => Ok(SFrameFREType::Addr4),
595            _ => Err(SFrameError::UnsupportedFREType),
596        }
597    }
598
599    /// Get SFrame FDE PC type
600    pub fn get_fde_pctype(&self) -> SFrameResult<SFrameFDEPCType> {
601        let fretype = (self.0 >> 4) & 0b1;
602        match fretype {
603            0 => Ok(SFrameFDEPCType::PCInc),
604            1 => Ok(SFrameFDEPCType::PCMask),
605            _ => unreachable!(),
606        }
607    }
608
609    /// Get SFrame AArch64 pauth key
610    pub fn get_aarch64_pauth_key(&self) -> SFrameResult<SFrameAArch64PAuthKey> {
611        let fretype = (self.0 >> 5) & 0b1;
612        match fretype {
613            0 => Ok(SFrameAArch64PAuthKey::KeyA),
614            1 => Ok(SFrameAArch64PAuthKey::KeyB),
615            _ => unreachable!(),
616        }
617    }
618
619    /// Is Signal Frame?
620    pub fn is_signal_frame(&self) -> SFrameResult<bool> {
621        let fretype = (self.0 >> 7) & 0b1;
622        match fretype {
623            0 => Ok(false),
624            1 => Ok(true),
625            _ => unreachable!(),
626        }
627    }
628}
629
630/// SFrame FDE Info Byte 2
631///
632/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FDE-Info-Bytes>
633#[derive(Debug, Clone, Copy)]
634#[repr(transparent)]
635pub struct SFrameFDEInfo2(u8);
636
637impl SFrameFDEInfo2 {
638    /// Get SFrame FDE type
639    pub fn get_fde_type(&self) -> SFrameResult<SFrameFDEType> {
640        let fdetype = self.0 & 0b11111;
641        match fdetype {
642            0 => Ok(SFrameFDEType::Default),
643            1 => Ok(SFrameFDEType::Flex),
644            _ => Err(SFrameError::UnsupportedFDEType),
645        }
646    }
647}
648
649/// SFrame FRE Types
650///
651/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FRE-Types>
652#[derive(Debug, Clone, Copy)]
653pub enum SFrameFREType {
654    /// SFRAME_FRE_TYPE_ADDR1
655    /// The start address offset (in bytes) of the SFrame FRE is an unsigned
656    /// 8-bit value.
657    Addr1,
658    /// SFRAME_FRE_TYPE_ADDR2
659    /// The start address offset (in bytes) of the SFrame FRE is an unsigned
660    /// 16-bit value.
661    Addr2,
662    /// SFRAME_FRE_TYPE_ADDR4
663    /// The start address offset (in bytes) of the SFrame FRE is an unsigned
664    /// 32-bit value.
665    Addr4,
666}
667
668/// SFrame FDE PC Types
669///
670/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FDE-Types>
671#[derive(Debug, Clone, Copy)]
672pub enum SFrameFDEPCType {
673    /// SFRAME_V3_FDE_TYPE_PCINC
674    PCInc,
675    /// SFRAME_V3_FDE_TYPE_PCMASK
676    PCMask,
677}
678
679/// SFrame FDE Types
680///
681/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FDE-Types>
682#[derive(Debug, Clone, Copy)]
683pub enum SFrameFDEType {
684    /// SFRAME_FDE_TYPE_DEFAULT
685    /// The default FDE type.
686    /// CFA is recovered using the Stack Pointer (SP) or Frame Pointer (FP) plus
687    /// a signed offset. Return Address (RA) and Frame Pointer (FP) are
688    /// recovered using the CFA plus a signed offset (or a fixed register for
689    /// specific architectures like s390x).
690    /// The variable-length array of bytes trailing each SFrame FRE are
691    /// interpreted according to the ABI/arch-specific rules for the target
692    /// architecture. More details in Default FDE Type Interpretation.
693    Default,
694
695    /// SFRAME_FDE_TYPE_FLEX
696    /// The flexible FDE type.
697    /// Used for complex cases such as stack realignment (DRAP), non-standard
698    /// CFA base registers, or when RA/FP recovery requires dereferencing or
699    /// non-CFA base registers.
700    /// The variable-length array of bytes may be interpreted as pairs of
701    /// Control Data and Offset Data (or Padding Data), allowing for complex
702    /// recovery rules (e.g., DRAP on AMD64, Stack Realignment). More details in
703    /// Flexible FDE Type Interpretation.
704    Flex,
705}
706
707/// SFrame PAuth key
708///
709/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FDE-Info-Word>
710#[derive(Debug, Clone, Copy)]
711pub enum SFrameAArch64PAuthKey {
712    /// SFRAME_AARCH64_PAUTH_KEY_A
713    KeyA,
714    /// SFRAME_AARCH64_PAUTH_KEY_B
715    KeyB,
716}
717
718/// SFrame FDE
719///
720/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#SFrame-Function-Descriptor-Entries>
721#[derive(Debug, Clone, Copy)]
722pub struct SFrameFDE {
723    /// Offset from the beginning of sframe section
724    offset: usize,
725    /// Signed 64-bit integral field specifying the offset to the start address
726    /// of the described function. If the flag SFRAME_F_FDE_FUNC_START_PCREL,
727    /// See SFrame Flags, in the SFrame header is set, the value encoded in the
728    /// sfdi_func_start_offset field is the offset in bytes to the function’s
729    /// start address from the sfdi_func_start_offset field itself. Otherwise,
730    /// it is the offset in bytes from the start of the SFrame section.
731    pub func_start_offset: i64,
732    /// Unsigned 32-bit integral field specifying the size of the function in
733    /// bytes.
734    pub func_size: u32,
735    /// Unsigned 32-bit integral field specifying the offset to the start of the
736    /// function’s stack trace data (SFrame FREs). This offset is relative to
737    /// the beginning of the SFrame FRE sub-section.
738    pub func_start_fre_off: u32,
739    /// Unsigned 16-bit integral field specifying the total number of SFrame
740    /// FREs used for the function.
741    pub func_num_fres: u16,
742    /// Unsigned 8-bit integral field specifying the SFrame FDE info word. See
743    /// The SFrame FDE Info Word.
744    pub func_info: SFrameFDEInfo,
745    /// Additional unsigned 8-bit integral field specifying the SFrame FDE info byte.
746    pub func_info2: SFrameFDEInfo2,
747    /// Unsigned 8-bit integral field specifying the size of the repetitive code
748    /// block for which an SFrame FDE of type SFRAME_FDE_PCTYPE_MASK is used.
749    /// For example, in AMD64, the size of a pltN entry is 16 bytes.
750    pub func_rep_size: u8,
751}
752
753impl SFrameFDE {
754    /// Get the FDE type (Default or Flex)
755    pub fn get_fde_type(&self) -> SFrameResult<SFrameFDEType> {
756        self.func_info2.get_fde_type()
757    }
758
759    /// Compute pc of the function
760    pub fn get_pc(&self, section: &SFrameSection<'_>) -> u64 {
761        // "If the flag SFRAME_F_FDE_FUNC_START_PCREL, See SFrame Flags, in the
762        // SFrame header is set, the value encoded in the
763        // sfde_func_start_address field is the offset in bytes to the
764        // function’s start address, from the SFrame sfde_func_start_address
765        // field."
766        if section
767            .flags
768            .contains(SFrameFlags::SFRAME_F_FDE_FUNC_START_PCREL)
769        {
770            self.func_start_offset
771                .wrapping_add_unsigned(self.offset as u64)
772                .wrapping_add_unsigned(section.section_base) as u64
773        } else {
774            self.func_start_offset
775                .wrapping_add_unsigned(section.section_base) as u64
776        }
777    }
778
779    /// Iterate FRE entries
780    pub fn iter_fre<'a>(&'a self, section: &'a SFrameSection<'a>) -> SFrameFREIterator<'a> {
781        // "The sub-section offsets, namely sfh_fdeoff and sfh_freoff, in the
782        // SFrame header are relative to the end of the SFrame header; they are
783        // each an offset in bytes into the SFrame section where the SFrame FDE
784        // sub-section and the SFrame FRE sub-section respectively start."
785        // "sfde_func_start_fre_off is the offset to the first SFrame FRE for
786        // the function. This offset is relative to the end of the SFrame FDE
787        // sub-section (unlike the sub-section offsets in the SFrame header,
788        // which are relative to the end of the SFrame header)."
789        let offset = section.freoff as usize
790            + core::mem::size_of::<RawSFrameHeader>()
791            + self.func_start_fre_off as usize
792            + core::mem::size_of::<RawSFrameFDEAttr>();
793        SFrameFREIterator {
794            fde: self,
795            section,
796            offset,
797            index: 0,
798        }
799    }
800
801    /// Find FRE entry by pc
802    pub fn find_fre(
803        &self,
804        section: &SFrameSection<'_>,
805        pc: u64,
806    ) -> SFrameResult<Option<SFrameFRE>> {
807        let fde_pc = self.get_pc(section);
808        if pc < fde_pc || pc - fde_pc >= self.func_size as u64 {
809            // out of bounds
810            return Ok(None);
811        }
812
813        match self.func_info.get_fde_pctype()? {
814            SFrameFDEPCType::PCInc => {
815                // find matching fre entry with max pc
816                let mut last: Option<SFrameFRE> = None;
817                let mut iter = self.iter_fre(section);
818                while let Some(fre) = iter.next()? {
819                    if fre.start_address.get() as u64 + fde_pc > pc {
820                        // last is the matching one
821                        break;
822                    }
823                    last = Some(fre);
824                }
825                if let Some(fre) = last {
826                    // PC >= FRE_START_ADDR
827                    if fre.start_address.get() as u64 + fde_pc <= pc {
828                        return Ok(Some(fre));
829                    }
830                }
831                Ok(None)
832            }
833            SFrameFDEPCType::PCMask => {
834                // match by pc masking
835                let mut iter = self.iter_fre(section);
836                while let Some(fre) = iter.next()? {
837                    // PC % REP_BLOCK_SIZE >= FRE_START_ADDR
838                    if self.func_rep_size != 0
839                        && pc % self.func_rep_size as u64 >= fre.start_address.get() as u64
840                    {
841                        // found
842                        return Ok(Some(fre));
843                    }
844                }
845                Ok(None)
846            }
847        }
848    }
849}
850
851/// SFrame FRE Start Address
852///
853/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#SFrame-Frame-Row-Entries>
854#[derive(Debug, Clone, Copy)]
855pub enum SFrameFREStartAddress {
856    U8(u8),
857    U16(u16),
858    U32(u32),
859}
860
861impl SFrameFREStartAddress {
862    /// Convert the variable sized address to u32
863    pub fn get(&self) -> u32 {
864        match self {
865            SFrameFREStartAddress::U8(i) => *i as u32,
866            SFrameFREStartAddress::U16(i) => *i as u32,
867            SFrameFREStartAddress::U32(i) => *i,
868        }
869    }
870}
871
872/// SFrame FRE Stack Offset
873///
874/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#SFrame-Frame-Row-Entries>
875#[derive(Debug, Clone, Copy)]
876pub enum SFrameFREStackOffset {
877    I8(i8),
878    I16(i16),
879    I32(i32),
880}
881
882impl SFrameFREStackOffset {
883    /// Convert the variable sized offset to i32
884    pub fn get(&self) -> i32 {
885        match self {
886            SFrameFREStackOffset::I8(i) => *i as i32,
887            SFrameFREStackOffset::I16(i) => *i as i32,
888            SFrameFREStackOffset::I32(i) => *i,
889        }
890    }
891}
892
893/// SFrame FRE
894///
895/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#SFrame-Frame-Row-Entries>
896#[derive(Debug, Clone)]
897pub struct SFrameFRE {
898    /// Start address (in offset form) of the function
899    pub start_address: SFrameFREStartAddress,
900    /// FRE info
901    pub info: SFrameFREInfo,
902    /// Stack offsets to access CFA, FP and RA
903    pub stack_offsets: Vec<SFrameFREStackOffset>,
904}
905
906/// Flex FDE Control Data Word
907///
908/// For flexible FDE types (SFRAME_FDE_TYPE_FLEX), the variable-length bytes
909/// trailing an SFrame FRE are interpreted as pairs of Control Data and Offset
910/// Data for each tracked entity (CFA, RA, FP).
911///
912/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#Flexible-FDE-Type-Interpretation>
913#[derive(Debug, Clone, Copy)]
914pub struct SFrameFlexControlData {
915    /// The raw control data value
916    value: i32,
917}
918
919impl SFrameFlexControlData {
920    /// Create a new Control Data from raw value
921    pub fn new(value: i32) -> Self {
922        Self { value }
923    }
924
925    /// Check if this is a padding data word (value is 0)
926    ///
927    /// A value of 0 indicates that no further data words follow for the
928    /// tracked entity, meaning the fixed offsets apply or the entity is absent.
929    pub fn is_padding(&self) -> bool {
930        self.value == 0
931    }
932
933    /// Register-based Location Rule
934    ///
935    /// If true, the base is a DWARF register (encoded in regnum bits).
936    /// If false, the base is the CFA (used for RA/FP recovery).
937    pub fn reg_p(&self) -> bool {
938        (self.value & 0b1) != 0
939    }
940
941    /// Dereference Flag
942    ///
943    /// If true, the location of the value is the address (Base + Offset):
944    ///   value = *(Base + Offset)
945    /// If false, the value is (Base + Offset).
946    pub fn deref_p(&self) -> bool {
947        (self.value & 0b10) != 0
948    }
949
950    /// DWARF register number used as the base
951    ///
952    /// Effective only if reg_p is true.
953    pub fn regnum(&self) -> i32 {
954        self.value >> 3
955    }
956}
957
958/// Flex FDE Recovery Rule
959///
960/// Contains the control and offset data for recovering a tracked entity
961/// (CFA, RA, or FP) in a flexible FDE.
962#[derive(Debug, Clone, Copy)]
963pub struct SFrameFlexRecoveryRule {
964    /// Control data word
965    pub control: SFrameFlexControlData,
966    /// Offset data (signed offset to be added to the base)
967    pub offset: i32,
968}
969
970impl SFrameFRE {
971    /// Get CFA offset against base reg (for Default FDE type)
972    pub fn get_cfa_offset(&self, section: &SFrameSection<'_>) -> Option<i32> {
973        self.stack_offsets.first().map(|offset| {
974            let offset = offset.get();
975            match section.abi {
976                // On s390x, the CFA offset is encoded. Decode it.
977                SFrameABI::S390XBigEndian => s390x::cfa_offset_decode(offset),
978                _ => offset,
979            }
980        })
981    }
982
983    /// Get RA offset against CFA (for Default FDE type)
984    ///
985    /// For s390x, the returned value may represent:
986    /// - A stack slot offset if the LSB is 0
987    /// - A DWARF register number (encoded as (regnum << 1) | 1) if the LSB is 1
988    /// - SFRAME_FRE_RA_OFFSET_INVALID (0) if RA is not saved
989    pub fn get_ra_offset(&self, section: &SFrameSection<'_>) -> Option<i32> {
990        match section.abi {
991            // the second offset for aarch64
992            SFrameABI::AArch64BigEndian | SFrameABI::AArch64LittleEndian => {
993                self.stack_offsets.get(1).map(|offset| offset.get())
994            }
995            // always fixed for amd64
996            SFrameABI::AMD64LittleEndian => Some(section.cfa_fixed_ra_offset as i32),
997            // the second offset for s390x
998            SFrameABI::S390XBigEndian => self.stack_offsets.get(1).map(|offset| offset.get()),
999        }
1000    }
1001
1002    /// Get FP offset against CFA (for Default FDE type)
1003    ///
1004    /// For s390x, the returned value may represent:
1005    /// - A stack slot offset if the LSB is 0
1006    /// - A DWARF register number (encoded as (regnum << 1) | 1) if the LSB is 1
1007    pub fn get_fp_offset(&self, section: &SFrameSection<'_>) -> Option<i32> {
1008        match section.abi {
1009            // the third offset for aarch64
1010            SFrameABI::AArch64BigEndian | SFrameABI::AArch64LittleEndian => {
1011                self.stack_offsets.get(2).map(|offset| offset.get())
1012            }
1013            // the second offset for amd64
1014            SFrameABI::AMD64LittleEndian => self.stack_offsets.get(1).map(|offset| offset.get()),
1015            // the third offset for s390x
1016            SFrameABI::S390XBigEndian => self.stack_offsets.get(2).map(|offset| offset.get()),
1017        }
1018    }
1019
1020    /// Get Flex FDE recovery rules
1021    ///
1022    /// For flexible FDE types, returns the recovery rules for CFA, RA, and FP.
1023    /// Each rule consists of a Control Data word and an Offset Data word.
1024    ///
1025    /// Returns None if the data is invalid.
1026    pub fn get_flex_recovery_rules(&self) -> Option<SFrameFlexRecoveryRules> {
1027        let data_words: Vec<i32> = self.stack_offsets.iter().map(|o| o.get()).collect();
1028
1029        // Parse CFA recovery rule (always present)
1030        let cfa_control = SFrameFlexControlData::new(*data_words.get(0)?);
1031        let cfa_offset = *data_words.get(1)?;
1032        let cfa = SFrameFlexRecoveryRule {
1033            control: cfa_control,
1034            offset: cfa_offset,
1035        };
1036
1037        // Parse RA recovery rule (if present)
1038        let mut offset = 2;
1039        let mut ra = None;
1040        if data_words.len() >= 3 {
1041            let ra_control = SFrameFlexControlData::new(*data_words.get(2)?);
1042            if ra_control.is_padding() {
1043                ra = Some(SFrameFlexRecoveryRule {
1044                    control: ra_control,
1045                    offset: 0,
1046                });
1047                offset += 1;
1048            } else {
1049                ra = Some(SFrameFlexRecoveryRule {
1050                    control: ra_control,
1051                    offset: *data_words.get(3)?,
1052                });
1053                offset += 2;
1054            }
1055        }
1056
1057        // Parse FP recovery rule (if present)
1058        let mut fp = None;
1059        if data_words.len() >= offset + 1 {
1060            let fp_control = SFrameFlexControlData::new(*data_words.get(offset)?);
1061            if fp_control.is_padding() {
1062                fp = Some(SFrameFlexRecoveryRule {
1063                    control: fp_control,
1064                    offset: 0,
1065                });
1066            } else {
1067                fp = Some(SFrameFlexRecoveryRule {
1068                    control: fp_control,
1069                    offset: *data_words.get(offset + 1)?,
1070                });
1071            }
1072        }
1073
1074        Some(SFrameFlexRecoveryRules { cfa, ra, fp })
1075    }
1076}
1077
1078/// Flex FDE Recovery Rules for all tracked entities
1079#[derive(Debug, Clone)]
1080pub struct SFrameFlexRecoveryRules {
1081    /// CFA recovery rule (always present)
1082    pub cfa: SFrameFlexRecoveryRule,
1083    /// RA recovery rule (None if padding)
1084    pub ra: Option<SFrameFlexRecoveryRule>,
1085    /// FP recovery rule (None if padding)
1086    pub fp: Option<SFrameFlexRecoveryRule>,
1087}
1088
1089/// SFrame FRE Info Byte
1090///
1091/// Ref: <https://sourceware.org/binutils/docs-2.46/sframe-spec.html#The-SFrame-FRE-Info-Word>
1092#[derive(Debug, Clone, Copy)]
1093#[repr(transparent)]
1094pub struct SFrameFREInfo(u8);
1095
1096impl SFrameFREInfo {
1097    /// Indicate whether the return address is mangled with any authorization
1098    /// bits (signed RA).
1099    pub fn get_mangled_ra_p(&self) -> bool {
1100        (self.0 >> 7) & 0b1 == 1
1101    }
1102
1103    /// Size of data word in bytes.
1104    pub fn get_dataword_size(&self) -> SFrameResult<usize> {
1105        match (self.0 >> 5) & 0b11 {
1106            // SFRAME_FRE_DATAWORD_1B
1107            0x0 => Ok(1),
1108            // SFRAME_FRE_DATAWORD_2B
1109            0x1 => Ok(2),
1110            // SFRAME_FRE_DATAWORD_4B
1111            0x2 => Ok(4),
1112            _ => Err(SFrameError::UnsupportedFREDataWordSize),
1113        }
1114    }
1115
1116    /// The number of data word in the FRE
1117    pub fn get_dataword_count(&self) -> u8 {
1118        (self.0 >> 1) & 0b1111
1119    }
1120
1121    /// Distinguish between SP or FP based CFA recovery.
1122    pub fn get_cfa_base_reg_id(&self) -> u8 {
1123        self.0 & 0b1
1124    }
1125}
1126
1127/// Iterator for SFrame FRE
1128pub struct SFrameFREIterator<'a> {
1129    fde: &'a SFrameFDE,
1130    section: &'a SFrameSection<'a>,
1131    index: u16,
1132    offset: usize,
1133}
1134
1135impl<'a> FallibleIterator for SFrameFREIterator<'a> {
1136    type Item = SFrameFRE;
1137    type Error = SFrameError;
1138
1139    fn next(&mut self) -> SFrameResult<Option<SFrameFRE>> {
1140        if self.index >= self.fde.func_num_fres {
1141            return Ok(None);
1142        }
1143
1144        let fre_type = self.fde.func_info.get_fre_type()?;
1145        let entry_size = match fre_type {
1146            SFrameFREType::Addr1 => 1 + 1,
1147            SFrameFREType::Addr2 => 2 + 1,
1148            SFrameFREType::Addr4 => 4 + 1,
1149        } as usize;
1150        let offset = self.offset;
1151        if offset + entry_size > self.section.data.len() {
1152            return Err(SFrameError::UnexpectedEndOfData);
1153        }
1154
1155        let (start_address, info) = match self.fde.func_info.get_fre_type()? {
1156            SFrameFREType::Addr1 => (
1157                SFrameFREStartAddress::U8(self.section.data[offset]),
1158                SFrameFREInfo(self.section.data[offset + 1]),
1159            ),
1160            SFrameFREType::Addr2 => (
1161                SFrameFREStartAddress::U16(read_binary!(
1162                    self.section.data,
1163                    self.section.little_endian,
1164                    u16,
1165                    offset
1166                )),
1167                SFrameFREInfo(self.section.data[offset + 2]),
1168            ),
1169            SFrameFREType::Addr4 => (
1170                SFrameFREStartAddress::U32(read_binary!(
1171                    self.section.data,
1172                    self.section.little_endian,
1173                    u32,
1174                    offset
1175                )),
1176                SFrameFREInfo(self.section.data[offset + 4]),
1177            ),
1178        };
1179
1180        let offset_size = info.get_dataword_size()?;
1181        let offset_count = info.get_dataword_count() as usize;
1182        let offset_total_size = offset_size * offset_count;
1183        if offset + entry_size + offset_total_size > self.section.data.len() {
1184            return Err(SFrameError::UnexpectedEndOfData);
1185        }
1186
1187        let mut stack_offsets = vec![];
1188        for i in 0..offset_count {
1189            match offset_size {
1190                1 => stack_offsets.push(SFrameFREStackOffset::I8(
1191                    self.section.data[offset + entry_size + i * offset_size] as i8,
1192                )),
1193                2 => stack_offsets.push(SFrameFREStackOffset::I16(read_binary!(
1194                    self.section.data,
1195                    self.section.little_endian,
1196                    i16,
1197                    offset + entry_size + i * offset_size
1198                ))),
1199                4 => stack_offsets.push(SFrameFREStackOffset::I32(read_binary!(
1200                    self.section.data,
1201                    self.section.little_endian,
1202                    i32,
1203                    offset + entry_size + i * offset_size
1204                ))),
1205                _ => unreachable!(),
1206            }
1207        }
1208
1209        self.offset += entry_size + offset_total_size;
1210        self.index += 1;
1211
1212        Ok(Some(SFrameFRE {
1213            start_address,
1214            info,
1215            stack_offsets,
1216        }))
1217    }
1218}
1219
1220/// Iterator for SFrame FDE
1221pub struct SFrameFDEIterator<'a> {
1222    section: &'a SFrameSection<'a>,
1223    index: u32,
1224}
1225
1226impl<'a> FallibleIterator for SFrameFDEIterator<'a> {
1227    type Item = SFrameFDE;
1228    type Error = SFrameError;
1229
1230    fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
1231        let res = self.section.get_fde(self.index);
1232        if let Ok(Some(_)) = res {
1233            self.index += 1;
1234        }
1235        res
1236    }
1237}