Skip to main content

riscv_etrace/binary/
elf.rs

1// Copyright (C) 2025, 2026 FZI Forschungszentrum Informatik
2// SPDX-License-Identifier: Apache-2.0
3//! ELF related utilities
4
5use core::borrow::Borrow;
6use core::fmt;
7
8use elf::ElfBytes;
9use elf::endian::EndianParse;
10
11use crate::instruction::{Instruction, base, decode, info};
12
13use super::{Binary, error};
14
15/// Static ELF [`Binary`]
16///
17/// This [`Binary`] retrieves [`Instruction`]s from executable `LOAD` segments
18/// found in [`ElfBytes`] based on virtual address mapping. Neither
19/// decompression nor dynamic linking are supported.
20#[derive(Copy, Clone)]
21pub struct Elf<'d, E, P, D = base::Set>
22where
23    E: Borrow<ElfBytes<'d, P>>,
24    P: EndianParse,
25{
26    elf: E,
27    last_segment: (u64, &'d [u8]),
28    base: D,
29    phantom: core::marker::PhantomData<P>,
30}
31
32impl<'d, E, P, D> Elf<'d, E, P, D>
33where
34    E: Borrow<ElfBytes<'d, P>>,
35    P: EndianParse,
36    D: decode::MakeDecode,
37{
38    /// Create a new ELF [`Binary`]
39    pub fn new(elf: E) -> Result<Self, Error> {
40        use elf::abi;
41
42        let hdr = &elf.borrow().ehdr;
43        if hdr.e_machine != abi::EM_RISCV {
44            Err(Error::UnsupportedArchitecture)
45        } else if !hdr.endianness.is_little() {
46            Err(Error::UnsupportedEndianess)
47        } else {
48            let base = match hdr.class {
49                elf::file::Class::ELF32 => decode::MakeDecode::rv32i_full(),
50                elf::file::Class::ELF64 => decode::MakeDecode::rv64i_full(),
51            };
52
53            Ok(Self {
54                elf,
55                last_segment: (u64::MAX, &[]),
56                base,
57                phantom: Default::default(),
58            })
59        }
60    }
61
62    /// Retrieve the inner [`ElfBytes`]
63    pub fn inner(&self) -> &ElfBytes<'d, P> {
64        self.elf.borrow()
65    }
66
67    /// Retrieve the [`base::Set`] of the instruction in this ELF
68    pub fn base_set(&self) -> &D {
69        &self.base
70    }
71}
72
73impl<'d, E, P, D, I> Binary<I> for Elf<'d, E, P, D>
74where
75    E: Borrow<ElfBytes<'d, P>>,
76    P: EndianParse,
77    I: info::Info,
78    D: decode::Decode<I>,
79{
80    type Error = Error;
81
82    fn get_insn(&mut self, address: u64) -> Result<Instruction<I>, Self::Error> {
83        // Iterator over all relevant segments' offset and data
84        let segments = self
85            .elf
86            .borrow()
87            .segments()
88            .into_iter()
89            .flat_map(|s| s.iter())
90            .filter(|s| s.p_type == elf::abi::PT_LOAD && s.p_flags & elf::abi::PF_X != 0)
91            .map(|s| {
92                self.elf
93                    .borrow()
94                    .segment_data(&s)
95                    .map(|d| (s.p_vaddr, d))
96                    .map_err(Error::CouldNotRetrieveData)
97            });
98
99        // Find the relevant instruction data, starting with the last segment
100        // used since that's most likely to be the relevant one. We accept that
101        // we may fail if we could not retrieve data for a segment known to not
102        // contain the address.
103        let (insn_data, segment) = core::iter::once(Ok(self.last_segment))
104            .chain(segments)
105            .map(|s| {
106                let (base, data) = s?;
107                let Some(offset) = address.checked_sub(base) else {
108                    // `address` < segment start
109                    return Ok(None);
110                };
111                let offset = offset.try_into().map_err(Error::ExceededHostUSize)?;
112                let res = data
113                    .split_at_checked(offset)
114                    .filter(|(_, insn_data)| !insn_data.is_empty())
115                    .map(|(_, insn_data)| (insn_data, (base, data)));
116                Ok(res)
117            })
118            .find_map(Result::transpose)
119            .ok_or(Error::NoSegmentFound)??;
120
121        self.last_segment = segment;
122        Instruction::extract(insn_data, &self.base)
123            .map(|(i, _)| i)
124            .ok_or(Error::InvalidInstruction)
125    }
126}
127
128/// ELF specific error type
129#[derive(Debug)]
130pub enum Error {
131    /// No segment was found containing the address
132    NoSegmentFound,
133    /// The data for a segment could not be retrieved
134    CouldNotRetrieveData(elf::parse::ParseError),
135    /// Could not use an address or offset because it is too big for the host
136    ExceededHostUSize(core::num::TryFromIntError),
137    /// An [`Instruction`] could not be extracted from the data
138    InvalidInstruction,
139    /// The ELF file is not an RV32 ELF file
140    UnsupportedArchitecture,
141    /// The ELF file is not little endian
142    UnsupportedEndianess,
143}
144
145impl error::Miss for Error {
146    fn miss(_: u64) -> Self {
147        Self::NoSegmentFound
148    }
149}
150
151impl error::MaybeMiss for Error {
152    fn is_miss(&self) -> bool {
153        matches!(self, Self::NoSegmentFound)
154    }
155}
156
157impl core::error::Error for Error {
158    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
159        match self {
160            Self::CouldNotRetrieveData(e) => Some(e),
161            Self::ExceededHostUSize(e) => Some(e),
162            _ => None,
163        }
164    }
165}
166
167impl fmt::Display for Error {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        match self {
170            Self::NoSegmentFound => write!(f, "Could not find segment for address"),
171            Self::CouldNotRetrieveData(_) => write!(f, "Could not retrieve data for segment"),
172            Self::ExceededHostUSize(_) => write!(
173                f,
174                "An offset exceeds what can be represented with host native addresses"
175            ),
176            Self::InvalidInstruction => write!(f, "No valid instruction at address"),
177            Self::UnsupportedArchitecture => write!(f, "The target architecture is not supported"),
178            Self::UnsupportedEndianess => write!(f, "The target is not little endian"),
179        }
180    }
181}
182
183impl PartialEq for Error {
184    fn eq(&self, other: &Self) -> bool {
185        match (self, other) {
186            (Self::NoSegmentFound, Self::NoSegmentFound) => true,
187            (Self::CouldNotRetrieveData(_), Self::CouldNotRetrieveData(_)) => true,
188            (Self::ExceededHostUSize(l), Self::ExceededHostUSize(r)) => l == r,
189            (Self::InvalidInstruction, Self::InvalidInstruction) => true,
190            (Self::UnsupportedArchitecture, Self::UnsupportedArchitecture) => true,
191            (Self::UnsupportedEndianess, Self::UnsupportedEndianess) => true,
192            _ => false,
193        }
194    }
195}