aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/pprof
diff options
context:
space:
mode:
authorCosmos Nicolaou <cosmos.nicolaou@gmail.com>2023-06-14 14:33:43 -0700
committerCherry Mui <cherryyz@google.com>2023-08-03 16:07:59 +0000
commitb7c826d2c4576dbe04a79ab7d0dfa03c722c0ab9 (patch)
tree3da7ecc90e526b16339b493d61a07d91f7b87dab /src/runtime/pprof
parentd50272a8c17920d0eac2d765194d4cd0355a1d34 (diff)
downloadgo-b7c826d2c4576dbe04a79ab7d0dfa03c722c0ab9.tar.xz
runtime,runtime/pprof: get memory mappings on darwin.
Displaying assembly language has never worked for Apple Silicon macs (see #50891). This change uses mach_vm_region to obtain the necessary VM mappings to allow for locating assembly instructions for a cpu profile. Fixes #50891 Change-Id: Ib968c55a19b481b82f63337276b552f3b18f69d1 Reviewed-on: https://go-review.googlesource.com/c/go/+/503919 Run-TryBot: Cherry Mui <cherryyz@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: David Chase <drchase@google.com>
Diffstat (limited to 'src/runtime/pprof')
-rw-r--r--src/runtime/pprof/defs_darwin.go30
-rw-r--r--src/runtime/pprof/defs_darwin_amd64.go26
-rw-r--r--src/runtime/pprof/defs_darwin_arm64.go26
-rw-r--r--src/runtime/pprof/proto_darwin.go36
-rw-r--r--src/runtime/pprof/proto_other.go6
-rw-r--r--src/runtime/pprof/proto_test.go13
-rw-r--r--src/runtime/pprof/proto_windows.go13
-rw-r--r--src/runtime/pprof/vminfo_darwin.go71
-rw-r--r--src/runtime/pprof/vminfo_darwin_test.go122
9 files changed, 327 insertions, 16 deletions
diff --git a/src/runtime/pprof/defs_darwin.go b/src/runtime/pprof/defs_darwin.go
new file mode 100644
index 0000000000..2b2f68132c
--- /dev/null
+++ b/src/runtime/pprof/defs_darwin.go
@@ -0,0 +1,30 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file is used as input to cgo --godefs (GOOS=arm64 or amd64) to
+// generate the types used in viminfo_darwin_{arm64,amd64}.go which are
+// hand edited as appropriate, primarily to avoid exporting the types.
+
+//go:build ignore
+
+package pprof
+
+/*
+#include <sys/param.h>
+#include <mach/vm_prot.h>
+#include <mach/vm_region.h>
+*/
+import "C"
+
+type machVMRegionBasicInfoData C.vm_region_basic_info_data_64_t
+
+const (
+ _VM_PROT_READ = C.VM_PROT_READ
+ _VM_PROT_WRITE = C.VM_PROT_WRITE
+ _VM_PROT_EXECUTE = C.VM_PROT_EXECUTE
+
+ _MACH_SEND_INVALID_DEST = C.MACH_SEND_INVALID_DEST
+
+ _MAXPATHLEN = C.MAXPATHLEN
+)
diff --git a/src/runtime/pprof/defs_darwin_amd64.go b/src/runtime/pprof/defs_darwin_amd64.go
new file mode 100644
index 0000000000..14226495d8
--- /dev/null
+++ b/src/runtime/pprof/defs_darwin_amd64.go
@@ -0,0 +1,26 @@
+// Code generated by cmd/cgo -godefs; DO NOT EDIT.
+// cgo -godefs defs_darwin.go
+
+package pprof
+
+type machVMRegionBasicInfoData struct {
+ Protection int32
+ Max_protection int32
+ Inheritance uint32
+ Shared uint32
+ Reserved uint32
+ Offset uint64 // This is hand-edited since godefs generates: Pad_cgo_0 [8]byte
+ Behavior int32
+ User_wired_count uint16
+ Pad_cgo_1 [2]byte
+}
+
+const (
+ _VM_PROT_READ = 0x1
+ _VM_PROT_WRITE = 0x2
+ _VM_PROT_EXECUTE = 0x4
+
+ _MACH_SEND_INVALID_DEST = 0x10000003
+
+ _MAXPATHLEN = 0x400
+)
diff --git a/src/runtime/pprof/defs_darwin_arm64.go b/src/runtime/pprof/defs_darwin_arm64.go
new file mode 100644
index 0000000000..2d34a80d80
--- /dev/null
+++ b/src/runtime/pprof/defs_darwin_arm64.go
@@ -0,0 +1,26 @@
+// Code generated by cmd/cgo -godefs; DO NOT EDIT.
+// cgo -godefs defs_darwin.go
+
+package pprof
+
+type machVMRegionBasicInfoData struct {
+ Protection int32
+ Max_protection int32
+ Inheritance uint32
+ Shared int32
+ Reserved int32
+ Offset uint64 // This is hand-edited since godefs generates: Pad_cgo_0 [8]byte
+ Behavior int32
+ User_wired_count uint16
+ Pad_cgo_1 [2]byte
+}
+
+const (
+ _VM_PROT_READ = 0x1
+ _VM_PROT_WRITE = 0x2
+ _VM_PROT_EXECUTE = 0x4
+
+ _MACH_SEND_INVALID_DEST = 0x10000003
+
+ _MAXPATHLEN = 0x400
+)
diff --git a/src/runtime/pprof/proto_darwin.go b/src/runtime/pprof/proto_darwin.go
new file mode 100644
index 0000000000..8db9e1d2b3
--- /dev/null
+++ b/src/runtime/pprof/proto_darwin.go
@@ -0,0 +1,36 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pprof
+
+import (
+ "errors"
+)
+
+// readMapping adds a mapping entry for the text region of the running process.
+// It uses the mach_vm_region region system call to add mapping entries for the
+// text region of the running process. Note that currently no attempt is
+// made to obtain the buildID information.
+func (b *profileBuilder) readMapping() {
+ if !machVMInfo(b.addMapping) {
+ b.addMappingEntry(0, 0, 0, "", "", true)
+ }
+}
+
+func readMainModuleMapping() (start, end uint64, exe, buildID string, err error) {
+ first := true
+ ok := machVMInfo(func(lo, hi, off uint64, file, build string) {
+ if first {
+ start, end = lo, hi
+ exe, buildID = file, build
+ }
+ // May see multiple text segments if rosetta is used for running
+ // the go toolchain itself.
+ first = false
+ })
+ if !ok {
+ return 0, 0, "", "", errors.New("machVMInfo failed")
+ }
+ return start, end, exe, buildID, nil
+}
diff --git a/src/runtime/pprof/proto_other.go b/src/runtime/pprof/proto_other.go
index 4a7fe79501..7322e84803 100644
--- a/src/runtime/pprof/proto_other.go
+++ b/src/runtime/pprof/proto_other.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !windows
+//go:build !windows && !darwin
package pprof
@@ -25,6 +25,6 @@ func (b *profileBuilder) readMapping() {
}
}
-func readMainModuleMapping() (start, end uint64, err error) {
- return 0, 0, errors.New("not implemented")
+func readMainModuleMapping() (start, end uint64, exe, buildID string, err error) {
+ return 0, 0, "", "", errors.New("not implemented")
}
diff --git a/src/runtime/pprof/proto_test.go b/src/runtime/pprof/proto_test.go
index 8ec9c9109a..eb43816b7c 100644
--- a/src/runtime/pprof/proto_test.go
+++ b/src/runtime/pprof/proto_test.go
@@ -101,16 +101,11 @@ func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
addr2 = mprof.Mapping[1].Start
map2 = mprof.Mapping[1]
map2.BuildID, _ = elfBuildID(map2.File)
- case "windows":
+ case "windows", "darwin":
addr1 = uint64(abi.FuncPCABIInternal(f1))
addr2 = uint64(abi.FuncPCABIInternal(f2))
- exe, err := os.Executable()
- if err != nil {
- t.Fatal(err)
- }
-
- start, end, err := readMainModuleMapping()
+ start, end, exe, buildID, err := readMainModuleMapping()
if err != nil {
t.Fatal(err)
}
@@ -120,7 +115,7 @@ func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
Start: start,
Limit: end,
File: exe,
- BuildID: peBuildID(exe),
+ BuildID: buildID,
HasFunctions: true,
}
map2 = &profile.Mapping{
@@ -128,7 +123,7 @@ func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
Start: start,
Limit: end,
File: exe,
- BuildID: peBuildID(exe),
+ BuildID: buildID,
HasFunctions: true,
}
case "js", "wasip1":
diff --git a/src/runtime/pprof/proto_windows.go b/src/runtime/pprof/proto_windows.go
index d5ae4a5eec..f4dc44bd07 100644
--- a/src/runtime/pprof/proto_windows.go
+++ b/src/runtime/pprof/proto_windows.go
@@ -7,6 +7,7 @@ package pprof
import (
"errors"
"internal/syscall/windows"
+ "os"
"syscall"
)
@@ -42,10 +43,14 @@ func (b *profileBuilder) readMapping() {
}
}
-func readMainModuleMapping() (start, end uint64, err error) {
+func readMainModuleMapping() (start, end uint64, exe, buildID string, err error) {
+ exe, err = os.Executable()
+ if err != nil {
+ return 0, 0, "", "", err
+ }
snap, err := createModuleSnapshot()
if err != nil {
- return 0, 0, err
+ return 0, 0, "", "", err
}
defer func() { _ = syscall.CloseHandle(snap) }()
@@ -53,10 +58,10 @@ func readMainModuleMapping() (start, end uint64, err error) {
module.Size = uint32(windows.SizeofModuleEntry32)
err = windows.Module32First(snap, &module)
if err != nil {
- return 0, 0, err
+ return 0, 0, "", "", err
}
- return uint64(module.ModBaseAddr), uint64(module.ModBaseAddr) + uint64(module.ModBaseSize), nil
+ return uint64(module.ModBaseAddr), uint64(module.ModBaseAddr) + uint64(module.ModBaseSize), exe, peBuildID(exe), nil
}
func createModuleSnapshot() (syscall.Handle, error) {
diff --git a/src/runtime/pprof/vminfo_darwin.go b/src/runtime/pprof/vminfo_darwin.go
new file mode 100644
index 0000000000..7ddb0d1c68
--- /dev/null
+++ b/src/runtime/pprof/vminfo_darwin.go
@@ -0,0 +1,71 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pprof
+
+import (
+ "os"
+ "unsafe"
+)
+
+func isExecutable(protection int32) bool {
+ return (protection&_VM_PROT_EXECUTE) != 0 && (protection&_VM_PROT_READ) != 0
+}
+
+// machVMInfo uses the mach_vm_region region system call to add mapping entries
+// for the text region of the running process.
+func machVMInfo(addMapping func(lo, hi, offset uint64, file, buildID string)) bool {
+ added := false
+ var addr uint64 = 0x1
+ for {
+ var memRegionSize uint64
+ var info machVMRegionBasicInfoData
+ // Get the first address and page size.
+ kr := mach_vm_region(
+ &addr,
+ &memRegionSize,
+ unsafe.Pointer(&info))
+ if kr != 0 {
+ if kr == _MACH_SEND_INVALID_DEST {
+ // No more memory regions.
+ return true
+ }
+ return added // return true if at least one mapping was added
+ }
+ if isExecutable(info.Protection) {
+ // NOTE: the meaning/value of Offset is unclear. However,
+ // this likely doesn't matter as the text segment's file
+ // offset is usually 0.
+ addMapping(addr,
+ addr+memRegionSize,
+ uint64(info.Offset),
+ regionFilename(addr),
+ "")
+ added = true
+ }
+ addr += memRegionSize
+ }
+}
+
+func regionFilename(address uint64) string {
+ buf := make([]byte, _MAXPATHLEN)
+ r := proc_regionfilename(
+ os.Getpid(),
+ address,
+ unsafe.SliceData(buf),
+ int64(cap(buf)))
+ if r == 0 {
+ return ""
+ }
+ return string(buf[:r])
+}
+
+// mach_vm_region and proc_regionfilename are implemented by
+// the runtime package (runtime/sys_darwin.go).
+//
+//go:noescape
+func mach_vm_region(address, region_size *uint64, info unsafe.Pointer) int32
+
+//go:noescape
+func proc_regionfilename(pid int, address uint64, buf *byte, buflen int64) int32
diff --git a/src/runtime/pprof/vminfo_darwin_test.go b/src/runtime/pprof/vminfo_darwin_test.go
new file mode 100644
index 0000000000..3023878fde
--- /dev/null
+++ b/src/runtime/pprof/vminfo_darwin_test.go
@@ -0,0 +1,122 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pprof
+
+import (
+ "bufio"
+ "bytes"
+ "internal/abi"
+ "os"
+ "os/exec"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+func TestVMInfo(t *testing.T) {
+ var begin, end, offset uint64
+ var filename string
+ first := true
+ machVMInfo(func(lo, hi, off uint64, file, buildID string) {
+ if first {
+ begin = lo
+ end = hi
+ offset = off
+ filename = file
+ }
+ // May see multiple text segments if rosetta is used for running
+ // the go toolchain itself.
+ first = false
+ })
+ lo, hi := useVMMap(t)
+ if got, want := begin, lo; got != want {
+ t.Errorf("got %x, want %x", got, want)
+ }
+ if got, want := end, hi; got != want {
+ t.Errorf("got %x, want %x", got, want)
+ }
+ if got, want := offset, uint64(0); got != want {
+ t.Errorf("got %x, want %x", got, want)
+ }
+ if !strings.HasSuffix(filename, "pprof.test") {
+ t.Errorf("got %s, want pprof.test", filename)
+ }
+ addr := uint64(abi.FuncPCABIInternal(TestVMInfo))
+ if addr < lo || addr > hi {
+ t.Errorf("%x..%x does not contain function %p (%x)", lo, hi, TestVMInfo, addr)
+ }
+}
+
+func useVMMap(t *testing.T) (hi, lo uint64) {
+ pid := strconv.Itoa(os.Getpid())
+ out, err := exec.Command("vmmap", pid).Output()
+ if err != nil {
+ t.Fatal(err)
+ }
+ return parseVmmap(t, out)
+}
+
+// parseVmmap parses the output of vmmap and calls addMapping for the first r-x TEXT segment in the output.
+func parseVmmap(t *testing.T, data []byte) (hi, lo uint64) {
+ // vmmap 53799
+ // Process: gopls [53799]
+ // Path: /Users/USER/*/gopls
+ // Load Address: 0x1029a0000
+ // Identifier: gopls
+ // Version: ???
+ // Code Type: ARM64
+ // Platform: macOS
+ // Parent Process: Code Helper (Plugin) [53753]
+ //
+ // Date/Time: 2023-05-25 09:45:49.331 -0700
+ // Launch Time: 2023-05-23 09:35:37.514 -0700
+ // OS Version: macOS 13.3.1 (22E261)
+ // Report Version: 7
+ // Analysis Tool: /Applications/Xcode.app/Contents/Developer/usr/bin/vmmap
+ // Analysis Tool Version: Xcode 14.3 (14E222b)
+ //
+ // Physical footprint: 1.2G
+ // Physical footprint (peak): 1.2G
+ // Idle exit: untracked
+ // ----
+ //
+ // Virtual Memory Map of process 53799 (gopls)
+ // Output report format: 2.4 -64-bit process
+ // VM page size: 16384 bytes
+ //
+ // ==== Non-writable regions for process 53799
+ // REGION TYPE START END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL
+ // __TEXT 1029a0000-1033bc000 [ 10.1M 7360K 0K 0K] r-x/rwx SM=COW /Users/USER/*/gopls
+ // __DATA_CONST 1033bc000-1035bc000 [ 2048K 2000K 0K 0K] r--/rwSM=COW /Users/USER/*/gopls
+ // __DATA_CONST 1035bc000-103a48000 [ 4656K 3824K 0K 0K] r--/rwSM=COW /Users/USER/*/gopls
+ // __LINKEDIT 103b00000-103c98000 [ 1632K 1616K 0K 0K] r--/r-SM=COW /Users/USER/*/gopls
+ // dyld private memory 103cd8000-103cdc000 [ 16K 0K 0K 0K] ---/--SM=NUL
+ // shared memory 103ce4000-103ce8000 [ 16K 16K 16K 0K] r--/r-SM=SHM
+ // MALLOC metadata 103ce8000-103cec000 [ 16K 16K 16K 0K] r--/rwx SM=COW DefaultMallocZone_0x103ce8000 zone structure
+ // MALLOC guard page 103cf0000-103cf4000 [ 16K 0K 0K 0K] ---/rwx SM=COW
+ // MALLOC guard page 103cfc000-103d00000 [ 16K 0K 0K 0K] ---/rwx SM=COW
+ // MALLOC guard page 103d00000-103d04000 [ 16K 0K 0K 0K] ---/rwx SM=NUL
+
+ banner := "==== Non-writable regions for process"
+ grabbing := false
+ sc := bufio.NewScanner(bytes.NewReader(data))
+ for sc.Scan() {
+ l := sc.Text()
+ if grabbing {
+ p := strings.Fields(l)
+ if len(p) > 7 && p[0] == "__TEXT" && p[7] == "r-x/rwx" {
+ locs := strings.Split(p[1], "-")
+ start, _ := strconv.ParseUint(locs[0], 16, 64)
+ end, _ := strconv.ParseUint(locs[1], 16, 64)
+ return start, end
+ }
+ }
+ if strings.HasPrefix(l, banner) {
+ grabbing = true
+ }
+ }
+ t.Fatal("vmmap no text segment found")
+ return 0, 0
+}