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}