rudy_db/
data.rs

1//! Data resolver trait for reading variables from memory during debugging.
2
3use std::collections::BTreeMap;
4
5use anyhow::{Context, Result};
6use rudy_dwarf::{Die, types::DieTypeDefinition};
7use rudy_types::{
8    ArrayLayout, BTreeNodeLayout, CEnumLayout, EnumLayout, Layout, MapLayout, MapVariant,
9    OptionLayout, PointerLayout, PrimitiveLayout, ReferenceLayout, SliceLayout, SmartPtrVariant,
10    StdLayout, StrSliceLayout, VecLayout,
11};
12
13use crate::{Value, database::Db, outputs::TypedPointer};
14
15/// Trait for resolving data from memory during debugging.
16///
17/// Implementors provide access to the target process's memory and registers,
18/// allowing the debug info library to read variable values and follow pointers.
19///
20/// NOTE: All of the addresses passed to/return from this trait will be relative
21/// to the target _binary_ and does not account for ASLR (Address Space Layout Randomization).
22pub trait DataResolver {
23    /// Reads raw bytes from memory at the given address.
24    ///
25    /// # Arguments
26    ///
27    /// * `address` - The memory address to read from
28    /// * `size` - Number of bytes to read
29    ///
30    /// # Returns
31    ///
32    /// The bytes read from memory
33    fn read_memory(&self, address: u64, size: usize) -> Result<Vec<u8>> {
34        Err(anyhow::anyhow!(
35            "read_memory({address:#x}, {size}) not implemented for this DataResolver",
36        ))
37    }
38
39    /// Reads a 64-bit address from memory.
40    ///
41    /// This method handles pointer dereferencing.
42    ///
43    /// # Arguments
44    ///
45    /// * `address` - The memory address to read the pointer from
46    ///
47    /// # Returns
48    ///
49    /// The dereferenced address
50    fn read_address(&self, address: u64) -> Result<u64> {
51        let data = self.read_memory(address, std::mem::size_of::<u64>())?;
52        if data.len() != std::mem::size_of::<u64>() {
53            return Err(anyhow::anyhow!("Failed to read address"));
54        }
55        let addr = u64::from_le_bytes(data.try_into().unwrap());
56        tracing::trace!("read raw address: {addr:#x}");
57        Ok(addr)
58    }
59    /// Gets a specific register value by index.
60    ///
61    /// # Arguments
62    ///
63    /// * `idx` - The register index (architecture-specific)
64    ///
65    /// # Returns
66    ///
67    /// The register value
68    fn get_register(&self, idx: usize) -> Result<u64> {
69        Err(anyhow::anyhow!(
70            "get_register({idx}) not implemented for this DataResolver"
71        ))
72    }
73
74    fn get_stack_pointer(&self) -> Result<u64> {
75        Err(anyhow::anyhow!("get_stack_pointer() not implemented"))
76    }
77
78    /// Allocates memory in the target process.
79    ///
80    /// # Arguments
81    ///
82    /// * `size` - Number of bytes to allocate
83    ///
84    /// # Returns
85    ///
86    /// The address of the allocated memory
87    fn allocate_memory(&self, size: usize) -> Result<u64> {
88        Err(anyhow::anyhow!(
89            "allocate_memory({size:#x}) not implemented"
90        ))
91    }
92
93    /// Writes data to memory in the target process.
94    ///
95    /// # Arguments
96    ///
97    /// * `address` - The memory address to write to
98    /// * `data` - The bytes to write
99    fn write_memory(&self, address: u64, data: &[u8]) -> Result<()> {
100        let _ = data;
101        Err(anyhow::anyhow!(
102            "write_memory({address:#x}, &[..]) not implemented"
103        ))
104    }
105}
106
107pub(crate) struct DataResolverExpressionContext<'a, T: ?Sized>(pub &'a T);
108
109impl<'a, R: DataResolver + ?Sized> rudy_dwarf::expressions::ExpressionContext
110    for DataResolverExpressionContext<'a, R>
111{
112    fn get_register(&self, register: u16) -> Result<u64> {
113        self.0.get_register(register as usize)
114    }
115
116    fn get_stack_pointer(&self) -> Result<u64> {
117        self.0.get_stack_pointer()
118    }
119}
120
121/// Returns a list of map entries from a memory address.
122pub fn read_map_entries(
123    address: u64,
124    def: &MapLayout<Die>,
125    data_resolver: &dyn crate::DataResolver,
126) -> Result<Vec<(TypedPointer, TypedPointer)>> {
127    tracing::trace!("read_map_entries {address:#x} {}", def.display_name());
128
129    match def.variant.clone() {
130        MapVariant::HashMap {
131            bucket_mask_offset,
132            ctrl_offset,
133            items_offset,
134            pair_size,
135            key_offset,
136            value_offset,
137        } => {
138            let bucket_mask_address = address + bucket_mask_offset as u64;
139            let ctrl_address = address + ctrl_offset as u64;
140            let items_address = address + items_offset as u64;
141            // Read item count
142            let items = data_resolver.read_memory(items_address, 8)?;
143            let items = usize::from_le_bytes(items.try_into().unwrap());
144
145            if items == 0 {
146                return Ok(vec![]);
147            }
148
149            tracing::trace!(
150                "reading HashMap at {address:#x}, items: {items}, bucket_mask_addr: {bucket_mask_address:#x}, ctrl_addr: {ctrl_address:#x}"
151            );
152
153            // Read bucket mask to get capacity
154            let bucket_mask = data_resolver.read_memory(bucket_mask_address, 8)?;
155            let capacity = usize::from_le_bytes(bucket_mask.try_into().unwrap()) + 1;
156
157            // Read control bytes pointer
158            let ctrl_ptr = data_resolver.read_address(ctrl_address)?;
159
160            tracing::trace!(
161                "HashMap capacity: {capacity}, ctrl_ptr: {ctrl_ptr:#x}, items: {items}"
162            );
163
164            // Calculate size of key-value pair
165            // Data starts BEFORE the control bytes, counting backwards!
166            let mut slot_addr = ctrl_ptr - pair_size as u64;
167
168            // Read control bytes
169            let ctrl_bytes = data_resolver.read_memory(ctrl_ptr, capacity)?;
170
171            let mut entries = Vec::new();
172
173            for &ctrl in &ctrl_bytes {
174                if ctrl < 0x80 {
175                    // Occupied slot
176                    let key = TypedPointer {
177                        address: slot_addr + key_offset as u64,
178                        type_def: def.key_type.clone(),
179                    };
180                    let value = TypedPointer {
181                        address: slot_addr + value_offset as u64,
182                        type_def: def.value_type.clone(),
183                    };
184
185                    entries.push((key, value));
186
187                    // Stop if we've found all items
188                    if entries.len() >= items {
189                        break;
190                    }
191                }
192                // decrement address for the next slot
193                slot_addr -= pair_size as u64;
194            }
195            Ok(entries)
196        }
197        MapVariant::BTreeMap {
198            length_offset,
199            root_offset,
200            root_layout,
201            node_layout,
202        } => {
203            // Read the length field
204            let length_addr = address + length_offset as u64;
205            let length_bytes = data_resolver.read_memory(length_addr, 8)?;
206            let length = usize::from_le_bytes(length_bytes.try_into().unwrap());
207
208            tracing::trace!("BTreeMap at {address:#x}, length: {length}");
209
210            if length == 0 {
211                return Ok(vec![]);
212            }
213
214            // Read the root field (Option<Root>)
215            let root_addr = address + root_offset as u64;
216
217            // Check if the Option is Some (non-zero discriminant)
218            let discriminant_bytes = data_resolver.read_memory(root_addr, 8)?;
219            let discriminant = u64::from_le_bytes(discriminant_bytes.try_into().unwrap());
220
221            if discriminant == 0 {
222                // None variant - empty map
223                return Ok(vec![]);
224            }
225
226            // Read the root node pointer and height
227            let node_ptr_addr = root_addr + root_layout.node_offset as u64;
228            let height_addr = root_addr + root_layout.height_offset as u64;
229
230            let node_ptr = data_resolver.read_address(node_ptr_addr)?;
231            let height_bytes = data_resolver.read_memory(height_addr, 8)?;
232            let height = usize::from_le_bytes(height_bytes.try_into().unwrap());
233
234            tracing::trace!("BTreeMap root node: {node_ptr:#x}, height: {height}");
235
236            // Traverse the tree starting from the root
237            let mut entries = Vec::new();
238            read_btree_node_entries(
239                node_ptr,
240                height,
241                &def.key_type,
242                &def.value_type,
243                &node_layout,
244                data_resolver,
245                &mut entries,
246            )?;
247
248            Ok(entries)
249        }
250        MapVariant::IndexMap => {
251            unimplemented!(
252                "read_std_from_memory: MapVariant::IndexMap not implemented yet: {def:#?}"
253            )
254        }
255    }
256}
257
258fn read_enum(
259    db: &dyn Db,
260    address: u64,
261    enum_def: &EnumLayout<Die>,
262    data_resolver: &dyn crate::DataResolver,
263) -> Result<Value> {
264    tracing::trace!("read_enum {address:#x} {enum_def:#?}");
265
266    let EnumLayout {
267        name,
268        variants,
269        discriminant,
270        ..
271    } = enum_def;
272
273    let disc_offset = discriminant.offset as u64;
274    let disc_address = address + disc_offset;
275
276    let explicit_disc = !matches!(discriminant.ty, rudy_types::DiscriminantType::Implicit);
277
278    let disc_value = match &discriminant.ty {
279        rudy_types::DiscriminantType::Int(int_def) => {
280            let memory = data_resolver.read_memory(disc_address, int_def.size)?;
281
282            match int_def.size {
283                1 => i8::from_le_bytes(memory.try_into().unwrap()) as i128,
284                2 => i16::from_le_bytes(memory.try_into().unwrap()) as i128,
285                4 => i32::from_le_bytes(memory.try_into().unwrap()) as i128,
286                8 => i64::from_le_bytes(memory.try_into().unwrap()) as i128,
287                _ => {
288                    anyhow::bail!(
289                        "read_primitive_from_memory: unsupported IntDef size {} at address {address:#x}",
290                        int_def.size
291                    )
292                }
293            }
294        }
295        rudy_types::DiscriminantType::UnsignedInt(unsigned_int_def) => {
296            let memory = data_resolver.read_memory(disc_address, unsigned_int_def.size)?;
297            match unsigned_int_def.size {
298                1 => u8::from_le_bytes(memory.try_into().unwrap()) as i128,
299                2 => u16::from_le_bytes(memory.try_into().unwrap()) as i128,
300                4 => u32::from_le_bytes(memory.try_into().unwrap()) as i128,
301                8 => u64::from_le_bytes(memory.try_into().unwrap()) as i128,
302                _ => {
303                    anyhow::bail!(
304                        "read_primitive_from_memory: unsupported UnsignedIntDef size {} at address {address:#x}",
305                        unsigned_int_def.size
306                    )
307                }
308            }
309        }
310        rudy_types::DiscriminantType::Implicit => {
311            // I guess we'll just read 4 bytes and see what happens?
312            let memory = data_resolver.read_memory(disc_address + disc_offset, 4)?;
313            i32::from_le_bytes(memory.try_into().unwrap()) as i128
314        }
315    };
316
317    tracing::trace!("read_enum: at {disc_address:#x}, discriminant value: {disc_value}");
318
319    let maybe_variant = variants.iter().enumerate().find(|(i, v)| {
320        if explicit_disc {
321            // explicit discriminant, check against the value
322
323            v.discriminant == Some(disc_value)
324        } else {
325            // otherwise we'll assume the discriminant is just the index
326            *i as i128 == disc_value
327        }
328    });
329
330    let matching_variant = match (maybe_variant, explicit_disc) {
331        (Some((_, v)), _) => v,
332        (None, true) => {
333            // if we have _no_ discriminant, we assume this is the
334            // fancy niche optimization that Rust does to pack in values
335            variants.iter().find(|v| v.discriminant.is_none()).with_context(|| {
336                format!(
337                    "read_enum: No matching variant found for discriminant value {disc_value} in {name} with explicit discriminant"
338                )
339            })?
340        }
341        (None, false) => {
342            anyhow::bail!(
343                "read_enum: No matching variant found for discriminant value {disc_value} in {name} with implicit discriminant"
344            )
345        }
346    };
347
348    tracing::trace!("found matching variant: {matching_variant:#?}");
349
350    // read the inner value
351    let inner = read_from_memory(db, address, &matching_variant.layout, data_resolver)?;
352
353    // re-format the value based on what we get back
354    Ok(match inner {
355        Value::Struct { fields, .. } => {
356            if fields.is_empty() {
357                // unit variant
358                Value::Scalar {
359                    ty: format!("{name}::{}", matching_variant.name),
360                    value: matching_variant.name.to_string(),
361                }
362            } else if fields.keys().all(|k| k.starts_with("__")) {
363                // anonymous struct, likely a tuple variant
364                let values: Vec<_> = fields.into_values().collect();
365                Value::Tuple {
366                    ty: format!("{name}::{}", matching_variant.name),
367                    entries: values,
368                }
369            } else {
370                // struct variant
371                Value::Struct {
372                    ty: format!("{name}::{}", matching_variant.name),
373                    fields,
374                }
375            }
376        }
377        v => {
378            // unexpected, but we can at least return it
379            tracing::error!("read_enum: Expected a struct for enum variant, got: {v:#?}");
380            v
381        }
382    })
383}
384
385fn read_c_enum(
386    address: u64,
387    c_enum_def: &CEnumLayout,
388    data_resolver: &dyn crate::DataResolver,
389) -> Result<Value> {
390    let CEnumLayout {
391        name,
392        discriminant_type,
393        variants,
394        ..
395    } = c_enum_def;
396
397    let disc_value = match discriminant_type {
398        rudy_types::DiscriminantType::Int(int_def) => {
399            let memory = data_resolver.read_memory(address, int_def.size)?;
400
401            match int_def.size {
402                1 => i8::from_le_bytes(memory.try_into().unwrap()) as i128,
403                2 => i16::from_le_bytes(memory.try_into().unwrap()) as i128,
404                4 => i32::from_le_bytes(memory.try_into().unwrap()) as i128,
405                8 => i64::from_le_bytes(memory.try_into().unwrap()) as i128,
406                _ => {
407                    anyhow::bail!(
408                        "read_primitive_from_memory: unsupported IntDef size {} at address {address:#x}",
409                        int_def.size
410                    )
411                }
412            }
413        }
414        rudy_types::DiscriminantType::UnsignedInt(unsigned_int_def) => {
415            let memory = data_resolver.read_memory(address, unsigned_int_def.size)?;
416            match unsigned_int_def.size {
417                1 => u8::from_le_bytes(memory.try_into().unwrap()) as i128,
418                2 => u16::from_le_bytes(memory.try_into().unwrap()) as i128,
419                4 => u32::from_le_bytes(memory.try_into().unwrap()) as i128,
420                8 => u64::from_le_bytes(memory.try_into().unwrap()) as i128,
421                _ => {
422                    anyhow::bail!(
423                        "read_primitive_from_memory: unsupported UnsignedIntDef size {} at address {address:#x}",
424                        unsigned_int_def.size
425                    )
426                }
427            }
428        }
429        rudy_types::DiscriminantType::Implicit => {
430            anyhow::bail!("read_c_enum: Implicit discriminant type is not supported yet")
431        }
432    };
433
434    let matching_variant = variants
435        .iter()
436        .find_map(|v| (v.value == disc_value).then_some(&v.name))
437        .ok_or_else(|| {
438            anyhow::anyhow!(
439                "read_c_enum: No matching variant found for discriminant value {disc_value} in {name}"
440            )
441        })?;
442
443    Ok(Value::Scalar {
444        ty: format!("{name}::{matching_variant}"),
445        value: disc_value.to_string(),
446    })
447}
448
449pub fn read_from_memory(
450    db: &dyn Db,
451    address: u64,
452    ty: &DieTypeDefinition,
453    data_resolver: &dyn crate::DataResolver,
454) -> Result<Value> {
455    tracing::trace!("read_from_memory {address:#x} {}", ty.display_name());
456    match ty.layout.as_ref() {
457        Layout::Primitive(primitive_def) => {
458            read_primitive_from_memory(db, address, primitive_def, data_resolver)
459        }
460        Layout::Struct(struct_def) => {
461            let mut fields = BTreeMap::new();
462            for field in &struct_def.fields {
463                let field_name = &field.name;
464                let field_ty = &field.ty;
465                let field_address = address + (field.offset as u64);
466                // Return a pointer instead of recursively reading
467                let field_value = Value::Pointer(TypedPointer {
468                    address: field_address,
469                    type_def: field_ty.clone(),
470                });
471                fields.insert(field_name.to_string(), field_value);
472            }
473            Ok(Value::Struct {
474                ty: struct_def.name.clone(),
475                fields,
476            })
477        }
478        Layout::Std(std_def) => read_std_from_memory(db, address, std_def, data_resolver),
479        Layout::Enum(enum_def) => read_enum(db, address, enum_def, data_resolver),
480        Layout::CEnum(c_enum_def) => read_c_enum(address, c_enum_def, data_resolver),
481        Layout::Alias { .. } => {
482            // For aliases, we'll resolve the underlying type and read that
483            let underlying_type = rudy_dwarf::types::resolve_type_offset(db, ty.location)?;
484            // now read the memory itself
485            read_from_memory(db, address, &underlying_type, data_resolver)
486        }
487    }
488}
489
490/// Extract pointer, length, and capacity from a Vec Value
491pub fn extract_vec_info(
492    base_address: u64,
493    def: &VecLayout<Die>,
494    data_resolver: &dyn crate::DataResolver,
495) -> Result<(u64, usize)> {
496    let VecLayout {
497        length_offset,
498        data_ptr_offset,
499        ..
500    } = def;
501    let length = data_resolver
502        .read_memory(base_address + *length_offset as u64, 8)?
503        .try_into()
504        .map(usize::from_le_bytes)
505        .map_err(|_| {
506            anyhow::anyhow!("Failed to read length for Vec at address {base_address:#x}")
507        })?;
508    tracing::trace!("Vec length: {length}");
509    let address = data_resolver
510        .read_address(base_address + *data_ptr_offset as u64)
511        .with_context(|| {
512            format!(
513                "Failed to read Vec data pointer at {:#x}",
514                base_address + *data_ptr_offset as u64
515            )
516        })?;
517    Ok((address, length))
518}
519
520fn read_primitive_from_memory(
521    db: &dyn Db,
522    address: u64,
523    def: &PrimitiveLayout<Die>,
524    data_resolver: &dyn crate::DataResolver,
525) -> Result<Value> {
526    let value = match def {
527        PrimitiveLayout::Bool(_) => {
528            let memory = data_resolver.read_memory(address, 1)?;
529            let bool_value = memory[0] != 0;
530            Value::Scalar {
531                ty: "bool".to_string(),
532                value: bool_value.to_string(),
533            }
534        }
535        PrimitiveLayout::Char(()) => {
536            let memory = data_resolver.read_memory(address, 4)?;
537            let char_value = char::from_u32(u32::from_le_bytes(memory.try_into().unwrap()))
538                .ok_or_else(|| anyhow::anyhow!("Invalid char value at address {address:#x}"))?;
539            Value::Scalar {
540                ty: "char".to_string(),
541                value: format!("'{char_value}'"),
542            }
543        }
544        PrimitiveLayout::Function(function_def) => Value::Scalar {
545            ty: function_def.display_name(),
546            value: format!("fn at {address:#x}"),
547        },
548        PrimitiveLayout::Array(ArrayLayout {
549            element_type,
550            length,
551        }) => {
552            let element_type = resolve_alias(db, element_type)?;
553            let element_size = element_type.size().with_context(|| {
554                format!(
555                    "inner type: {} has unknown size",
556                    element_type.display_name()
557                )
558            })? as u64;
559
560            let mut address = address;
561            let mut values = Vec::with_capacity(*length);
562            for _ in 0..*length {
563                // Return pointers instead of recursively reading
564                let value = Value::Pointer(TypedPointer {
565                    address,
566                    type_def: element_type.clone(),
567                });
568                values.push(value);
569                address += element_size;
570            }
571            Value::Array {
572                ty: format!("[{}; {length}]", element_type.display_name()),
573                items: values,
574            }
575        }
576        PrimitiveLayout::Pointer(PointerLayout { pointed_type, .. }) => {
577            let address = data_resolver.read_address(address)?;
578            read_from_memory(db, address, pointed_type, data_resolver)?.prefix_type("*")
579        }
580        PrimitiveLayout::Reference(ReferenceLayout { pointed_type, .. }) => {
581            let address = data_resolver.read_address(address)?;
582            read_from_memory(db, address, pointed_type, data_resolver)?.prefix_type("&")
583        }
584        PrimitiveLayout::Slice(SliceLayout {
585            element_type,
586            data_ptr_offset,
587            length_offset,
588        }) => {
589            let length = address + *length_offset as u64;
590            let length_bytes = data_resolver.read_memory(length, 8)?;
591            let length = u64::from_le_bytes(length_bytes.try_into().unwrap());
592            tracing::trace!("length: {length}");
593
594            let element_type = resolve_alias(db, element_type)?;
595
596            let element_size = element_type.size().with_context(|| {
597                format!(
598                    "inner type: {} has unknown size",
599                    element_type.display_name()
600                )
601            })? as u64;
602            let data_ptr_address = data_resolver.read_address(address + *data_ptr_offset as u64)?;
603            let mut current_addr = data_ptr_address;
604            let mut values = Vec::with_capacity(length as usize);
605            for _ in 0..length {
606                // Return pointers instead of recursively reading
607                let value = Value::Pointer(TypedPointer {
608                    address: current_addr,
609                    type_def: element_type.clone(),
610                });
611                values.push(value);
612                current_addr += element_size;
613            }
614            Value::Array {
615                ty: format!("&[{}]", element_type.display_name()),
616                items: values,
617            }
618        }
619        PrimitiveLayout::StrSlice(StrSliceLayout {
620            data_ptr_offset,
621            length_offset,
622        }) => {
623            let data_ptr = address + *data_ptr_offset as u64;
624            let length = address + *length_offset as u64;
625
626            let data_address = data_resolver.read_address(data_ptr)?;
627            let memory = data_resolver.read_memory(length, 8)?;
628            let length = u64::from_le_bytes(memory.try_into().unwrap());
629            tracing::trace!("length: {length}");
630
631            let memory = data_resolver.read_memory(data_address, length as usize)?;
632            let string_value = String::from_utf8_lossy(&memory).to_string();
633            Value::Scalar {
634                ty: "str".to_string(),
635                value: format!("\"{string_value}\""),
636            }
637        }
638        PrimitiveLayout::UnsignedInt(unsigned_int_def) => {
639            let memory = data_resolver.read_memory(address, unsigned_int_def.size)?;
640
641            let num_string = match unsigned_int_def.size {
642                1 => u8::from_le_bytes(memory.try_into().unwrap()).to_string(),
643                2 => u16::from_le_bytes(memory.try_into().unwrap()).to_string(),
644                4 => u32::from_le_bytes(memory.try_into().unwrap()).to_string(),
645                8 => u64::from_le_bytes(memory.try_into().unwrap()).to_string(),
646                16 => u128::from_le_bytes(memory.try_into().unwrap()).to_string(),
647                _ => {
648                    anyhow::bail!(
649                        "read_primitive_from_memory: unsupported UnsignedIntDef size {} at address {address:#x}",
650                        unsigned_int_def.size
651                    )
652                }
653            };
654            Value::Scalar {
655                ty: unsigned_int_def.display_name(),
656                value: num_string,
657            }
658        }
659        PrimitiveLayout::Float(float_def) => {
660            let memory = data_resolver.read_memory(address, float_def.size)?;
661
662            let num_string = match float_def.size {
663                4 => f32::from_le_bytes(memory.try_into().unwrap()).to_string(),
664                8 => f64::from_le_bytes(memory.try_into().unwrap()).to_string(),
665                16 => {
666                    anyhow::bail!("f128 is not supported yet, found at address {address:#x}");
667                }
668                _ => {
669                    anyhow::bail!(
670                        "read_primitive_from_memory: unsupported FloatDef size {} at address {address:#x}",
671                        float_def.size
672                    )
673                }
674            };
675            Value::Scalar {
676                ty: format!("f{}", float_def.size * 8),
677                value: num_string,
678            }
679        }
680        PrimitiveLayout::Int(int_def) => {
681            let memory = data_resolver.read_memory(address, int_def.size)?;
682
683            let num_string = match int_def.size {
684                1 => i8::from_le_bytes(memory.try_into().unwrap()).to_string(),
685                2 => i16::from_le_bytes(memory.try_into().unwrap()).to_string(),
686                4 => i32::from_le_bytes(memory.try_into().unwrap()).to_string(),
687                8 => i64::from_le_bytes(memory.try_into().unwrap()).to_string(),
688                16 => i128::from_le_bytes(memory.try_into().unwrap()).to_string(),
689                _ => {
690                    anyhow::bail!(
691                        "read_primitive_from_memory: unsupported IntDef size {} at address {address:#x}",
692                        int_def.size
693                    )
694                }
695            };
696            Value::Scalar {
697                ty: int_def.display_name(),
698                value: num_string,
699            }
700        }
701        PrimitiveLayout::Never(_) => {
702            // The Never type is a zero-sized type, so we return a placeholder value.
703            Value::Scalar {
704                ty: "Never".to_string(),
705                value: "unreachable".to_string(),
706            }
707        }
708        PrimitiveLayout::Str(()) => {
709            unimplemented!("read_primitive_from_memory: bare `str` is not supported yet");
710        }
711        PrimitiveLayout::Tuple(tuple_def) => {
712            unimplemented!(
713                "read_primitive_from_memory: TupleDef not implemented yet: {tuple_def:#?}"
714            );
715        }
716        PrimitiveLayout::Unit(_) => {
717            // The Unit type is a zero-sized type, so we return a placeholder value.
718            Value::Scalar {
719                ty: "()".to_string(),
720                value: "()".to_string(),
721            }
722        }
723    };
724
725    Ok(value)
726}
727
728fn resolve_alias(db: &dyn Db, def: &DieTypeDefinition) -> Result<DieTypeDefinition> {
729    if let Layout::Alias { name } = def.layout.as_ref() {
730        rudy_dwarf::types::resolve_type_offset(db, def.location)
731            .with_context(|| format!("Failed to resolve alias for {name}"))
732    } else {
733        Ok(def.clone())
734    }
735}
736
737fn read_option_from_memory(
738    db: &dyn Db,
739    address: u64,
740    opt_def: &OptionLayout<Die>,
741    data_resolver: &dyn crate::DataResolver,
742) -> Result<Value> {
743    let OptionLayout {
744        discriminant,
745        some_offset,
746        some_type,
747        ..
748    } = opt_def;
749
750    let first_byte = data_resolver.read_memory(
751        address + opt_def.discriminant.offset as u64,
752        discriminant.size(),
753    )?;
754    Ok(if first_byte[0] == 0 {
755        Value::Scalar {
756            ty: some_type.display_name(),
757            value: "None".to_string(),
758        }
759    } else {
760        tracing::debug!("Found Some variant at {address:#x}: {some_type:#?}");
761        // we have a `Some` variant
762        // we should get the address of the inner value
763        read_from_memory(db, address + *some_offset as u64, some_type, data_resolver)?
764    }
765    .wrap_type("Option"))
766}
767
768fn read_std_from_memory(
769    db: &dyn Db,
770    address: u64,
771    def: &StdLayout<Die>,
772    data_resolver: &dyn crate::DataResolver,
773) -> Result<Value> {
774    let value = match def {
775        StdLayout::Option(enum_def) => {
776            tracing::trace!("reading Option at {address:#x}");
777            read_option_from_memory(db, address, enum_def, data_resolver)?
778        }
779        StdLayout::Vec(
780            v @ VecLayout {
781                length_offset,
782                data_ptr_offset,
783                inner_type,
784                ..
785            },
786        ) => {
787            tracing::trace!(
788                "reading Vec at {address:#x}, length_offset: {length_offset:#x}, data_ptr_offset: {data_ptr_offset:#x}",
789            );
790            let element_type = resolve_alias(db, inner_type)?;
791
792            let element_size = element_type.size().with_context(|| {
793                format!(
794                    "inner type: {} has unknown size",
795                    element_type.display_name()
796                )
797            })? as u64;
798
799            let (mut address, length) = extract_vec_info(address, v, data_resolver)?;
800            let mut values = Vec::with_capacity(length);
801            tracing::trace!("reading Vec data at {address:#016x}");
802            for _ in 0..length {
803                // Return pointers instead of recursively reading
804                let value = Value::Pointer(TypedPointer {
805                    address,
806                    type_def: element_type.clone(),
807                });
808                values.push(value);
809                address += element_size;
810            }
811            Value::Array {
812                ty: format!("Vec<{}>", element_type.display_name()),
813                items: values,
814            }
815        }
816        StdLayout::String(s) => {
817            let v = &s.0;
818            tracing::trace!(
819                "reading String length at {:#x}",
820                address + v.length_offset as u64
821            );
822            let length = data_resolver
823                .read_memory(address + v.length_offset as u64, 8)?
824                .try_into()
825                .map(usize::from_le_bytes)
826                .map_err(|_| {
827                    anyhow::anyhow!("Failed to read length for Vec at address {address:#x}")
828                })?;
829            tracing::trace!("String length: {length}");
830            // read the bytes from the data pointer
831            let data_address = address + s.0.data_ptr_offset as u64;
832            let data = data_resolver.read_address(data_address).with_context(|| {
833                format!("Failed to read String data pointer at {data_address:#x}")
834            })?;
835            tracing::trace!("reading String data at {data:#016x}");
836            let bytes = data_resolver.read_memory(data, length)?;
837            let value = String::from_utf8_lossy(&bytes).to_string();
838            Value::Scalar {
839                ty: "String".to_string(),
840                value: format!("\"{value}\""),
841            }
842        }
843        StdLayout::Map(def) => {
844            let entries = read_map_entries(address, def, data_resolver)?
845                .into_iter()
846                .map(|(key, value)| Ok((Value::Pointer(key), Value::Pointer(value))))
847                .collect::<Result<Vec<_>>>()?;
848            Value::Map {
849                ty: def.display_name(),
850                entries,
851            }
852        }
853        StdLayout::SmartPtr(s) => match s.variant {
854            SmartPtrVariant::Mutex | SmartPtrVariant::RefCell => {
855                let inner_type = s.inner_type.clone();
856                let address = address + s.inner_ptr_offset as u64;
857                read_from_memory(db, address, &inner_type, data_resolver)?
858                    .wrap_type(s.variant.name())
859            }
860            SmartPtrVariant::Box => {
861                let inner_type = s.inner_type.clone();
862                let address = data_resolver.read_address(address)?;
863                read_from_memory(db, address, &inner_type, data_resolver)?
864                    .wrap_type(s.variant.name())
865            }
866            SmartPtrVariant::Rc | SmartPtrVariant::Arc => {
867                let inner_type = s.inner_type.clone();
868                let inner_address =
869                    data_resolver.read_address(address + s.inner_ptr_offset as u64)?;
870                let data_address = inner_address + s.data_ptr_offset as u64;
871                read_from_memory(db, data_address, &inner_type, data_resolver)?
872                    .wrap_type(s.variant.name())
873            }
874            _ => {
875                unimplemented!("read_std_from_memory: SmartPtrVariant not implemented yet: {s:#?}")
876            }
877        },
878        StdLayout::Result(result_def) => {
879            unimplemented!("read_std_from_memory: ResultDef not implemented yet: {result_def:#?}")
880        }
881    };
882
883    Ok(value)
884}
885
886/// Recursively read entries from a BTree node following the algorithm from the Python code
887#[allow(clippy::too_many_arguments)]
888fn read_btree_node_entries(
889    node_ptr: u64,
890    height: usize,
891    key_type: &DieTypeDefinition,
892    value_type: &DieTypeDefinition,
893    node_layout: &BTreeNodeLayout,
894    data_resolver: &dyn crate::DataResolver,
895    entries: &mut Vec<(TypedPointer, TypedPointer)>,
896) -> Result<()> {
897    tracing::trace!("read_btree_node_entries at {node_ptr:#x}, height: {height}");
898
899    // Read the node length (number of key-value pairs in this node)
900    let len_addr = node_ptr + node_layout.len_offset as u64;
901    let len_bytes = data_resolver.read_memory(len_addr, 2)?; // len is u16
902    let len = u16::from_le_bytes(len_bytes.try_into().unwrap()) as usize;
903
904    tracing::trace!("Node at {node_ptr:#x} has {len} entries");
905
906    // Calculate sizes for key and value types
907    let key_size = key_type
908        .size()
909        .ok_or_else(|| anyhow::anyhow!("Cannot determine key size for BTreeMap"))?;
910    let value_size = value_type
911        .size()
912        .ok_or_else(|| anyhow::anyhow!("Cannot determine value size for BTreeMap"))?;
913
914    // Get the base addresses for keys and values arrays
915    let keys_addr = node_ptr + node_layout.keys_offset as u64;
916    let vals_addr = node_ptr + node_layout.vals_offset as u64;
917
918    // If this is an internal node (height > 0), we need to traverse edges
919    let edges_addr = if height > 0 {
920        Some(node_ptr + node_layout.edges_offset as u64)
921    } else {
922        None
923    };
924
925    // For each index from 0 to len (inclusive for edges)
926    for i in 0..=len {
927        // If this is an internal node, traverse the edge before processing the key/value
928        if let Some(edges_base) = edges_addr
929            && i <= len
930        {
931            // Read the edge pointer (edges[i])
932            // Edges are MaybeUninit<NodePtr>, so we need to handle the pointer size
933            let edge_addr = edges_base + (i * 8) as u64; // Assume 8-byte pointers
934            let edge_ptr = data_resolver.read_address(edge_addr)?;
935
936            if edge_ptr != 0 {
937                // Recursively process the child node
938                read_btree_node_entries(
939                    edge_ptr,
940                    height - 1,
941                    key_type,
942                    value_type,
943                    node_layout,
944                    data_resolver,
945                    entries,
946                )?;
947            }
948        }
949
950        // Process the key-value pair (only if i < len)
951        if i < len {
952            // Read key and value from the MaybeUninit arrays
953            // Skip zero-sized types as per the Python code
954            if key_size > 0 && value_size > 0 {
955                let key_addr = keys_addr + (i * key_size) as u64;
956                let value_addr = vals_addr + (i * value_size) as u64;
957
958                let key_ptr = TypedPointer {
959                    address: key_addr,
960                    type_def: key_type.clone(),
961                };
962
963                let value_ptr = TypedPointer {
964                    address: value_addr,
965                    type_def: value_type.clone(),
966                };
967
968                entries.push((key_ptr, value_ptr));
969            }
970        }
971    }
972
973    Ok(())
974}