Skip to main content

objdiff_core/arch/
mips.rs

1use alloc::{
2    collections::{BTreeMap, BTreeSet},
3    string::ToString,
4    vec::Vec,
5};
6
7use anyhow::{Result, bail};
8use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
9use rabbitizer::{
10    IsaExtension, IsaVersion, Vram, abi::Abi, operands::ValuedOperand, registers_meta::Register,
11};
12
13use crate::{
14    arch::{Arch, RelocationOverride, RelocationOverrideTarget},
15    diff::{DiffObjConfig, DiffSide, MipsAbi, MipsInstrCategory, display::InstructionPart},
16    obj::{
17        InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
18        ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
19    },
20};
21
22#[derive(Debug)]
23pub struct ArchMips {
24    pub endianness: object::Endianness,
25    pub abi: Abi,
26    pub isa_extension: Option<IsaExtension>,
27    pub ri_gp_value: i32,
28    pub paired_relocations: Vec<BTreeMap<u64, i64>>,
29    pub ignored_symbols: BTreeSet<usize>,
30    pub diff_side: DiffSide,
31}
32
33const EF_MIPS_ABI: u32 = 0x0000F000;
34const EF_MIPS_MACH: u32 = 0x00FF0000;
35
36const EF_MIPS_MACH_ALLEGREX: u32 = 0x00840000;
37const EF_MIPS_MACH_5900: u32 = 0x00920000;
38
39const R_MIPS15_S3: u32 = 119;
40
41impl ArchMips {
42    pub fn new(object: &object::File, diff_side: DiffSide) -> Result<Self> {
43        let mut abi = Abi::O32;
44        let mut isa_extension = None;
45        match object.flags() {
46            object::FileFlags::None => {}
47            object::FileFlags::Elf { e_flags, .. } => {
48                abi = match e_flags & EF_MIPS_ABI {
49                    elf::EF_MIPS_ABI_O32 => Abi::O32,
50                    elf::EF_MIPS_ABI_O64 if e_flags & elf::EF_MIPS_ABI2 != 0 => Abi::N64,
51                    elf::EF_MIPS_ABI_O64 => Abi::O64,
52                    elf::EF_MIPS_ABI_EABI32 => Abi::EABI32,
53                    elf::EF_MIPS_ABI_EABI64 => Abi::EABI64,
54                    _ => {
55                        if e_flags & elf::EF_MIPS_ABI2 != 0 {
56                            Abi::N32
57                        } else {
58                            Abi::O32
59                        }
60                    }
61                };
62                isa_extension = match e_flags & EF_MIPS_MACH {
63                    EF_MIPS_MACH_ALLEGREX => Some(IsaExtension::R4000ALLEGREX),
64                    EF_MIPS_MACH_5900 => Some(IsaExtension::R5900EE),
65                    _ => None,
66                };
67            }
68            _ => bail!("Unsupported MIPS file flags"),
69        }
70
71        // Parse the ri_gp_value stored in .reginfo to be able to correctly
72        // calculate R_MIPS_GPREL16 relocations later. The value is stored
73        // 0x14 bytes into .reginfo (on 32-bit platforms)
74        let endianness = object.endianness();
75        let ri_gp_value = object
76            .section_by_name(".reginfo")
77            .and_then(|section| section.data().ok())
78            .and_then(|data| data.get(0x14..0x18))
79            .and_then(|s| s.try_into().ok())
80            .map(|bytes| endianness.read_i32(bytes))
81            .unwrap_or(0);
82
83        // Parse all relocations to pair R_MIPS_HI16 and R_MIPS_LO16. Since the instructions only
84        // have 16-bit immediate fields, the 32-bit addend is split across the two relocations.
85        // R_MIPS_LO16 relocations without an immediately preceding R_MIPS_HI16 use the last seen
86        // R_MIPS_HI16 addend.
87        // See https://refspecs.linuxfoundation.org/elf/mipsabi.pdf pages 4-17 and 4-18
88        let mut paired_relocations = Vec::with_capacity(object.sections().count() + 1);
89        for obj_section in object.sections() {
90            let data = obj_section.data()?;
91            let mut last_hi = None;
92            let mut last_hi_addend = 0;
93            let mut addends = BTreeMap::new();
94            for (addr, reloc) in obj_section.relocations() {
95                if !reloc.has_implicit_addend() {
96                    continue;
97                }
98                match reloc.flags() {
99                    object::RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
100                        let code = data[addr as usize..addr as usize + 4].try_into()?;
101                        let addend = ((endianness.read_u32(code) & 0x0000FFFF) << 16) as i32;
102                        last_hi = Some(addr);
103                        last_hi_addend = addend;
104                    }
105                    object::RelocationFlags::Elf { r_type: elf::R_MIPS_LO16 } => {
106                        let code = data[addr as usize..addr as usize + 4].try_into()?;
107                        let addend = (endianness.read_u32(code) & 0x0000FFFF) as i16 as i32;
108                        let full_addend = (last_hi_addend + addend) as i64;
109                        if let Some(hi_addr) = last_hi.take() {
110                            addends.insert(hi_addr, full_addend);
111                        }
112                        addends.insert(addr, full_addend);
113                    }
114                    _ => {
115                        last_hi = None;
116                    }
117                }
118            }
119            let section_index = obj_section.index().0;
120            if section_index >= paired_relocations.len() {
121                paired_relocations.resize_with(section_index + 1, BTreeMap::new);
122            }
123            paired_relocations[section_index] = addends;
124        }
125
126        let mut ignored_symbols = BTreeSet::new();
127        for obj_symbol in object.symbols() {
128            let Ok(name) = obj_symbol.name() else { continue };
129            if let Some(prefix) = name.strip_suffix(".NON_MATCHING") {
130                ignored_symbols.insert(obj_symbol.index().0);
131                // Only remove the prefixless symbols if we are on the Base side of the diff,
132                // to allow diffing against target objects that contain `.NON_MATCHING` markers.
133                if diff_side == DiffSide::Base
134                    && let Some(target_symbol) = object.symbol_by_name(prefix)
135                {
136                    ignored_symbols.insert(target_symbol.index().0);
137                }
138            }
139        }
140
141        Ok(Self {
142            endianness,
143            abi,
144            isa_extension,
145            ri_gp_value,
146            paired_relocations,
147            ignored_symbols,
148            diff_side,
149        })
150    }
151
152    fn default_instruction_flags(&self) -> rabbitizer::InstructionFlags {
153        match self.isa_extension {
154            Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
155            None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
156        }
157        .with_abi(self.abi)
158    }
159
160    fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
161        let isa_extension = match diff_config.mips_instr_category {
162            MipsInstrCategory::Auto => self.isa_extension,
163            MipsInstrCategory::Cpu => None,
164            MipsInstrCategory::Rsp => Some(IsaExtension::RSP),
165            MipsInstrCategory::R3000gte => Some(IsaExtension::R3000GTE),
166            MipsInstrCategory::R4000allegrex => Some(IsaExtension::R4000ALLEGREX),
167            MipsInstrCategory::R5900 => Some(IsaExtension::R5900EE),
168        };
169        match isa_extension {
170            Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
171            None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
172        }
173        .with_abi(match diff_config.mips_abi {
174            MipsAbi::Auto => self.abi,
175            MipsAbi::O32 => Abi::O32,
176            MipsAbi::O64 => Abi::O64,
177            MipsAbi::N32 => Abi::N32,
178            MipsAbi::N64 => Abi::N64,
179            MipsAbi::Eabi32 => Abi::EABI32,
180            MipsAbi::Eabi64 => Abi::EABI64,
181        })
182    }
183
184    fn instruction_display_flags(
185        &self,
186        diff_config: &DiffObjConfig,
187    ) -> rabbitizer::InstructionDisplayFlags {
188        rabbitizer::InstructionDisplayFlags::default()
189            .with_unknown_instr_comment(false)
190            .with_use_dollar(diff_config.mips_register_prefix)
191            .with_r5900ee_prodg_sn_as_inverted_regs(diff_config.mips_prodg_sn_as_inverted_regs)
192    }
193
194    fn parse_ins_ref(
195        &self,
196        ins_ref: InstructionRef,
197        code: &[u8],
198        diff_config: &DiffObjConfig,
199    ) -> Result<rabbitizer::Instruction> {
200        Ok(rabbitizer::Instruction::new(
201            self.endianness.read_u32(code.try_into()?),
202            Vram::new(ins_ref.address as u32),
203            self.instruction_flags(diff_config),
204        ))
205    }
206}
207
208impl Arch for ArchMips {
209    fn scan_instructions_internal(
210        &self,
211        address: u64,
212        code: &[u8],
213        _section_index: usize,
214        _relocations: &[Relocation],
215        diff_config: &DiffObjConfig,
216    ) -> Result<Vec<InstructionRef>> {
217        let instruction_flags = self.instruction_flags(diff_config);
218        let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4);
219        let mut cur_addr = address as u32;
220        for chunk in code.as_chunks::<4>().0 {
221            let code = self.endianness.read_u32(*chunk);
222            let instruction =
223                rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
224            let opcode = instruction.opcode() as u16;
225            let branch_dest = instruction.get_branch_vram_generic().map(|v| v.inner() as u64);
226            ops.push(InstructionRef { address: cur_addr as u64, size: 4, opcode, branch_dest });
227            cur_addr += 4;
228        }
229        Ok(ops)
230    }
231
232    fn display_instruction(
233        &self,
234        resolved: ResolvedInstructionRef,
235        diff_config: &DiffObjConfig,
236        cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
237    ) -> Result<()> {
238        let instruction = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
239        let display_flags = self.instruction_display_flags(diff_config);
240        let opcode = instruction.opcode();
241        let mnemonic = instruction.mnemonic_display(&display_flags).to_string();
242        cb(InstructionPart::opcode(mnemonic, opcode as u16))?;
243        push_args(&instruction, resolved.relocation, &display_flags, cb)?;
244        Ok(())
245    }
246
247    fn relocation_override(
248        &self,
249        file: &object::File<'_>,
250        section: &object::Section,
251        address: u64,
252        relocation: &object::Relocation,
253    ) -> Result<Option<RelocationOverride>> {
254        match relocation.flags() {
255            // Handle ELF implicit relocations
256            object::RelocationFlags::Elf { r_type } => {
257                if relocation.has_implicit_addend() {
258                    // Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
259                    if let elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type
260                        && let Some(addend) = self
261                            .paired_relocations
262                            .get(section.index().0)
263                            .and_then(|m| m.get(&address).copied())
264                    {
265                        return Ok(Some(RelocationOverride {
266                            target: RelocationOverrideTarget::Keep,
267                            addend,
268                        }));
269                    }
270
271                    let data = section.data()?;
272                    let code = self
273                        .endianness
274                        .read_u32(data[address as usize..address as usize + 4].try_into()?);
275                    let addend = match r_type {
276                        elf::R_MIPS_32 => code as i64,
277                        elf::R_MIPS_26 => ((code & 0x03FFFFFF) << 2) as i64,
278                        elf::R_MIPS_HI16 => ((code & 0x0000FFFF) << 16) as i32 as i64,
279                        elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16 => {
280                            (code & 0x0000FFFF) as i16 as i64
281                        }
282                        elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL => {
283                            let object::RelocationTarget::Symbol(idx) = relocation.target() else {
284                                bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
285                            };
286                            let sym = file.symbol_by_index(idx)?;
287
288                            // if the symbol we are relocating against is in a local section we need to add
289                            // the ri_gp_value from .reginfo to the addend.
290                            if sym.section().index().is_some() {
291                                ((code & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
292                            } else {
293                                (code & 0x0000FFFF) as i16 as i64
294                            }
295                        }
296                        elf::R_MIPS_PC16 => 0, // PC-relative relocation
297                        R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
298                        elf::R_MIPS_GPREL32 => (code as i32 as i64) + self.ri_gp_value as i64,
299                        flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
300                    };
301                    Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
302                } else {
303                    Ok(None)
304                }
305            }
306            _ => Ok(None),
307        }
308    }
309
310    fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
311        match flags {
312            RelocationFlags::Elf(r_type) => match r_type {
313                elf::R_MIPS_NONE => Some("R_MIPS_NONE"),
314                elf::R_MIPS_16 => Some("R_MIPS_16"),
315                elf::R_MIPS_32 => Some("R_MIPS_32"),
316                elf::R_MIPS_26 => Some("R_MIPS_26"),
317                elf::R_MIPS_HI16 => Some("R_MIPS_HI16"),
318                elf::R_MIPS_LO16 => Some("R_MIPS_LO16"),
319                elf::R_MIPS_GPREL16 => Some("R_MIPS_GPREL16"),
320                elf::R_MIPS_LITERAL => Some("R_MIPS_LITERAL"),
321                elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
322                elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
323                elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
324                elf::R_MIPS_GPREL32 => Some("R_MIPS_GPREL32"),
325                R_MIPS15_S3 => Some("R_MIPS15_S3"),
326                _ => None,
327            },
328            _ => None,
329        }
330    }
331
332    fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
333        match flags {
334            RelocationFlags::Elf(r_type) => match r_type {
335                elf::R_MIPS_16 => 2,
336                elf::R_MIPS_32 => 4,
337                _ => 1,
338            },
339            _ => 1,
340        }
341    }
342
343    fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
344        let mut flags = SymbolFlagSet::default();
345        if self.ignored_symbols.contains(&symbol.index().0) {
346            flags |= SymbolFlag::Ignored;
347        }
348        flags
349    }
350
351    fn infer_function_size(
352        &self,
353        symbol: &Symbol,
354        section: &Section,
355        next_address: u64,
356    ) -> Result<u64> {
357        // Trim any trailing 4-byte zeroes from the end (nops)
358        let mut new_address = next_address;
359        while new_address >= symbol.address + 4
360            && let Some(data) = section.data_range(new_address - 4, 4)
361            && data == [0u8; 4]
362            && section.relocation_at(next_address - 4, 4).is_none()
363        {
364            new_address -= 4;
365        }
366        // Check if the last instruction has a delay slot, if so, include the delay slot nop
367        if new_address + 4 <= next_address
368            && new_address >= symbol.address + 4
369            && let Some(data) = section.data_range(new_address - 4, 4)
370            && let instruction = rabbitizer::Instruction::new(
371                self.endianness.read_u32(data.try_into().unwrap()),
372                Vram::new((new_address - 4) as u32),
373                self.default_instruction_flags(),
374            )
375            && instruction.opcode().has_delay_slot()
376        {
377            new_address += 4;
378        }
379        Ok(new_address.saturating_sub(symbol.address))
380    }
381}
382
383fn push_args(
384    instruction: &rabbitizer::Instruction,
385    relocation: Option<ResolvedRelocation>,
386    display_flags: &rabbitizer::InstructionDisplayFlags,
387    mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
388) -> Result<()> {
389    let operands = instruction.valued_operands_iter();
390    for (idx, op) in operands.enumerate() {
391        if idx > 0 {
392            arg_cb(InstructionPart::separator())?;
393        }
394
395        match op {
396            ValuedOperand::core_imm_i16(imm) => {
397                if let Some(resolved) = relocation {
398                    push_reloc(resolved.relocation, &mut arg_cb)?;
399                } else {
400                    arg_cb(InstructionPart::signed(imm))?;
401                }
402            }
403            ValuedOperand::core_imm_u16(imm) => {
404                if let Some(resolved) = relocation {
405                    push_reloc(resolved.relocation, &mut arg_cb)?;
406                } else {
407                    arg_cb(InstructionPart::unsigned(imm))?;
408                }
409            }
410            ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
411                if let Some(resolved) = relocation {
412                    push_reloc(resolved.relocation, &mut arg_cb)?;
413                } else if let Some(branch_dest) = instruction
414                    .get_branch_offset_generic()
415                    .map(|o| (instruction.vram() + o).inner() as u64)
416                {
417                    arg_cb(InstructionPart::branch_dest(branch_dest))?;
418                } else {
419                    arg_cb(InstructionPart::opaque(
420                        op.display(instruction, display_flags, None::<&str>).to_string(),
421                    ))?;
422                }
423            }
424            ValuedOperand::core_imm_rs(imm, base) => {
425                if let Some(resolved) = relocation {
426                    push_reloc(resolved.relocation, &mut arg_cb)?;
427                } else {
428                    arg_cb(InstructionPart::Arg(InstructionArg::Value(
429                        InstructionArgValue::Signed(imm as i64),
430                    )))?;
431                }
432                arg_cb(InstructionPart::basic("("))?;
433                arg_cb(InstructionPart::opaque(base.either_name(
434                    instruction.flags().abi(),
435                    display_flags.named_gpr(),
436                    !display_flags.use_dollar(),
437                )))?;
438                arg_cb(InstructionPart::basic(")"))?;
439            }
440            // ValuedOperand::r5900_immediate15(..) => match relocation {
441            //     Some(resolved)
442            //         if resolved.relocation.flags == RelocationFlags::Elf(R_MIPS15_S3) =>
443            //     {
444            //         push_reloc(&resolved.relocation, &mut arg_cb)?;
445            //     }
446            //     _ => {
447            //         arg_cb(InstructionPart::opaque(op.disassemble(&instruction, None)))?;
448            //     }
449            // },
450            _ => {
451                arg_cb(InstructionPart::opaque(
452                    op.display(instruction, display_flags, None::<&str>).to_string(),
453                ))?;
454            }
455        }
456    }
457    Ok(())
458}
459
460fn push_reloc(
461    reloc: &Relocation,
462    mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
463) -> Result<()> {
464    match reloc.flags {
465        RelocationFlags::Elf(r_type) => match r_type {
466            elf::R_MIPS_HI16 => {
467                arg_cb(InstructionPart::basic("%hi("))?;
468                arg_cb(InstructionPart::reloc())?;
469                arg_cb(InstructionPart::basic(")"))?;
470            }
471            elf::R_MIPS_LO16 => {
472                arg_cb(InstructionPart::basic("%lo("))?;
473                arg_cb(InstructionPart::reloc())?;
474                arg_cb(InstructionPart::basic(")"))?;
475            }
476            elf::R_MIPS_GOT16 => {
477                arg_cb(InstructionPart::basic("%got("))?;
478                arg_cb(InstructionPart::reloc())?;
479                arg_cb(InstructionPart::basic(")"))?;
480            }
481            elf::R_MIPS_CALL16 => {
482                arg_cb(InstructionPart::basic("%call16("))?;
483                arg_cb(InstructionPart::reloc())?;
484                arg_cb(InstructionPart::basic(")"))?;
485            }
486            elf::R_MIPS_GPREL16 => {
487                arg_cb(InstructionPart::basic("%gp_rel("))?;
488                arg_cb(InstructionPart::reloc())?;
489                arg_cb(InstructionPart::basic(")"))?;
490            }
491            elf::R_MIPS_32
492            | elf::R_MIPS_26
493            | elf::R_MIPS_LITERAL
494            | elf::R_MIPS_PC16
495            | R_MIPS15_S3 => {
496                arg_cb(InstructionPart::reloc())?;
497            }
498            _ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
499        },
500        flags => panic!("Unsupported MIPS relocation flags {flags:?}"),
501    }
502    Ok(())
503}