Skip to main content

macho_core/format/
relocations.rs

1use crate::error::{Error, Result};
2use crate::format::constants::R_SCATTERED;
3use crate::format::io::pod::{self, RawRelocationInfo};
4use crate::model::macho_file::MachoFile;
5use crate::model::relocation::{Relocation, ScatteredRelocation, StandardRelocation};
6use crate::model::section::Section;
7
8const MAX_RELOCS_PER_SECTION: usize = 1_000_000;
9
10/// Parse relocation entries for a given section.
11pub fn relocations_for_section(
12    macho: &MachoFile<'_>,
13    section: &Section,
14) -> Result<Vec<Relocation>> {
15    let nreloc = section.nreloc as usize;
16    if nreloc == 0 {
17        return Ok(Vec::new());
18    }
19    if nreloc > MAX_RELOCS_PER_SECTION {
20        return Err(Error::format(format!(
21            "section {},{} claims {nreloc} relocations, exceeding limit of {MAX_RELOCS_PER_SECTION}",
22            section.segment_name, section.section_name
23        )));
24    }
25
26    let data = macho.bytes();
27    let endian = macho.endian();
28    let entry_size = size_of::<RawRelocationInfo>();
29    let offset = section.reloff.as_usize();
30
31    let max_cap = data.len().saturating_sub(offset) / entry_size;
32    let mut relocs = Vec::with_capacity(nreloc.min(max_cap));
33
34    for i in 0..nreloc {
35        let raw: RawRelocationInfo = pod::read_pod(data, offset + i * entry_size)?;
36        let r_address = endian.interpret_i32(raw.r_address);
37        let r_info = endian.interpret_u32(raw.r_symbolnum_and_flags);
38
39        // On LE hosts (all modern Apple), the r_address word doubles as the
40        // scattered relocation indicator. Check the high bit of the
41        // little-endian interpretation of the first word.
42        let first_word = endian.interpret_u32(raw.r_address as u32);
43        if first_word & R_SCATTERED != 0 {
44            // Scattered relocation (LE bit layout):
45            //   bits 0-23:  r_address
46            //   bit  24:    r_type (bit 0)
47            //   bits 25-27: r_type (bits 1-3)
48            //   bits 28-29: r_length
49            //   bit  30:    r_pcrel
50            //   bit  31:    R_SCATTERED (already checked)
51            // The r_type/r_length/r_pcrel bit layout matches Apple's
52            // scattered_relocation_info on little-endian hosts. Big-endian
53            // layouts are not decoded.
54            let address = first_word & 0x00FF_FFFF;
55            let reloc_type = ((first_word >> 24) & 0xF) as u8;
56            let length = ((first_word >> 28) & 0x3) as u8;
57            let pc_relative = (first_word >> 30) & 1 != 0;
58
59            relocs.push(Relocation::Scattered(ScatteredRelocation {
60                reloc_type,
61                length,
62                pc_relative,
63                address,
64                value: r_info as i32,
65            }));
66        } else {
67            // Standard relocation (LE bit layout of r_info):
68            //   bits 0-23:  r_symbolnum
69            //   bit  24:    r_pcrel
70            //   bits 25-26: r_length
71            //   bit  27:    r_extern
72            //   bits 28-31: r_type
73            let symbol_num = r_info & 0x00FF_FFFF;
74            let pc_relative = (r_info >> 24) & 1 != 0;
75            let length = ((r_info >> 25) & 0x3) as u8;
76            let is_extern = (r_info >> 27) & 1 != 0;
77            let reloc_type = ((r_info >> 28) & 0xF) as u8;
78
79            relocs.push(Relocation::Standard(StandardRelocation {
80                address: r_address as u32,
81                symbol_num,
82                pc_relative,
83                length,
84                is_extern,
85                reloc_type,
86            }));
87        }
88    }
89
90    Ok(relocs)
91}