rudy_dwarf/function/
index.rs

1// ===== TARGETED INDEXING FUNCTIONS =====
2
3use 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    /// Die entry for the function
25    pub declaration_die: Die,
26    /// Address range of the function relative to the binary
27    pub address_range: Option<(u64, u64)>,
28    pub name: String,
29    pub specification_die: Option<Die>,
30    /// Sometimes we'll find the same definition mulitple times
31    /// in the same file due to compilation units
32    ///
33    /// For now, we'll just store the alternate locations
34    /// although we'll probably need to do something else
35    pub alternate_locations: Vec<Die>,
36}
37
38/// Targeted function index containing only functions
39#[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                // we've found a function that contains this address
58                // next, shift the address to a relative address
59                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
78/// Visitor for building function index efficiently
79struct 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    /// Create a new function index builder
87    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        // TODO: Replace all of this with a parser
118
119        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, '_'); // Ensure linkage name starts with underscore for relocatable files
140        }
141
142        // find if the symbol is actually linked in the binary
143        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            // Add to address tree if we have address range
166            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/// Index only functions in debug file using visitor pattern
202#[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}