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

     1  // Package disasm provides general disassembler primitives.
     2  package disasm
     3  
     4  import (
     5  	"log"
     6  	"os"
     7  	"sort"
     8  
     9  	"github.com/decomp/exp/bin"
    10  	"github.com/mewkiz/pkg/jsonutil"
    11  	"github.com/mewkiz/pkg/osutil"
    12  	"github.com/mewkiz/pkg/term"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  // TODO: Remove loggers once the library matures.
    17  
    18  // Loggers.
    19  var (
    20  	// dbg represents a logger with the "disasm:" prefix, which logs debug
    21  	// messages to standard error.
    22  	dbg = log.New(os.Stderr, term.BlueBold("disasm:")+" ", 0)
    23  	// warn represents a logger with the "warning:" prefix, which logs warning
    24  	// messages to standard error.
    25  	warn = log.New(os.Stderr, term.RedBold("warning:")+" ", 0)
    26  )
    27  
    28  // A Disasm tracks information required to disassemble a binary executable.
    29  //
    30  // Data should only be written to this structure during initialization. After
    31  // initialization the structure is considered in read-only mode to allow for
    32  // concurrent decoding of functions.
    33  type Disasm struct {
    34  	// Binary executable.
    35  	File *bin.File
    36  	// Function addresses.
    37  	FuncAddrs []bin.Address
    38  	// Basic block addresses.
    39  	BlockAddrs []bin.Address
    40  	// Map from jump table address to target addresses.
    41  	Tables map[bin.Address][]bin.Address
    42  	// Map from basic block address to function address. The basic block is a
    43  	// function chunk and part of a discontinuous function.
    44  	Chunks map[bin.Address]map[bin.Address]bool
    45  	// Fragments; sequences of bytes.
    46  	Frags []*Fragment
    47  }
    48  
    49  // New creates a new Disasm for accessing the assembly instructions of the given
    50  // binary executable, and the information contained within associated JSON and
    51  // LLVM IR files.
    52  //
    53  // Associated files of the generic disassembler.
    54  //
    55  //    funcs.json
    56  //    blocks.json
    57  //    tables.json
    58  //    chunks.json
    59  //    data.json
    60  func New(file *bin.File) (*Disasm, error) {
    61  	// Prepare generic disassembler.
    62  	dis := &Disasm{
    63  		File:   file,
    64  		Tables: make(map[bin.Address][]bin.Address),
    65  		Chunks: make(map[bin.Address]map[bin.Address]bool),
    66  	}
    67  
    68  	// Parse function addresses.
    69  	if err := parseJSON("funcs.json", &dis.FuncAddrs); err != nil {
    70  		return nil, errors.WithStack(err)
    71  	}
    72  	sort.Sort(bin.Addresses(dis.FuncAddrs))
    73  
    74  	// Parse basic block addresses.
    75  	if err := parseJSON("blocks.json", &dis.BlockAddrs); err != nil {
    76  		return nil, errors.WithStack(err)
    77  	}
    78  	sort.Sort(bin.Addresses(dis.BlockAddrs))
    79  
    80  	// Add entry point function to function and basic block addresses.
    81  	dis.FuncAddrs = bin.InsertAddr(dis.FuncAddrs, dis.File.Entry)
    82  	dis.BlockAddrs = bin.InsertAddr(dis.BlockAddrs, dis.File.Entry)
    83  
    84  	// Add export functions to function and basic block addresses.
    85  	for addr := range dis.File.Exports {
    86  		dis.FuncAddrs = bin.InsertAddr(dis.FuncAddrs, addr)
    87  		dis.BlockAddrs = bin.InsertAddr(dis.BlockAddrs, addr)
    88  	}
    89  
    90  	// Parse jump table targets.
    91  	if err := parseJSON("tables.json", &dis.Tables); err != nil {
    92  		return nil, errors.WithStack(err)
    93  	}
    94  
    95  	// Parse function chunks.
    96  	if err := parseJSON("chunks.json", &dis.Chunks); err != nil {
    97  		return nil, errors.WithStack(err)
    98  	}
    99  
   100  	// Compute fragments of the binary; distinct byte sequences of either code or
   101  	// data.
   102  	//
   103  	// Parse data addresses.
   104  	var dataAddrs []bin.Address
   105  	if err := parseJSON("data.json", &dataAddrs); err != nil {
   106  		return nil, errors.WithStack(err)
   107  	}
   108  	// Append basic block addresses to fragments.
   109  	for _, blockAddr := range dis.BlockAddrs {
   110  		frag := &Fragment{
   111  			Addr: blockAddr,
   112  			Kind: KindCode,
   113  		}
   114  		dis.Frags = append(dis.Frags, frag)
   115  	}
   116  	// Append data addresses to fragments.
   117  	for _, dataAddr := range dataAddrs {
   118  		frag := &Fragment{
   119  			Addr: dataAddr,
   120  			Kind: KindData,
   121  		}
   122  		dis.Frags = append(dis.Frags, frag)
   123  	}
   124  	// Sort fragments based on address.
   125  	less := func(i, j int) bool {
   126  		return dis.Frags[i].Addr < dis.Frags[j].Addr
   127  	}
   128  	sort.Slice(dis.Frags, less)
   129  
   130  	return dis, nil
   131  }
   132  
   133  // IsFunc reports whether the given address is the entry address of a function.
   134  func (dis *Disasm) IsFunc(addr bin.Address) bool {
   135  	less := func(i int) bool {
   136  		return addr <= dis.FuncAddrs[i]
   137  	}
   138  	index := sort.Search(len(dis.FuncAddrs), less)
   139  	if index < len(dis.FuncAddrs) {
   140  		return dis.FuncAddrs[index] == addr
   141  	}
   142  	if _, ok := dis.File.Imports[addr]; ok {
   143  		return true
   144  	}
   145  	return false
   146  }
   147  
   148  // A Fragment represents a sequence of bytes (either code or data).
   149  type Fragment struct {
   150  	// Start address of fragment.
   151  	Addr bin.Address
   152  	// Byte sequence type (code or data).
   153  	Kind FragmentKind
   154  }
   155  
   156  // FragmentKind specifies the set of byte sequence types (either code or data).
   157  type FragmentKind uint
   158  
   159  // Fragment kinds.
   160  const (
   161  	// The sequence of bytes contains code.
   162  	KindCode = iota + 1
   163  	// The sequence of bytes contains data.
   164  	KindData
   165  )
   166  
   167  // ### [ Helper functions ] ####################################################
   168  
   169  // parseJSON parses the given JSON file and stores the result into v.
   170  func parseJSON(jsonPath string, v interface{}) error {
   171  	if !osutil.Exists(jsonPath) {
   172  		warn.Printf("unable to locate JSON file %q", jsonPath)
   173  		return nil
   174  	}
   175  	dbg.Printf("parsing: %q", jsonPath)
   176  	return jsonutil.ParseFile(jsonPath, v)
   177  }