Skip to content

Commit 6ed07bf

Browse files
committed
MIPS relocation pairing, '$' prefix option & fixes
Implements MIPS relocation pairing logic. New option for "Register '$' prefix", off by default. Fixes various regressions introduced by refactoring. Resolves #122 Resolves #156
1 parent 80e9396 commit 6ed07bf

File tree

10 files changed

+2333
-62
lines changed

10 files changed

+2333
-62
lines changed

objdiff-core/config-schema.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@
187187
}
188188
]
189189
},
190+
{
191+
"id": "mips.registerPrefix",
192+
"type": "boolean",
193+
"default": false,
194+
"name": "Register '$' prefix",
195+
"description": "Display MIPS register names with a '$' prefix."
196+
},
190197
{
191198
"id": "x86.formatter",
192199
"type": "choice",
@@ -242,7 +249,8 @@
242249
"name": "MIPS",
243250
"properties": [
244251
"mips.abi",
245-
"mips.instrCategory"
252+
"mips.instrCategory",
253+
"mips.registerPrefix"
246254
]
247255
},
248256
{

objdiff-core/src/arch/mips.rs

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use alloc::{string::ToString, vec::Vec};
1+
use alloc::{collections::BTreeMap, string::ToString, vec::Vec};
22
use core::ops::Range;
33

44
use anyhow::{bail, Result};
@@ -25,6 +25,7 @@ pub struct ArchMips {
2525
pub abi: Abi,
2626
pub isa_extension: Option<IsaExtension>,
2727
pub ri_gp_value: i32,
28+
pub paired_relocations: Vec<BTreeMap<u64, i64>>,
2829
}
2930

3031
const EF_MIPS_ABI: u32 = 0x0000F000;
@@ -64,16 +65,60 @@ impl ArchMips {
6465

6566
// Parse the ri_gp_value stored in .reginfo to be able to correctly
6667
// calculate R_MIPS_GPREL16 relocations later. The value is stored
67-
// 0x14 bytes into .reginfo (on 32 bit platforms)
68+
// 0x14 bytes into .reginfo (on 32-bit platforms)
69+
let endianness = object.endianness();
6870
let ri_gp_value = object
6971
.section_by_name(".reginfo")
7072
.and_then(|section| section.data().ok())
7173
.and_then(|data| data.get(0x14..0x18))
7274
.and_then(|s| s.try_into().ok())
73-
.map(|bytes| object.endianness().read_i32_bytes(bytes))
75+
.map(|bytes| endianness.read_i32_bytes(bytes))
7476
.unwrap_or(0);
7577

76-
Ok(Self { endianness: object.endianness(), abi, isa_extension, ri_gp_value })
78+
// Parse all relocations to pair R_MIPS_HI16 and R_MIPS_LO16. Since the instructions only
79+
// have 16-bit immediate fields, the 32-bit addend is split across the two relocations.
80+
// R_MIPS_LO16 relocations without an immediately preceding R_MIPS_HI16 use the last seen
81+
// R_MIPS_HI16 addend.
82+
// See https://refspecs.linuxfoundation.org/elf/mipsabi.pdf pages 4-17 and 4-18
83+
let mut paired_relocations = Vec::with_capacity(object.sections().count() + 1);
84+
for obj_section in object.sections() {
85+
let data = obj_section.data()?;
86+
let mut last_hi = None;
87+
let mut last_hi_addend = 0;
88+
let mut addends = BTreeMap::new();
89+
for (addr, reloc) in obj_section.relocations() {
90+
if !reloc.has_implicit_addend() {
91+
continue;
92+
}
93+
match reloc.flags() {
94+
object::RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
95+
let code = data[addr as usize..addr as usize + 4].try_into()?;
96+
let addend = ((endianness.read_u32_bytes(code) & 0x0000FFFF) << 16) as i32;
97+
last_hi = Some(addr);
98+
last_hi_addend = addend;
99+
}
100+
object::RelocationFlags::Elf { r_type: elf::R_MIPS_LO16 } => {
101+
let code = data[addr as usize..addr as usize + 4].try_into()?;
102+
let addend = (endianness.read_u32_bytes(code) & 0x0000FFFF) as i16 as i32;
103+
let full_addend = (last_hi_addend + addend) as i64;
104+
if let Some(hi_addr) = last_hi.take() {
105+
addends.insert(hi_addr, full_addend);
106+
}
107+
addends.insert(addr, full_addend);
108+
}
109+
_ => {
110+
last_hi = None;
111+
}
112+
}
113+
}
114+
let section_index = obj_section.index().0;
115+
if section_index >= paired_relocations.len() {
116+
paired_relocations.resize_with(section_index + 1, BTreeMap::new);
117+
}
118+
paired_relocations[section_index] = addends;
119+
}
120+
121+
Ok(Self { endianness, abi, isa_extension, ri_gp_value, paired_relocations })
77122
}
78123

