playdate_symbolize/
db.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::path::{Path, PathBuf};
4use std::io::{Error as IoError, ErrorKind as IoErrorKind};
5
6use anyhow::Result;
7use rusqlite::Connection;
8use symbolic::common::{Language, Name, NameMangling};
9use utils::toolchain::sdk::Sdk;
10
11use crate::fmt::report::{self, UNKNOWN};
12
13
14const QUERY_FN: &str = include_str!("query-fn.sql");
15const QUERY_LN: &str = include_str!("query-ln.sql");
16
17
18pub struct Resolver {
19	conn: Connection,
20}
21
22impl Resolver {
23	pub async fn new(sdk: Option<&Path>) -> Result<Self> {
24		let sdk = sdk.map_or_else(Sdk::try_new, Sdk::try_new_exact)?;
25		let path = if cfg!(target_os = "macos") {
26			"Playdate Simulator.app/Contents/Resources/symbols.db"
27		} else if cfg!(unix) {
28			"symbols.db"
29		} else if cfg!(windows) {
30			"symbols.db"
31		} else {
32			const MSG: &str = "Unsupported platform, can't find symbols.db";
33			return Err(IoError::new(IoErrorKind::Unsupported, MSG).into());
34		};
35
36		let path = sdk.bin().join(path).canonicalize()?;
37		Self::with_exact(&path).await
38	}
39
40	pub async fn with_exact(db_path: &Path) -> Result<Self> {
41		let conn = Connection::open(db_path)?;
42		Ok(Self { conn })
43	}
44
45	pub async fn close(self) -> rusqlite::Result<()> {
46		self.conn.close().map_err(|(conn, err)| {
47			                 conn.close().ok();
48			                 err
49		                 })
50	}
51
52
53	pub async fn resolve(&self, addr: u32) -> anyhow::Result<report::Report> {
54		let fun = self.resolve_fn(addr).await;
55		let ln = self.resolve_ln(addr).await;
56
57		// merge ln -> fn:
58		match fun {
59			Ok(mut fun) => {
60				match ln {
61					Ok(mut ln) => {
62						let mut to_add = Vec::new();
63						for b in ln.symbols.drain(..) {
64							let mut exists = false;
65							'fun: for a in fun.symbols.iter() {
66								if a.lines == b.lines {
67									exists = true;
68									break 'fun;
69								}
70							}
71							if !exists {
72								to_add.push(b);
73							}
74						}
75						fun.symbols.extend(to_add);
76					},
77					Err(err) => error!("{err}"),
78				}
79				Ok(fun)
80			},
81			Err(err_fn) => {
82				match ln {
83					Err(err_ln) => Err(err_fn.context(err_ln)),
84					ok => ok,
85				}
86			},
87		}
88	}
89
90	pub async fn resolve_fn(&self, addr: u32) -> anyhow::Result<report::Report> {
91		struct Record {
92			name: Option<String>,
93			low: Option<i64>,
94			size: Option<i64>,
95			fn_hw_id: Option<i64>,
96			ln_low: Option<i64>,
97			ln_hw_id: Option<i64>,
98			lineno: Option<i64>,
99			path: Option<String>,
100		}
101
102		let mut stmt = self.conn.prepare(QUERY_FN)?;
103		let records = stmt.query_map([addr], |row| {
104			                  Ok(Record { name: row.get(0)?,
105			                              low: row.get(1)?,
106			                              size: row.get(2)?,
107			                              fn_hw_id: row.get(3)?,
108			                              ln_low: row.get(4)?,
109			                              ln_hw_id: row.get(4)?,
110			                              lineno: row.get(6)?,
111			                              path: row.get(7)? })
112		                  })?;
113
114		let mut results = HashMap::new();
115
116		for record in records {
117			let func = record?;
118			let fn_name = func.name.as_deref().map(Cow::from).unwrap_or(UNKNOWN.into());
119			let fn_id = format_args!("{fn_name}{}", func.fn_hw_id.unwrap_or_default()).to_string();
120			let name = Name::new(fn_name.to_string(), NameMangling::Unmangled, Language::C);
121
122			let line = report::Span { hw_id: func.ln_hw_id,
123			                          address: func.ln_low.map(|p| p as _).unwrap_or_default(),
124			                          size: None,
125			                          line: func.lineno.map(|p| p as _),
126			                          file: func.path.unwrap_or(UNKNOWN.into()).into() };
127
128			if let Some(item) = results.get_mut(&fn_id) {
129				let report::Symbol { lines, .. } = item;
130				lines.push(line);
131				lines.sort();
132			} else {
133				let res = report::Symbol { hw_id: func.fn_hw_id,
134				                           name: Some(name),
135				                           address: func.low.map(|p| p as _).unwrap_or_default(),
136				                           size: func.size.map(|p| p as _),
137				                           lines: vec![line] };
138				results.insert(fn_id, res);
139			}
140		}
141
142		let result = report::Report { addr: (addr as u64).into(),
143		                              symbols: results.into_values().collect() };
144
145		Ok(result)
146	}
147
148
149	pub async fn resolve_ln(&self, addr: u32) -> anyhow::Result<report::Report> {
150		struct Record {
151			low: Option<i64>,
152			hw_id: Option<i64>,
153			lineno: Option<i64>,
154			path: Option<String>,
155		}
156
157		let mut stmt = self.conn.prepare(QUERY_LN)?;
158		let records = stmt.query_map([addr], |row| {
159			                  Ok(Record { low: row.get(0)?,
160			                              hw_id: row.get(1)?,
161			                              lineno: row.get(2)?,
162			                              path: row.get(3)? })
163		                  })?;
164
165		let mut lines = records.into_iter()
166		                       .try_fold(Vec::new(), |mut acc, res| -> rusqlite::Result<_> {
167			                       let ln = res?;
168			                       let span = report::Span { hw_id: ln.hw_id,
169			                                                 address: ln.low.map(|p| p as _).unwrap_or_default(),
170			                                                 size: None,
171			                                                 line: ln.lineno.map(|p| p as _),
172			                                                 file: ln.path.unwrap_or(UNKNOWN.into()).into() };
173			                       acc.push(span);
174			                       Ok(acc)
175		                       })?;
176
177		let result = if lines.is_empty() {
178			report::Report { addr: (addr as u64).into(),
179			                 symbols: vec![] }
180		} else {
181			let exact = lines.iter()
182			                 .enumerate()
183			                 .find_map(|(i, ln)| (ln.address == addr as u64).then_some(i));
184
185			let mut lines = if let Some(exact) = exact {
186				vec![lines.remove(exact)]
187			} else {
188				lines
189			};
190			lines.sort();
191
192			let sym = report::Symbol { hw_id: None,
193			                           name: None,
194			                           address: addr as _,
195			                           size: None,
196			                           lines };
197			report::Report { addr: (addr as u64).into(),
198			                 symbols: vec![sym] }
199		};
200
201		Ok(result)
202	}
203}
204
205
206pub struct SystemDbResult<'t> {
207	pub hw_id: i64,
208
209	pub name: Name<'t>,
210	pub low: i64,
211	pub size: i64,
212	pub lines: Vec<SystemDbSpan>,
213}
214
215pub struct SystemDbSpan {
216	pub hw_id: i64,
217	pub low: i64,
218	pub number: i64,
219	pub file: PathBuf,
220}