rudy_db/
debug_info.rs

1use std::{collections::BTreeMap, fmt, path::PathBuf};
2
3use anyhow::{Context, Result};
4use rudy_dwarf::{
5    Die, SourceFile, SymbolName,
6    function::resolve_function_variables,
7    types::{DieTypeDefinition, resolve_type_offset},
8};
9use rudy_types::{Layout, PrimitiveLayout, StdLayout};
10
11use crate::{
12    DiscoveredMethod, ResolvedLocation,
13    database::Db,
14    function_discovery::SymbolAnalysisResult,
15    index,
16    outputs::{ResolvedFunction, TypedPointer},
17    query::{lookup_address, lookup_position},
18};
19
20/// Main interface for accessing debug information from binary files.
21///
22/// `DebugInfo` provides methods to resolve addresses to source locations,
23/// look up function information, and inspect variables at runtime.
24///
25/// The struct holds a reference to the debug database and manages the
26/// binary file and associated debug files.
27#[derive(Clone)]
28pub struct DebugInfo<'db> {
29    pub(crate) binary: rudy_dwarf::Binary,
30    pub(crate) db: &'db crate::database::DebugDatabaseImpl,
31}
32
33impl<'db> fmt::Debug for DebugInfo<'db> {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        let db = self.db;
36        salsa::attach(db, || {
37            let index = crate::index::debug_index(db, self.binary);
38
39            f.debug_struct("DebugInfo")
40                // .field("debug_files", &index.debug_files(db))
41                .field("symbol_index", &index.symbol_index(db))
42                .field("indexed_debug_files", &index.indexed_debug_files(db))
43                .finish()
44        })
45    }
46}
47
48impl<'db> DebugInfo<'db> {
49    /// Creates a new `DebugInfo` instance for analyzing a binary file.
50    ///
51    /// # Arguments
52    ///
53    /// * `db` - Reference to the debug database
54    /// * `binary_path` - Path to the binary file to analyze
55    ///
56    /// # Returns
57    ///
58    /// A `DebugInfo` instance or an error if the binary cannot be loaded
59    ///
60    /// # Examples
61    ///
62    /// ```no_run
63    /// use rudy_db::{DebugDb, DebugInfo};
64    ///
65    /// let db = DebugDb::new();
66    /// let debug_info = DebugInfo::new(&db, "/path/to/binary").unwrap();
67    /// ```
68    pub fn new<P: AsRef<std::path::Path>>(
69        db: &'db crate::database::DebugDatabaseImpl,
70        binary_path: P,
71    ) -> Result<Self> {
72        let binary_path = binary_path.as_ref();
73        let binary = db
74            .load_binary(binary_path.to_owned())
75            .with_context(|| format!("Failed to analyze binary file: {}", binary_path.display()))?;
76
77        let pb = Self { db, binary };
78
79        Ok(pb)
80    }
81
82    /// Resolves a memory address to its source location.
83    ///
84    /// # Arguments
85    ///
86    /// * `address` - The memory address to resolve
87    ///
88    /// # Returns
89    ///
90    /// The source location if found, or `None` if the address cannot be resolved
91    ///
92    /// # Examples
93    ///
94    /// ```no_run
95    /// # use rudy_db::{DebugDb, DebugInfo};
96    /// # let db = DebugDb::new();
97    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
98    /// if let Ok(Some(location)) = debug_info.address_to_location(0x12345) {
99    ///     println!("Address 0x12345 is at {}:{}", location.file, location.line);
100    /// }
101    /// ```
102    pub fn address_to_location(&self, address: u64) -> Result<Option<ResolvedLocation>> {
103        let db = self.db;
104        let Some((name, loc)) = lookup_address(db, self.binary, address) else {
105            tracing::debug!("no function found for address {address:#x}");
106            return Ok(None);
107        };
108
109        Ok(Some(crate::ResolvedLocation {
110            function: name.to_string(),
111            file: loc.file.path_str().to_string(),
112            line: loc.line,
113        }))
114    }
115
116    /// Resolves a function name to its debug information.
117    ///
118    /// The function name can include module paths using `::` separators.
119    ///
120    /// # Arguments
121    ///
122    /// * `function` - The function name to resolve (e.g., "main" or "module::function")
123    ///
124    /// # Returns
125    ///
126    /// The resolved function information if found, or `None` if not found
127    ///
128    /// # Examples
129    ///
130    /// ```no_run
131    /// # use rudy_db::{DebugDb, DebugInfo};
132    /// # let db = DebugDb::new();
133    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
134    /// if let Some(func) = debug_info.find_function_by_name("main").unwrap() {
135    ///     println!("Function 'main' is at address {:#x}", func.address);
136    /// }
137    /// ```
138    pub fn find_function_by_name(&self, function: &str) -> Result<Option<ResolvedFunction>> {
139        let Some((name, _)) = index::find_closest_function(self.db, self.binary, function) else {
140            tracing::debug!("no function found for {function}");
141            return Ok(None);
142        };
143
144        let index = crate::index::debug_index(self.db, self.binary);
145        let symbol_index = index.symbol_index(self.db);
146
147        let symbol = symbol_index.get_function(&name).cloned().with_context(|| {
148            tracing::debug!(?name, "{:#?}", symbol_index);
149            "Failed to get base address for function"
150        })?;
151
152        let (_debug_file, fie) = index
153            .get_function(self.db, &name)
154            .ok_or_else(|| anyhow::anyhow!("Function not found in index: {name:?}"))?;
155
156        let params = resolve_function_variables(self.db, fie)?;
157
158        Ok(Some(ResolvedFunction {
159            name: name.to_string(),
160            address: symbol.address,
161            size: fie
162                .data(self.db)
163                .address_range
164                .map_or(0, |(start, end)| end - start),
165            params: params
166                .params
167                .into_iter()
168                .enumerate()
169                .map(|(i, var)| crate::Variable {
170                    name: var
171                        .name
172                        .as_ref()
173                        .map_or_else(|| format!("__{i}"), |s| s.to_string()),
174                    ty: var.ty.clone(),
175                    value: None,
176                })
177                .collect(),
178        }))
179    }
180
181    pub fn find_symbol_by_name(&self, symbol: &str) -> Result<Option<rudy_dwarf::symbols::Symbol>> {
182        let index = crate::index::debug_index(self.db, self.binary);
183        let symbol_index = index.symbol_index(self.db);
184
185        let Some(symbols) = symbol_index.symbols.get(symbol) else {
186            return Ok(None);
187        };
188
189        Ok(symbols.first_key_value().map(|(_, s)| s.clone()))
190    }
191
192    /// Resolves a source file position to a memory address.
193    ///
194    /// # Arguments
195    ///
196    /// * `file` - The source file path
197    /// * `line` - The line number in the source file
198    /// * `column` - Optional column number
199    ///
200    /// # Returns
201    ///
202    /// The memory address if the position can be resolved
203    ///
204    /// # Examples
205    ///
206    /// ```no_run
207    /// # use rudy_db::{DebugDb, DebugInfo};
208    /// # let db = DebugDb::new();
209    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
210    /// if let Some(addr) = debug_info.find_address_from_source_location("src/main.rs", 42, None).unwrap() {
211    ///     println!("Line 42 of src/main.rs is at address {:#x}", addr.address);
212    /// }
213    /// ```
214    pub fn find_address_from_source_location(
215        &self,
216        file: &str,
217        line: u64,
218        column: Option<u64>,
219    ) -> Result<Option<crate::ResolvedAddress>> {
220        let index = crate::index::debug_index(self.db, self.binary);
221
222        let path = PathBuf::from(file.to_string());
223        let source_file = SourceFile::new(path);
224
225        let file = if index.source_to_file(self.db).contains_key(&source_file) {
226            // already indexed file, so we can use it directly
227            source_file
228        } else {
229            // otherwise, we need to find the file in the index
230            if let Some(source_file) = index
231                .source_to_file(self.db)
232                .keys()
233                .find(|f| f.path.ends_with(file))
234            {
235                tracing::debug!(
236                    "found file `{file}` in debug index as `{}`",
237                    source_file.path_str()
238                );
239                source_file.clone()
240            } else {
241                tracing::warn!("file `{file}` not found in debug index");
242                return Ok(None);
243            }
244        };
245
246        let query = rudy_dwarf::file::SourceLocation::new(file, line, column);
247        let pos = lookup_position(self.db, self.binary, query);
248        Ok(pos.map(|address| crate::ResolvedAddress { address }))
249    }
250
251    /// Gets metadata for a specific variable at a memory address without reading its value.
252    ///
253    /// This method is useful for expression evaluation where you need type information
254    /// and memory addresses without immediately reading the value.
255    ///
256    /// # Arguments
257    ///
258    /// * `address` - The memory address to inspect
259    /// * `name` - The name of the variable to find
260    /// * `data_resolver` - Interface for reading memory and register values
261    ///
262    /// # Returns
263    ///
264    /// Variable metadata if found, or `None` if the variable is not found
265    ///
266    /// # Examples
267    ///
268    /// ```no_run
269    /// # use rudy_db::{DebugDb, DebugInfo, DataResolver};
270    /// # struct MyResolver;
271    /// # impl DataResolver for MyResolver { }
272    /// # let db = DebugDb::new();
273    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
274    /// # let resolver = MyResolver;
275    /// if let Some(var_info) = debug_info.get_variable_at_pc(0x12345, "foo", &resolver).unwrap() {
276    ///     println!("Variable '{}' at address {:?}", var_info.name, var_info.address);
277    /// }
278    /// ```
279    pub fn get_variable_at_pc(
280        &self,
281        address: u64,
282        name: &str,
283        data_resolver: &dyn crate::DataResolver,
284    ) -> Result<Option<crate::VariableInfo>> {
285        let db = self.db;
286        let f = lookup_address(db, self.binary, address);
287
288        let Some((function_name, _loc)) = f else {
289            tracing::debug!("no function found for address {address:#x}");
290            return Ok(None);
291        };
292
293        tracing::info!("Address {address:#08x} found in function {function_name}");
294
295        let index = crate::index::debug_index(db, self.binary);
296        let Some((_, fie)) = index.get_function(db, &function_name) else {
297            tracing::debug!("no function found for {function_name}");
298            return Ok(None);
299        };
300
301        let vars = resolve_function_variables(db, fie)?;
302
303        let base_addr = crate::index::debug_index(db, self.binary)
304            .symbol_index(db)
305            .get_function(&function_name)
306            .context("Failed to get base address for function")?
307            .address;
308
309        let fie = fie.data(db);
310        // Check parameters first
311        if let Some(param) = vars
312            .params
313            .into_iter()
314            .find(|var| var.name.as_deref() == Some(name))
315        {
316            tracing::info!(
317                "Found parameter {name} in function {function_name} with type: {}",
318                param.ty.display_name()
319            );
320            return variable_info(db, fie.declaration_die, base_addr, param, data_resolver)
321                .map(Some);
322        }
323
324        // Then check locals
325        if let Some(local) = vars
326            .locals
327            .into_iter()
328            .find(|var| var.name.as_deref() == Some(name))
329        {
330            tracing::info!(
331                "Found variable {name} in function {function_name} with type: {}",
332                local.ty.display_name()
333            );
334            return variable_info(db, fie.declaration_die, base_addr, local, data_resolver)
335                .map(Some);
336        }
337
338        Ok(None)
339    }
340
341    /// Gets metadata for all variables at a memory address without reading their values.
342    ///
343    /// This method returns three categories of variables:
344    /// - Function parameters
345    /// - Local variables  
346    /// - Global variables
347    ///
348    /// # Arguments
349    ///
350    /// * `address` - The memory address to inspect
351    /// * `data_resolver` - Interface for reading memory and register values
352    ///
353    /// # Returns
354    ///
355    /// A tuple of (parameters, locals, globals)
356    ///
357    /// # Examples
358    ///
359    /// ```no_run
360    /// # use rudy_db::{DebugDb, DebugInfo, DataResolver};
361    /// # struct MyResolver;
362    /// # impl DataResolver for MyResolver { }
363    /// # let db = DebugDb::new();
364    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
365    /// # let resolver = MyResolver;
366    /// let (params, locals, globals) = debug_info
367    ///     .get_all_variables_at_pc(0x12345, &resolver)
368    ///     .unwrap();
369    /// println!("Found {} parameters, {} locals, {} globals",
370    ///          params.len(), locals.len(), globals.len());
371    /// ```
372    pub fn get_all_variables_at_pc(
373        &self,
374        address: u64,
375        data_resolver: &dyn crate::DataResolver,
376    ) -> Result<(
377        Vec<crate::VariableInfo>,
378        Vec<crate::VariableInfo>,
379        Vec<crate::VariableInfo>,
380    )> {
381        let db = self.db;
382        let f = lookup_address(db, self.binary, address);
383
384        let Some((function_name, loc)) = f else {
385            tracing::debug!("no function found for address {address:#x}");
386            return Ok(Default::default());
387        };
388
389        let index = crate::index::debug_index(db, self.binary);
390        let Some((_, fie)) = index.get_function(db, &function_name) else {
391            tracing::debug!("no function found for {function_name}");
392            return Ok(Default::default());
393        };
394
395        let vars = resolve_function_variables(db, fie)?;
396
397        let base_addr = crate::index::debug_index(db, self.binary)
398            .symbol_index(db)
399            .get_function(&function_name)
400            .context("Failed to get base address for function")?
401            .address;
402
403        let fie = fie.data(db);
404        let params = vars
405            .params
406            .into_iter()
407            .map(|param| variable_info(db, fie.declaration_die, base_addr, param, data_resolver))
408            .collect::<Result<Vec<_>>>()?;
409
410        let locals = vars
411            .locals
412            .into_iter()
413            .filter(|var| {
414                // for local variables, we want to make sure the variable
415                // is defined before the current location
416                var.location
417                    .as_ref()
418                    .is_some_and(|var_loc| loc.line > var_loc.line)
419            })
420            .map(|local| variable_info(db, fie.declaration_die, base_addr, local, data_resolver))
421            .collect::<Result<Vec<_>>>()?;
422
423        // TODO: handle globals
424        Ok((params, locals, vec![]))
425    }
426
427    /// Resolve a type by name in the debug information
428    ///
429    /// Note: The type name _must_ be fully qualified, e.g., "alloc::string::String",
430    /// and must include any generic parameters if applicable (e.g., "alloc::vec::Vec<u8>").
431    ///
432    /// Where possible it's better to find a variable at an address, and then
433    /// get the type of the variable.
434    ///
435    /// # Arguments
436    ///
437    /// * `type_name` - The name of the type to resolve
438    ///
439    /// # Returns
440    ///
441    /// The resolved type definition if found, or `None` if the type cannot be found
442    ///
443    /// # Examples
444    ///
445    /// ```no_run
446    /// # use rudy_db::{DebugDb, DebugInfo};
447    /// # let db = DebugDb::new();
448    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
449    /// if let Some(typedef) = debug_info.lookup_type_by_name("alloc::string::String").unwrap() {
450    ///     println!("Found String type: {}", typedef.display_name());
451    /// }
452    /// ```
453    pub fn lookup_type_by_name(&self, type_name: &str) -> Result<Option<DieTypeDefinition>> {
454        crate::index::resolve_type(self.db, self.binary, type_name)
455    }
456
457    /// Read a value from memory using type information
458    ///
459    /// # Arguments
460    ///
461    /// * `address` - The memory address to read from
462    /// * `typed_pointer` - TODO
463    /// * `data_resolver` - Interface for reading memory and register values
464    ///
465    /// # Returns
466    ///
467    /// The interpreted value from memory
468    /// ```
469    pub fn read_pointer(
470        &self,
471        typed_pointer: &TypedPointer,
472        data_resolver: &dyn crate::DataResolver,
473    ) -> Result<crate::Value> {
474        let TypedPointer { address, type_def } = typed_pointer;
475        crate::data::read_from_memory(self.db, *address, type_def, data_resolver)
476    }
477
478    /// Access a field of a struct/union/enum value
479    ///
480    /// # Arguments
481    ///
482    /// * `base_address` - Memory address of the base value
483    /// * `base_type` - Type definition of the base value
484    /// * `field_name` - Name of the field to access
485    ///
486    /// # Returns
487    ///
488    /// Variable information for the field if found
489    ///
490    /// # Examples
491    ///
492    /// ```no_run
493    /// # use rudy_db::{DebugDb, DebugInfo, VariableInfo};
494    /// # let db = DebugDb::new();
495    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
496    /// # let var_info: VariableInfo = unimplemented!();
497    /// if let Ok(field_info) = debug_info.get_struct_field(var_info.address.unwrap(), &var_info.type_def, "name") {
498    ///     println!("Field 'name' at address {:?}", field_info.address);
499    /// }
500    /// ```
501    pub fn get_struct_field(
502        &self,
503        base_address: u64,
504        base_type: &DieTypeDefinition,
505        field_name: &str,
506    ) -> Result<TypedPointer> {
507        match base_type.layout.as_ref() {
508            Layout::Struct(struct_def) => {
509                let field = struct_def
510                    .fields
511                    .iter()
512                    .find(|f| f.name == field_name)
513                    .ok_or_else(|| {
514                        anyhow::anyhow!(
515                            "Field '{}' not found in struct '{}'",
516                            field_name,
517                            struct_def.name
518                        )
519                    })?;
520
521                let field_address = base_address + field.offset as u64;
522                Ok(TypedPointer {
523                    address: field_address,
524                    type_def: field.ty.clone(),
525                })
526            }
527            Layout::Enum(enum_def) => {
528                // For enums, field access might be variant data access
529                // This is complex - for now return an error
530                Err(anyhow::anyhow!(
531                    "Enum field access not yet implemented for '{}'",
532                    enum_def.name
533                ))
534            }
535            _ => Err(anyhow::anyhow!(
536                "Cannot access field '{}' on type '{}'",
537                field_name,
538                base_type.display_name()
539            )),
540        }
541    }
542
543    /// Index into an array/slice/vector by integer index
544    ///
545    /// # Arguments
546    ///
547    /// * `base_address` - Memory address of the base array/slice/vector
548    /// * `base_type` - Type definition of the base value
549    /// * `index` - Integer index to access
550    /// * `data_resolver` - Interface for reading memory and register values
551    ///
552    /// # Returns
553    ///
554    /// Variable information for the element at the given index
555    ///
556    /// # Examples
557    ///
558    /// ```no_run
559    /// # use rudy_db::{DebugDb, DebugInfo, TypedPointer, VariableInfo, DataResolver};
560    /// # struct MyResolver;
561    /// # impl DataResolver for MyResolver { }
562    /// # let db = DebugDb::new();
563    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
564    /// # let resolver = MyResolver;
565    /// # let var_pointer: TypedPointer = unimplemented!();
566    /// if let Ok(element_info) = debug_info.index_array_or_slice(&var_pointer, 0, &resolver) {
567    ///     println!("Element 0 at address {:?}", element_info.address);
568    /// }
569    /// ```
570    pub fn index_array_or_slice(
571        &self,
572        type_pointer: &TypedPointer,
573        index: u64,
574        data_resolver: &dyn crate::DataResolver,
575    ) -> Result<TypedPointer> {
576        let TypedPointer {
577            address: base_address,
578            type_def: base_type,
579        } = type_pointer;
580        let base_address = *base_address;
581
582        match base_type.layout.as_ref() {
583            Layout::Primitive(PrimitiveLayout::Array(array_def)) => {
584                // Fixed-size array [T; N]
585                if index >= array_def.length as u64 {
586                    return Err(anyhow::anyhow!(
587                        "Index {} out of bounds for array of length {}",
588                        index,
589                        array_def.length
590                    ));
591                }
592
593                let element_size = array_def.element_type.size().with_context(|| {
594                    format!(
595                        "Failed to get size for array element type '{}'",
596                        array_def.element_type.display_name()
597                    )
598                })? as u64;
599                let element_address = base_address + (index * element_size);
600                Ok(TypedPointer {
601                    address: element_address,
602                    type_def: array_def.element_type.clone(),
603                })
604            }
605            Layout::Primitive(PrimitiveLayout::Slice(slice_def)) => {
606                // Slice [T] - need to read the fat pointer to get actual data pointer and length
607                let slice_value =
608                    crate::data::read_from_memory(self.db, base_address, base_type, data_resolver)?;
609                let (data_ptr, slice_len) = extract_slice_info(&slice_value)?;
610
611                if index >= slice_len {
612                    return Err(anyhow::anyhow!(
613                        "Index {} out of bounds for slice of length {}",
614                        index,
615                        slice_len
616                    ));
617                }
618
619                let element_size = slice_def.element_type.size().with_context(|| {
620                    format!(
621                        "Failed to get size for slice element type '{}'",
622                        slice_def.element_type.display_name()
623                    )
624                })? as u64;
625                let element_address = data_ptr + (index * element_size);
626
627                Ok(TypedPointer {
628                    address: element_address,
629                    type_def: slice_def.element_type.clone(),
630                })
631            }
632            Layout::Std(std_def) => match std_def {
633                StdLayout::Vec(vec_def) => {
634                    let (data_ptr, vec_len) =
635                        crate::data::extract_vec_info(base_address, vec_def, data_resolver)?;
636
637                    if index as usize >= vec_len {
638                        return Err(anyhow::anyhow!(
639                            "Index {} out of bounds for Vec of length {}",
640                            index,
641                            vec_len
642                        ));
643                    }
644
645                    let element_size = vec_def.inner_type.size().with_context(|| {
646                        format!(
647                            "Failed to get size for Vec element type '{}'",
648                            vec_def.inner_type.display_name()
649                        )
650                    })? as u64;
651                    let element_address = data_ptr + (index * element_size);
652                    Ok(TypedPointer {
653                        address: element_address,
654                        type_def: vec_def.inner_type.clone(),
655                    })
656                }
657                _ => Err(anyhow::anyhow!(
658                    "Cannot index std type '{}' by integer",
659                    base_type.display_name()
660                )),
661            },
662            _ => Err(anyhow::anyhow!(
663                "Cannot index type '{}' by integer",
664                base_type.display_name()
665            )),
666        }
667    }
668
669    /// Index into a map/dictionary by value key
670    ///
671    /// # Arguments
672    ///
673    /// * `base_address` - Memory address of the base map
674    /// * `base_type` - Type definition of the base map
675    /// * `key` - Key value to look up
676    /// * `data_resolver` - Interface for reading memory and register values
677    ///
678    /// # Returns
679    ///
680    /// Variable information for the value at the given key
681    ///
682    /// # Examples
683    ///
684    /// ```no_run
685    /// # use rudy_db::{DebugDb, DebugInfo, VariableInfo, DataResolver, Value};
686    /// # struct MyResolver;
687    /// # impl DataResolver for MyResolver { }
688    /// # let db = DebugDb::new();
689    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
690    /// # let resolver = MyResolver;
691    /// # let var_info: VariableInfo = unimplemented!();
692    /// # let key: Value = unimplemented!();
693    /// if let Ok(value_info) = debug_info.index_map(var_info.address.unwrap(), &var_info.type_def, &key, &resolver) {
694    ///     println!("Map value at address {:?}", value_info.address);
695    /// }
696    /// ```
697    pub fn index_map(
698        &self,
699        base_address: u64,
700        base_type: &DieTypeDefinition,
701        key: &crate::Value,
702        data_resolver: &dyn crate::DataResolver,
703    ) -> Result<TypedPointer> {
704        match base_type.layout.as_ref() {
705            Layout::Std(StdLayout::Map(map_def)) => {
706                // For maps, we'll iterate through all key-value pairs
707                // and return the variable info for the value that matches the key.
708                let map_entries =
709                    crate::data::read_map_entries(base_address, map_def, data_resolver)?;
710
711                for (k, v) in map_entries {
712                    let map_key = crate::data::read_from_memory(
713                        self.db,
714                        k.address,
715                        &k.type_def,
716                        data_resolver,
717                    )?;
718
719                    if values_equal(key, &map_key) {
720                        return Ok(TypedPointer {
721                            address: v.address,
722                            type_def: v.type_def.clone(),
723                        });
724                    }
725                }
726
727                Err(anyhow::anyhow!(
728                    "Key '{}' not found in map",
729                    format_value_key(key)
730                ))
731            }
732            _ => Err(anyhow::anyhow!(
733                "Value-based indexing not supported for type '{}'",
734                base_type.display_name()
735            )),
736        }
737    }
738
739    pub fn discover_all_methods(&self) -> Result<BTreeMap<String, Vec<DiscoveredMethod>>> {
740        crate::function_discovery::discover_all_methods(self.db, self.binary)
741    }
742
743    pub fn discover_all_methods_debug(&self) -> Result<BTreeMap<String, SymbolAnalysisResult>> {
744        crate::function_discovery::discover_all_functions_debug(self.db, self.binary)
745    }
746
747    pub fn discover_methods_for_pointer(
748        &self,
749        typed_pointer: &TypedPointer,
750    ) -> Result<Vec<DiscoveredMethod>> {
751        Ok(crate::function_discovery::discover_methods_for_type(
752            self.db,
753            self.binary,
754            &typed_pointer.type_def,
755        )?
756        .into_iter()
757        // filter out associated methods that are not self methods
758        .filter(|m| m.self_type.is_some())
759        .collect())
760    }
761
762    pub fn discover_methods_for_type(
763        &self,
764        type_def: &DieTypeDefinition,
765    ) -> Result<Vec<DiscoveredMethod>> {
766        crate::function_discovery::discover_methods_for_type(self.db, self.binary, type_def)
767    }
768
769    /// Discover functions in the binary that match a given pattern
770    ///
771    /// This method searches through all function symbols in the binary and returns
772    /// functions that match the provided pattern. It supports:
773    /// - Exact matches (e.g., "main")
774    /// - Fuzzy matches (e.g., "calc" matching "calculate_sum")
775    /// - Fully qualified names (e.g., "test_mod1::my_fn")
776    ///
777    /// # Arguments
778    ///
779    /// * `pattern` - The pattern to match against function names
780    ///
781    /// # Returns
782    ///
783    /// A vector of discovered functions sorted by match quality (exact matches first)
784    ///
785    /// # Examples
786    ///
787    /// ```no_run
788    /// # use rudy_db::{DebugDb, DebugInfo};
789    /// # let db = DebugDb::new();
790    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
791    /// // Find all functions containing "main"
792    /// let functions = debug_info.discover_functions("main").unwrap();
793    /// for func in functions {
794    ///     println!("Found function: {} at address {:#x}", func.name, func.address);
795    /// }
796    /// ```
797    pub fn discover_functions(&self, pattern: &str) -> Result<Vec<crate::DiscoveredFunction>> {
798        let pattern = SymbolName::parse(pattern)
799            .with_context(|| format!("Failed to parse function pattern: {pattern}"))?;
800        crate::function_discovery::discover_functions(self.db, self.binary, &pattern)
801    }
802
803    /// Discover all functions in the binary
804    ///
805    /// Returns a map of function name to discovered function information.
806    /// This includes both functions with debug information and those without.
807    ///
808    /// # Returns
809    ///
810    /// A map of function name to discovered function information
811    ///
812    /// # Examples
813    ///
814    /// ```no_run
815    /// # use rudy_db::{DebugDb, DebugInfo};
816    /// # let db = DebugDb::new();
817    /// # let debug_info = DebugInfo::new(&db, "binary").unwrap();
818    /// let all_functions = debug_info.discover_all_functions().unwrap();
819    /// println!("Found {} functions in binary", all_functions.len());
820    /// for (name, func) in all_functions {
821    ///     println!("Function: {} -> {}", name, func.signature);
822    /// }
823    /// ```
824    pub fn discover_all_functions(&self) -> Result<BTreeMap<String, crate::DiscoveredFunction>> {
825        crate::function_discovery::discover_all_functions(self.db, self.binary)
826    }
827
828    /// Create a typed value in the target process based on the target type.
829    ///
830    /// This method uses DWARF type information to determine the correct conversion
831    /// strategy for creating values that match function parameter types.
832    ///
833    /// # Arguments
834    ///
835    /// * `source_value` - The source value to convert (e.g., string literal, number)
836    /// * `target_type` - The target type definition from DWARF
837    /// * `data_resolver` - DataResolver for memory allocation and writing
838    ///
839    /// # Returns
840    ///
841    /// The address where the typed value was created in target memory
842    pub fn create_typed_value(
843        &self,
844        source_value: &str,
845        target_type: &DieTypeDefinition,
846        data_resolver: &dyn crate::DataResolver,
847    ) -> Result<u64> {
848        match target_type.layout.as_ref() {
849            Layout::Primitive(PrimitiveLayout::StrSlice(str_slice_layout)) => {
850                // Create &str fat pointer using the actual layout
851                self.create_str_slice_with_layout(source_value, str_slice_layout, data_resolver)
852            }
853            Layout::Std(StdLayout::String(string_layout)) => {
854                // Create owned String using the actual layout
855                self.create_owned_string_with_layout(source_value, string_layout, data_resolver)
856            }
857            _ => Err(anyhow::anyhow!(
858                "Cannot convert string literal '{}' to type '{}'. Only &str and String are currently supported.",
859                source_value,
860                target_type.display_name()
861            )),
862        }
863    }
864
865    /// Create a Rust string slice (&str) in the target process using the actual layout
866    fn create_str_slice_with_layout(
867        &self,
868        value: &str,
869        layout: &rudy_types::StrSliceLayout,
870        data_resolver: &dyn crate::DataResolver,
871    ) -> Result<u64> {
872        let bytes = value.as_bytes();
873        let data_size = bytes.len();
874
875        // Log the layout for debugging
876        tracing::debug!(
877            "Creating &str '{}' using layout: data_ptr_offset={}, length_offset={}",
878            value,
879            layout.data_ptr_offset,
880            layout.length_offset
881        );
882
883        // Validate layout offsets are reasonable for a &str (should be 0 and 8 typically)
884        if layout.data_ptr_offset > 16 || layout.length_offset > 16 {
885            return Err(anyhow::anyhow!(
886                "Invalid StrSliceLayout: data_ptr_offset={}, length_offset={}. Expected offsets <= 16.",
887                layout.data_ptr_offset,
888                layout.length_offset
889            ));
890        }
891
892        // &str is typically 16 bytes (8-byte pointer + 8-byte length)
893        let str_slice_size = 16;
894        let total_size = data_size + str_slice_size;
895
896        // Allocate memory for both the string data and the fat pointer
897        let base_addr = data_resolver.allocate_memory(total_size)?;
898
899        // Write the string data first
900        let data_addr = base_addr;
901        data_resolver.write_memory(data_addr, bytes)?;
902
903        // Create the fat pointer at the end of the allocated memory using actual offsets
904        let fat_ptr_addr = base_addr + data_size as u64;
905
906        tracing::debug!(
907            "Memory layout: base_addr={:#x}, data_addr={:#x}, fat_ptr_addr={:#x}, data_size={}",
908            base_addr,
909            data_addr,
910            fat_ptr_addr,
911            data_size
912        );
913
914        // Write the data pointer at the correct offset
915        let data_ptr_addr = fat_ptr_addr + layout.data_ptr_offset as u64;
916        tracing::debug!(
917            "Writing data pointer {:#x} to address {:#x} (fat_ptr_addr + {})",
918            data_addr,
919            data_ptr_addr,
920            layout.data_ptr_offset
921        );
922        data_resolver.write_memory(data_ptr_addr, &data_addr.to_le_bytes())?;
923
924        // Write the length at the correct offset
925        let length_addr = fat_ptr_addr + layout.length_offset as u64;
926        tracing::debug!(
927            "Writing length {} to address {:#x} (fat_ptr_addr + {})",
928            data_size,
929            length_addr,
930            layout.length_offset
931        );
932        data_resolver.write_memory(length_addr, &(data_size as u64).to_le_bytes())?;
933
934        // Validate the created &str by reading it back
935        tracing::debug!(
936            "Created &str at {:#x}: data_ptr at offset {}, length {} at offset {}",
937            fat_ptr_addr,
938            layout.data_ptr_offset,
939            data_size,
940            layout.length_offset
941        );
942
943        // Read back the pointer and length to validate
944        let read_data_ptr_bytes = data_resolver.read_memory(data_ptr_addr, 8)?;
945        let read_length_bytes = data_resolver.read_memory(length_addr, 8)?;
946        let read_data_ptr = u64::from_le_bytes(read_data_ptr_bytes.try_into().unwrap());
947        let read_length = u64::from_le_bytes(read_length_bytes.try_into().unwrap());
948
949        tracing::debug!(
950            "Validation: &str at {:#x} -> data_ptr={:#x}, length={}",
951            fat_ptr_addr,
952            read_data_ptr,
953            read_length
954        );
955
956        // Sanity check the values
957        if read_data_ptr != data_addr {
958            return Err(anyhow::anyhow!(
959                "Invalid &str: data pointer mismatch. Expected {:#x}, got {:#x}",
960                data_addr,
961                read_data_ptr
962            ));
963        }
964        if read_length != data_size as u64 {
965            return Err(anyhow::anyhow!(
966                "Invalid &str: length mismatch. Expected {}, got {}",
967                data_size,
968                read_length
969            ));
970        }
971        if read_length > 1024 * 1024 {
972            // Sanity check: strings > 1MB are suspicious
973            return Err(anyhow::anyhow!(
974                "Invalid &str: length {} is suspiciously large (> 1MB)",
975                read_length
976            ));
977        }
978
979        // Final validation: read the actual string data to make sure it's correct
980        let read_string_data = data_resolver.read_memory(read_data_ptr, read_length as usize)?;
981        let read_string = String::from_utf8_lossy(&read_string_data);
982        tracing::debug!(
983            "Final validation: &str points to data '{}' (expected '{}')",
984            read_string,
985            value
986        );
987
988        if read_string != value {
989            return Err(anyhow::anyhow!(
990                "Invalid &str: string data mismatch. Expected '{}', got '{}'",
991                value,
992                read_string
993            ));
994        }
995
996        Ok(fat_ptr_addr)
997    }
998
999    /// Create an owned String in the target process using the actual DWARF layout
1000    fn create_owned_string_with_layout(
1001        &self,
1002        value: &str,
1003        string_layout: &rudy_types::StringLayout<rudy_dwarf::Die>,
1004        data_resolver: &dyn crate::DataResolver,
1005    ) -> Result<u64> {
1006        use rudy_types::Layout;
1007
1008        tracing::debug!("Creating owned String with layout for value: '{}'", value);
1009
1010        let vec_layout = &string_layout.0; // StringLayout(VecLayout)
1011        let string_len = value.len();
1012
1013        // Step 1: Allocate memory for the string content (heap data)
1014        let content_addr = data_resolver.allocate_memory(string_len)?;
1015        tracing::debug!("Allocated string content at: {:#x}", content_addr);
1016
1017        // Step 2: Write the string bytes to the content memory
1018        data_resolver.write_memory(content_addr, value.as_bytes())?;
1019        tracing::debug!("Wrote {} bytes of string content", string_len);
1020
1021        // Step 3: Calculate the size of the String struct from the layout
1022        let string_struct_size = Layout::Std(rudy_types::StdLayout::String(string_layout.clone()))
1023            .size()
1024            .ok_or_else(|| anyhow::anyhow!("Could not determine String struct size from layout"))?;
1025
1026        // Step 4: Allocate memory for the String struct itself
1027        let string_addr = data_resolver.allocate_memory(string_struct_size)?;
1028        tracing::debug!(
1029            "Allocated String struct at: {:#x} (size: {} bytes)",
1030            string_addr,
1031            string_struct_size
1032        );
1033
1034        // Step 5: Zero out the struct memory first
1035        let zero_bytes = vec![0u8; string_struct_size];
1036        data_resolver.write_memory(string_addr, &zero_bytes)?;
1037
1038        // Step 6: Populate the String struct fields using the layout offsets
1039
1040        // Write the length field (vec.len)
1041        let len_bytes = (string_len as u64).to_le_bytes();
1042        data_resolver.write_memory(string_addr + vec_layout.length_offset as u64, &len_bytes)?;
1043        tracing::debug!(
1044            "Set length field at offset {:#x} to {}",
1045            vec_layout.length_offset,
1046            string_len
1047        );
1048
1049        // Write the data pointer field (vec.buf.inner.ptr.pointer)
1050        let ptr_bytes = content_addr.to_le_bytes();
1051        data_resolver.write_memory(string_addr + vec_layout.data_ptr_offset as u64, &ptr_bytes)?;
1052        tracing::debug!(
1053            "Set data pointer field at offset {:#x} to {:#x}",
1054            vec_layout.data_ptr_offset,
1055            content_addr
1056        );
1057
1058        // Write the capacity field (vec.buf.inner.cap.__0)
1059        // For simplicity, set capacity equal to length
1060        let cap_bytes = (string_len as u64).to_le_bytes();
1061        data_resolver.write_memory(string_addr + vec_layout.capacity_offset as u64, &cap_bytes)?;
1062        tracing::debug!(
1063            "Set capacity field at offset {:#x} to {}",
1064            vec_layout.capacity_offset,
1065            string_len
1066        );
1067
1068        // Note: Other fields (allocator, phantom data, etc.) are left as zero,
1069        // which should be appropriate for the Global allocator and PhantomData
1070
1071        tracing::debug!(
1072            "Successfully created String at {:#x} pointing to data at {:#x}",
1073            string_addr,
1074            content_addr
1075        );
1076
1077        Ok(string_addr)
1078    }
1079}
1080
1081fn variable_info(
1082    db: &dyn Db,
1083    function: Die,
1084    base_address: u64,
1085    var: rudy_dwarf::function::Variable,
1086    data_resolver: &dyn crate::DataResolver,
1087) -> Result<crate::VariableInfo> {
1088    let die = var.origin;
1089    let location = rudy_dwarf::expressions::resolve_data_location(
1090        db,
1091        function,
1092        base_address,
1093        die,
1094        &crate::data::DataResolverExpressionContext(data_resolver),
1095    )?;
1096
1097    tracing::debug!("variable info: {:?} at {:?}", var.name, location);
1098
1099    let ty = var.ty;
1100    let type_def = match ty.layout.as_ref() {
1101        Layout::Alias { .. } => {
1102            // For type aliases, resolve the actual type
1103            resolve_type_offset(db, ty.location).context("Failed to resolve type alias")?
1104        }
1105        _ => ty.clone(),
1106    };
1107
1108    Ok(crate::VariableInfo {
1109        name: var
1110            .name
1111            .as_ref()
1112            .map_or_else(|| "_".to_string(), |s| s.to_string()),
1113        address: location,
1114        type_def,
1115    })
1116}
1117
1118/// Extract pointer and length from a slice Value
1119fn extract_slice_info(slice_value: &crate::Value) -> Result<(u64, u64)> {
1120    match slice_value {
1121        crate::Value::Struct { fields, .. } => {
1122            // Look for ptr/data_ptr and len fields
1123            let ptr = fields
1124                .get("data_ptr")
1125                .or_else(|| fields.get("ptr"))
1126                .ok_or_else(|| anyhow::anyhow!("Slice missing data pointer field"))?;
1127
1128            let len = fields
1129                .get("len")
1130                .or_else(|| fields.get("length"))
1131                .ok_or_else(|| anyhow::anyhow!("Slice missing length field"))?;
1132
1133            let ptr_value = extract_numeric_value(ptr)?;
1134            let len_value = extract_numeric_value(len)?;
1135
1136            Ok((ptr_value, len_value))
1137        }
1138        _ => Err(anyhow::anyhow!(
1139            "Expected struct representation for slice, got: {:?}",
1140            slice_value
1141        )),
1142    }
1143}
1144
1145/// Extract a numeric value from a Value (handles various scalar representations)
1146fn extract_numeric_value(value: &crate::Value) -> Result<u64> {
1147    match value {
1148        crate::Value::Scalar { value, .. } => {
1149            // Try to parse as different number formats
1150            if let Ok(num) = value.parse::<u64>() {
1151                Ok(num)
1152            } else if let Some(hex_value) = value.strip_prefix("0x") {
1153                u64::from_str_radix(hex_value, 16)
1154                    .with_context(|| format!("Failed to parse hex value: {value}"))
1155            } else {
1156                Err(anyhow::anyhow!("Could not parse numeric value: {}", value))
1157            }
1158        }
1159        _ => Err(anyhow::anyhow!("Expected scalar value, got: {:?}", value)),
1160    }
1161}
1162
1163/// Compare two Values for equality (approximate, for HashMap key matching)
1164fn values_equal(a: &crate::Value, b: &crate::Value) -> bool {
1165    match (a, b) {
1166        (crate::Value::Scalar { value: a_val, .. }, crate::Value::Scalar { value: b_val, .. }) => {
1167            // For strings, compare the actual string content (strip quotes if present)
1168            let a_clean = a_val.trim_matches('"');
1169            let b_clean = b_val.trim_matches('"');
1170            a_clean == b_clean
1171        }
1172        // For more complex types, could add more sophisticated comparison
1173        _ => false,
1174    }
1175}
1176
1177/// Format a Value as a key for display purposes
1178fn format_value_key(value: &crate::Value) -> String {
1179    match value {
1180        crate::Value::Scalar { value, .. } => {
1181            // Strip quotes for cleaner display
1182            value.trim_matches('"').to_string()
1183        }
1184        _ => format!("{value:?}"),
1185    }
1186}