diff --git a/src/cmd/internal/objfile/disasm.go b/src/cmd/internal/objfile/disasm.go
index 8af0c8f859e5fb8b06f500eec9b6543abc7ea22b..d61cb27182dd39e99d2ffc56286b631f710f1ce6 100644
--- a/src/cmd/internal/objfile/disasm.go
+++ b/src/cmd/internal/objfile/disasm.go
@@ -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])
diff --git a/src/cmd/objdump/main.go b/src/cmd/objdump/main.go
index 8bf9e4e306bcc96c893e2cc5cb14f43848924451..7a3ba555172d55192535a1efc88957fa5d72c308 100644
--- a/src/cmd/objdump/main.go
+++ b/src/cmd/objdump/main.go
@@ -43,11 +43,12 @@ import (
 	"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)
 }
@@ -88,7 +89,7 @@ func main() {
 		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:
@@ -101,7 +102,7 @@ func main() {
 		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)
 	}
 }
diff --git a/src/cmd/objdump/objdump_test.go b/src/cmd/objdump/objdump_test.go
index 419be6717aa6cca724863e5960f7e9eadba42238..91adde3eb3ced05ff64de31839b1fbbc347fd64c 100644
--- a/src/cmd/objdump/objdump_test.go
+++ b/src/cmd/objdump/objdump_test.go
@@ -57,24 +57,18 @@ func buildObjdump() error {
 }
 
 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",
@@ -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
 // 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, "/")
@@ -114,9 +108,15 @@ func testDisasm(t *testing.T, flags ...string) {
 		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...)
@@ -126,7 +126,16 @@ func testDisasm(t *testing.T, flags ...string) {
 		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)
 	}
@@ -153,7 +162,19 @@ func TestDisasm(t *testing.T) {
 	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) {
@@ -178,5 +199,5 @@ 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")
 }