aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmd/internal/objfile/elf.go9
-rw-r--r--src/cmd/internal/objfile/goobj.go4
-rw-r--r--src/cmd/internal/objfile/macho.go4
-rw-r--r--src/cmd/internal/objfile/objfile.go8
-rw-r--r--src/cmd/internal/objfile/pe.go4
-rw-r--r--src/cmd/internal/objfile/plan9obj.go4
-rw-r--r--src/cmd/pprof/pprof.go13
-rw-r--r--src/go/build/deps_test.go2
-rw-r--r--src/runtime/crash_cgo_test.go30
-rw-r--r--src/runtime/crash_test.go18
-rw-r--r--src/runtime/pprof/pprof.go37
-rw-r--r--src/runtime/pprof/pprof_test.go10
12 files changed, 128 insertions, 15 deletions
diff --git a/src/cmd/internal/objfile/elf.go b/src/cmd/internal/objfile/elf.go
index 3bad034097..c8114603d7 100644
--- a/src/cmd/internal/objfile/elf.go
+++ b/src/cmd/internal/objfile/elf.go
@@ -106,6 +106,15 @@ func (f *elfFile) goarch() string {
return ""
}
+func (f *elfFile) loadAddress() (uint64, error) {
+ for _, p := range f.elf.Progs {
+ if p.Type == elf.PT_LOAD {
+ return p.Vaddr, nil
+ }
+ }
+ return 0, fmt.Errorf("unknown load address")
+}
+
func (f *elfFile) dwarf() (*dwarf.Data, error) {
return f.elf.DWARF()
}
diff --git a/src/cmd/internal/objfile/goobj.go b/src/cmd/internal/objfile/goobj.go
index 5a084a94be..43435efc68 100644
--- a/src/cmd/internal/objfile/goobj.go
+++ b/src/cmd/internal/objfile/goobj.go
@@ -94,6 +94,10 @@ func (f *goobjFile) goarch() string {
return "GOARCH unimplemented for debug/goobj files"
}
+func (f *goobjFile) loadAddress() (uint64, error) {
+ return 0, fmt.Errorf("unknown load address")
+}
+
func (f *goobjFile) dwarf() (*dwarf.Data, error) {
return nil, errors.New("no DWARF data in go object file")
}
diff --git a/src/cmd/internal/objfile/macho.go b/src/cmd/internal/objfile/macho.go
index 754674d757..1d22a09b13 100644
--- a/src/cmd/internal/objfile/macho.go
+++ b/src/cmd/internal/objfile/macho.go
@@ -125,6 +125,10 @@ func (x uint64s) Len() int { return len(x) }
func (x uint64s) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x uint64s) Less(i, j int) bool { return x[i] < x[j] }
+func (f *machoFile) loadAddress() (uint64, error) {
+ return 0, fmt.Errorf("unknown load address")
+}
+
func (f *machoFile) dwarf() (*dwarf.Data, error) {
return f.macho.DWARF()
}
diff --git a/src/cmd/internal/objfile/objfile.go b/src/cmd/internal/objfile/objfile.go
index 48ed9ed489..e5d99f086b 100644
--- a/src/cmd/internal/objfile/objfile.go
+++ b/src/cmd/internal/objfile/objfile.go
@@ -18,6 +18,7 @@ type rawFile interface {
pcln() (textStart uint64, symtab, pclntab []byte, err error)
text() (textStart uint64, text []byte, err error)
goarch() string
+ loadAddress() (uint64, error)
dwarf() (*dwarf.Data, error)
}
@@ -95,6 +96,13 @@ func (f *File) GOARCH() string {
return f.raw.goarch()
}
+// LoadAddress returns the expected load address of the file.
+// This differs from the actual load address for a position-independent
+// executable.
+func (f *File) LoadAddress() (uint64, error) {
+ return f.raw.loadAddress()
+}
+
// DWARF returns DWARF debug data for the file, if any.
// This is for cmd/pprof to locate cgo functions.
func (f *File) DWARF() (*dwarf.Data, error) {
diff --git a/src/cmd/internal/objfile/pe.go b/src/cmd/internal/objfile/pe.go
index c024762371..46b2317242 100644
--- a/src/cmd/internal/objfile/pe.go
+++ b/src/cmd/internal/objfile/pe.go
@@ -199,6 +199,10 @@ func (f *peFile) goarch() string {
return ""
}
+func (f *peFile) loadAddress() (uint64, error) {
+ return 0, fmt.Errorf("unknown load address")
+}
+
func (f *peFile) dwarf() (*dwarf.Data, error) {
return f.pe.DWARF()
}
diff --git a/src/cmd/internal/objfile/plan9obj.go b/src/cmd/internal/objfile/plan9obj.go
index 6ee389dc2e..3e34f65ae7 100644
--- a/src/cmd/internal/objfile/plan9obj.go
+++ b/src/cmd/internal/objfile/plan9obj.go
@@ -147,6 +147,10 @@ func (f *plan9File) goarch() string {
return ""
}
+func (f *plan9File) loadAddress() (uint64, error) {
+ return 0, fmt.Errorf("unknown load address")
+}
+
func (f *plan9File) dwarf() (*dwarf.Data, error) {
return nil, errors.New("no DWARF data in Plan 9 file")
}
diff --git a/src/cmd/pprof/pprof.go b/src/cmd/pprof/pprof.go
index bce37dcb97..0187045b4a 100644
--- a/src/cmd/pprof/pprof.go
+++ b/src/cmd/pprof/pprof.go
@@ -117,6 +117,9 @@ func (*objTool) Open(name string, start uint64) (plugin.ObjFile, error) {
name: name,
file: of,
}
+ if load, err := of.LoadAddress(); err == nil {
+ f.offset = start - load
+ }
return f, nil
}
@@ -169,10 +172,11 @@ func (*objTool) SetConfig(config string) {
// (instead of invoking GNU binutils).
// A file represents a single executable being analyzed.
type file struct {
- name string
- sym []objfile.Sym
- file *objfile.File
- pcln *gosym.Table
+ name string
+ offset uint64
+ sym []objfile.Sym
+ file *objfile.File
+ pcln *gosym.Table
triedDwarf bool
dwarf *dwarf.Data
@@ -200,6 +204,7 @@ func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
}
f.pcln = pcln
}
+ addr -= f.offset
file, line, fn := f.pcln.PCToLine(addr)
if fn != nil {
frame := []plugin.Frame{
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index f9a428edd4..335e774a7c 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -173,7 +173,7 @@ var pkgDeps = map[string][]string{
"regexp": {"L2", "regexp/syntax"},
"regexp/syntax": {"L2"},
"runtime/debug": {"L2", "fmt", "io/ioutil", "os", "time"},
- "runtime/pprof": {"L2", "fmt", "text/tabwriter"},
+ "runtime/pprof": {"L2", "fmt", "os", "text/tabwriter"},
"runtime/trace": {"L0"},
"text/tabwriter": {"L2"},
diff --git a/src/runtime/crash_cgo_test.go b/src/runtime/crash_cgo_test.go
index 5d1cc77c98..4f7c10b923 100644
--- a/src/runtime/crash_cgo_test.go
+++ b/src/runtime/crash_cgo_test.go
@@ -263,3 +263,33 @@ func TestCgoPprof(t *testing.T) {
t.Error("missing cpuHog in pprof output")
}
}
+
+func TestCgoPprofPIE(t *testing.T) {
+ if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
+ t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
+ }
+ testenv.MustHaveGoRun(t)
+
+ exe, err := buildTestProg(t, "testprogcgo", "-ldflags=-extldflags=-pie")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ got, err := testEnv(exec.Command(exe, "CgoPprof")).CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ fn := strings.TrimSpace(string(got))
+ defer os.Remove(fn)
+
+ top, err := exec.Command("go", "tool", "pprof", "-top", "-nodecount=1", exe, fn).CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Logf("%s", top)
+
+ if !bytes.Contains(top, []byte("cpuHog")) {
+ t.Error("missing cpuHog in pprof output")
+ }
+}
diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go
index 2941b8e8f8..ec740990dc 100644
--- a/src/runtime/crash_test.go
+++ b/src/runtime/crash_test.go
@@ -69,7 +69,7 @@ func runTestProg(t *testing.T, binary, name string) string {
return string(got)
}
-func buildTestProg(t *testing.T, binary string) (string, error) {
+func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
checkStaleRuntime(t)
testprog.Lock()
@@ -86,23 +86,27 @@ func buildTestProg(t *testing.T, binary string) (string, error) {
if testprog.target == nil {
testprog.target = make(map[string]buildexe)
}
- target, ok := testprog.target[binary]
+ name := binary
+ if len(flags) > 0 {
+ name += "_" + strings.Join(flags, "_")
+ }
+ target, ok := testprog.target[name]
if ok {
return target.exe, target.err
}
- exe := filepath.Join(testprog.dir, binary+".exe")
- cmd := exec.Command("go", "build", "-o", exe)
+ exe := filepath.Join(testprog.dir, name+".exe")
+ cmd := exec.Command("go", append([]string{"build", "-o", exe}, flags...)...)
cmd.Dir = "testdata/" + binary
out, err := testEnv(cmd).CombinedOutput()
if err != nil {
exe = ""
- target.err = fmt.Errorf("building %s: %v\n%s", binary, err, out)
- testprog.target[binary] = target
+ target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
+ testprog.target[name] = target
return "", target.err
}
target.exe = exe
- testprog.target[binary] = target
+ testprog.target[name] = target
return exe, nil
}
diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go
index 728c3dc24a..b05c925ad1 100644
--- a/src/runtime/pprof/pprof.go
+++ b/src/runtime/pprof/pprof.go
@@ -13,6 +13,7 @@ import (
"bytes"
"fmt"
"io"
+ "os"
"runtime"
"sort"
"strings"
@@ -620,6 +621,42 @@ func profileWriter(w io.Writer) {
}
w.Write(data)
}
+
+ // We are emitting the legacy profiling format, which permits
+ // a memory map following the CPU samples. The memory map is
+ // simply a copy of the GNU/Linux /proc/self/maps file. The
+ // profiler uses the memory map to map PC values in shared
+ // libraries to a shared library in the filesystem, in order
+ // to report the correct function and, if the shared library
+ // has debug info, file/line. This is particularly useful for
+ // PIE (position independent executables) as on ELF systems a
+ // PIE is simply an executable shared library.
+ //
+ // Because the profiling format expects the memory map in
+ // GNU/Linux format, we only do this on GNU/Linux for now. To
+ // add support for profiling PIE on other ELF-based systems,
+ // it may be necessary to map the system-specific mapping
+ // information to the GNU/Linux format. For a reasonably
+ // portable C++ version, see the FillProcSelfMaps function in
+ // https://github.com/gperftools/gperftools/blob/master/src/base/sysinfo.cc
+ //
+ // The code that parses this mapping for the pprof tool is
+ // ParseMemoryMap in cmd/internal/pprof/legacy_profile.go, but
+ // don't change that code, as similar code exists in other
+ // (non-Go) pprof readers. Change this code so that that code works.
+ //
+ // We ignore errors reading or copying the memory map; the
+ // profile is likely usable without it, and we have no good way
+ // to report errors.
+ if runtime.GOOS == "linux" {
+ f, err := os.Open("/proc/self/maps")
+ if err == nil {
+ io.WriteString(w, "\nMAPPED_LIBRARIES:\n")
+ io.Copy(w, f)
+ f.Close()
+ }
+ }
+
cpu.done <- true
}
diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go
index 3852d93e72..a6f5eda458 100644
--- a/src/runtime/pprof/pprof_test.go
+++ b/src/runtime/pprof/pprof_test.go
@@ -86,10 +86,14 @@ func TestCPUProfileMultithreaded(t *testing.T) {
})
}
-func parseProfile(t *testing.T, bytes []byte, f func(uintptr, []uintptr)) {
+func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []uintptr)) {
// Convert []byte to []uintptr.
- l := len(bytes) / int(unsafe.Sizeof(uintptr(0)))
- val := *(*[]uintptr)(unsafe.Pointer(&bytes))
+ l := len(valBytes)
+ if i := bytes.Index(valBytes, []byte("\nMAPPED_LIBRARIES:\n")); i >= 0 {
+ l = i
+ }
+ l /= int(unsafe.Sizeof(uintptr(0)))
+ val := *(*[]uintptr)(unsafe.Pointer(&valBytes))
val = val[:l]
// 5 for the header, 3 for the trailer.