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

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