Skip to main content

macho_objc/
class.rs

1use crate::error::Result;
2use crate::format::io::pod::{self, RawClassRoT64};
3use crate::model::addr::Va;
4use crate::objc::ivar::parse_ivar_list;
5use crate::objc::method::parse_method_list;
6use crate::objc::property::{parse_property_list, parse_property_list_with_kind};
7use crate::objc::protocol::parse_protocol_name_list;
8use crate::objc::resolve::ObjCResolver;
9use crate::objc::types::{ObjCClass, ObjCMethod, ObjCProperty, RO_META};
10
11/// Performs parse_class.
12pub fn parse_class(resolver: &ObjCResolver<'_>, class_va: Va) -> Result<ObjCClass> {
13    let class_offset = resolver.va_to_offset(class_va)?;
14    let data = resolver.macho().bytes();
15    let endian = resolver.endian();
16
17    // Read the data pointer (class_ro_t). Low bit is the Swift class flag.
18    let data_ptr_offset = class_offset.as_usize() as u64 + 32; // offset of 'data' field
19    let raw_data_ptr = match resolver.read_pointer_at_offset(data_ptr_offset)? {
20        Some(va) => va,
21        None => {
22            return Err(crate::error::Error::format("null class_ro_t pointer"));
23        }
24    };
25    let is_swift = raw_data_ptr.0 & 1 != 0;
26    let data_va = Va(raw_data_ptr.0 & crate::types::CLASS_DATA_POINTER_MASK);
27
28    // Parse class_ro_t
29    let ro_offset = resolver.va_to_offset(data_va)?;
30    let ro: RawClassRoT64 = pod::read_pod(data, ro_offset.as_usize())?;
31    let flags = endian.interpret_u32(ro.flags);
32    let instance_size = endian.interpret_u32(ro.instance_size);
33    let is_meta = flags & RO_META != 0;
34
35    // Read class name
36    let name_ptr_offset = ro_offset.as_usize() as u64 + 24; // offset of 'name' field in class_ro_t
37    let name = match resolver.read_pointer_at_offset(name_ptr_offset)? {
38        Some(va) => resolver.read_cstring(va).unwrap_or("<invalid>").to_string(),
39        None => "<null>".to_string(),
40    };
41
42    // Resolve superclass name from either a bind or an in-image class pointer.
43    let superclass_offset = class_offset.as_usize() as u64 + 8;
44    let superclass_name = resolve_class_ref_name(resolver, superclass_offset);
45
46    // Parse methods
47    let methods_ptr_offset = ro_offset.as_usize() as u64 + 32;
48    let instance_methods = match resolver.read_pointer_at_offset(methods_ptr_offset)? {
49        Some(va) if va.0 != 0 => parse_method_list(resolver, va).unwrap_or_default(),
50        _ => Vec::new(),
51    };
52
53    // Parse protocols
54    let protos_ptr_offset = ro_offset.as_usize() as u64 + 40;
55    let protocols = match resolver.read_pointer_at_offset(protos_ptr_offset)? {
56        Some(va) if va.0 != 0 => parse_protocol_name_list(resolver, va).unwrap_or_default(),
57        _ => Vec::new(),
58    };
59
60    // Parse ivars
61    let ivars_ptr_offset = ro_offset.as_usize() as u64 + 48;
62    let ivars = match resolver.read_pointer_at_offset(ivars_ptr_offset)? {
63        Some(va) if va.0 != 0 => parse_ivar_list(resolver, va).unwrap_or_default(),
64        _ => Vec::new(),
65    };
66
67    // Parse properties
68    let props_ptr_offset = ro_offset.as_usize() as u64 + 64;
69    let properties = match resolver.read_pointer_at_offset(props_ptr_offset)? {
70        Some(va) if va.0 != 0 => parse_property_list(resolver, va).unwrap_or_default(),
71        _ => Vec::new(),
72    };
73
74    // Parse metaclass for class methods
75    let meta_offset = class_offset.as_usize() as u64; // isa field
76    let (class_methods, class_properties) = match resolver.read_pointer_at_offset(meta_offset)? {
77        Some(meta_va) => parse_metaclass_surface(resolver, meta_va).unwrap_or_default(),
78        None => (Vec::new(), Vec::new()),
79    };
80
81    let mut properties = properties;
82    properties.extend(class_properties);
83
84    Ok(ObjCClass {
85        name,
86        superclass_name,
87        instance_methods,
88        class_methods,
89        ivars,
90        properties,
91        protocols,
92        instance_size,
93        is_meta,
94        is_swift,
95    })
96}
97
98pub(crate) fn resolve_class_ref_name(
99    resolver: &ObjCResolver<'_>,
100    class_ref_offset: u64,
101) -> Option<String> {
102    if let Some(bind_name) = resolver.bind_name_at_offset(class_ref_offset) {
103        return Some(
104            bind_name
105                .strip_prefix("_OBJC_CLASS_$_")
106                .unwrap_or(bind_name)
107                .to_string(),
108        );
109    }
110
111    let class_va = resolver.read_pointer_at_offset(class_ref_offset).ok()??;
112    resolve_class_name_from_va(resolver, class_va)
113}
114
115pub(crate) fn resolve_class_name_from_va(
116    resolver: &ObjCResolver<'_>,
117    class_va: Va,
118) -> Option<String> {
119    let class_offset = resolver.va_to_offset(class_va).ok()?;
120
121    // data field is at +32 in objc_class, with bit 0 used for the Swift flag.
122    let data_ptr_offset = class_offset.as_usize() as u64 + 32;
123    let data_va = resolver.read_pointer_at_offset(data_ptr_offset).ok()??;
124    let data_va = Va(data_va.0 & crate::types::CLASS_DATA_POINTER_MASK);
125    let ro_offset = resolver.va_to_offset(data_va).ok()?;
126
127    // name field is at +24 in class_ro_t.
128    let name_ptr_offset = ro_offset.as_usize() as u64 + 24;
129    let name_va = resolver.read_pointer_at_offset(name_ptr_offset).ok()??;
130    resolver.read_cstring(name_va).ok().map(|s| s.to_string())
131}
132
133fn parse_metaclass_surface(
134    resolver: &ObjCResolver<'_>,
135    meta_va: Va,
136) -> Result<(Vec<ObjCMethod>, Vec<ObjCProperty>)> {
137    let meta_offset = resolver.va_to_offset(meta_va)?;
138
139    let data_ptr_offset = meta_offset.as_usize() as u64 + 32;
140    let data_va = match resolver.read_pointer_at_offset(data_ptr_offset)? {
141        Some(va) => Va(va.0 & crate::types::CLASS_DATA_POINTER_MASK),
142        None => return Ok((Vec::new(), Vec::new())),
143    };
144
145    let ro_offset = resolver.va_to_offset(data_va)?;
146
147    let methods_ptr_offset = ro_offset.as_usize() as u64 + 32;
148    let methods = match resolver.read_pointer_at_offset(methods_ptr_offset)? {
149        Some(va) if va.0 != 0 => parse_method_list(resolver, va),
150        _ => Ok(Vec::new()),
151    }?;
152
153    let props_ptr_offset = ro_offset.as_usize() as u64 + 64;
154    let properties = match resolver.read_pointer_at_offset(props_ptr_offset)? {
155        Some(va) if va.0 != 0 => parse_property_list_with_kind(resolver, va, true),
156        _ => Ok(Vec::new()),
157    }?;
158
159    Ok((methods, properties))
160}