1use std::collections::BTreeMap;
4
5use itertools::Itertools;
6
7use crate::{
8 address::{address_to_location, location_to_address, AddressTree, FunctionAddressInfo},
9 die::utils::{get_string_attr, pretty_print_die_entry, to_range},
10 file::SourceLocation,
11 symbols::{RawSymbol, Symbol},
12 visitor::{walk_file, DieVisitor, DieWalker, VisitorNode},
13 DebugFile, Die, DwarfDb, SymbolName,
14};
15
16#[salsa::tracked(debug)]
17pub struct FunctionIndexEntry<'db> {
18 #[returns(ref)]
19 pub data: FunctionData,
20}
21
22#[derive(Debug, Clone, Hash, PartialEq, Eq, salsa::Update)]
23pub struct FunctionData {
24 pub declaration_die: Die,
26 pub address_range: Option<(u64, u64)>,
28 pub name: String,
29 pub specification_die: Option<Die>,
30 pub alternate_locations: Vec<Die>,
36}
37
38#[salsa::tracked(debug)]
40pub struct FunctionIndex<'db> {
41 #[returns(ref)]
42 pub by_symbol_name: BTreeMap<SymbolName, FunctionIndexEntry<'db>>,
43 #[returns(ref)]
44 pub by_address: AddressTree,
45}
46
47impl<'db> FunctionIndex<'db> {
48 pub fn address_to_locations(
49 &self,
50 db: &dyn DwarfDb,
51 address: u64,
52 ) -> Vec<(SymbolName, SourceLocation)> {
53 self.by_address(db)
54 .query_address(address, true)
55 .into_iter()
56 .filter_map(|f| {
57 let relative_address = f.relative_start + (address - f.absolute_start);
60
61 let function = self.by_symbol_name(db).get(&f.name)?.data(db);
62
63 address_to_location(db, relative_address, function).map(|loc| (f.name.clone(), loc))
64 })
65 .collect()
66 }
67
68 pub fn location_to_address(
69 &self,
70 db: &dyn DwarfDb,
71 debug_file: DebugFile,
72 location: &SourceLocation,
73 ) -> Option<(u64, u64)> {
74 location_to_address(db, debug_file, self, location)
75 }
76}
77
78struct FunctionIndexBuilder<'db> {
80 functions: BTreeMap<SymbolName, FunctionIndexEntry<'db>>,
81 by_address: Vec<FunctionAddressInfo>,
82 symbol_map: &'db BTreeMap<RawSymbol, Symbol>,
83}
84
85impl<'db> FunctionIndexBuilder<'db> {
86 pub fn new(symbol_map: &'db BTreeMap<RawSymbol, Symbol>) -> Self {
88 Self {
89 functions: BTreeMap::new(),
90 by_address: Vec::new(),
91 symbol_map,
92 }
93 }
94}
95
96impl<'db> DieVisitor<'db> for FunctionIndexBuilder<'db> {
97 fn visit_die<'a>(
98 walker: &mut DieWalker<'a, 'db, Self>,
99 node: VisitorNode<'a>,
100 ) -> anyhow::Result<()> {
101 match node.die.tag() {
102 gimli::DW_TAG_subprogram => {
103 Self::visit_function(walker, node)?;
104 }
105 gimli::DW_TAG_namespace | gimli::DW_TAG_structure_type => {
106 walker.walk_children()?;
107 }
108 _ => {}
109 }
110 Ok(())
111 }
112
113 fn visit_function<'a>(
114 walker: &mut DieWalker<'a, 'db, Self>,
115 node: VisitorNode<'a>,
116 ) -> anyhow::Result<()> {
117 let mut linkage_name =
120 match get_string_attr(&node.die, gimli::DW_AT_linkage_name, &node.unit_ref) {
121 Ok(Some(linkage_name)) => linkage_name,
122 Ok(None) => {
123 tracing::trace!(
124 "Skipping function with no linkage name: {}",
125 pretty_print_die_entry(&node.die, &node.unit_ref)
126 );
127 return Ok(());
128 }
129 Err(e) => {
130 tracing::error!(
131 "Failed to get linkage name for function: {e}: \n{}",
132 pretty_print_die_entry(&node.die, &node.unit_ref)
133 );
134 return Err(e);
135 }
136 };
137
138 if walker.file.relocatable(walker.db) {
139 linkage_name.insert(0, '_'); }
141
142 if let Some(symbol) = walker
144 .visitor
145 .symbol_map
146 .get(&RawSymbol::new(linkage_name.as_bytes().to_vec()))
147 {
148 let address_range = node
149 .unit_ref
150 .die_ranges(&node.die)
151 .map_err(anyhow::Error::from)
152 .and_then(to_range)
153 .unwrap_or(None);
154
155 let die = walker.get_die(node.die);
156
157 let function_data = FunctionData {
158 declaration_die: die,
159 address_range,
160 name: symbol.name.lookup_name.clone(),
161 specification_die: None,
162 alternate_locations: vec![],
163 };
164
165 if let Some((relative_start, relative_end)) = address_range {
167 walker.visitor.by_address.push(FunctionAddressInfo {
168 absolute_start: symbol.address,
169 absolute_end: symbol.address + relative_end - relative_start,
170 relative_start,
171 relative_end,
172 name: symbol.name.clone(),
173 file: walker.file,
174 });
175 } else {
176 tracing::trace!(
177 "Function {} has no address range in debug file {}",
178 symbol.name,
179 walker.file.name(walker.db)
180 );
181 }
182
183 let entry = FunctionIndexEntry::new(walker.db, function_data);
184 walker.visitor.functions.insert(symbol.name.clone(), entry);
185 } else {
186 tracing::trace!(
187 "Skipping unlinked function: {linkage_name} in {:#?}",
188 walker
189 .visitor
190 .symbol_map
191 .values()
192 .map(|s| &s.name)
193 .join("\n")
194 );
195 }
196
197 Ok(())
198 }
199}
200
201#[salsa::tracked(returns(ref))]
203pub fn function_index<'db>(
204 db: &'db dyn DwarfDb,
205 debug_file: DebugFile,
206 symbol_map: &'db BTreeMap<RawSymbol, Symbol>,
207) -> FunctionIndex<'db> {
208 let start = std::time::Instant::now();
209 let mut builder = FunctionIndexBuilder::new(symbol_map);
210 if let Err(e) = walk_file(db, debug_file, &mut builder) {
211 tracing::error!("Failed to walk debug file: {e}");
212 }
213
214 let elapsed = start.elapsed();
215 if elapsed.as_secs() > 1 {
216 tracing::info!(
217 "Indexed {} functions in debug file {} in {}.{:03}s",
218 builder.functions.len(),
219 debug_file.name(db),
220 elapsed.as_secs(),
221 elapsed.subsec_millis()
222 );
223 } else {
224 tracing::debug!(
225 "Indexed {} functions in debug file {} in {:03}ms",
226 builder.functions.len(),
227 debug_file.name(db),
228 elapsed.as_millis()
229 );
230 }
231
232 FunctionIndex::new(db, builder.functions, AddressTree::new(builder.by_address))
233}