Skip to main content

macho_objc/
protocol.rs

1use crate::error::Result;
2use crate::format::io::pod::{self, RawProtocolT64};
3use crate::model::addr::Va;
4use crate::objc::method::parse_method_list;
5use crate::objc::property::{parse_property_list, parse_property_list_with_kind};
6use crate::objc::resolve::ObjCResolver;
7use crate::objc::types::ObjCProtocol;
8
9/// Performs parse_protocol.
10pub fn parse_protocol(resolver: &ObjCResolver<'_>, proto_va: Va) -> Result<ObjCProtocol> {
11    let offset = resolver.va_to_offset(proto_va)?;
12    let data = resolver.macho().bytes();
13    let endian = resolver.endian();
14    let raw: RawProtocolT64 = pod::read_pod(data, offset.as_usize())?;
15    let protocol_size = endian.interpret_u32(raw.size) as usize;
16    let name_ptr_offset = offset.as_usize() as u64 + 8;
17    let name = match resolver.read_pointer_at_offset(name_ptr_offset)? {
18        Some(va) => resolver.read_cstring(va).unwrap_or("<invalid>").to_string(),
19        None => "<null>".to_string(),
20    };
21
22    let inst_methods_offset = offset.as_usize() as u64 + 24;
23    let instance_methods = match resolver.read_pointer_at_offset(inst_methods_offset)? {
24        Some(va) if va.0 != 0 => parse_method_list(resolver, va).unwrap_or_default(),
25        _ => Vec::new(),
26    };
27
28    let cls_methods_offset = offset.as_usize() as u64 + 32;
29    let class_methods = match resolver.read_pointer_at_offset(cls_methods_offset)? {
30        Some(va) if va.0 != 0 => parse_method_list(resolver, va).unwrap_or_default(),
31        _ => Vec::new(),
32    };
33
34    let opt_inst_offset = offset.as_usize() as u64 + 40;
35    let optional_instance_methods = match resolver.read_pointer_at_offset(opt_inst_offset)? {
36        Some(va) if va.0 != 0 => parse_method_list(resolver, va).unwrap_or_default(),
37        _ => Vec::new(),
38    };
39
40    let opt_cls_offset = offset.as_usize() as u64 + 48;
41    let optional_class_methods = match resolver.read_pointer_at_offset(opt_cls_offset)? {
42        Some(va) if va.0 != 0 => parse_method_list(resolver, va).unwrap_or_default(),
43        _ => Vec::new(),
44    };
45
46    let props_offset = offset.as_usize() as u64 + 56;
47    let properties = match resolver.read_pointer_at_offset(props_offset)? {
48        Some(va) if va.0 != 0 => parse_property_list(resolver, va).unwrap_or_default(),
49        _ => Vec::new(),
50    };
51
52    let class_properties = if protocol_size >= 96 {
53        let class_props_offset = offset.as_usize() as u64 + 88;
54        match resolver.read_pointer_at_offset(class_props_offset) {
55            Ok(Some(va)) if va.0 != 0 => {
56                parse_property_list_with_kind(resolver, va, true).unwrap_or_default()
57            }
58            _ => Vec::new(),
59        }
60    } else {
61        Vec::new()
62    };
63
64    let protos_offset = offset.as_usize() as u64 + 16;
65    let adopted_protocols = match resolver.read_pointer_at_offset(protos_offset)? {
66        Some(va) if va.0 != 0 => parse_protocol_name_list(resolver, va).unwrap_or_default(),
67        _ => Vec::new(),
68    };
69
70    let mut properties = properties;
71    properties.extend(class_properties);
72
73    Ok(ObjCProtocol {
74        name,
75        instance_methods,
76        class_methods,
77        optional_instance_methods,
78        optional_class_methods,
79        properties,
80        adopted_protocols,
81    })
82}
83
84/// Parse a protocol_list_t and return just the protocol names.
85pub fn parse_protocol_name_list(resolver: &ObjCResolver<'_>, va: Va) -> Result<Vec<String>> {
86    let offset = resolver.va_to_offset(va)?;
87    let data = resolver.macho().bytes();
88    let endian = resolver.endian();
89
90    // protocol_list_t: first 8 bytes is count (as u64)
91    let count = endian.interpret_u64(pod::read_pod::<u64>(data, offset.as_usize())?) as usize;
92    if count > 10_000 {
93        return Ok(Vec::new());
94    }
95
96    let mut names = Vec::with_capacity(count);
97    for i in 0..count {
98        let ptr_offset = offset.as_usize() as u64 + 8 + i as u64 * 8;
99        if let Ok(Some(proto_va)) = resolver.read_pointer_at_offset(ptr_offset) {
100            if proto_va.0 != 0 {
101                // Read the protocol's name field (at offset +8 in protocol_t)
102                let name_ptr_offset = match resolver.va_to_offset(proto_va) {
103                    Ok(off) => off.as_usize() as u64 + 8,
104                    Err(_) => continue,
105                };
106                if let Ok(Some(name_va)) = resolver.read_pointer_at_offset(name_ptr_offset) {
107                    if let Ok(name) = resolver.read_cstring(name_va) {
108                        names.push(name.to_string());
109                    }
110                }
111            }
112        }
113    }
114
115    Ok(names)
116}