framehop/
dwarf.rs

1use core::marker::PhantomData;
2
3use alloc::vec::Vec;
4use gimli::{
5    CfaRule, CieOrFde, DebugFrame, EhFrame, EhFrameHdr, Encoding, EndianSlice, Evaluation,
6    EvaluationResult, EvaluationStorage, Expression, LittleEndian, Location, ParsedEhFrameHdr,
7    Reader, ReaderOffset, Register, RegisterRule, UnwindContext, UnwindContextStorage,
8    UnwindOffset, UnwindSection, UnwindTableRow, Value,
9};
10
11pub(crate) use gimli::BaseAddresses;
12
13use crate::{arch::Arch, unwind_result::UnwindResult, ModuleSectionInfo};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum DwarfUnwinderError {
17    FdeFromOffsetFailed(gimli::Error),
18    UnwindInfoForAddressFailed(gimli::Error),
19    StackPointerMovedBackwards,
20    DidNotAdvance,
21    CouldNotRecoverCfa,
22    CouldNotRecoverReturnAddress,
23    CouldNotRecoverFramePointer,
24}
25
26impl core::fmt::Display for DwarfUnwinderError {
27    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28        match self {
29            Self::FdeFromOffsetFailed(err) => {
30                write!(f, "Could not get the FDE for the supplied offset: {err}")
31            }
32            Self::UnwindInfoForAddressFailed(err) => write!(
33                f,
34                "Could not find DWARF unwind info for the requested address: {err}"
35            ),
36            Self::StackPointerMovedBackwards => write!(f, "Stack pointer moved backwards"),
37            Self::DidNotAdvance => write!(f, "Did not advance"),
38            Self::CouldNotRecoverCfa => write!(f, "Could not recover the CFA"),
39            Self::CouldNotRecoverReturnAddress => write!(f, "Could not recover the return address"),
40            Self::CouldNotRecoverFramePointer => write!(f, "Could not recover the frame pointer"),
41        }
42    }
43}
44
45#[cfg(feature = "std")]
46impl std::error::Error for DwarfUnwinderError {
47    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
48        match self {
49            Self::FdeFromOffsetFailed(e) => Some(e),
50            Self::UnwindInfoForAddressFailed(e) => Some(e),
51            _ => None,
52        }
53    }
54}
55
56#[derive(Clone, Debug)]
57pub enum ConversionError {
58    CfaIsExpression,
59    CfaIsOffsetFromUnknownRegister,
60    ReturnAddressRuleWithUnexpectedOffset,
61    ReturnAddressRuleWasWeird,
62    SpOffsetDoesNotFit,
63    RegisterNotStoredRelativeToCfa,
64    RestoringFpButNotLr,
65    LrStorageOffsetDoesNotFit,
66    FpStorageOffsetDoesNotFit,
67    SpOffsetFromFpDoesNotFit,
68    FramePointerRuleDoesNotRestoreLr,
69    FramePointerRuleDoesNotRestoreFp,
70    FramePointerRuleDoesNotRestoreBp,
71    FramePointerRuleHasStrangeBpOffset,
72}
73
74pub trait DwarfUnwinding: Arch {
75    fn unwind_frame<F, R, UCS, ES>(
76        section: &impl UnwindSection<R>,
77        unwind_info: &UnwindTableRow<R::Offset, UCS>,
78        encoding: Encoding,
79        regs: &mut Self::UnwindRegs,
80        is_first_frame: bool,
81        read_stack: &mut F,
82    ) -> Result<UnwindResult<Self::UnwindRule>, DwarfUnwinderError>
83    where
84        F: FnMut(u64) -> Result<u64, ()>,
85        R: Reader,
86        UCS: UnwindContextStorage<R::Offset>,
87        ES: EvaluationStorage<R>;
88
89    fn rule_if_uncovered_by_fde() -> Self::UnwindRule;
90}
91
92pub enum UnwindSectionType {
93    EhFrame,
94    DebugFrame,
95}
96
97pub struct DwarfUnwinder<'a, R, A, UCS>
98where
99    R: Reader,
100    A: DwarfUnwinding,
101    UCS: UnwindContextStorage<R::Offset>,
102{
103    unwind_section_data: R,
104    unwind_section_type: UnwindSectionType,
105    eh_frame_hdr: Option<ParsedEhFrameHdr<EndianSlice<'a, R::Endian>>>,
106    unwind_context: &'a mut UnwindContext<R::Offset, UCS>,
107    base_svma: u64,
108    bases: BaseAddresses,
109    _arch: PhantomData<A>,
110}
111
112impl<'a, R, A, UCS> DwarfUnwinder<'a, R, A, UCS>
113where
114    R: Reader,
115    A: DwarfUnwinding,
116    UCS: UnwindContextStorage<R::Offset>,
117{
118    pub fn new(
119        unwind_section_data: R,
120        unwind_section_type: UnwindSectionType,
121        eh_frame_hdr_data: Option<&'a [u8]>,
122        unwind_context: &'a mut UnwindContext<R::Offset, UCS>,
123        bases: BaseAddresses,
124        base_svma: u64,
125    ) -> Self {
126        let eh_frame_hdr = match eh_frame_hdr_data {
127            Some(eh_frame_hdr_data) => {
128                let hdr = EhFrameHdr::new(eh_frame_hdr_data, unwind_section_data.endian());
129                hdr.parse(&bases, 8).ok()
130            }
131            None => None,
132        };
133        Self {
134            unwind_section_data,
135            unwind_section_type,
136            eh_frame_hdr,
137            unwind_context,
138            bases,
139            base_svma,
140            _arch: PhantomData,
141        }
142    }
143
144    pub fn get_fde_offset_for_relative_address(&self, rel_lookup_address: u32) -> Option<u32> {
145        let lookup_svma = self.base_svma + rel_lookup_address as u64;
146        let eh_frame_hdr = self.eh_frame_hdr.as_ref()?;
147        let table = eh_frame_hdr.table()?;
148        let fde_ptr = table.lookup(lookup_svma, &self.bases).ok()?;
149        let fde_offset = table.pointer_to_offset(fde_ptr).ok()?;
150        fde_offset.0.into_u64().try_into().ok()
151    }
152
153    pub fn unwind_frame_with_fde<F, ES>(
154        &mut self,
155        regs: &mut A::UnwindRegs,
156        is_first_frame: bool,
157        rel_lookup_address: u32,
158        fde_offset: u32,
159        read_stack: &mut F,
160    ) -> Result<UnwindResult<A::UnwindRule>, DwarfUnwinderError>
161    where
162        F: FnMut(u64) -> Result<u64, ()>,
163        ES: EvaluationStorage<R>,
164    {
165        let lookup_svma = self.base_svma + rel_lookup_address as u64;
166        let unwind_section_data = self.unwind_section_data.clone();
167        match self.unwind_section_type {
168            UnwindSectionType::EhFrame => {
169                let mut eh_frame = EhFrame::from(unwind_section_data);
170                eh_frame.set_address_size(8);
171                let unwind_info = self.unwind_info_for_fde(&eh_frame, lookup_svma, fde_offset);
172                if let Err(DwarfUnwinderError::UnwindInfoForAddressFailed(_)) = unwind_info {
173                    return Ok(UnwindResult::ExecRule(A::rule_if_uncovered_by_fde()));
174                }
175                let (unwind_info, encoding) = unwind_info?;
176                A::unwind_frame::<F, R, UCS, ES>(
177                    &eh_frame,
178                    unwind_info,
179                    encoding,
180                    regs,
181                    is_first_frame,
182                    read_stack,
183                )
184            }
185            UnwindSectionType::DebugFrame => {
186                let mut debug_frame = DebugFrame::from(unwind_section_data);
187                debug_frame.set_address_size(8);
188                let unwind_info = self.unwind_info_for_fde(&debug_frame, lookup_svma, fde_offset);
189                if let Err(DwarfUnwinderError::UnwindInfoForAddressFailed(_)) = unwind_info {
190                    return Ok(UnwindResult::ExecRule(A::rule_if_uncovered_by_fde()));
191                }
192                let (unwind_info, encoding) = unwind_info?;
193                A::unwind_frame::<F, R, UCS, ES>(
194                    &debug_frame,
195                    unwind_info,
196                    encoding,
197                    regs,
198                    is_first_frame,
199                    read_stack,
200                )
201            }
202        }
203    }
204
205    fn unwind_info_for_fde<US: UnwindSection<R>>(
206        &mut self,
207        unwind_section: &US,
208        lookup_svma: u64,
209        fde_offset: u32,
210    ) -> Result<(&UnwindTableRow<R::Offset, UCS>, Encoding), DwarfUnwinderError> {
211        let fde = unwind_section.fde_from_offset(
212            &self.bases,
213            US::Offset::from(R::Offset::from_u32(fde_offset)),
214            US::cie_from_offset,
215        );
216        let fde = fde.map_err(DwarfUnwinderError::FdeFromOffsetFailed)?;
217        let encoding = fde.cie().encoding();
218        let unwind_info: &UnwindTableRow<_, _> = fde
219            .unwind_info_for_address(
220                unwind_section,
221                &self.bases,
222                self.unwind_context,
223                lookup_svma,
224            )
225            .map_err(DwarfUnwinderError::UnwindInfoForAddressFailed)?;
226        Ok((unwind_info, encoding))
227    }
228}
229
230pub(crate) fn base_addresses_for_sections<D>(
231    section_info: &mut impl ModuleSectionInfo<D>,
232) -> BaseAddresses {
233    let mut start_addr = |names: &[&[u8]]| -> u64 {
234        names
235            .iter()
236            .find_map(|name| section_info.section_svma_range(name))
237            .map(|r| r.start)
238            .unwrap_or_default()
239    };
240    BaseAddresses::default()
241        .set_eh_frame(start_addr(&[b"__eh_frame", b".eh_frame"]))
242        .set_eh_frame_hdr(start_addr(&[b"__eh_frame_hdr", b".eh_frame_hdr"]))
243        .set_text(start_addr(&[b"__text", b".text"]))
244        .set_got(start_addr(&[b"__got", b".got"]))
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum DwarfCfiIndexError {
249    Gimli(gimli::Error),
250    CouldNotSubtractBaseAddress,
251    RelativeAddressTooBig,
252    FdeOffsetTooBig,
253}
254
255impl core::fmt::Display for DwarfCfiIndexError {
256    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
257        match self {
258            Self::Gimli(e) => write!(f, "EhFrame processing failed: {e}"),
259            Self::CouldNotSubtractBaseAddress => {
260                write!(f, "Could not subtract base address to create relative pc")
261            }
262            Self::RelativeAddressTooBig => write!(f, "Relative address did not fit into u32"),
263            Self::FdeOffsetTooBig => write!(f, "FDE offset did not fit into u32"),
264        }
265    }
266}
267
268impl From<gimli::Error> for DwarfCfiIndexError {
269    fn from(e: gimli::Error) -> Self {
270        Self::Gimli(e)
271    }
272}
273
274#[cfg(feature = "std")]
275impl std::error::Error for DwarfCfiIndexError {
276    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
277        match self {
278            Self::Gimli(e) => Some(e),
279            _ => None,
280        }
281    }
282}
283
284/// A binary search table for eh_frame FDEs. We generate this whenever a module
285/// without eh_frame_hdr is added.
286pub struct DwarfCfiIndex {
287    /// Contains the initial address for every FDE, relative to the base address.
288    /// This vector is sorted so that it can be used for binary search.
289    /// It has the same length as `fde_offsets`.
290    sorted_fde_pc_starts: Vec<u32>,
291    /// Contains the FDE offset for every FDE. The FDE at offset `fde_offsets[i]`
292    /// has a PC range which starts at `sorted_fde_pc_starts[i]`.
293    fde_offsets: Vec<u32>,
294}
295
296impl DwarfCfiIndex {
297    pub fn try_new<R, US>(
298        unwind_section: US,
299        bases: BaseAddresses,
300        base_svma: u64,
301    ) -> Result<Self, DwarfCfiIndexError>
302    where
303        R: Reader,
304        R::Offset: TryInto<u32>,
305        US: UnwindSection<R>,
306    {
307        let mut fde_pc_and_offset = Vec::new();
308
309        let mut cur_cie = None;
310        let mut entries_iter = unwind_section.entries(&bases);
311        while let Some(entry) = entries_iter.next()? {
312            let fde = match entry {
313                CieOrFde::Cie(cie) => {
314                    cur_cie = Some(cie);
315                    continue;
316                }
317                CieOrFde::Fde(partial_fde) => {
318                    partial_fde.parse(|unwind_section, bases, cie_offset| {
319                        if let Some(cie) = &cur_cie {
320                            if cie.offset()
321                                == <US::Offset as UnwindOffset<R::Offset>>::into(cie_offset)
322                            {
323                                return Ok(cie.clone());
324                            }
325                        }
326                        let cie = unwind_section.cie_from_offset(bases, cie_offset);
327                        if let Ok(cie) = &cie {
328                            cur_cie = Some(cie.clone());
329                        }
330                        cie
331                    })?
332                }
333            };
334            let pc = fde.initial_address();
335            let relative_pc = pc
336                .checked_sub(base_svma)
337                .ok_or(DwarfCfiIndexError::CouldNotSubtractBaseAddress)?;
338            let relative_pc = u32::try_from(relative_pc)
339                .map_err(|_| DwarfCfiIndexError::RelativeAddressTooBig)?;
340            let fde_offset = <R::Offset as TryInto<u32>>::try_into(fde.offset())
341                .map_err(|_| DwarfCfiIndexError::FdeOffsetTooBig)?;
342            fde_pc_and_offset.push((relative_pc, fde_offset));
343        }
344        fde_pc_and_offset.sort_by_key(|(pc, _)| *pc);
345        let sorted_fde_pc_starts = fde_pc_and_offset.iter().map(|(pc, _)| *pc).collect();
346        let fde_offsets = fde_pc_and_offset.into_iter().map(|(_, fde)| fde).collect();
347        Ok(Self {
348            sorted_fde_pc_starts,
349            fde_offsets,
350        })
351    }
352
353    pub fn try_new_eh_frame<D>(
354        eh_frame_data: &[u8],
355        section_info: &mut impl ModuleSectionInfo<D>,
356    ) -> Result<Self, DwarfCfiIndexError> {
357        let bases = base_addresses_for_sections(section_info);
358        let mut eh_frame = EhFrame::from(EndianSlice::new(eh_frame_data, LittleEndian));
359        eh_frame.set_address_size(8);
360
361        Self::try_new(eh_frame, bases, section_info.base_svma())
362    }
363
364    pub fn try_new_debug_frame<D>(
365        debug_frame_data: &[u8],
366        section_info: &mut impl ModuleSectionInfo<D>,
367    ) -> Result<Self, DwarfCfiIndexError> {
368        let bases = base_addresses_for_sections(section_info);
369        let mut debug_frame = DebugFrame::from(EndianSlice::new(debug_frame_data, LittleEndian));
370        debug_frame.set_address_size(8);
371
372        Self::try_new(debug_frame, bases, section_info.base_svma())
373    }
374
375    pub fn fde_offset_for_relative_address(&self, rel_lookup_address: u32) -> Option<u32> {
376        let i = match self.sorted_fde_pc_starts.binary_search(&rel_lookup_address) {
377            Err(0) => return None,
378            Ok(i) => i,
379            Err(i) => i - 1,
380        };
381        Some(self.fde_offsets[i])
382    }
383}
384
385pub trait DwarfUnwindRegs {
386    fn get(&self, register: Register) -> Option<u64>;
387}
388
389pub fn eval_cfa_rule<R: Reader, UR: DwarfUnwindRegs, S: EvaluationStorage<R>>(
390    section: &impl UnwindSection<R>,
391    rule: &CfaRule<R::Offset>,
392    encoding: Encoding,
393    regs: &UR,
394) -> Option<u64> {
395    match rule {
396        CfaRule::RegisterAndOffset { register, offset } => {
397            let val = regs.get(*register)?;
398            u64::try_from(i64::try_from(val).ok()?.checked_add(*offset)?).ok()
399        }
400        CfaRule::Expression(expr) => {
401            let expr = expr.get(section).ok()?;
402            eval_expr::<R, UR, S>(expr, encoding, regs)
403        }
404    }
405}
406
407fn eval_expr<R: Reader, UR: DwarfUnwindRegs, S: EvaluationStorage<R>>(
408    expr: Expression<R>,
409    encoding: Encoding,
410    regs: &UR,
411) -> Option<u64> {
412    let mut eval = Evaluation::<R, S>::new_in(expr.0, encoding);
413    let mut result = eval.evaluate().ok()?;
414    loop {
415        match result {
416            EvaluationResult::Complete => break,
417            EvaluationResult::RequiresRegister { register, .. } => {
418                let value = regs.get(register)?;
419                result = eval.resume_with_register(Value::Generic(value as _)).ok()?;
420            }
421            _ => return None,
422        }
423    }
424    let x = &eval.as_result().last()?.location;
425    if let Location::Address { address } = x {
426        Some(*address)
427    } else {
428        None
429    }
430}
431
432pub fn eval_register_rule<R, F, UR, S>(
433    section: &impl UnwindSection<R>,
434    rule: RegisterRule<R::Offset>,
435    cfa: u64,
436    encoding: Encoding,
437    val: u64,
438    regs: &UR,
439    read_stack: &mut F,
440) -> Option<u64>
441where
442    R: Reader,
443    F: FnMut(u64) -> Result<u64, ()>,
444    UR: DwarfUnwindRegs,
445    S: EvaluationStorage<R>,
446{
447    match rule {
448        RegisterRule::Undefined => None,
449        RegisterRule::SameValue => Some(val),
450        RegisterRule::Offset(offset) => {
451            let cfa_plus_offset =
452                u64::try_from(i64::try_from(cfa).ok()?.checked_add(offset)?).ok()?;
453            read_stack(cfa_plus_offset).ok()
454        }
455        RegisterRule::ValOffset(offset) => {
456            u64::try_from(i64::try_from(cfa).ok()?.checked_add(offset)?).ok()
457        }
458        RegisterRule::Register(register) => regs.get(register),
459        RegisterRule::Expression(expr) => {
460            let expr = expr.get(section).ok()?;
461            let val = eval_expr::<R, UR, S>(expr, encoding, regs)?;
462            read_stack(val).ok()
463        }
464        RegisterRule::ValExpression(expr) => {
465            let expr = expr.get(section).ok()?;
466            eval_expr::<R, UR, S>(expr, encoding, regs)
467        }
468        RegisterRule::Architectural => {
469            // Unimplemented
470            // TODO: Find out what the architectural rules for x86_64 and for aarch64 are, if any.
471            None
472        }
473        _ => None,
474    }
475}