1515
1616import java .util .ArrayList ;
1717import java .util .Arrays ;
18+ import java .util .Collections ;
19+ import java .util .HashSet ;
1820import java .util .List ;
21+ import java .util .Set ;
22+ import java .util .stream .Collectors ;
1923
2024import ghidra .app .analyzers .relocations .emitters .BundleRelocationEmitter ;
2125import ghidra .app .analyzers .relocations .emitters .FunctionInstructionSink ;
2630import ghidra .app .analyzers .relocations .utils .SymbolWithOffset ;
2731import ghidra .app .util .importer .MessageLog ;
2832import 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 ;
2939import ghidra .program .model .lang .Processor ;
3040import ghidra .program .model .lang .Register ;
3141import ghidra .program .model .listing .Function ;
3242import ghidra .program .model .listing .Instruction ;
43+ import ghidra .program .model .listing .Listing ;
3344import ghidra .program .model .listing .Program ;
3445import ghidra .program .model .mem .MemoryAccessException ;
3546import ghidra .program .model .relocobj .RelocationHighPair ;
3647import ghidra .program .model .relocobj .RelocationTable ;
48+ import ghidra .program .model .symbol .FlowType ;
3749import ghidra .program .model .symbol .Reference ;
3850import ghidra .program .model .symbol .Symbol ;
3951import 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