Skip to content

Commit 6df91a0

Browse files
committed
fix(MIPS): handle branch delay slots with duplicated HI16 relocations
1 parent 0bc41dc commit 6df91a0

File tree

1 file changed

+147
-6
lines changed

1 file changed

+147
-6
lines changed

src/main/java/ghidra/app/analyzers/relocations/MipsCodeRelocationSynthesizer.java

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515

1616
import java.util.ArrayList;
1717
import java.util.Arrays;
18+
import java.util.Collections;
19+
import java.util.HashSet;
1820
import java.util.List;
21+
import java.util.Set;
22+
import java.util.stream.Collectors;
1923

2024
import ghidra.app.analyzers.relocations.emitters.BundleRelocationEmitter;
2125
import ghidra.app.analyzers.relocations.emitters.FunctionInstructionSink;
@@ -26,14 +30,22 @@
2630
import ghidra.app.analyzers.relocations.utils.SymbolWithOffset;
2731
import ghidra.app.util.importer.MessageLog;
2832
import ghidra.program.model.address.Address;
33+
import ghidra.program.model.block.BasicBlockModel;
34+
import ghidra.program.model.block.CodeBlock;
35+
import ghidra.program.model.block.CodeBlockIterator;
36+
import ghidra.program.model.block.CodeBlockModel;
37+
import ghidra.program.model.block.CodeBlockReference;
38+
import ghidra.program.model.block.CodeBlockReferenceIterator;
2939
import ghidra.program.model.lang.Processor;
3040
import ghidra.program.model.lang.Register;
3141
import ghidra.program.model.listing.Function;
3242
import ghidra.program.model.listing.Instruction;
43+
import ghidra.program.model.listing.Listing;
3344
import ghidra.program.model.listing.Program;
3445
import ghidra.program.model.mem.MemoryAccessException;
3546
import ghidra.program.model.relocobj.RelocationHighPair;
3647
import ghidra.program.model.relocobj.RelocationTable;
48+
import ghidra.program.model.symbol.FlowType;
3749
import ghidra.program.model.symbol.Reference;
3850
import ghidra.program.model.symbol.Symbol;
3951
import ghidra.program.model.symbol.SymbolIterator;
@@ -48,10 +60,14 @@ public class MipsCodeRelocationSynthesizer
4860
private static class MIPS_26_InstructionRelocationEmitter extends InstructionRelocationEmitter {
4961
private static final List<Byte> OPMASK_JTYPE = Arrays.asList(new Byte[] { -1, -1, -1, 3 });
5062

63+
private final Set<Instruction> branchesToShiftByOne;
64+
5165
public MIPS_26_InstructionRelocationEmitter(Program program,
52-
RelocationTable relocationTable, Function function, TaskMonitor monitor,
53-
MessageLog log) {
66+
RelocationTable relocationTable, Function function,
67+
Set<Instruction> branchesToShiftByOne, TaskMonitor monitor, MessageLog log) {
5468
super(program, relocationTable, function, monitor, log);
69+
70+
this.branchesToShiftByOne = branchesToShiftByOne;
5571
}
5672

5773
@Override
@@ -88,6 +104,11 @@ public long computeAddend(Instruction instruction, int operandIndex,
88104
public boolean emitRelocation(Instruction instruction, int operandIndex,
89105
SymbolWithOffset symbol, Reference reference, int offset, List<Byte> mask,
90106
long addend) throws MemoryAccessException {
107+
if (branchesToShiftByOne.contains(instruction)) {
108+
logBranchDelaySlotWithHI16(instruction.getAddress(), getMessageLog());
109+
addend -= 1;
110+
}
111+
91112
if (addend < -0x4000000 || addend > 0x3ffffff) {
92113
return false;
93114
}
@@ -330,10 +351,14 @@ private Register getOutputRegister(Instruction instruction) {
330351
private static class MIPS_PC16_InstructionRelocationEmitter
331352
extends RelativeNextInstructionRelocationEmitter {
332353

354+
private final Set<Instruction> branchesToShiftByOne;
355+
333356
public MIPS_PC16_InstructionRelocationEmitter(Program program,
334-
RelocationTable relocationTable, Function function, TaskMonitor monitor,
335-
MessageLog log) {
357+
RelocationTable relocationTable, Function function,
358+
Set<Instruction> branchesToShiftByOne, TaskMonitor monitor, MessageLog log) {
336359
super(program, relocationTable, function, monitor, log);
360+
361+
this.branchesToShiftByOne = branchesToShiftByOne;
337362
}
338363

339364
@Override
@@ -349,6 +374,28 @@ public long computeValue(Instruction instruction, int operandIndex, Reference re
349374
int offset, List<Byte> mask) throws MemoryAccessException {
350375
return super.computeValue(instruction, operandIndex, reference, offset, mask) << 2;
351376
}
377+
378+
@Override
379+
public boolean emitRelocation(Instruction instruction, int operandIndex,
380+
SymbolWithOffset symbol, Reference reference, int offset, List<Byte> mask,
381+
long addend) throws MemoryAccessException {
382+
if (branchesToShiftByOne.contains(instruction)) {
383+
// FIXME: clean up hack job for branch delay slots with HI16.
384+
logBranchDelaySlotWithHI16(instruction.getAddress(), getMessageLog());
385+
addend -= 1;
386+
387+
RelocationTable relocationTable = getRelocationTable();
388+
Address fromAddress = instruction.getAddress();
389+
390+
relocationTable.addRelativePC(fromAddress.add(offset), getSizeFromMask(mask),
391+
symbol.name, addend, false);
392+
return true;
393+
}
394+
else {
395+
return super.emitRelocation(instruction, operandIndex, symbol, reference, offset,
396+
mask, addend);
397+
}
398+
}
352399
}
353400

354401
private static class MIPS_GPREL16_InstructionRelocationEmitter
@@ -392,13 +439,15 @@ public boolean matches(Instruction instruction, int operandIndex, Reference refe
392439
public List<FunctionInstructionSink> getFunctionInstructionSinks(Program program,
393440
RelocationTable relocationTable, Function function, TaskMonitor monitor,
394441
MessageLog log) throws CancelledException {
442+
Set<Instruction> branchesToShiftByOne =
443+
detectBranchDelaySlotsWithHI16(program, function, monitor);
395444
List<FunctionInstructionSink> sinks = new ArrayList<>();
396445
sinks.add(new MIPS_26_InstructionRelocationEmitter(program, relocationTable, function,
397-
monitor, log));
446+
branchesToShiftByOne, monitor, log));
398447
sinks.add(new MIPS_HI16LO16_BundleRelocationEmitter(program, relocationTable, function,
399448
monitor, log));
400449
sinks.add(new MIPS_PC16_InstructionRelocationEmitter(program, relocationTable, function,
401-
monitor, log));
450+
branchesToShiftByOne, monitor, log));
402451

403452
SymbolTable symbolTable = program.getSymbolTable();
404453
SymbolIterator _gp = symbolTable.getSymbols("_gp");
@@ -410,6 +459,98 @@ public List<FunctionInstructionSink> getFunctionInstructionSinks(Program program
410459
return sinks;
411460
}
412461

462+
/**
463+
* The MIPS instruction set has the following quirks that can combine into a complete mess:
464+
* * It relies on pairs of HI16/LO16 relocations to load absolute 32-bit pointers.
465+
* * It has branch delay slots.
466+
*
467+
* One common MIPS optimization trick done by assemblers is to fill these delay slots by
468+
* duplicating the target instruction inside the delay slot and shifting the branch target one
469+
* instruction forward, resulting in an instruction stream shortened by one instruction.
470+
*
471+
* Unfortunately, some assemblers can decide to vacuum up instructions with a HI16 relocation,
472+
* meaning that a LO16 relocation could have multiple HI16 relocation parents. This pattern
473+
* can't be expressed by any object file format that I'm aware of. Therefore, we need to undo
474+
* this optimization by shifting these branch targets back one instruction in order to recover
475+
* valid HI16/LO16 relocation pairs.
476+
*/
477+
private Set<Instruction> detectBranchDelaySlotsWithHI16(Program program, Function function,
478+
TaskMonitor monitor) throws CancelledException {
479+
Set<Instruction> set = new HashSet<>();
480+
CodeBlockModel codeBlockModel = new BasicBlockModel(program);
481+
482+
CodeBlockIterator it = codeBlockModel.getCodeBlocksContaining(function.getBody(), monitor);
483+
while (it.hasNext()) {
484+
set.addAll(
485+
detectBranchDelaySlotsWithHI16_CodeBlock(program, function, it.next(), monitor));
486+
}
487+
488+
return set;
489+
}
490+
491+
private Set<Instruction> detectBranchDelaySlotsWithHI16_CodeBlock(Program program,
492+
Function function, CodeBlock codeBlock, TaskMonitor monitor) throws CancelledException {
493+
CodeBlockReferenceIterator refIt = codeBlock.getSources(monitor);
494+
List<CodeBlock> sources = new ArrayList<>();
495+
CodeBlock fallThrough = null;
496+
497+
while (refIt.hasNext()) {
498+
CodeBlockReference ref = refIt.next();
499+
CodeBlock sourceCodeBlock = ref.getSourceBlock();
500+
if (!function.getBody().contains(sourceCodeBlock)) {
501+
// This deoptimization is strictly scoped to within a function, bail out.
502+
return Collections.emptySet();
503+
}
504+
505+
if (ref.getFlowType() == FlowType.FALL_THROUGH) {
506+
fallThrough = sourceCodeBlock;
507+
}
508+
else {
509+
sources.add(sourceCodeBlock);
510+
}
511+
}
512+
513+
if (!sources.isEmpty() && fallThrough != null) {
514+
return detectBranchDelaySlotsWithHI16_CodeBlockSources(program, function, codeBlock,
515+
sources);
516+
}
517+
518+
return Collections.emptySet();
519+
}
520+
521+
private Set<Instruction> detectBranchDelaySlotsWithHI16_CodeBlockSources(Program program,
522+
Function function, CodeBlock codeBlock, List<CodeBlock> sources)
523+
throws CancelledException {
524+
Listing listing = program.getListing();
525+
Instruction targetInstruction =
526+
(Instruction) listing.getCodeUnitAt(codeBlock.getMinAddress());
527+
Instruction previousInstruction =
528+
(Instruction) listing.getCodeUnitBefore(targetInstruction.getAddress());
529+
if (!previousInstruction.getMnemonicString().equals("lui") &&
530+
!previousInstruction.getMnemonicString().equals("_lui")) {
531+
return Collections.emptySet();
532+
}
533+
534+
return sources.stream()
535+
.map(c -> (Instruction) listing.getCodeUnitContaining(c.getMaxAddress()))
536+
.filter(i -> {
537+
try {
538+
return Arrays.equals(i.getBytes(), previousInstruction.getBytes());
539+
}
540+
catch (MemoryAccessException ex) {
541+
return false;
542+
}
543+
})
544+
.map(i -> (Instruction) listing.getCodeUnitBefore(i.getAddress()))
545+
.filter(i -> i.getAddress().compareTo(targetInstruction.getAddress()) < 0)
546+
.collect(Collectors.toSet());
547+
}
548+
549+
private static void logBranchDelaySlotWithHI16(Address address, MessageLog log) {
550+
log.appendMsg(address.toString(),
551+
"Branch instruction target adjusted to deoptimize possible HI16 relocation inside delay slot");
552+
}
553+
413554
@Override
414555
public boolean canAnalyze(Program program) {
415556
// Check language

0 commit comments

Comments
 (0)