github.com/decomp/exp@v0.0.0-20210624183419-6d058f5e1da6/disasm/mips/inst.go (about)

     1  package mips
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/decomp/exp/bin"
     8  )
     9  
    10  // MIPS instruction length in bytes.
    11  const mipsInstLen = 4
    12  
    13  // MIPS $ra register index.
    14  const mipsRegRA = 31
    15  
    16  // String returns the string representation of the instruction.
    17  func (inst *Inst) String() string {
    18  	if inst.IsDummyTerm() {
    19  		return fmt.Sprintf("; fallthrough %v", inst.Addr)
    20  	}
    21  	line, err := inst.Render()
    22  	if err != nil {
    23  		panic(fmt.Errorf("unable to pretty-print instruction; %v", err))
    24  	}
    25  	return line.String()
    26  }
    27  
    28  // isTerm reports whether the given instruction is a terminating instruction.
    29  func (inst *Inst) isTerm() bool {
    30  	switch inst.Name {
    31  	// Conditional branch instructions.
    32  	case "BEQ", "BGEZ", "BGTZ", "BLEZ", "BLTZ", "BNE":
    33  		return true
    34  	// Unconditional jump instructions.
    35  	case "J", "JAL":
    36  		return true
    37  	// Unconditional indirect jump instructions.
    38  	case "JALR", "JR":
    39  		return true
    40  	}
    41  	return false
    42  }
    43  
    44  // IsDummyTerm reports whether the given instruction is a dummy terminating
    45  // instruction. Dummy terminators are used when a basic block is missing a
    46  // terminator and falls through into the succeeding basic block, the address of
    47  // which is denoted by inst.Addr.
    48  func (inst *Inst) IsDummyTerm() bool {
    49  	return inst.Instruction == nil
    50  }
    51  
    52  // Targets returns the targets of the given terminator instruction. Entry
    53  // denotes the entry address of the function containing the terminator
    54  // instruction.
    55  func (dis *Disasm) Targets(term *Inst, funcEntry bin.Address) []bin.Address {
    56  	if term.IsDummyTerm() {
    57  		// Dummy terminator; fall through into the succeeding basic block, the
    58  		// address of which is denoted by term.Addr.
    59  		return []bin.Address{term.Addr}
    60  	}
    61  	next := term.Addr + mipsInstLen
    62  	switch term.Name {
    63  	// Conditional branch instructions.
    64  	case "BEQ", "BGEZ", "BGTZ", "BLEZ", "BLTZ", "BNE":
    65  		var targets []bin.Address
    66  		cp := term.CodePointer
    67  		if cp.IsSymbol {
    68  			// TODO: Add support for symbol code pointers.
    69  			panic(fmt.Errorf("support for terminators with symbol code pointers not yet implemented; %v", term))
    70  		}
    71  		if cp.Absolute {
    72  			target := term.Addr&0xF0000000 | bin.Address(cp.Constant)
    73  			targets = append(targets, target)
    74  		} else {
    75  			// Relative target.
    76  			var target bin.Address
    77  			switch dis.Mode {
    78  			case 32:
    79  				target = bin.Address(uint32(term.Addr) + cp.Constant + mipsInstLen)
    80  			case 64:
    81  				target = term.Addr + bin.Address(cp.Constant) + mipsInstLen
    82  			default:
    83  				panic(fmt.Errorf("support for CPU mode %d not yet implemented", dis.Mode))
    84  			}
    85  			targets = append(targets, target)
    86  		}
    87  		targets = append(targets, next)
    88  		return targets
    89  	// Unconditional jump instructions.
    90  	case "J", "JAL":
    91  		var targets []bin.Address
    92  		cp := term.CodePointer
    93  		if cp.IsSymbol {
    94  			// TODO: Add support for symbol code pointers.
    95  			panic(fmt.Errorf("support for terminators with symbol code pointers not yet implemented; %v", term))
    96  			// Return terminators.
    97  			//if cp.Symbol == "$ra" {
    98  			//	// no targets.
    99  			//	return nil
   100  			//}
   101  		}
   102  		if cp.Absolute {
   103  			target := term.Addr&0xF0000000 | bin.Address(cp.Constant)
   104  			targets = append(targets, target)
   105  		} else {
   106  			// Relative target.
   107  			target := term.Addr + bin.Address(cp.Constant) + mipsInstLen
   108  			targets = append(targets, target)
   109  		}
   110  		return targets
   111  	// Unconditional indirect jump instructions.
   112  	case "JALR", "JR":
   113  		reg := term.Registers[len(term.Registers)-1]
   114  		fmt.Println("term:", term)
   115  		fmt.Println("   reg:", reg)
   116  		if reg == mipsRegRA {
   117  			return nil
   118  		}
   119  		// TODO: Handle indirect jumps. Note, the $ra return register has no
   120  		// targets. Other registers need context information.
   121  		//panic(fmt.Errorf("support for indirect jump to register %v not yet implemented; %v", reg, term))
   122  		return nil // TODO: Remove, once context is implemented.
   123  	}
   124  	panic(fmt.Errorf("support for terminator instruction %v not yet implemented", term.Name))
   125  }
   126  
   127  // isTailCall reports whether the given JMP instruction is a tail call
   128  // instruction.
   129  func (dis *Disasm) isTailCall(funcEntry bin.Address, target bin.Address) bool {
   130  	funcEnd := dis.funcEnd(funcEntry)
   131  	if funcEntry <= target && target < funcEnd {
   132  		// Target inside function body.
   133  		return false
   134  	}
   135  	if chunk, ok := dis.Chunks[target]; ok {
   136  		if chunk[funcEntry] {
   137  			// Target part of function chunk.
   138  			return false
   139  		}
   140  	}
   141  	if !dis.IsFunc(target) {
   142  		panic(fmt.Errorf("tail call to non-function address %v from function at %v.\n\ttip: %v may be a function chunk of the parent function at %v\n\tadd to lst:  FUNCTION CHUNK AT .text:%08X\n\tadd to json: %q: %q,", target, funcEntry, target, funcEntry, uint64(target), target, funcEntry))
   143  	}
   144  	// Target is a tail call.
   145  	return true
   146  }
   147  
   148  // funcEnd returns the end address of the function, under the assumption that
   149  // the function is continuous.
   150  func (dis *Disasm) funcEnd(funcEntry bin.Address) bin.Address {
   151  	less := func(i int) bool {
   152  		return funcEntry < dis.FuncAddrs[i]
   153  	}
   154  	index := sort.Search(len(dis.FuncAddrs), less)
   155  	if 0 <= index && index < len(dis.FuncAddrs) {
   156  		return dis.FuncAddrs[index]
   157  	}
   158  	return dis.codeEnd()
   159  }