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 }