mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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:
parent
acc1f47299
commit
476f55fd8a
3 changed files with 141 additions and 17 deletions
|
|
@ -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 {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
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) {
|
||||||
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 (
|
||||||
"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() {
|
||||||
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() {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
// 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) {
|
||||||
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) {
|
||||||
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) {
|
||||||
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) {
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue