Commit 476f55fd authored by Lorenzo Masini's avatar Lorenzo Masini Committed by Brad Fitzpatrick

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: 's avatarKeith Randall <khr@golang.org>
parent acc1f472
...@@ -6,10 +6,16 @@ package objfile ...@@ -6,10 +6,16 @@ package objfile
import ( import (
"bufio" "bufio"
"bytes"
"cmd/internal/src"
"container/list"
"debug/gosym" "debug/gosym"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os"
"path/filepath"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
...@@ -102,10 +108,82 @@ func base(path string) string { ...@@ -102,10 +108,82 @@ func base(path string) string {
return path 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. // Print prints a disassembly of the file to w.
// If filter is non-nil, the disassembly only includes functions with names matching filter. // 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). // 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 { if start < d.textStart {
start = d.textStart start = d.textStart
} }
...@@ -114,6 +192,12 @@ func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64) { ...@@ -114,6 +192,12 @@ func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64) {
} }
printed := false printed := false
bw := bufio.NewWriter(w) bw := bufio.NewWriter(w)
var fc *FileCache
if printCode {
fc = NewFileCache(8)
}
for _, sym := range d.syms { for _, sym := range d.syms {
symStart := sym.Addr symStart := sym.Addr
symEnd := sym.Addr + uint64(sym.Size) symEnd := sym.Addr + uint64(sym.Size)
...@@ -132,14 +216,32 @@ func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64) { ...@@ -132,14 +216,32 @@ func (d *Disasm) Print(w io.Writer, filter *regexp.Regexp, start, end uint64) {
file, _, _ := d.pcln.PCToLine(sym.Addr) file, _, _ := d.pcln.PCToLine(sym.Addr)
fmt.Fprintf(bw, "TEXT %s(SB) %s\n", sym.Name, file) 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 { if symEnd > end {
symEnd = end symEnd = end
} }
code := d.text[:end-d.textStart] 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) { d.Decode(symStart, symEnd, relocs, func(pc, size uint64, file string, line int, text string) {
i := pc - d.textStart 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" { if size%4 != 0 || d.goarch == "386" || d.goarch == "amd64" {
// Print instruction as bytes. // Print instruction as bytes.
fmt.Fprintf(tw, "%x", code[i:i+size]) fmt.Fprintf(tw, "%x", code[i:i+size])
......
...@@ -43,11 +43,12 @@ import ( ...@@ -43,11 +43,12 @@ import (
"cmd/internal/objfile" "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 symregexp = flag.String("s", "", "only dump symbols matching this regexp")
var symRE *regexp.Regexp var symRE *regexp.Regexp
func usage() { 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() flag.PrintDefaults()
os.Exit(2) os.Exit(2)
} }
...@@ -88,7 +89,7 @@ func main() { ...@@ -88,7 +89,7 @@ func main() {
usage() usage()
case 1: case 1:
// disassembly of entire object // disassembly of entire object
dis.Print(os.Stdout, symRE, 0, ^uint64(0)) dis.Print(os.Stdout, symRE, 0, ^uint64(0), *printCode)
os.Exit(0) os.Exit(0)
case 3: case 3:
...@@ -101,7 +102,7 @@ func main() { ...@@ -101,7 +102,7 @@ func main() {
if err != nil { if err != nil {
log.Fatalf("invalid end PC: %v", err) 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) os.Exit(0)
} }
} }
...@@ -57,24 +57,18 @@ func buildObjdump() error { ...@@ -57,24 +57,18 @@ func buildObjdump() error {
} }
var x86Need = []string{ var x86Need = []string{
"fmthello.go:6",
"TEXT main.main(SB)",
"JMP main.main(SB)", "JMP main.main(SB)",
"CALL main.Println(SB)", "CALL main.Println(SB)",
"RET", "RET",
} }
var armNeed = []string{ var armNeed = []string{
"fmthello.go:6",
"TEXT main.main(SB)",
//"B.LS main.main(SB)", // TODO(rsc): restore; golang.org/issue/9021 //"B.LS main.main(SB)", // TODO(rsc): restore; golang.org/issue/9021
"BL main.Println(SB)", "BL main.Println(SB)",
"RET", "RET",
} }
var ppcNeed = []string{ var ppcNeed = []string{
"fmthello.go:6",
"TEXT main.main(SB)",
"BR main.main(SB)", "BR main.main(SB)",
"CALL main.Println(SB)", "CALL main.Println(SB)",
"RET", "RET",
...@@ -91,7 +85,7 @@ var target = flag.String("target", "", "test disassembly of `goos/goarch` binary ...@@ -91,7 +85,7 @@ var target = flag.String("target", "", "test disassembly of `goos/goarch` binary
// binary for the current system (only) and test that objdump // binary for the current system (only) and test that objdump
// can handle that one. // can handle that one.
func testDisasm(t *testing.T, flags ...string) { func testDisasm(t *testing.T, printCode bool, flags ...string) {
goarch := runtime.GOARCH goarch := runtime.GOARCH
if *target != "" { if *target != "" {
f := strings.Split(*target, "/") f := strings.Split(*target, "/")
...@@ -114,9 +108,15 @@ func testDisasm(t *testing.T, flags ...string) { ...@@ -114,9 +108,15 @@ func testDisasm(t *testing.T, flags ...string) {
t.Fatalf("go build fmthello.go: %v\n%s", err, out) t.Fatalf("go build fmthello.go: %v\n%s", err, out)
} }
need := []string{ need := []string{
"fmthello.go:6",
"TEXT main.main(SB)", "TEXT main.main(SB)",
} }
if printCode {
need = append(need, ` Println("hello, world")`)
} else {
need = append(need, "fmthello.go:6")
}
switch goarch { switch goarch {
case "amd64", "386": case "amd64", "386":
need = append(need, x86Need...) need = append(need, x86Need...)
...@@ -126,7 +126,16 @@ func testDisasm(t *testing.T, flags ...string) { ...@@ -126,7 +126,16 @@ func testDisasm(t *testing.T, flags ...string) {
need = append(need, ppcNeed...) 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 { if err != nil {
t.Fatalf("objdump fmthello.exe: %v\n%s", err, out) t.Fatalf("objdump fmthello.exe: %v\n%s", err, out)
} }
...@@ -153,7 +162,19 @@ func TestDisasm(t *testing.T) { ...@@ -153,7 +162,19 @@ func TestDisasm(t *testing.T) {
case "s390x": case "s390x":
t.Skipf("skipping on %s, issue 15255", runtime.GOARCH) 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) { func TestDisasmExtld(t *testing.T) {
...@@ -178,5 +199,5 @@ func TestDisasmExtld(t *testing.T) { ...@@ -178,5 +199,5 @@ func TestDisasmExtld(t *testing.T) {
if !build.Default.CgoEnabled { if !build.Default.CgoEnabled {
t.Skip("skipping because cgo is not enabled") t.Skip("skipping because cgo is not enabled")
} }
testDisasm(t, "-ldflags=-linkmode=external") testDisasm(t, false, "-ldflags=-linkmode=external")
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment