import (
"bufio"
+ "bytes"
+ "cmd/internal/src"
+ "container/list"
"debug/gosym"
"encoding/binary"
"fmt"
"io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"regexp"
"sort"
"strings"
return path
}
+// CachedFile contains the content of a file split into lines.
+type CachedFile struct {
+ FileName string
+ Lines [][]byte
+}
+
+// FileCache is a simple LRU cache of file contents.
+type FileCache struct {
+ files *list.List
+ maxLen int
+}
+
+// NewFileCache returns a FileCache which can contain up to maxLen cached file contents.
+func NewFileCache(maxLen int) *FileCache {
+ return &FileCache{
+ files: list.New(),
+ maxLen: maxLen,
+ }
+}
+
+// Line returns the source code line for the given file and line number.
+// If the file is not already cached, reads it , inserts it into the cache,
+// and removes the least recently used file if necessary.
+// If the file is in cache, moves it up to the front of the list.
+func (fc *FileCache) Line(filename string, line int) ([]byte, error) {
+ if filepath.Ext(filename) != ".go" {
+ return nil, nil
+ }
+
+ // Clean filenames returned by src.Pos.SymFilename()
+ // or src.PosBase.SymFilename() removing
+ // the leading src.FileSymPrefix.
+ if strings.HasPrefix(filename, src.FileSymPrefix) {
+ filename = filename[len(src.FileSymPrefix):]
+ }
+
+ // Expand literal "$GOROOT" rewrited by obj.AbsFile()
+ filename = filepath.Clean(os.ExpandEnv(filename))
+
+ var cf *CachedFile
+ var e *list.Element
+
+ for e = fc.files.Front(); e != nil; e = e.Next() {
+ cf = e.Value.(*CachedFile)
+ if cf.FileName == filename {
+ break
+ }
+ }
+
+ if e == nil {
+ content, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ cf = &CachedFile{
+ FileName: filename,
+ Lines: bytes.Split(content, []byte{'\n'}),
+ }
+ fc.files.PushFront(cf)
+
+ if fc.files.Len() >= fc.maxLen {
+ fc.files.Remove(fc.files.Back())
+ }
+ } else {
+ fc.files.MoveToFront(e)
+ }
+
+ return cf.Lines[line-1], nil
+}
+
// Print prints a disassembly of the file to w.
// If filter is non-nil, the disassembly only includes functions with names matching filter.
+// If printCode is true, the disassembly includs corresponding source lines.
// The disassembly only includes functions that overlap the range [start, end).
-func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64) {
+func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64, printCode bool) {
if start < d.textStart {
start = d.textStart
}
}
printed := false
bw := bufio.NewWriter(w)
+
+ var fc *FileCache
+ if printCode {
+ fc = NewFileCache(8)
+ }
+
for _, sym := range d.syms {
symStart := sym.Addr
symEnd := sym.Addr + uint64(sym.Size)
file, _, _ := d.pcln.PCToLine(sym.Addr)
fmt.Fprintf(bw, "TEXT %s(SB) %s\n", sym.Name, file)
- tw := tabwriter.NewWriter(bw, 1, 8, 1, '\t', 0)
+ tw := tabwriter.NewWriter(bw, 18, 8, 1, '\t', tabwriter.StripEscape)
if symEnd > end {
symEnd = end
}
code := d.text[:end-d.textStart]
+
+ var lastFile string
+ var lastLine int
+
d.Decode(symStart, symEnd, relocs, func(pc, size uint64, file string, line int, text string) {
i := pc - d.textStart
- fmt.Fprintf(tw, "\t%s:%d\t%#x\t", base(file), line, pc)
+
+ if printCode {
+ if file != lastFile || line != lastLine {
+ if srcLine, err := fc.Line(file, line); err == nil {
+ fmt.Fprintf(tw, "%s%s%s\n", []byte{tabwriter.Escape}, srcLine, []byte{tabwriter.Escape})
+ }
+
+ lastFile, lastLine = file, line
+ }
+
+ fmt.Fprintf(tw, " %#x\t", pc)
+ } else {
+ fmt.Fprintf(tw, " %s:%d\t%#x\t", base(file), line, pc)
+ }
+
if size%4 != 0 || d.goarch == "386" || d.goarch == "amd64" {
// Print instruction as bytes.
fmt.Fprintf(tw, "%x", code[i:i+size])
"cmd/internal/objfile"
)
+var printCode = flag.Bool("S", false, "print go code alongside assembly")
var symregexp = flag.String("s", "", "only dump symbols matching this regexp")
var symRE *regexp.Regexp
func usage() {
- fmt.Fprintf(os.Stderr, "usage: go tool objdump [-s symregexp] binary [start end]\n\n")
+ fmt.Fprintf(os.Stderr, "usage: go tool objdump [-S] [-s symregexp] binary [start end]\n\n")
flag.PrintDefaults()
os.Exit(2)
}
usage()
case 1:
// disassembly of entire object
- dis.Print(os.Stdout, symRE, 0, ^uint64(0))
+ dis.Print(os.Stdout, symRE, 0, ^uint64(0), *printCode)
os.Exit(0)
case 3:
if err != nil {
log.Fatalf("invalid end PC: %v", err)
}
- dis.Print(os.Stdout, symRE, start, end)
+ dis.Print(os.Stdout, symRE, start, end, *printCode)
os.Exit(0)
}
}
}
var x86Need = []string{
- "fmthello.go:6",
- "TEXT main.main(SB)",
"JMP main.main(SB)",
"CALL main.Println(SB)",
"RET",
}
var armNeed = []string{
- "fmthello.go:6",
- "TEXT main.main(SB)",
//"B.LS main.main(SB)", // TODO(rsc): restore; golang.org/issue/9021
"BL main.Println(SB)",
"RET",
}
var ppcNeed = []string{
- "fmthello.go:6",
- "TEXT main.main(SB)",
"BR main.main(SB)",
"CALL main.Println(SB)",
"RET",
// binary for the current system (only) and test that objdump
// can handle that one.
-func testDisasm(t *testing.T, flags ...string) {
+func testDisasm(t *testing.T, printCode bool, flags ...string) {
goarch := runtime.GOARCH
if *target != "" {
f := strings.Split(*target, "/")
t.Fatalf("go build fmthello.go: %v\n%s", err, out)
}
need := []string{
- "fmthello.go:6",
"TEXT main.main(SB)",
}
+
+ if printCode {
+ need = append(need, ` Println("hello, world")`)
+ } else {
+ need = append(need, "fmthello.go:6")
+ }
+
switch goarch {
case "amd64", "386":
need = append(need, x86Need...)
need = append(need, ppcNeed...)
}
- out, err = exec.Command(exe, "-s", "main.main", hello).CombinedOutput()
+ args = []string{
+ "-s", "main.main",
+ hello,
+ }
+
+ if printCode {
+ args = append([]string{"-S"}, args...)
+ }
+
+ out, err = exec.Command(exe, args...).CombinedOutput()
if err != nil {
t.Fatalf("objdump fmthello.exe: %v\n%s", err, out)
}
case "s390x":
t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
}
- testDisasm(t)
+ testDisasm(t, false)
+}
+
+func TestDisasmCode(t *testing.T) {
+ switch runtime.GOARCH {
+ case "arm64":
+ t.Skipf("skipping on %s, issue 10106", runtime.GOARCH)
+ case "mips", "mipsle", "mips64", "mips64le":
+ t.Skipf("skipping on %s, issue 12559", runtime.GOARCH)
+ case "s390x":
+ t.Skipf("skipping on %s, issue 15255", runtime.GOARCH)
+ }
+ testDisasm(t, true)
}
func TestDisasmExtld(t *testing.T) {
if !build.Default.CgoEnabled {
t.Skip("skipping because cgo is not enabled")
}
- testDisasm(t, "-ldflags=-linkmode=external")
+ testDisasm(t, false, "-ldflags=-linkmode=external")
}