Skip to main content

macho_objc/
ivar.rs

1use crate::error::Result;
2use crate::format::io::pod::{self, RawIvarT64, RawMethodListHeader};
3use crate::model::addr::Va;
4use crate::objc::resolve::ObjCResolver;
5use crate::objc::types::{METHOD_LIST_ENTSIZE_MASK, ObjCIvar};
6
7/// Performs parse_ivar_list.
8pub fn parse_ivar_list(resolver: &ObjCResolver<'_>, va: Va) -> Result<Vec<ObjCIvar>> {
9    let offset = resolver.va_to_offset(va)?;
10    let data = resolver.macho().bytes();
11    let endian = resolver.endian();
12
13    let header: RawMethodListHeader = pod::read_pod(data, offset.as_usize())?;
14    let entsize_and_flags = endian.interpret_u32(header.entsize_and_flags);
15    let count = endian.interpret_u32(header.count) as usize;
16    let header_size = size_of::<RawMethodListHeader>();
17
18    let entsize = (entsize_and_flags & METHOD_LIST_ENTSIZE_MASK) as usize;
19    let entry_size = if entsize > 0 {
20        entsize
21    } else {
22        size_of::<RawIvarT64>()
23    };
24
25    let mut ivars = Vec::with_capacity(count.min(1000));
26
27    for i in 0..count {
28        let entry_offset = offset.as_usize() + header_size + i * entry_size;
29
30        let name_ptr = resolver.read_pointer_at_offset(entry_offset as u64 + 8)?;
31        let type_ptr = resolver.read_pointer_at_offset(entry_offset as u64 + 16)?;
32
33        // Read alignment and size from the raw struct
34        let raw: RawIvarT64 = pod::read_pod(data, entry_offset)?;
35        let alignment = endian.interpret_u32(raw.alignment);
36        let size = endian.interpret_u32(raw.size);
37
38        // Read the ivar offset value (offset_ptr points to a u32)
39        let ivar_offset = resolver
40            .read_pointer_at_offset(entry_offset as u64)
41            .ok()
42            .flatten()
43            .and_then(|va| resolver.va_to_offset(va).ok())
44            .and_then(|offset| pod::read_pod::<u32>(data, offset.as_usize()).ok())
45            .map(|value| endian.interpret_u32(value));
46
47        let name = match name_ptr {
48            Some(va) => resolver.read_cstring(va).unwrap_or("<invalid>"),
49            None => "<null>",
50        };
51        // `read_pointer_at_offset` reports a null slot and a bind it cannot
52        // resolve identically, so the raw slot decides between them: a zero slot
53        // means the ivar carries no type encoding, which is how a Swift stored
54        // property appears, while a non-zero slot that did not resolve is a
55        // reference this image cannot follow. Reporting either as a malformed
56        // encoding would blame the metadata for a claim it never made.
57        let type_slot = pod::read_pod::<u64>(data, entry_offset + 16)
58            .map(|value| endian.interpret_u64(value))
59            .unwrap_or(0);
60        let type_encoding = match type_ptr {
61            Some(va) => resolver.read_cstring(va).ok().map(str::to_owned),
62            None if type_slot == 0 => Some(String::new()),
63            None => None,
64        };
65
66        ivars.push(ObjCIvar {
67            name: name.to_string(),
68            type_encoding,
69            offset: ivar_offset,
70            size,
71            alignment: 1u32.wrapping_shl(alignment),
72        });
73    }
74
75    Ok(ivars)
76}