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}