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 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 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 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 object::RelocationFlags::Elf { r_type } => {
257 if relocation.has_implicit_addend() {
258 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 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, 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 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 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 _ => {
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}