diff options
Diffstat (limited to 'src/cmd/objdump/main.go')
-rw-r--r-- | src/cmd/objdump/main.go | 249 |
1 files changed, 224 insertions, 25 deletions
diff --git a/src/cmd/objdump/main.go b/src/cmd/objdump/main.go index 82b896f44c..62cbdec90d 100644 --- a/src/cmd/objdump/main.go +++ b/src/cmd/objdump/main.go @@ -2,17 +2,24 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Objdump is a minimal simulation of the GNU objdump tool, -// just enough to support pprof. +// Objdump disassembles executable files. // // Usage: +// +// go tool objdump [-s symregexp] binary +// +// Objdump prints a disassembly of all text symbols (code) in the binary. +// If the -s option is present, objdump only disassembles +// symbols with names matching the regular expression. +// +// Alternate usage: +// // go tool objdump binary start end // -// Objdump disassembles the binary starting at the start address and +// In this mode, objdump disassembles the binary starting at the start address and // stopping at the end address. The start and end addresses are program // counters written in hexadecimal with optional leading 0x prefix. -// -// It prints a sequence of stanzas of the form: +// In this mode, objdump prints a sequence of stanzas of the form: // // file:line // address: assembly @@ -21,49 +28,61 @@ // // Each stanza gives the disassembly for a contiguous range of addresses // all mapped to the same original source file and line number. +// This mode is intended for use by pprof. // -// The disassembler is missing (golang.org/issue/7452) but will be added +// The ARM disassembler is missing (golang.org/issue/7452) but will be added // before the Go 1.3 release. -// -// This tool is intended for use only by pprof; its interface may change or -// it may be deleted entirely in future releases. package main import ( "bufio" + "bytes" "debug/elf" "debug/gosym" "debug/macho" "debug/pe" "flag" "fmt" + "io" "log" "os" + "regexp" + "sort" "strconv" "strings" + "text/tabwriter" ) -func printUsage(w *os.File) { - fmt.Fprintf(w, "usage: objdump binary start end\n") - fmt.Fprintf(w, "disassembles binary from start PC to end PC.\n") - fmt.Fprintf(w, "start and end are hexadecimal numbers with optional leading 0x prefix.\n") -} +var symregexp = flag.String("s", "", "only dump symbols matching this regexp") +var symRE *regexp.Regexp func usage() { - printUsage(os.Stderr) + fmt.Fprintf(os.Stderr, "usage: go tool objdump [-s symregexp] binary [start end]\n\n") + flag.PrintDefaults() os.Exit(2) } +type lookupFunc func(addr uint64) (sym string, base uint64) +type disasmFunc func(code []byte, pc uint64, lookup lookupFunc) (text string, size int) + func main() { log.SetFlags(0) log.SetPrefix("objdump: ") flag.Usage = usage flag.Parse() - if flag.NArg() != 3 { + if flag.NArg() != 1 && flag.NArg() != 3 { usage() } + if *symregexp != "" { + re, err := regexp.Compile(*symregexp) + if err != nil { + log.Fatalf("invalid -s regexp: %v", err) + } + symRE = re + } + f, err := os.Open(flag.Arg(0)) if err != nil { log.Fatal(err) @@ -74,12 +93,140 @@ func main() { log.Fatalf("reading %s: %v", flag.Arg(0), err) } + syms, goarch, err := loadSymbols(f) + if err != nil { + log.Fatalf("reading %s: %v", flag.Arg(0), err) + } + + // Filter out section symbols, overwriting syms in place. + keep := syms[:0] + for _, sym := range syms { + switch sym.Name { + case "text", "_text", "etext", "_etext": + // drop + default: + keep = append(keep, sym) + } + } + syms = keep + + disasm := disasms[goarch] + if disasm == nil { + log.Fatalf("reading %s: unknown architecture", flag.Arg(0)) + } + + lookup := func(addr uint64) (string, uint64) { + i := sort.Search(len(syms), func(i int) bool { return syms[i].Addr > addr }) + if i > 0 { + s := syms[i-1] + if s.Addr <= addr && addr < s.Addr+uint64(s.Size) && s.Name != "etext" && s.Name != "_etext" { + return s.Name, s.Addr + } + } + return "", 0 + } + pcln := gosym.NewLineTable(pclntab, textStart) tab, err := gosym.NewTable(symtab, pcln) if err != nil { log.Fatalf("reading %s: %v", flag.Arg(0), err) } + if flag.NArg() == 1 { + // disassembly of entire object - our format + dump(tab, lookup, disasm, syms, textData, textStart) + os.Exit(exitCode) + } + + // disassembly of specific piece of object - gnu objdump format for pprof + gnuDump(tab, lookup, disasm, textData, textStart) + os.Exit(exitCode) +} + +// base returns the final element in the path. +// It works on both Windows and Unix paths. +func base(path string) string { + path = path[strings.LastIndex(path, "/")+1:] + path = path[strings.LastIndex(path, `\`)+1:] + return path +} + +func dump(tab *gosym.Table, lookup lookupFunc, disasm disasmFunc, syms []Sym, textData []byte, textStart uint64) { + stdout := bufio.NewWriter(os.Stdout) + defer stdout.Flush() + + printed := false + for _, sym := range syms { + if sym.Code != 'T' || sym.Size == 0 || sym.Name == "_text" || sym.Name == "text" || sym.Addr < textStart || symRE != nil && !symRE.MatchString(sym.Name) { + continue + } + if sym.Addr >= textStart+uint64(len(textData)) || sym.Addr+uint64(sym.Size) > textStart+uint64(len(textData)) { + break + } + if printed { + fmt.Fprintf(stdout, "\n") + } else { + printed = true + } + file, _, _ := tab.PCToLine(sym.Addr) + fmt.Fprintf(stdout, "TEXT %s(SB) %s\n", sym.Name, file) + tw := tabwriter.NewWriter(stdout, 1, 8, 1, '\t', 0) + start := sym.Addr + end := sym.Addr + uint64(sym.Size) + for pc := start; pc < end; { + i := pc - textStart + text, size := disasm(textData[i:end-textStart], pc, lookup) + file, line, _ := tab.PCToLine(pc) + fmt.Fprintf(tw, "\t%s:%d\t%#x\t%x\t%s\n", base(file), line, pc, textData[i:i+uint64(size)], text) + pc += uint64(size) + } + tw.Flush() + } +} + +func disasm_386(code []byte, pc uint64, lookup lookupFunc) (string, int) { + return disasm_x86(code, pc, lookup, 32) +} + +func disasm_amd64(code []byte, pc uint64, lookup lookupFunc) (string, int) { + return disasm_x86(code, pc, lookup, 64) +} + +func disasm_x86(code []byte, pc uint64, lookup lookupFunc, arch int) (string, int) { + inst, err := x86_Decode(code, 64) + var text string + size := inst.Len + if err != nil || size == 0 || inst.Op == 0 { + size = 1 + text = "?" + } else { + text = x86_plan9Syntax(inst, pc, lookup) + } + return text, size +} + +func disasm_arm(code []byte, pc uint64, lookup lookupFunc) (string, int) { + /* + inst, size, err := arm_Decode(code, 64) + var text string + if err != nil || size == 0 || inst.Op == 0 { + size = 1 + text = "?" + } else { + text = arm_plan9Syntax(inst, pc, lookup) + } + return text, size + */ + return "?", 4 +} + +var disasms = map[string]disasmFunc{ + "386": disasm_386, + "amd64": disasm_amd64, + "arm": disasm_arm, +} + +func gnuDump(tab *gosym.Table, lookup lookupFunc, disasm disasmFunc, textData []byte, textStart uint64) { start, err := strconv.ParseUint(strings.TrimPrefix(flag.Arg(1), "0x"), 16, 64) if err != nil { log.Fatalf("invalid start PC: %v", err) @@ -90,6 +237,7 @@ func main() { } stdout := bufio.NewWriter(os.Stdout) + defer stdout.Flush() // For now, find spans of same PC/line/fn and // emit them as having dummy instructions. @@ -105,13 +253,10 @@ func main() { return } fmt.Fprintf(stdout, "%s:%d\n", spanFile, spanLine) - for pc := spanPC; pc < endPC; pc++ { - // TODO(rsc): Disassemble instructions here. - if textStart <= pc && pc-textStart < uint64(len(textData)) { - fmt.Fprintf(stdout, " %x: byte %#x\n", pc, textData[pc-textStart]) - } else { - fmt.Fprintf(stdout, " %x: ?\n", pc) - } + for pc := spanPC; pc < endPC; { + text, size := disasm(textData[pc-textStart:], pc, lookup) + fmt.Fprintf(stdout, " %x: %s\n", pc, text) + pc += uint64(size) } spanPC = 0 } @@ -124,8 +269,6 @@ func main() { } } flush(end) - - stdout.Flush() } func loadTables(f *os.File) (textStart uint64, textData, symtab, pclntab []byte, err error) { @@ -217,3 +360,59 @@ func loadPETable(f *pe.File, sname, ename string) ([]byte, error) { } return data[ssym.Value:esym.Value], nil } + +// TODO(rsc): This code is taken from cmd/nm. Arrange some way to share the code. + +var exitCode = 0 + +func errorf(format string, args ...interface{}) { + log.Printf(format, args...) + exitCode = 1 +} + +func loadSymbols(f *os.File) (syms []Sym, goarch string, err error) { + f.Seek(0, 0) + buf := make([]byte, 16) + io.ReadFull(f, buf) + f.Seek(0, 0) + + for _, p := range parsers { + if bytes.HasPrefix(buf, p.prefix) { + syms, goarch = p.parse(f) + sort.Sort(byAddr(syms)) + return + } + } + err = fmt.Errorf("unknown file format") + return +} + +type Sym struct { + Addr uint64 + Size int64 + Code rune + Name string + Type string +} + +var parsers = []struct { + prefix []byte + parse func(*os.File) ([]Sym, string) +}{ + {[]byte("\x7FELF"), elfSymbols}, + {[]byte("\xFE\xED\xFA\xCE"), machoSymbols}, + {[]byte("\xFE\xED\xFA\xCF"), machoSymbols}, + {[]byte("\xCE\xFA\xED\xFE"), machoSymbols}, + {[]byte("\xCF\xFA\xED\xFE"), machoSymbols}, + {[]byte("MZ"), peSymbols}, + {[]byte("\x00\x00\x01\xEB"), plan9Symbols}, // 386 + {[]byte("\x00\x00\x04\x07"), plan9Symbols}, // mips + {[]byte("\x00\x00\x06\x47"), plan9Symbols}, // arm + {[]byte("\x00\x00\x8A\x97"), plan9Symbols}, // amd64 +} + +type byAddr []Sym + +func (x byAddr) Len() int { return len(x) } +func (x byAddr) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byAddr) Less(i, j int) bool { return x[i].Addr < x[j].Addr } |