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

     1  // The bin2dot tool generates control flow graphs from binary executables (*.exe
     2  // -> *.dot).
     3  package main
     4  
     5  import (
     6  	"flag"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"strconv"
    13  
    14  	"gonum.org/v1/gonum/graph/encoding/dot"
    15  
    16  	"github.com/decomp/exp/bin"
    17  	_ "github.com/decomp/exp/bin/elf" // register ELF decoder
    18  	_ "github.com/decomp/exp/bin/pe"  // register PE decoder
    19  	_ "github.com/decomp/exp/bin/pef" // register PEF decoder
    20  	"github.com/decomp/exp/bin/raw"
    21  	"github.com/decomp/exp/disasm/x86"
    22  	"github.com/mewkiz/pkg/term"
    23  	"github.com/mewrev/pe"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  // Loggers.
    28  var (
    29  	// dbg represents a logger with the "bin2dot:" prefix, which logs debug
    30  	// messages to standard error.
    31  	dbg = log.New(os.Stderr, term.YellowBold("bin2dot:")+" ", 0)
    32  	// warn represents a logger with the "warning:" prefix, which logs warning
    33  	// messages to standard error.
    34  	warn = log.New(os.Stderr, term.RedBold("warning:")+" ", 0)
    35  )
    36  
    37  func usage() {
    38  	const use = `
    39  Generate control flow graphs from binary executables (*.exe -> *.dot).
    40  
    41  Usage:
    42  
    43  	bin2dot [OPTION]... FILE
    44  
    45  Flags:
    46  `
    47  	fmt.Fprint(os.Stderr, use[1:])
    48  	flag.PrintDefaults()
    49  }
    50  
    51  func main() {
    52  	// Parse command line arguments.
    53  	var (
    54  		// blockAddr specifies a basic block address to disassemble.
    55  		blockAddr bin.Address
    56  		// TODO: Remove -first flag and firstAddr.
    57  		// firstAddr specifies the first function address to disassemble.
    58  		firstAddr bin.Address
    59  		// funcAddr specifies a function address to disassemble.
    60  		funcAddr bin.Address
    61  		// TODO: Remove -last flag and lastAddr.
    62  		// lastAddr specifies the last function address to disassemble.
    63  		lastAddr bin.Address
    64  		// quiet specifies whether to suppress non-error messages.
    65  		quiet bool
    66  		// rawArch specifies the machine architecture of a raw binary executable.
    67  		rawArch bin.Arch
    68  		// rawEntry specifies the entry point of a raw binary executable.
    69  		rawEntry bin.Address
    70  		// rawBase specifies the base address of a raw binary executable.
    71  		rawBase bin.Address
    72  	)
    73  	flag.Usage = usage
    74  	flag.Var(&blockAddr, "block", "basic block address to disassemble")
    75  	flag.Var(&firstAddr, "first", "first function address to disassemble")
    76  	flag.Var(&funcAddr, "func", "function address to disassemble")
    77  	flag.Var(&lastAddr, "last", "last function address to disassemble")
    78  	flag.BoolVar(&quiet, "q", false, "suppress non-error messages")
    79  	flag.Var(&rawArch, "raw", "machine architecture of raw binary executable (x86_32, x86_64, MIPS_32, PowerPC_32, ...)")
    80  	flag.Var(&rawEntry, "rawentry", "entry point of raw binary executable")
    81  	flag.Var(&rawBase, "rawbase", "base address of raw binary executable")
    82  	flag.Parse()
    83  	if flag.NArg() != 1 {
    84  		flag.Usage()
    85  		os.Exit(1)
    86  	}
    87  	binPath := flag.Arg(0)
    88  	// Mute debug and warning messages if `-q` is set.
    89  	if quiet {
    90  		dbg.SetOutput(ioutil.Discard)
    91  		warn.SetOutput(ioutil.Discard)
    92  	}
    93  
    94  	// Prepare disassembler for the binary executable.
    95  	dis, err := newDisasm(binPath, rawArch, rawEntry, rawBase)
    96  	if err != nil {
    97  		log.Fatalf("%+v", err)
    98  	}
    99  	// Disassemble basic block.
   100  	if blockAddr != 0 {
   101  		block, err := dis.DecodeBlock(blockAddr)
   102  		if err != nil {
   103  			log.Fatalf("%+v", err)
   104  		}
   105  		_ = block
   106  		return
   107  	}
   108  
   109  	// Disassemble function specified by `-func` flag.
   110  	funcAddrs := dis.FuncAddrs
   111  	if funcAddr != 0 {
   112  		funcAddrs = []bin.Address{funcAddr}
   113  	}
   114  
   115  	// Disassemble functions.
   116  	var fs []*x86.Func
   117  	for _, funcAddr := range funcAddrs {
   118  		if firstAddr != 0 && funcAddr < firstAddr {
   119  			// skip functions before first address.
   120  			continue
   121  		}
   122  		if lastAddr != 0 && funcAddr >= lastAddr {
   123  			// skip functions after last address.
   124  			break
   125  		}
   126  		f, err := dis.DecodeFunc(funcAddr)
   127  		if err != nil {
   128  			log.Fatalf("%+v", err)
   129  		}
   130  		fs = append(fs, f)
   131  	}
   132  
   133  	// Create output directory.
   134  	if err := os.RemoveAll(outDir); err != nil {
   135  		log.Fatalf("%+v", err)
   136  	}
   137  	if err := os.MkdirAll(outDir, 0755); err != nil {
   138  		log.Fatalf("%+v", err)
   139  	}
   140  
   141  	// Dump main file.
   142  	file, err := pe.Open(binPath)
   143  	if err != nil {
   144  		log.Fatalf("%+v", err)
   145  	}
   146  	defer file.Close()
   147  
   148  	// Dump control flow graphs.
   149  	for _, f := range fs {
   150  		filename := fmt.Sprintf("f_%06X.dot", uint64(f.Addr))
   151  		path := filepath.Join(outDir, filename)
   152  		g, err := dumpCFG(dis, f)
   153  		if err != nil {
   154  			log.Fatalf("%+v", err)
   155  		}
   156  		name := strconv.Quote(f.Addr.String())
   157  		data, err := dot.Marshal(g, name, "", "\t")
   158  		if err != nil {
   159  			log.Fatalf("%+v", errors.WithStack(err))
   160  		}
   161  		if err := ioutil.WriteFile(path, data, 0644); err != nil {
   162  			log.Fatalf("%+v", errors.WithStack(err))
   163  		}
   164  	}
   165  }
   166  
   167  // outDir specifies the output direcotry.
   168  const outDir = "_dump_"
   169  
   170  // newDisasm returns a new disassembler for the given binary executable.
   171  func newDisasm(binPath string, rawArch bin.Arch, rawEntry, rawBase bin.Address) (*x86.Disasm, error) {
   172  	// Parse raw binary executable.
   173  	if rawArch != 0 {
   174  		file, err := raw.ParseFile(binPath, rawArch)
   175  		if err != nil {
   176  			return nil, errors.WithStack(err)
   177  		}
   178  		file.Entry = rawEntry
   179  		file.Sections[0].Addr = rawBase
   180  		return x86.NewDisasm(file)
   181  	}
   182  	// Parse binary executable.
   183  	file, err := bin.ParseFile(binPath)
   184  	if err != nil {
   185  		return nil, errors.WithStack(err)
   186  	}
   187  	return x86.NewDisasm(file)
   188  }