playdate_symbolize/
elf.rs

1use std::borrow::Cow;
2use std::path::Path;
3
4use symbolic::common::{ByteView, Language, Name, NameMangling};
5use symbolic::debuginfo::{Function, LineInfo, Object, ObjectError, Symbol};
6use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
7use tokio_stream::Stream;
8use anyhow::{Context, Result};
9
10use crate::fmt::addr::Addr;
11use crate::fmt::addr::DEF;
12use crate::fmt::report::{self, Report};
13
14
15pub struct Resolver<'elf> {
16	data: ByteView<'elf>,
17	functions: bool,
18	inlinees: bool,
19}
20
21impl<'t> Resolver<'t> {
22	pub fn new(path: &Path, functions: bool, inlinees: bool) -> Result<Self> {
23		let data = ByteView::open(path).context("failed to open file")?;
24		Ok(Self { data,
25		          functions,
26		          inlinees })
27	}
28
29
30	// TODO: Methods with other receivers.
31	// `pub fn into_stream_lazy_with(self, mut rx: tokio::sync::broadcast::Receiver<u32>)...`
32	// For example:
33	// ```
34	// let (tx, rx) = tokio::sync::broadcast::channel(16);
35	// let resolver = Box::pin(resolver.into_stream_lazy_with(rx.clone())?);
36	//
37	// let rx2 = tx.subscribe();
38	// let resolver2 = SomeAnotherResolver::new(rx2);
39	//
40	// // Send addresses to both resolvers:
41	// let send_addrs = tokio::spawn(async move {
42	// 	for addr in addrs {
43	// 		rx.send(addr)?;
44	// 	}
45	// 	Ok::<_, tokio::sync::mpsc::error::SendError<u32>>(())
46	// });
47	//
48	// // Receive reports from resolver:
49	// while let Some(res) = resolver.next().await {
50	// 	res?.default_print(std::io::stdout(), ...)?;
51	// }
52	// send_addrs.await??;
53	// ```
54
55	/// Example:
56	/// ```ignore
57	/// let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<u32>();
58	/// let resolver = Box::pin(resolver.into_stream_lazy_from(rx, None::<[_;0]>)?);
59	///
60	/// // Send addresses to resolver:
61	/// let send_addrs = tokio::spawn(async move {
62	/// 	for addr in addrs {
63	/// 		tx.send(addr)?;
64	/// 	}
65	/// 	Ok::<_, tokio::sync::mpsc::error::SendError<u32>>(())
66	/// });
67	///
68	/// // Receive reports from resolver:
69	/// while let Some(res) = resolver.next().await {
70	/// 	res?.default_print(
71	/// 	                   std::io::stdout(),
72	/// 	                   0,
73	/// 	                   cfg.flags.functions,
74	/// 	                   cfg.flags.basenames,
75	/// 	                   cfg.flags.ranges,
76	/// 	                   cfg.flags.demangle,
77	/// 	)?;
78	/// }
79	/// send_addrs.await??;
80	/// ```
81	pub fn into_stream_lazy_from<'a: 't, T: 'a + IntoIterator<Item = Addr<DEF>>>(
82		self,
83		mut rx: UnboundedReceiver<Addr>,
84		addrs: Option<T>)
85		-> Result<impl Stream<Item = Result<Report, ObjectError>> + 't> {
86		let stream = async_stream::try_stream! {
87			let data = self.data;
88			let object = Object::parse(&data)?;
89			let session = object.debug_session()?;
90			let symbols = object.symbol_map();
91
92			let load_address = object.load_address();
93			debug!("Elf's load-address: {load_address}");
94
95			let doit = |mut addr: Addr| {
96				trace!(" Resolving {addr:#08x?}");
97				{
98					// use crate::fmt::addr::FLASH_MEM_REV1;
99					use crate::fmt::addr::USER_HEAP_REV1;
100					if USER_HEAP_REV1.contains(&addr.value()) && USER_HEAP_REV1.start >= load_address  {
101						debug!("Seems to {addr:#08x?} is in the user-mem.");
102						let diff = USER_HEAP_REV1.start - load_address;
103						if addr.value() >= diff {
104							addr.set_fixed(addr.value() - diff);
105							trace!(" Now resolving {addr:#08x?}");
106						}
107					}
108				}
109
110				let mut result = None;
111				'funcs: for function in session.functions() {
112					match function.context("failed to read function") {
113						Ok(function) => {
114							let res = if self.inlinees {
115								resolve_with_inlinees(&function, addr).filter(|v| !v.is_empty())
116							} else {
117								resolve(&function, addr).map(|res| vec![res])
118							};
119
120							if let Some(resolved) = res {
121								debug_assert!(!resolved.is_empty());
122								let rep = report::Report::from_vec(addr, resolved);
123								trace!("Result of resolving fn: {addr:#08x?}, report addr: {:#08x?}", rep.addr);
124								result = Some(rep);
125								break 'funcs;
126							}
127						},
128						Err(err) => error!("{err}"),
129					}
130				}
131
132				if let Some(res) = result {
133					res
134				} else if self.functions {
135					trace!("Not found fn for {addr:#08x?}, symbols lookup...");
136					let resolved = symbols.lookup(addr.fixed()).or_else(||symbols.lookup(addr.value())).map(ResolvedAddrRef::Sym);
137
138					if let Some(resolved) = resolved {
139						trace!("Found sym for {addr:#08x?}");
140						report::Report::from_one(addr, resolved)
141					} else {
142						trace!("Not found sym for {addr:#08x?}");
143						report::Report{ addr, symbols: vec![] }
144					}
145				} else {
146					trace!("Not found fn for {addr:#08x?}");
147					report::Report{ addr: addr, symbols: vec![] }
148				}
149			};
150
151			if let Some(addrs) = addrs {
152				for addr in addrs {
153					yield doit(addr);
154				}
155			}
156
157			while let Some(addr) = rx.recv().await {
158				yield doit(addr);
159			}
160		};
161
162		Ok(stream)
163	}
164
165
166	/// Example:
167	/// ```ignore
168	/// let resolver = Box::pin(resolver.into_stream_resolve(rx, &addrs)?);
169	///
170	/// // Receive reports from resolver:
171	/// while let Some(res) = resolver.next().await {
172	/// 	res?.default_print(
173	/// 	                   std::io::stdout(),
174	/// 	                   0,
175	/// 	                   cfg.flags.functions,
176	/// 	                   cfg.flags.basenames,
177	/// 	                   cfg.flags.ranges,
178	/// 	                   cfg.flags.demangle,
179	/// 	)?;
180	/// }
181	/// ```
182	pub fn into_stream_resolve<'a: 't, T>(self,
183	                                      addrs: &'a [T])
184	                                      -> Result<impl Stream<Item = Result<Report, ObjectError>> + 't>
185		where T: Copy + Into<Addr<u64>>
186	{
187		let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
188		drop(tx);
189		self.into_stream_lazy_from(
190		                           rx,
191		                           Some(addrs.into_iter().map(|v| (*v).into()).collect::<Vec<_>>()),
192		)
193	}
194
195
196	pub fn into_stream_lazy<'a: 't, T: 'a + IntoIterator<Item = Addr<DEF>>>(
197		self,
198		addrs: Option<T>)
199		-> Result<(UnboundedSender<Addr>, impl Stream<Item = Result<Report, ObjectError>> + 't)> {
200		let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<Addr>();
201		self.into_stream_lazy_from(rx, addrs).map(|stream| (tx, stream))
202	}
203
204
205	// TODO: Methods with rev- iteration order. Also index.
206	// 1. Create index <== iterate over `session.functions()`
207	// 2. Iterate over addresses, search in index
208	// 3. Search in symbols-map as fallback.
209}
210
211
212pub async fn map_result_fallback_os(res: Result<Report, ObjectError>,
213                                    db: &crate::db::Resolver)
214                                    -> Result<Report, anyhow::Error> {
215	match res {
216		Ok(rep) => {
217			if rep.symbols.is_empty() {
218				db.resolve(rep.addr.value() as _).await
219			} else {
220				Ok(rep)
221			}
222		},
223		Err(err) => Err(err.into()),
224	}
225}
226
227
228pub fn resolve_with_inlinees<'t, 'elf>(f: &'t Function<'elf>,
229                                       addr: Addr)
230                                       -> Option<Vec<ResolvedAddrRef<'t, 'elf>>> {
231	if f.address > addr || f.address + f.size <= addr {
232		return None;
233	}
234
235	let main = resolve(f, addr)?;
236	let mut results = Vec::with_capacity(3); // usually 3 is statistically normal-high inline-chain len, so enough.
237
238	for il in &f.inlinees {
239		if let Some(mut res) = resolve_with_inlinees(il, addr) {
240			results.append(&mut res);
241		}
242	}
243
244	results.push(main);
245	Some(results)
246}
247
248pub fn resolve<'t, 'elf>(f: &'t Function<'elf>, addr: Addr) -> Option<ResolvedAddrRef<'t, 'elf>> {
249	if f.address > addr || f.address + f.size <= addr {
250		return None;
251	}
252
253	let mut indices = Vec::new();
254
255	for (i, line) in f.lines.iter().enumerate() {
256		if line.address + line.size.unwrap_or(1) <= addr {
257			// not yet there
258			continue;
259		} else if line.address > addr {
260			// already not there
261			break;
262		}
263		trace!("{addr:#08x}: found line {i}: ({})", line.file.name_str());
264		indices.push(i);
265	}
266
267	let result = ResolvedAddrRef::Fn { function: f,
268	                                   lines: indices };
269	Some(result)
270}
271
272
273pub enum ResolvedAddrRef<'t, 'elf> {
274	Fn {
275		function: &'t Function<'elf>,
276		/// Line indices
277		lines: Vec<usize>,
278	},
279	Sym(&'t Symbol<'elf>),
280}
281
282#[derive(Debug)]
283pub enum ResolvedAddr<'elf> {
284	Fn {
285		function: Function<'elf>,
286		/// Line indices
287		lines: Vec<usize>,
288	},
289	Sym(&'elf Symbol<'elf>),
290}
291
292impl<'t> From<ResolvedAddrRef<'t, 't>> for ResolvedAddr<'t> {
293	fn from(value: ResolvedAddrRef<'t, 't>) -> Self {
294		match value {
295			ResolvedAddrRef::Fn { function, lines } => {
296				Self::Fn { function: function.to_owned(),
297				           lines }
298			},
299			ResolvedAddrRef::Sym(sym) => Self::Sym(sym),
300		}
301	}
302}
303
304
305impl Report {
306	pub fn from_vec<T: Into<report::Symbol>>(addr: Addr, vec: Vec<T>) -> Self {
307		Self { addr,
308		       symbols: vec.into_iter().map(Into::into).collect() }
309	}
310	pub fn from_slice<T: Into<report::Symbol>>(addr: Addr, slice: &[T]) -> Self
311		where for<'t> &'t T: Into<report::Symbol> {
312		Self { addr,
313		       symbols: slice.into_iter().map(Into::into).collect() }
314	}
315	pub fn from_one<T: Into<report::Symbol>>(addr: Addr, result: T) -> Self {
316		Self { addr,
317		       symbols: vec![result.into()] }
318	}
319}
320
321
322impl Into<report::Symbol> for ResolvedAddrRef<'_, '_> {
323	fn into(self) -> report::Symbol {
324		let owned: ResolvedAddr = self.into();
325		owned.into()
326	}
327}
328impl Into<report::Symbol> for ResolvedAddr<'_> {
329	fn into(self) -> report::Symbol { (&self).into() }
330}
331
332impl Into<report::Symbol> for &ResolvedAddr<'_> {
333	fn into(self) -> report::Symbol {
334		match self {
335			ResolvedAddr::Fn { function, lines } => {
336				let name = Name::new(
337				                     function.name.as_str().to_string(),
338				                     function.name.mangling(),
339				                     function.name.language(),
340				);
341				let lines = lines.into_iter()
342				                 .map(|l| &function.lines[*l])
343				                 .map(Into::into)
344				                 .collect::<Vec<_>>();
345				report::Symbol { hw_id: None,
346				                 name: Some(name),
347				                 address: function.address,
348				                 size: Some(function.size),
349				                 lines }
350			},
351
352			ResolvedAddr::Sym(sym) => {
353				let name = sym.name
354				              .as_ref()
355				              .map(|s| Cow::<'static, str>::Owned(s.to_string()))
356				              .map(|name| Name::new(name, NameMangling::Unknown, Language::Rust));
357				let size = (sym.size != 0).then_some(sym.size);
358				report::Symbol { hw_id: None,
359				                 name,
360				                 address: sym.address,
361				                 size,
362				                 lines: Vec::with_capacity(0) }
363			},
364		}
365	}
366}
367
368
369impl<'elf> Into<report::Span> for &LineInfo<'elf> {
370	fn into(self) -> report::Span {
371		let line = (self.line != 0).then_some(self.line);
372		report::Span { hw_id: None,
373		               address: self.address,
374		               size: self.size,
375		               file: self.file.path_str().into(),
376		               line }
377	}
378}