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
10pub 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 let name_field_addr = entry_offset; 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 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 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 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 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}