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

     1  package mips
     2  
     3  import (
     4  	"encoding/binary"
     5  	"sort"
     6  
     7  	"github.com/decomp/exp/bin"
     8  	"github.com/pkg/errors"
     9  	"github.com/unixpickle/mips32"
    10  )
    11  
    12  // A Func is a function.
    13  type Func struct {
    14  	// Address of the function.
    15  	Addr bin.Address
    16  	// Basic blocks of the function.
    17  	Blocks map[bin.Address]*BasicBlock
    18  }
    19  
    20  // A BasicBlock is a basic block; a sequence of non-branching instructions
    21  // terminated by a branching instruction.
    22  type BasicBlock struct {
    23  	// Address of the basic block.
    24  	Addr bin.Address
    25  	// Sequence of non-branching instructions.
    26  	Insts []*Inst
    27  	// Terminating instruction.
    28  	Term *Inst
    29  }
    30  
    31  // An Inst is a single instruction.
    32  type Inst struct {
    33  	// Address of the instruction.
    34  	Addr bin.Address
    35  	// MIPS instruction.
    36  	*mips32.Instruction
    37  }
    38  
    39  // DecodeFunc decodes and returns the function at the given address.
    40  func (dis *Disasm) DecodeFunc(entry bin.Address) (*Func, error) {
    41  	dbg.Printf("decoding function at %v", entry)
    42  	f := &Func{
    43  		Addr:   entry,
    44  		Blocks: make(map[bin.Address]*BasicBlock),
    45  	}
    46  	queue := newQueue()
    47  	queue.push(entry)
    48  	for !queue.empty() {
    49  		blockAddr := queue.pop()
    50  		if _, ok := f.Blocks[blockAddr]; ok {
    51  			// skip basic block if already decoded.
    52  			continue
    53  		}
    54  		block, err := dis.DecodeBlock(blockAddr)
    55  		if err != nil {
    56  			return nil, errors.WithStack(err)
    57  		}
    58  		f.Blocks[blockAddr] = block
    59  		// Add block targets to queue.
    60  		targets := dis.Targets(block.Term, entry)
    61  		for _, target := range targets {
    62  			dbg.Printf("adding basic block address %v to queue", target)
    63  			queue.push(target)
    64  		}
    65  	}
    66  	return f, nil
    67  }
    68  
    69  // DecodeBlock decodes and returns the basic block at the given address.
    70  func (dis *Disasm) DecodeBlock(entry bin.Address) (*BasicBlock, error) {
    71  	dbg.Printf("decoding basic block at %v", entry)
    72  	// Compute end address of the basic block.
    73  	maxLen := dis.maxBlockLen(entry)
    74  	addr := entry
    75  	end := entry + bin.Address(maxLen)
    76  	// Decode instructions.
    77  	block := &BasicBlock{
    78  		Addr: entry,
    79  	}
    80  	for addr < end {
    81  		inst, err := dis.DecodeInst(addr)
    82  		if err != nil {
    83  			return nil, errors.WithStack(err)
    84  		}
    85  		dbg.Printf("   instruction at %v: %v", addr, inst)
    86  		addr += mipsInstLen
    87  		if inst.isTerm() {
    88  			block.Term = inst
    89  			// Decode delay slot instruction and attach to basic block.
    90  			inst, err := dis.DecodeInst(addr)
    91  			if err != nil {
    92  				return nil, errors.WithStack(err)
    93  			}
    94  			dbg.Printf("   delay slot instruction at %v: %v", addr, inst)
    95  			addr += mipsInstLen
    96  			block.Insts = append(block.Insts, inst)
    97  			break
    98  		}
    99  		block.Insts = append(block.Insts, inst)
   100  	}
   101  	// Sanity check.
   102  	if addr != end {
   103  		warn.Printf("unexpected end address of basic block at %v; expected %v, got %v", entry, end, addr)
   104  	}
   105  	// Add dummy terminator for fallthrough basic blocks.
   106  	if block.Term == nil {
   107  		block.Term = &Inst{
   108  			Addr: end,
   109  		}
   110  	}
   111  	return block, nil
   112  }
   113  
   114  // DecodeInst decodes and returns the instruction at the given address.
   115  func (dis *Disasm) DecodeInst(addr bin.Address) (*Inst, error) {
   116  	code := dis.File.Code(addr)
   117  	word := binary.LittleEndian.Uint32(code)
   118  	i := mips32.DecodeInstruction(word)
   119  	inst := &Inst{
   120  		Addr:        addr,
   121  		Instruction: i,
   122  	}
   123  	return inst, nil
   124  }
   125  
   126  // maxBlockLen returns the maximum length of the given basic block.
   127  func (dis *Disasm) maxBlockLen(blockAddr bin.Address) int64 {
   128  	less := func(i int) bool {
   129  		return blockAddr < dis.Frags[i].Addr
   130  	}
   131  	index := sort.Search(len(dis.Frags), less)
   132  	if 0 <= index && index < len(dis.Frags) {
   133  		return int64(dis.Frags[index].Addr - blockAddr)
   134  	}
   135  	return int64(dis.codeEnd() - blockAddr)
   136  }
   137  
   138  // codeStart returns the start address of the first code section.
   139  func (dis *Disasm) codeStart() bin.Address {
   140  	var min bin.Address
   141  	for _, sect := range dis.File.Sections {
   142  		if sect.Perm&bin.PermX != 0 {
   143  			start := sect.Addr
   144  			if min == 0 || min > start {
   145  				min = start
   146  			}
   147  		}
   148  	}
   149  	if min == 0 {
   150  		panic("unable to locate start address of first code section")
   151  	}
   152  	return min
   153  }
   154  
   155  // codeEnd returns the end address of the last code section.
   156  func (dis *Disasm) codeEnd() bin.Address {
   157  	var max bin.Address
   158  	for _, sect := range dis.File.Sections {
   159  		if sect.Perm&bin.PermX != 0 {
   160  			end := sect.Addr + bin.Address(len(sect.Data))
   161  			if max < end {
   162  				max = end
   163  			}
   164  		}
   165  	}
   166  	if max == 0 {
   167  		panic("unable to locate end address of last code section")
   168  	}
   169  	return max
   170  }