1mod names;
4
5use std::{collections::BTreeMap, path::PathBuf};
6
7use anyhow::{Context, Result};
8use itertools::Itertools;
9pub use names::{RawSymbol, SymbolName, TypeName};
10use object::{Object, ObjectSymbol};
11
12use crate::{
13 file::{load, Binary, DebugFile, File},
14 function::FunctionIndex,
15 DwarfDb,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub struct Symbol {
21 pub name: SymbolName,
22 pub address: u64,
23 pub debug_file: DebugFile,
24}
25pub type DebugFiles = BTreeMap<(PathBuf, Option<String>), DebugFile>;
26
27pub fn index_symbol_map(
32 db: &dyn DwarfDb,
33 binary: Binary,
34) -> anyhow::Result<(DebugFiles, SymbolIndex)> {
35 let mut debug_files = DebugFiles::new();
36
37 let binary_file = binary.file(db);
39 let loaded_file = match load(db, binary_file) {
40 Ok(file) => file,
41 Err(e) => {
42 return Err(e.clone())
43 .with_context(|| format!("Failed to load binary file: {}", binary_file.name(db)));
44 }
45 };
46
47 let debug_file = DebugFile::new(db, binary_file, false);
49 debug_files.insert((debug_file.file(db).path(db).clone(), None), debug_file);
50
51 let mut symbol_index = SymbolIndex::default();
53 if loaded_file.object.has_debug_symbols() {
54 symbol_index.index_binary(&loaded_file.object, debug_file)?;
55 }
56
57 let mut indexed_object_files = vec![None; loaded_file.object.object_map().objects().len()];
60 let object_map = loaded_file.object.object_map();
61 for (i, object_file) in object_map.objects().iter().enumerate() {
62 let object_path = object_file.path();
63 let Ok(object_path) = String::from_utf8(object_path.to_vec()) else {
64 tracing::debug!("Failed to parse object file path: {:?}", object_file.path());
65 continue;
66 };
67 let object_path = PathBuf::from(object_path);
68 let Ok(member) = object_file
69 .member()
70 .map(|m| String::from_utf8(m.to_vec()))
71 .transpose()
72 else {
73 tracing::debug!(
74 "Failed to parse object file member: {:?}",
75 object_file.member()
76 );
77 continue;
78 };
79
80 let file = match File::build(db, object_path.clone(), member.clone()) {
81 Ok(file) => file,
82 Err(e) => {
83 tracing::error!(
84 "Failed to load debug file {} with member: {member:?}: {e}",
85 object_path.display()
86 );
87 continue;
88 }
89 };
90 tracing::trace!("Found debug file: {}", file.name(db));
91 let debug_file = DebugFile::new(db, file, true);
93 debug_files.insert((file.path(db).clone(), member), debug_file);
94 indexed_object_files[i] = Some(debug_file);
95 }
96
97 let grouped_symbols = object_map
99 .symbols()
100 .iter()
101 .into_group_map_by(|s| s.object_index());
102
103 for (object_index, symbols) in grouped_symbols {
104 if let Some(debug_file) = indexed_object_files[object_index] {
105 tracing::trace!(
106 "Indexing mapped symbols for debug file: {}",
107 debug_file.name(db)
108 );
109 symbol_index.index_mapped_file(symbols.into_iter(), debug_file)?;
110 }
111 }
112
113 Ok((debug_files, symbol_index))
114}
115
116pub type DebugFileSymbols = BTreeMap<RawSymbol, Symbol>;
117
118#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
120pub struct SymbolIndex {
121 pub functions: BTreeMap<String, BTreeMap<SymbolName, Symbol>>,
124
125 pub symbols: BTreeMap<String, BTreeMap<SymbolName, Symbol>>,
127
128 pub symbols_by_file: BTreeMap<DebugFile, DebugFileSymbols>,
132
133 pub functions_by_address: BTreeMap<u64, Vec<Symbol>>,
136}
137
138impl SymbolIndex {
139 pub fn get_function(&self, name: &SymbolName) -> Option<&Symbol> {
141 self.functions.get(&name.lookup_name)?.get(name)
142 }
143
144 pub fn get_functions_by_lookup_name(
146 &self,
147 lookup_name: &str,
148 ) -> Option<&BTreeMap<SymbolName, Symbol>> {
149 self.functions.get(lookup_name)
150 }
151
152 pub fn function_at_address(&self, address: u64) -> Option<(u64, &Vec<Symbol>)> {
154 self.functions_by_address
156 .range(..=address)
157 .next_back()
158 .map(|(base_addr, v)| (*base_addr, v))
159 }
160
161 pub fn function_index<'db>(
162 &'db self,
163 db: &'db dyn DwarfDb,
164 debug_file: DebugFile,
165 ) -> Option<&'db FunctionIndex<'db>> {
166 Some(crate::function::function_index(
167 db,
168 debug_file,
169 self.symbols_by_file.get(&debug_file)?,
170 ))
171 }
172
173 pub fn index_binary(&mut self, object: &object::File<'_>, debug_file: DebugFile) -> Result<()> {
174 let file_symbols = self.symbols_by_file.entry(debug_file).or_default();
175
176 for s in object.symbols() {
177 let Ok(name) = s.name_bytes() else {
178 tracing::debug!("Failed to parse symbol name at: {:#010x}", s.address());
179 continue;
180 };
181
182 let symbol = RawSymbol::new(name.to_vec());
183
184 let Ok(demangled) = symbol.demangle() else {
185 tracing::trace!(
186 "Failed to demangle symbol at: {:#010x}: {}",
187 s.address(),
188 String::from_utf8_lossy(name)
189 );
190 continue;
191 };
192
193 let is_function = s.kind() == object::SymbolKind::Text;
196
197 let index_name = demangled.lookup_name.clone();
198 let entry = Symbol {
199 name: demangled.clone(),
200 address: s.address(),
201 debug_file,
202 };
203 file_symbols.insert(symbol.clone(), entry.clone());
204
205 let map = if is_function {
206 self.functions_by_address
207 .entry(entry.address)
208 .or_default()
209 .push(entry.clone());
210 &mut self.functions
211 } else {
212 &mut self.symbols
213 };
214
215 map.entry(index_name)
217 .or_default()
218 .insert(demangled.clone(), entry);
219 }
220 Ok(())
221 }
222
223 pub fn index_mapped_file<'a>(
224 &mut self,
225 symbol_iter: impl Iterator<Item = &'a object::ObjectMapEntry<'a>>,
226 debug_file: DebugFile,
227 ) -> Result<()> {
228 let file_symbols = self.symbols_by_file.entry(debug_file).or_default();
229 for s in symbol_iter {
230 let symbol = RawSymbol::new(s.name().to_vec());
231
232 let Ok(demangled) = symbol.demangle() else {
233 tracing::trace!(
234 "Failed to demangle symbol at: {:#010x} {}",
235 s.address(),
236 String::from_utf8_lossy(s.name())
237 );
238 continue;
239 };
240
241 let is_function = s.size() > 0;
244
245 tracing::trace!(
246 "Indexing symbol at: {:#010x} {} (is_function: {is_function})",
247 s.address(),
248 demangled.lookup_name
249 );
250
251 let index_name = demangled.lookup_name.clone();
252 let entry = Symbol {
253 name: demangled.clone(),
254 address: s.address(),
255 debug_file,
256 };
257 file_symbols.insert(symbol.clone(), entry.clone());
258 let map = if is_function {
259 self.functions_by_address
260 .entry(entry.address)
261 .or_default()
262 .push(entry.clone());
263 &mut self.functions
264 } else {
265 &mut self.symbols
266 };
267
268 map.entry(index_name)
270 .or_default()
271 .insert(demangled.clone(), entry);
272 }
273 Ok(())
274 }
275}
276
277#[cfg(test)]
278mod test {
279 use std::env::current_exe;
280
281 use anyhow::Result;
282
283 use super::*;
284
285 #[test]
286 fn test_symbol_index_basic() -> Result<()> {
287 crate::test_utils::init_tracing();
288
289 let artifact_dir = crate::test_utils::artifacts_dir(Some("aarch64-apple-darwin"));
292 let exe_path = artifact_dir.join("examples/small");
293
294 let db = crate::test_utils::test_db(Some("aarch64-apple-darwin"));
295 let db = &db;
296 let binary = crate::test_utils::load_binary(db, &exe_path);
297
298 let (_debug_files, symbol_index) = index_symbol_map(db, binary).unwrap();
300
301 assert!(
303 !symbol_index.functions.is_empty(),
304 "Should have indexed some functions"
305 );
306 assert!(
307 !symbol_index.functions_by_address.is_empty(),
308 "Should have functions grouped by address"
309 );
310
311 tracing::info!(
312 "Symbol index created successfully with {} function groups and {} total functions",
313 symbol_index.functions.len(),
314 symbol_index.functions_by_address.len()
315 );
316
317 if let Some((addr, first_funcs)) = symbol_index.functions_by_address.first_key_value() {
319 let (_, found_func) = symbol_index
320 .function_at_address(*addr)
321 .expect("Should find function at address");
322 assert_eq!(found_func, first_funcs);
323 }
324
325 Ok(())
326 }
327
328 #[test]
329 fn test_symbol_index_performance() -> Result<()> {
330 crate::test_utils::init_tracing();
331
332 let exe_path = current_exe().unwrap();
333
334 let db = crate::test_utils::test_db(Some("aarch64-apple-darwin"));
335 let db = &db;
336 let binary = crate::test_utils::load_binary(db, &exe_path);
337
338 let start = std::time::Instant::now();
339 let (_debug_files, symbol_index) = index_symbol_map(db, binary).unwrap();
340 let symbol_index_time = start.elapsed();
341
342 tracing::info!(
343 "Symbol index built in {:?}. Got: {} functions and {} symbols",
344 symbol_index_time,
345 symbol_index.functions.len(),
346 symbol_index.symbols.len()
347 );
348
349 assert!(
352 symbol_index_time.as_millis() < 5000,
353 "Symbol index should be fast, took {symbol_index_time:?}"
354 );
355
356 Ok(())
357 }
358}