Skip to main content

macho_objc/
method.rs

1use crate::error::Result;
2use crate::format::io::pod::{self, RawMethodListHeader, RawMethodT, RawRelativeMethodT};
3use crate::model::addr::{ThinFileOffset, Va};
4use crate::objc::resolve::ObjCResolver;
5use crate::objc::types::{
6    METHOD_LIST_ENTSIZE_MASK, METHOD_LIST_USES_DIRECT_SELECTOR_OFFSETS,
7    METHOD_LIST_USES_RELATIVE_OFFSETS, ObjCMethod,
8};
9
10/// Performs parse_method_list.
11pub fn parse_method_list(resolver: &ObjCResolver<'_>, va: Va) -> Result<Vec<ObjCMethod>> {
12    let offset = resolver.va_to_offset(va)?;
13    let data = resolver.macho().bytes();
14    let endian = resolver.endian();
15
16    let header: RawMethodListHeader = pod::read_pod(data, offset.as_usize())?;
17    let entsize_and_flags = endian.interpret_u32(header.entsize_and_flags);
18    let count = endian.interpret_u32(header.count) as usize;
19    let uses_relative = entsize_and_flags & METHOD_LIST_USES_RELATIVE_OFFSETS != 0;
20    let uses_direct_selectors = entsize_and_flags & METHOD_LIST_USES_DIRECT_SELECTOR_OFFSETS != 0;
21
22    let header_size = size_of::<RawMethodListHeader>();
23    let mut methods = Vec::with_capacity(count.min(10_000));
24
25    if uses_relative {
26        let entsize = (entsize_and_flags & METHOD_LIST_ENTSIZE_MASK) as usize;
27        if entsize < size_of::<RawRelativeMethodT>() {
28            return Err(crate::error::Error::format(format!(
29                "relative method entry size {entsize} is smaller than {}",
30                size_of::<RawRelativeMethodT>()
31            )));
32        }
33        let entry_size = entsize;
34
35        for i in 0..count {
36            let entry_offset = offset
37                .as_usize()
38                .checked_add(header_size)
39                .and_then(|v| v.checked_add(i.checked_mul(entry_size)?))
40                .ok_or_else(|| {
41                    crate::error::Error::format(format!("relative method[{i}] offset overflows"))
42                })?;
43            let raw: RawRelativeMethodT = pod::read_pod(data, entry_offset)?;
44            let name_rel = endian.interpret_i32(raw.name_offset) as isize;
45            let types_rel = endian.interpret_i32(raw.types_offset) as isize;
46            let imp_rel = endian.interpret_i32(raw.imp_offset) as i64;
47
48            // Each offset is relative to its own field address.
49            let name_field_addr = entry_offset; // field 0
50            let types_field_addr = entry_offset
51                .checked_add(4)
52                .ok_or_else(|| crate::error::Error::address("method type field overflows"))?;
53            let imp_field_addr = entry_offset
54                .checked_add(8)
55                .ok_or_else(|| crate::error::Error::address("method IMP field overflows"))?;
56
57            // The name offset points to a selector reference (a pointer in
58            // __objc_selrefs), not directly to the string. Follow the
59            // indirection: read the pointer at the sel ref, then read the
60            // string at that address.
61            let selector_offset = name_field_addr
62                .checked_add_signed(name_rel)
63                .ok_or_else(|| crate::error::Error::address("method selector offset overflows"))?;
64            let name = if uses_direct_selectors {
65                read_cstring_at_file_offset(data, selector_offset)?
66            } else {
67                let string_va = resolver
68                    .read_pointer_at_offset(selector_offset as u64)?
69                    .ok_or_else(|| {
70                        crate::error::Error::address(
71                            "relative method selector reference is unresolved",
72                        )
73                    })?;
74                resolver.read_cstring(string_va)?
75            };
76
77            let types_file_offset =
78                types_field_addr
79                    .checked_add_signed(types_rel)
80                    .ok_or_else(|| {
81                        crate::error::Error::address("method type-encoding offset overflows")
82                    })?;
83            let type_encoding = read_cstring_at_file_offset(data, types_file_offset)?;
84
85            // Relative IMP offsets use the runtime address of their own field
86            // as the basis, not the field's file offset.
87            let imp_field_va = resolver
88                .macho()
89                .address_map()
90                .thin_offset_to_va(ThinFileOffset(imp_field_addr as u64))?;
91            let imp_addr = imp_field_va
92                .0
93                .checked_add_signed(imp_rel)
94                .filter(|address| *address != 0)
95                .ok_or_else(|| {
96                    crate::error::Error::address("relative method IMP address is invalid")
97                })?;
98
99            methods.push(ObjCMethod {
100                name: name.to_string(),
101                type_encoding: type_encoding.to_string(),
102                imp: Va(imp_addr),
103            });
104        }
105    } else {
106        // Absolute method list
107        let entsize = (entsize_and_flags & METHOD_LIST_ENTSIZE_MASK) as usize;
108        if entsize < size_of::<RawMethodT>() {
109            return Err(crate::error::Error::format(format!(
110                "absolute method entry size {entsize} is smaller than {}",
111                size_of::<RawMethodT>()
112            )));
113        }
114        let entry_size = entsize;
115
116        for i in 0..count {
117            let entry_offset = offset
118                .as_usize()
119                .checked_add(header_size)
120                .and_then(|v| v.checked_add(i.checked_mul(entry_size)?))
121                .ok_or_else(|| {
122                    crate::error::Error::format(format!("absolute method[{i}] offset overflows"))
123                })?;
124
125            // All fields are pointers that may be chained fixups
126            let name_ptr = resolver
127                .read_pointer_at_offset(entry_offset as u64)?
128                .ok_or_else(|| crate::error::Error::address("method name pointer is unresolved"))?;
129            let types_ptr = resolver
130                .read_pointer_at_offset(entry_offset as u64 + 8)?
131                .ok_or_else(|| {
132                    crate::error::Error::address("method type-encoding pointer is unresolved")
133                })?;
134            let imp = resolver
135                .read_pointer_at_offset(entry_offset as u64 + 16)?
136                .filter(|value| value.0 != 0)
137                .ok_or_else(|| crate::error::Error::address("method IMP pointer is unresolved"))?;
138            let name = resolver.read_cstring(name_ptr)?;
139            let type_encoding = resolver.read_cstring(types_ptr)?;
140
141            methods.push(ObjCMethod {
142                name: name.to_string(),
143                type_encoding: type_encoding.to_string(),
144                imp,
145            });
146        }
147    }
148
149    Ok(methods)
150}
151
152fn read_cstring_at_file_offset(data: &[u8], offset: usize) -> Result<&str> {
153    let slice = data
154        .get(offset..)
155        .ok_or_else(|| crate::error::Error::bounds(offset as u64, 1, data.len() as u64))?;
156    let end = slice.iter().position(|&b| b == 0).ok_or_else(|| {
157        crate::error::Error::format(format!("unterminated string at file offset {offset:#x}"))
158    })?;
159    std::str::from_utf8(&slice[..end])
160        .map_err(|e| crate::error::Error::format(format!("invalid UTF-8 at offset {offset}: {e}")))
161}