79124
fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
@@ -127,18 +172,16 @@ impl Arch for ArchMips {
127172
diff_config: &DiffObjConfig,
128173
) -> Result<Vec<ScannedInstruction>> {
129174
let instruction_flags = self.instruction_flags(diff_config);
130-
let start_address = address;
131175
let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
132-
let mut cur_addr = start_address as u32;
176+
let mut cur_addr = address as u32;
133177
for chunk in code.chunks_exact(4) {
134178
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
135-
let vram = Vram::new(cur_addr);
136-
let instruction = rabbitizer::Instruction::new(code, vram, instruction_flags);
179+
let instruction =
180+
rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
137181
let opcode = instruction.opcode() as u16;
138-
let branch_dest =
139-
instruction.get_branch_offset_generic().map(|o| (vram + o).inner() as u64);
182+
let branch_dest = instruction.get_branch_vram_generic().map(|v| v.inner() as u64);
140183
ops.push(ScannedInstruction {
141-
ins_ref: InstructionRef { address, size: 4, opcode },
184+
ins_ref: InstructionRef { address: cur_addr as u64, size: 4, opcode },
142185
branch_dest,
143186
});
144187
cur_addr += 4;
@@ -164,6 +207,7 @@ impl Arch for ArchMips {
164207
function_range,
165208
resolved.section_index,
166209
&display_flags,
210+
diff_config,
167211
cb,
168212
)?;
169213
Ok(())
@@ -177,6 +221,17 @@ impl Arch for ArchMips {
177221
reloc: &object::Relocation,
178222
flags: RelocationFlags,
179223
) -> Result<i64> {
224+
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
225+
if let RelocationFlags::Elf(elf::R_MIPS_HI16 | elf::R_MIPS_LO16) = flags {
226+
if let Some(addend) = self
227+
.paired_relocations
228+
.get(section.index().0)
229+
.and_then(|m| m.get(&address).copied())
230+
{
231+
return Ok(addend);
232+
}
233+
}
234+
180235
let data = section.data()?;
181236
let code = data[address as usize..address as usize + 4].try_into()?;
182237
let addend = self.endianness.read_u32_bytes(code);
@@ -246,6 +301,7 @@ fn push_args(
246301
function_range: Range<u64>,
247302
section_index: usize,
248303
display_flags: &rabbitizer::InstructionDisplayFlags,
304+
diff_config: &DiffObjConfig,
249305
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
250306
) -> Result<()> {
251307
let operands = instruction.valued_operands_iter();
@@ -305,9 +361,14 @@ fn push_args(
305361
})))?;
306362
}
307363
arg_cb(InstructionPart::basic("("))?;
308-
arg_cb(InstructionPart::opaque(
309-
base.either_name(instruction.flags().abi(), display_flags.named_gpr()),
310-
))?;
364+
let mut value =
365+
base.either_name(instruction.flags().abi(), display_flags.named_gpr());
366+
if !diff_config.mips_register_prefix {
367+
if let Some(trimmed) = value.strip_prefix('$') {
368+
value = trimmed;
369+
}
370+
}
371+
arg_cb(InstructionPart::opaque(value))?;
311372
arg_cb(InstructionPart::basic(")"))?;
312373
}
313374
// ValuedOperand::r5900_immediate15(..) => match relocation {
@@ -321,9 +382,14 @@ fn push_args(
321382
// }
322383
// },
323384
_ => {
324-
arg_cb(InstructionPart::opaque(
325-
op.display(instruction, display_flags, None::<&str>).to_string(),
326-
))?;
385+
let value = op.display(instruction, display_flags, None::<&str>).to_string();
386+
if !diff_config.mips_register_prefix {
387+
if let Some(value) = value.strip_prefix('$') {
388+
arg_cb(InstructionPart::opaque(value))?;
389+
continue;
390+
}
391+
}
392+
arg_cb(InstructionPart::opaque(value))?;
327393
}
328394
}
329395
}

