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()
}
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")
}
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()
}
pcln() (textStart uint64, symtab, pclntab []byte, err error)
text() (textStart uint64, text []byte, err error)
goarch() string
+ loadAddress() (uint64, error)
dwarf() (*dwarf.Data, error)
}
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) {
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()
}
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")
}
name: name,
file: of,
}
+ if load, err := of.LoadAddress(); err == nil {
+ f.offset = start - load
+ }
return f, nil
}
// (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
}
f.pcln = pcln
}
+ addr -= f.offset
file, line, fn := f.pcln.PCToLine(addr)
if fn != nil {
frame := []plugin.Frame{
"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"},
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")
+ }
+}
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()
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
}
"bytes"
"fmt"
"io"
+ "os"
"runtime"
"sort"
"strings"
}
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
}
})
}
-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.