Skip to main content

riscv_etrace/
binary.rs

1// Copyright (C) 2025, 2026 FZI Forschungszentrum Informatik
2// SPDX-License-Identifier: Apache-2.0
3//! Binaries containing [`Instruction`]s
4//!
5//! Tracing requires knowledge about the program being traced. This module
6//! defines the [`Binary`] trait used by the [`Tracer`][super::tracer::Tracer]
7//! for retrieving [`Instruction`]s as well as a number of types implementing
8//! the [`Binary`] trait. These include:
9//!
10//! * some [basic] [`Binary`]s such as adapters that may be created through
11//!   free fns such as [`from_fn`] and [`from_segment`] and allow defining
12//!   [`Binary`]s from a wide range of types supplying data,
13//! * [combinators] that allow tracing multiple programs or program parts such
14//!   as a firmware and an appliction,
15//! * modifiers such as [`Offset`] that are usually created through provided fns
16//!   of the [`Adaptable`] trait and
17//! * feature-dependent [`Binary`]s, e.g. for using [ELF][elf] files as
18//!   [`Binary`]s.
19//!
20//! # Combining [`Binary`]s
21//!
22//! Usually, [`Binary`]s used in [combinators] all need to agree on the
23//! [`Binary::Error`] type. Combinators such as [`Multi`] in particular also
24//! requires the [`Binary`]s themselves to be of the same type. If the `alloc`
25//! feature is enabled, the error type may be erased through the provided method
26//! [`Adaptable::boxed`]. The lifetime of the original [`Binary`] is preserved
27//! in the resulting [`boxed::Binary`]. This is relevant when using an
28//! [`elf::Elf`] or when...
29//!
30//! # Sharing [`Binary`]s between [`Tracer`] instances
31//!
32//! [`Binary`]s are intended for use by a single [`Tracer`] and can not be
33//! easily shared between instances. They may be mutated when fetching an
34//! [`Instruction`], e.g. for caching purposes. For example, a [`Multi`] will
35//! remember the [`Binary`] it chooses and pick that particular one first the
36//! next time.
37//!
38//! Sharing a [`Binary`] between [`Tracer`]s by placing them behind a mutex of
39//! some kind defeates the caching, incurs considerable overhead and is highly
40//! discouraged. Instead, users should consider sharing the data backing the
41//! [`Binary`]s. For example, a [`basic::Segment`] may be created from a shared
42//! buffer or [`Arc`][alloc::sync::Arc] and then cloned.
43//!
44//! Unfortunately, [`Adaptable::boxed`] returns a [`boxed::Binary`] which cannot
45//! be cloned for this purpose. [`Adaptable::boxer`] may be used instead for
46//! creating a clonable factory of (non-clonable) [`boxed::Binary`]s. It may be
47//! necessary to construct new [combinators] from those for each new [`Tracer`].
48//!
49//! # Example
50//!
51//! The following constructs a [`Binary`] from a firmware image and a bootrom
52//! and clones it for use by a second [`Tracer`] instance.
53//!
54//! ```
55//! use riscv_etrace::binary::{self, Adaptable, Multi};
56//! use riscv_etrace::instruction::base;
57//!
58//! # let bootrom = b"\x97\x02\x00\x00\x93\x85\x02\x02\x73\x25\x40\xf1\x83\xb2\x82\x01\x67\x80\x02\x00";
59//! # let firmware = b"\x97\x02\x00\x00\x93\x82\x02\x00\x73\xa0\x52\x30\x73\x00\x50\x10\x6f\xf0\xdf\xff";
60//! let binary1 = Multi::new([
61//!     binary::from_segment(bootrom, base::Set::Rv32I).with_offset(0x1000),
62//!     binary::from_segment(firmware, base::Set::Rv32I).with_offset(0x80000000),
63//! ]);
64//! let binary2 = binary1.clone();
65//! ```
66//!
67//! [`Tracer`]: [super::tracer::Tracer]
68
69pub mod basic;
70#[cfg(feature = "alloc")]
71pub mod boxed;
72pub mod combinators;
73#[cfg(feature = "elf")]
74pub mod elf;
75pub mod error;
76
77#[cfg(test)]
78mod tests;
79
80#[cfg(feature = "alloc")]
81use alloc::boxed::Box;
82
83pub use basic::{Empty, from_fn, from_map, from_segment, from_sorted_map};
84pub use combinators::Multi;
85
86use crate::instruction::{self, Instruction};
87
88use error::Miss;
89use instruction::info::Info;
90
91/// A binary of some sort that contains [`Instruction`]s
92///
93/// See the [module level][self] documentation for more details.
94pub trait Binary<I: Info> {
95    /// Error type returned by [`get_insn`][Self::get_insn]
96    type Error;
97
98    /// Retrieve the [`Instruction`] at the given address
99    fn get_insn(&mut self, address: u64) -> Result<Instruction<I>, Self::Error>;
100}
101
102/// [`Binary`] implementation for a tuple of two binaries
103///
104/// This impl allows combining [`Binary`]s as long as they agree on their error
105/// type. If the first [`Binary`] returns a "miss", the second one is consulted.
106impl<A, B, I, E> Binary<I> for (A, B)
107where
108    A: Binary<I, Error = E>,
109    B: Binary<I, Error = E>,
110    I: Info,
111    E: error::MaybeMiss,
112{
113    type Error = B::Error;
114
115    fn get_insn(&mut self, address: u64) -> Result<Instruction<I>, Self::Error> {
116        use error::MaybeMiss;
117
118        let res = self.0.get_insn(address);
119        if res.is_miss() {
120            self.1.get_insn(address)
121        } else {
122            res
123        }
124    }
125}
126
127impl<B, I> Binary<I> for Option<B>
128where
129    B: Binary<I>,
130    B::Error: Miss,
131    I: Info,
132{
133    type Error = B::Error;
134
135    fn get_insn(&mut self, address: u64) -> Result<Instruction<I>, Self::Error> {
136        self.as_mut()
137            .map(|b| b.get_insn(address))
138            .unwrap_or_else(|| Miss::miss(address))
139    }
140}
141
142#[cfg(feature = "alloc")]
143impl<B: Binary<I> + ?Sized, I: Info> Binary<I> for Box<B> {
144    type Error = B::Error;
145
146    fn get_insn(&mut self, address: u64) -> Result<Instruction<I>, Self::Error> {
147        B::get_insn(self.as_mut(), address)
148    }
149}
150
151#[cfg(feature = "either")]
152impl<L, R, I, E> Binary<I> for either::Either<L, R>
153where
154    L: Binary<I, Error = E>,
155    R: Binary<I, Error = E>,
156    I: Info,
157{
158    type Error = E;
159
160    fn get_insn(&mut self, address: u64) -> Result<Instruction<I>, Self::Error> {
161        either::for_both!(self, b => b.get_insn(address))
162    }
163}
164
165/// Helper trait that allows adapting a [`Binary`]
166pub trait Adaptable: Sized {
167    /// "Move" this binary by the given offset
168    ///
169    /// See [`Offset`] for more details.
170    fn with_offset(self, offset: u64) -> Offset<Self> {
171        Offset::new(self, offset)
172    }
173
174    /// Box this binary for dynamic dispatching
175    ///
176    /// This allows combining binaries of different types with (originally)
177    /// different [`Binary::Error`] types in [combinators].
178    #[cfg(feature = "alloc")]
179    fn boxed<'a, I>(self) -> boxed::Binary<'a, I>
180    where
181        I: Info,
182        Self: Binary<I>,
183        Self: Send + Sync + 'a,
184        Self::Error: error::MaybeMissError + 'static,
185    {
186        Box::new(boxed::BoxedError::new(self))
187    }
188
189    /// Transfer this binary into a factory producing boxed clones
190    ///
191    /// This fn returns a dynamically dispatched [`Fn`] which returns a
192    /// [`boxed::Binary`] as returned by [`boxed`][Self::boxed]. Unlike the
193    /// result, the wrapped [`Fn`] is [`Clone`] and can thus be used for
194    /// creating identical binaries for different tracers.
195    #[cfg(feature = "alloc")]
196    fn boxer<'a, I>(self) -> alloc::sync::Arc<dyn Fn() -> boxed::Binary<'a, I> + 'a>
197    where
198        I: Info,
199        Self: Binary<I>,
200        Self: Clone + Send + Sync + 'a,
201        Self::Error: error::MaybeMissError + 'static,
202    {
203        let boxed = boxed::BoxedError::new(self);
204        alloc::sync::Arc::new(move || Box::new(boxed.clone()))
205    }
206}
207
208impl<T> Adaptable for T {}
209
210/// [`Binary`] moved by a fixed offset
211///
212/// Accesses will be mapped by subtracting the fixed offset from the address.
213/// Accesses to addresses lower than the offset will result in a [miss][Miss].
214#[derive(Copy, Clone, Debug)]
215pub struct Offset<B> {
216    inner: B,
217    offset: u64,
218}
219
220impl<B> Offset<B> {
221    /// Create a new offset [`Binary`]
222    pub fn new(inner: B, offset: u64) -> Self {
223        Self { inner, offset }
224    }
225
226    /// Retrieve the inner [`Binary`]
227    pub fn inner(&self) -> &B {
228        &self.inner
229    }
230
231    /// Retrieve the offset
232    pub fn offset(&self) -> u64 {
233        self.offset
234    }
235}
236
237impl<B, I> Binary<I> for Offset<B>
238where
239    B: Binary<I>,
240    B::Error: Miss,
241    I: Info,
242{
243    type Error = B::Error;
244
245    fn get_insn(&mut self, address: u64) -> Result<Instruction<I>, Self::Error> {
246        address
247            .checked_sub(self.offset)
248            .ok_or(B::Error::miss(address))
249            .and_then(|a| self.inner.get_insn(a))
250    }
251}