1use crate::error::{Error, Result};
2use crate::format::constants::*;
3use crate::format::io::pod::{self, RawNlist32, RawNlist64};
4use crate::model::ext::MachoExt;
5use crate::model::header::Bitness;
6use crate::model::macho_file::MachoFile;
7use crate::model::symbol::{StringTable, Symbol, SymbolTable, SymbolType};
8
9const MAX_SYMBOLS: usize = 10_000_000;
10
11impl<'data> MachoExt<'data> for SymbolTable<'data> {
12 type Error = Error;
13
14 fn parse<'mf>(macho: &'mf MachoFile<'data>) -> Result<Self>
15 where
16 'data: 'mf,
17 {
18 parse_symbol_table(macho)
19 }
20}
21
22pub fn parse_symbol_table<'data>(macho: &MachoFile<'data>) -> Result<SymbolTable<'data>> {
24 let layout = symbol_table_layout(macho)?;
25 let mut symbols = Vec::new();
26 fold_nlist_symbols(macho, &layout, &mut symbols, |symbols, symbol| {
27 symbols.push(symbol);
28 Ok(())
29 })?;
30
31 Ok(SymbolTable::new(symbols, layout.string_table))
32}
33
34pub fn fold_symbols<'data, State>(
44 macho: &MachoFile<'data>,
45 mut state: State,
46 mut folder: impl FnMut(&mut State, Symbol<'data>) -> Result<()>,
47) -> Result<State> {
48 let layout = symbol_table_layout(macho)?;
49 fold_nlist_symbols(macho, &layout, &mut state, &mut folder)?;
50 Ok(state)
51}
52
53struct SymbolTableLayout<'data> {
54 string_table: StringTable<'data>,
55 symbol_count: usize,
56 symbol_offset: usize,
57}
58
59fn symbol_table_layout<'data>(macho: &MachoFile<'data>) -> Result<SymbolTableLayout<'data>> {
60 let symtab = macho
61 .find_load_command(|lc| lc.as_symtab().is_some())
62 .and_then(|lc| lc.kind.as_symtab())
63 .ok_or_else(|| Error::format("no LC_SYMTAB load command found"))?;
64
65 let data = macho.bytes();
66
67 let str_start = symtab.str_offset as usize;
69 let str_end = str_start
70 .checked_add(symtab.str_size as usize)
71 .ok_or_else(|| {
72 Error::bounds(str_start as u64, symtab.str_size as u64, data.len() as u64)
73 })?;
74 if str_end > data.len() {
75 return Err(Error::bounds(
76 str_start as u64,
77 symtab.str_size as u64,
78 data.len() as u64,
79 ));
80 }
81 let string_table = StringTable::new(&data[str_start..str_end]);
82
83 let nsyms = symtab.nsyms as usize;
84 if nsyms > MAX_SYMBOLS {
85 return Err(Error::format(format!(
86 "symbol table claims {nsyms} symbols, which exceeds the limit of {MAX_SYMBOLS}"
87 )));
88 }
89
90 Ok(SymbolTableLayout {
91 string_table,
92 symbol_count: nsyms,
93 symbol_offset: symtab.sym_offset as usize,
94 })
95}
96
97fn fold_nlist_symbols<'data, State>(
98 macho: &MachoFile<'data>,
99 layout: &SymbolTableLayout<'data>,
100 state: &mut State,
101 mut folder: impl FnMut(&mut State, Symbol<'data>) -> Result<()>,
102) -> Result<()> {
103 match macho.bitness() {
104 Bitness::Bits64 => fold_nlist64(
105 macho.bytes(),
106 macho.endian(),
107 layout.symbol_offset,
108 layout.symbol_count,
109 &layout.string_table,
110 state,
111 &mut folder,
112 ),
113 Bitness::Bits32 => fold_nlist32(
114 macho.bytes(),
115 macho.endian(),
116 layout.symbol_offset,
117 layout.symbol_count,
118 &layout.string_table,
119 state,
120 &mut folder,
121 ),
122 }
123}
124
125#[allow(clippy::too_many_arguments)]
126fn fold_nlist64<'data, State>(
127 data: &'data [u8],
128 endian: crate::format::io::Endian,
129 offset: usize,
130 count: usize,
131 string_table: &StringTable<'data>,
132 state: &mut State,
133 folder: &mut impl FnMut(&mut State, Symbol<'data>) -> Result<()>,
134) -> Result<()> {
135 let entry_size = size_of::<RawNlist64>();
136
137 for i in 0..count {
138 let entry_off = offset
139 .checked_add(
140 i.checked_mul(entry_size)
141 .ok_or_else(|| Error::format(format!("nlist64[{i}]: stride overflows")))?,
142 )
143 .ok_or_else(|| Error::format(format!("nlist64[{i}]: offset overflows")))?;
144 let raw: RawNlist64 = pod::read_pod(data, entry_off)?;
145 let n_strx = endian.interpret_u32(raw.n_strx);
146 let n_desc = endian.interpret_u16(raw.n_desc);
147 let n_value = endian.interpret_u64(raw.n_value);
148
149 let name = string_table.get(n_strx)?;
150
151 folder(
152 state,
153 Symbol {
154 name,
155 sym_type: SymbolType::from_n_type(raw.n_type),
156 external: raw.n_type & N_EXT != 0,
157 private_external: raw.n_type & N_PEXT != 0,
158 section_index: raw.n_sect,
159 desc: n_desc,
160 value: n_value,
161 index: i,
162 },
163 )?;
164 }
165
166 Ok(())
167}
168
169#[allow(clippy::too_many_arguments)]
170fn fold_nlist32<'data, State>(
171 data: &'data [u8],
172 endian: crate::format::io::Endian,
173 offset: usize,
174 count: usize,
175 string_table: &StringTable<'data>,
176 state: &mut State,
177 folder: &mut impl FnMut(&mut State, Symbol<'data>) -> Result<()>,
178) -> Result<()> {
179 let entry_size = size_of::<RawNlist32>();
180
181 for i in 0..count {
182 let entry_off = offset
183 .checked_add(
184 i.checked_mul(entry_size)
185 .ok_or_else(|| Error::format(format!("nlist32[{i}]: stride overflows")))?,
186 )
187 .ok_or_else(|| Error::format(format!("nlist32[{i}]: offset overflows")))?;
188 let raw: RawNlist32 = pod::read_pod(data, entry_off)?;
189 let n_strx = endian.interpret_u32(raw.n_strx);
190 let n_desc = endian.interpret_u16(raw.n_desc as u16);
192 let n_value = endian.interpret_u32(raw.n_value) as u64;
193
194 let name = string_table.get(n_strx)?;
195
196 folder(
197 state,
198 Symbol {
199 name,
200 sym_type: SymbolType::from_n_type(raw.n_type),
201 external: raw.n_type & N_EXT != 0,
202 private_external: raw.n_type & N_PEXT != 0,
203 section_index: raw.n_sect,
204 desc: n_desc,
205 value: n_value,
206 index: i,
207 },
208 )?;
209 }
210
211 Ok(())
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 fn macho_with_symbols(second_name_offset: u32) -> Vec<u8> {
219 let command_size = 24u32;
220 let symbol_offset = 32u32 + command_size;
221 let symbol_count = 2u32;
222 let string_table = b"\0second\0first\0";
223 let string_offset = symbol_offset + symbol_count * size_of::<RawNlist64>() as u32;
224 let mut bytes = Vec::with_capacity(string_offset as usize + string_table.len());
225
226 bytes.extend_from_slice(&MH_MAGIC_64.to_le_bytes());
228 bytes.extend_from_slice(&CPU_TYPE_X86_64.to_le_bytes());
229 bytes.extend_from_slice(&CPU_SUBTYPE_X86_64_ALL.to_le_bytes());
230 bytes.extend_from_slice(&MH_EXECUTE.to_le_bytes());
231 bytes.extend_from_slice(&1u32.to_le_bytes());
232 bytes.extend_from_slice(&command_size.to_le_bytes());
233 bytes.extend_from_slice(&0u32.to_le_bytes());
234 bytes.extend_from_slice(&0u32.to_le_bytes());
235
236 bytes.extend_from_slice(&LC_SYMTAB.to_le_bytes());
238 bytes.extend_from_slice(&command_size.to_le_bytes());
239 bytes.extend_from_slice(&symbol_offset.to_le_bytes());
240 bytes.extend_from_slice(&symbol_count.to_le_bytes());
241 bytes.extend_from_slice(&string_offset.to_le_bytes());
242 bytes.extend_from_slice(&(string_table.len() as u32).to_le_bytes());
243
244 push_nlist64(&mut bytes, 8, 0x2000);
246 push_nlist64(&mut bytes, second_name_offset, 0x1000);
247 bytes.extend_from_slice(string_table);
248 bytes
249 }
250
251 fn push_nlist64(bytes: &mut Vec<u8>, name_offset: u32, value: u64) {
252 bytes.extend_from_slice(&name_offset.to_le_bytes());
253 bytes.push(N_SECT | N_EXT);
254 bytes.push(1);
255 bytes.extend_from_slice(&0u16.to_le_bytes());
256 bytes.extend_from_slice(&value.to_le_bytes());
257 }
258
259 fn parse_fixture(bytes: &[u8]) -> MachoFile<'_> {
260 crate::format::macho::parse_macho_file(bytes).unwrap()
261 }
262
263 #[test]
264 fn fold_symbols_uses_physical_nlist_order() {
265 let bytes = macho_with_symbols(1);
266 let macho = parse_fixture(&bytes);
267
268 let names = fold_symbols(&macho, Vec::new(), |names, symbol| {
269 names.push((symbol.index, symbol.name, symbol.value));
270 Ok(())
271 })
272 .unwrap();
273
274 assert_eq!(names, [(0, "first", 0x2000), (1, "second", 0x1000)]);
275 let table = parse_symbol_table(&macho).unwrap();
276 assert_eq!(
277 table
278 .symbols()
279 .iter()
280 .map(|symbol| symbol.name)
281 .collect::<Vec<_>>(),
282 ["first", "second"]
283 );
284 }
285
286 #[test]
287 fn fold_symbols_does_not_require_a_symbol_collection() {
288 let bytes = macho_with_symbols(1);
289 let macho = parse_fixture(&bytes);
290
291 let (count, value_sum) = fold_symbols(&macho, (0usize, 0u64), |summary, symbol| {
292 summary.0 += 1;
293 summary.1 += symbol.value;
294 Ok(())
295 })
296 .unwrap();
297
298 assert_eq!(count, 2);
299 assert_eq!(value_sum, 0x3000);
300 }
301
302 #[test]
303 fn malformed_suffix_does_not_return_partially_folded_symbol_state() {
304 let bytes = macho_with_symbols(99);
305 let macho = parse_fixture(&bytes);
306 let mut folder_calls = 0usize;
307
308 let result = fold_symbols(&macho, 0usize, |count, _symbol| {
309 folder_calls += 1;
310 *count += 1;
311 Ok(())
312 });
313
314 assert!(result.is_err());
315 assert_eq!(folder_calls, 1);
316 assert!(parse_symbol_table(&macho).is_err());
317 }
318}