objdiff-core/src/obj/read.rs

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ const LOW_PRIORITY_SYMBOLS: &[&str] =
263263
fn best_symbol<'r, 'data, 'file>(
264264
symbols: &'r [object::Symbol<'data, 'file>],
265265
address: u64,
266-
) -> Option<object::SymbolIndex> {
266+
) -> Option<(object::SymbolIndex, u64)> {
267267
let mut closest_symbol_index = match symbols.binary_search_by_key(&address, |s| s.address()) {
268268
Ok(index) => Some(index),
269269
Err(index) => index.checked_sub(1),
@@ -297,18 +297,31 @@ fn best_symbol<'r, 'data, 'file>(
297297
best_symbol = Some(symbol);
298298
}
299299
}
300-
best_symbol.map(|s| s.index())
300+
best_symbol.map(|s| (s.index(), s.address()))
301301
}
302302

303-
fn map_relocations(
303+
fn map_section_relocations(
304304
arch: &dyn Arch,
305305
obj_file: &object::File,
306306
obj_section: &object::Section,
307307
symbol_indices: &[usize],
308+
ordered_symbols: &[Vec<object::Symbol>],
308309
) -> Result<Vec<Relocation>> {
309310
let mut relocations = Vec::<Relocation>::with_capacity(obj_section.relocations().count());
310-
let mut ordered_symbols = None;
311311
for (address, reloc) in obj_section.relocations() {
312+
let flags = match reloc.flags() {
313+
object::RelocationFlags::Elf { r_type } => RelocationFlags::Elf(r_type),
314+
object::RelocationFlags::Coff { typ } => RelocationFlags::Coff(typ),
315+
flags => {
316+
bail!("Unhandled relocation flags: {:?}", flags);
317+
}
318+
};
319+
// TODO validate reloc here?
320+
let mut addend = if reloc.has_implicit_addend() {
321+
arch.implcit_addend(obj_file, obj_section, address, &reloc, flags)?
322+
} else {
323+
reloc.addend()
324+
};
312325
let target_symbol = match reloc.target() {
313326
object::RelocationTarget::Symbol(idx) => {
314327
if idx.0 == u32::MAX as usize {
@@ -323,24 +336,16 @@ fn map_relocations(
323336
{
324337
let section_index =
325338
section_symbol.section_index().context("Section symbol without section")?;
326-
let ordered_symbols = ordered_symbols.get_or_insert_with(|| {
327-
let mut vec = obj_file
328-
.symbols()
329-
.filter(|s| {
330-
s.section_index() == Some(section_index)
331-
&& s.kind() != object::SymbolKind::Section
332-
})
333-
.collect::<Vec<_>>();
334-
vec.sort_by(|a, b| {
335-
a.address().cmp(&b.address()).then(a.size().cmp(&b.size()))
336-
});
337-
vec
338-
});
339-
best_symbol(
340-
ordered_symbols,
341-
section_symbol.address().wrapping_add_signed(reloc.addend()),
342-
)
343-
.unwrap_or(idx)
339+
let target_address = section_symbol.address().wrapping_add_signed(addend);
340+
if let Some((new_idx, addr)) = ordered_symbols
341+
.get(section_index.0)
342+
.and_then(|symbols| best_symbol(symbols, target_address))
343+
{
344+
addend = target_address.wrapping_sub(addr) as i64;
345+
new_idx
346+
} else {
347+
idx
348+
}
344349
} else {
345350
idx
346351
};
@@ -359,24 +364,55 @@ fn map_relocations(
359364
}
360365
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
361366
};
362-
let flags = match reloc.flags() {
363-
object::RelocationFlags::Elf { r_type } => RelocationFlags::Elf(r_type),
364-
object::RelocationFlags::Coff { typ } => RelocationFlags::Coff(typ),
365-
flags => {
366-
bail!("Unhandled relocation flags: {:?}", flags);
367-
}
368-
};
369-
// TODO validate reloc here?
370-
let addend = if reloc.has_implicit_addend() {
371-
arch.implcit_addend(obj_file, obj_section, address, &reloc, flags)?
372-
} else {
373-
reloc.addend()
374-
};
375367
relocations.push(Relocation { address, flags, target_symbol, addend });
376368
}
369+
relocations.sort_by_key(|r| r.address);
377370
Ok(relocations)
378371
}
379372

373+
fn map_relocations(
374+
arch: &dyn Arch,
375+
obj_file: &object::File,
376+
sections: &mut [Section],
377+
section_indices: &[usize],
378+
symbol_indices: &[usize],
379+
) -> Result<()> {
380+
// Generate a list of symbols for each section
381+
let mut ordered_symbols =
382+
Vec::<Vec<object::Symbol>>::with_capacity(obj_file.sections().count() + 1);
383+
for symbol in obj_file.symbols() {
384+
let Some(section_index) = symbol.section_index() else {
385+
continue;
386+
};
387+
if symbol.kind() == object::SymbolKind::Section {
388+
continue;
389+
}
390+
if section_index.0 >= ordered_symbols.len() {
391+
ordered_symbols.resize_with(section_index.0 + 1, Vec::new);
392+
}
393+
ordered_symbols[section_index.0].push(symbol);
394+
}
395+
// Sort symbols by address and size
396+
for vec in &mut ordered_symbols {
397+
vec.sort_by(|a, b| a.address().cmp(&b.address()).then(a.size().cmp(&b.size())));
398+
}
399+
// Map relocations for each section. Section-relative relocations use the ordered symbols list
400+
// to find a better target symbol, if available.
401+
for obj_section in obj_file.sections() {
402+
let section = &mut sections[section_indices[obj_section.index().0]];
403+
if section.kind != SectionKind::Unknown {
404+
section.relocations = map_section_relocations(
405+
arch,
406+
obj_file,
407+
&obj_section,
408+
symbol_indices,
409+
&ordered_symbols,
410+
)?;
411+
}
412+
}
413+
Ok(())
414+
}
415+
380416
fn parse_line_info(
381417
obj_file: &object::File,
382418
sections: &mut [Section],
@@ -767,13 +803,7 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
767803
map_sections(arch.as_ref(), &obj_file, split_meta.as_ref())?;
768804
let (mut symbols, symbol_indices) =
769805
map_symbols(arch.as_ref(), &obj_file, &sections, &section_indices, split_meta.as_ref())?;
770-
for obj_section in obj_file.sections() {
771-
let section = &mut sections[section_indices[obj_section.index().0]];
772-
if section.kind != SectionKind::Unknown {
773-
section.relocations =
774-
map_relocations(arch.as_ref(), &obj_file, &obj_section, &symbol_indices)?;
775-
}
776-
}
806+
map_relocations(arch.as_ref(), &obj_file, &mut sections, &section_indices, &symbol_indices)?;
777807
parse_line_info(&obj_file, &mut sections, &section_indices, data)?;
778808
if config.combine_data_sections || config.combine_text_sections {
779809
combine_sections(&mut sections, &mut symbols, config)?;

objdiff-core/tests/arch_mips.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use objdiff_core::{diff, obj};
2+
3+
mod common;
4+
5+
#[test]
6+
#[cfg(feature = "mips")]
7+
fn read_mips() {
8+
let diff_config = diff::DiffObjConfig {
9+
mips_register_prefix: true,
10+
..Default::default()
11+
};
12+
let obj = obj::read::parse(include_object!("data/mips/main.c.o"), &diff_config).unwrap();
13+
insta::assert_debug_snapshot!(obj);
14+
let symbol_idx = obj.symbols.iter().position(|s| s.name == "ControlEntry").unwrap();
15+
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
16+
insta::assert_debug_snapshot!(diff.instruction_rows);
17+
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
18+
insta::assert_snapshot!(output);
19+
}

objdiff-core/tests/data/mips/main.c.o

5.39 KB
Binary file not shown.

0 commit comments

Comments
 (0)