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 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}