cmd/objdump: print Go code alongside assembly

Added -S flag to print go source file line above corresponding disassembly:

$ go tool objdump -S -s main.main fmthello
TEXT main.main(SB) /home/rugginoso/Documents/src/go/src/cmd/objdump/testdata/fmthello.go
func main() {
  0x47d450		64488b0c25f8ffffff	FS MOVQ FS:0xfffffff8, CX
  0x47d459		483b6110		CMPQ 0x10(CX), SP
  0x47d45d		7631			JBE 0x47d490
  0x47d45f		4883ec18		SUBQ $0x18, SP
  0x47d463		48896c2410		MOVQ BP, 0x10(SP)
  0x47d468		488d6c2410		LEAQ 0x10(SP), BP
	Println("hello, world")
  0x47d46d		488d0563b00200		LEAQ 0x2b063(IP), AX
  0x47d474		48890424		MOVQ AX, 0(SP)
  0x47d478		48c74424080c000000	MOVQ $0xc, 0x8(SP)
  0x47d481		e81a000000		CALL main.Println(SB)
}
  0x47d486		488b6c2410		MOVQ 0x10(SP), BP
  0x47d48b		4883c418		ADDQ $0x18, SP
  0x47d48f		c3			RET
func main() {
  0x47d490		e8ebf1fcff		CALL runtime.morestack_noctxt(SB)
  0x47d495		ebb9			JMP main.main(SB)

Execution time:

$ time go tool objdump testdata/fmthello > /dev/null
real	0m0.430s
user	0m0.440s
sys	0m0.000s

$ time go tool objdump -S testdata/fmthello > /dev/null
real	0m0.471s
user	0m0.476s
sys	0m0.012s

Fixes #18245

Change-Id: I9b2f8338f9ee443c1352efd270d3ba85e3dd9b78
Reviewed-on: https://go-review.googlesource.com/37953
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Lorenzo Masini 2017-03-08 16:45:23 +01:00 committed by Brad Fitzpatrick
parent acc1f47299
commit 476f55fd8a
3 changed files with 141 additions and 17 deletions

View file

@ -6,10 +6,16 @@ package objfile
import (
"bufio"
"bytes"
"cmd/internal/src"
"container/list"
"debug/gosym"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
@ -102,10 +108,82 @@ func base(path string) string {
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
}
@ -114,6 +192,12 @@ func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64) {
}
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)
@ -132,14 +216,32 @@ func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64) {
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])