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 }