func (x uint64s) Less(i, j int) bool { return x[i] < x[j] }
func (f *machoFile) loadAddress() (uint64, error) {
+ if seg := f.macho.Segment("__TEXT"); seg != nil {
+ return seg.Addr, nil
+ }
return 0, fmt.Errorf("unknown load address")
}
// pprof can only disassemble PIE on some platforms.
// Skip the ones it can't handle yet.
- if (runtime.GOOS == "darwin" && runtime.GOARCH == "arm64") ||
- (runtime.GOOS == "android" && runtime.GOARCH == "arm") {
+ if runtime.GOOS == "android" && runtime.GOARCH == "arm" {
t.Skipf("skipping on %s/%s, issue 46639", runtime.GOOS, runtime.GOARCH)
}
}
O_NONBLOCK = C.O_NONBLOCK
O_CREAT = C.O_CREAT
O_TRUNC = C.O_TRUNC
+
+ VM_REGION_BASIC_INFO_COUNT_64 = C.VM_REGION_BASIC_INFO_COUNT_64
+ VM_REGION_BASIC_INFO_64 = C.VM_REGION_BASIC_INFO_64
)
type StackT C.struct_sigaltstack
type PthreadCondAttr C.pthread_condattr_t
type MachTimebaseInfo C.mach_timebase_info_data_t
+
+type MachPort C.mach_port_t
+type MachVMMapRead C.vm_map_read_t
+type MachVMAddress C.mach_vm_address_t
+type MachVMSize C.mach_vm_size_t
+type MachVMRegionFlavour C.vm_region_flavor_t
+type MachVMRegionInfo C.vm_region_info_t
+type MachMsgTypeNumber C.mach_msg_type_number_t
_O_NONBLOCK = 0x4
_O_CREAT = 0x200
_O_TRUNC = 0x400
+
+ _VM_REGION_BASIC_INFO_COUNT_64 = 0x9
+ _VM_REGION_BASIC_INFO_64 = 0x9
)
type stackt struct {
numer uint32
denom uint32
}
+
+type machPort uint32
+type machVMMapRead uint32
+type machVMAddress uint64
+type machVMSize uint64
+type machVMRegionFlavour int32
+type machVMRegionInfo *int32
+type machMsgTypeNumber uint32
_O_NONBLOCK = 0x4
_O_CREAT = 0x200
_O_TRUNC = 0x400
+
+ _VM_REGION_BASIC_INFO_COUNT_64 = 0x9
+ _VM_REGION_BASIC_INFO_64 = 0x9
)
type stackt struct {
}
type pthreadkey uint64
+
+type machPort uint32
+type machVMMapRead uint32
+type machVMAddress uint64
+type machVMSize uint64
+type machVMRegionFlavour int32
+type machVMRegionInfo *int32
+type machMsgTypeNumber uint32
--- /dev/null
+// 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
+)
--- /dev/null
+// 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
+)
--- /dev/null
+// 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
+)
--- /dev/null
+// 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
+}
// 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
}
}
-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")
}
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)
}
Start: start,
Limit: end,
File: exe,
- BuildID: peBuildID(exe),
+ BuildID: buildID,
HasFunctions: true,
}
map2 = &profile.Mapping{
Start: start,
Limit: end,
File: exe,
- BuildID: peBuildID(exe),
+ BuildID: buildID,
HasFunctions: true,
}
case "js", "wasip1":
import (
"errors"
"internal/syscall/windows"
+ "os"
"syscall"
)
}
}
-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) }()
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) {
--- /dev/null
+// 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
--- /dev/null
+// 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
+}
}
func issetugid_trampoline()
+// mach_vm_region is used to obtain virtual memory mappings for use by the
+// profiling system and is only exported to runtime/pprof. It is restricted
+// to obtaining mappings for the current process.
+//
+//go:linkname mach_vm_region runtime/pprof.mach_vm_region
+func mach_vm_region(address, region_size *uint64, info unsafe.Pointer) int32 {
+ // kern_return_t mach_vm_region(
+ // vm_map_read_t target_task,
+ // mach_vm_address_t *address,
+ // mach_vm_size_t *size,
+ // vm_region_flavor_t flavor,
+ // vm_region_info_t info,
+ // mach_msg_type_number_t *infoCnt,
+ // mach_port_t *object_name);
+ var count machMsgTypeNumber = _VM_REGION_BASIC_INFO_COUNT_64
+ var object_name machPort
+ args := struct {
+ address *uint64
+ size *uint64
+ flavor machVMRegionFlavour
+ info unsafe.Pointer
+ count *machMsgTypeNumber
+ object_name *machPort
+ }{
+ address: address,
+ size: region_size,
+ flavor: _VM_REGION_BASIC_INFO_64,
+ info: info,
+ count: &count,
+ object_name: &object_name,
+ }
+ return libcCall(unsafe.Pointer(abi.FuncPCABI0(mach_vm_region_trampoline)), unsafe.Pointer(&args))
+}
+func mach_vm_region_trampoline()
+
+//go:linkname proc_regionfilename runtime/pprof.proc_regionfilename
+func proc_regionfilename(pid int, address uint64, buf *byte, buflen int64) int32 {
+ args := struct {
+ pid int
+ address uint64
+ buf *byte
+ bufSize int64
+ }{
+ pid: pid,
+ address: address,
+ buf: buf,
+ bufSize: buflen,
+ }
+ return libcCall(unsafe.Pointer(abi.FuncPCABI0(proc_regionfilename_trampoline)), unsafe.Pointer(&args))
+}
+func proc_regionfilename_trampoline()
+
// Tell the linker that the libc_* functions are to be found
// in a system library, with the libc_ prefix missing.
//go:cgo_import_dynamic libc_error __error "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib"
+//go:cgo_import_dynamic libc_proc_regionfilename proc_regionfilename "/usr/lib/libSystem.B.dylib"
+//go:cgo_import_dynamic libc_mach_task_self_ mach_task_self_ "/usr/lib/libSystem.B.dylib""
+//go:cgo_import_dynamic libc_mach_vm_region mach_vm_region "/usr/lib/libSystem.B.dylib""
//go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_mach_absolute_time mach_absolute_time "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_clock_gettime clock_gettime "/usr/lib/libSystem.B.dylib"
TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
CALL libc_issetugid(SB)
RET
+
+// mach_vm_region_trampoline calls mach_vm_region from libc.
+TEXT runtime·mach_vm_region_trampoline(SB),NOSPLIT,$0
+ MOVQ 0(DI), SI // address
+ MOVQ 8(DI), DX // size
+ MOVL 16(DI), CX // flavor
+ MOVQ 24(DI), R8 // info
+ MOVQ 32(DI), R9 // count
+ MOVQ 40(DI), R10 // object_name
+ MOVQ $libc_mach_task_self_(SB), DI
+ MOVL 0(DI), DI
+ CALL libc_mach_vm_region(SB)
+ RET
+
+// proc_regionfilename_trampoline calls proc_regionfilename.
+TEXT runtime·proc_regionfilename_trampoline(SB),NOSPLIT,$0
+ MOVQ 8(DI), SI // address
+ MOVQ 16(DI), DX // buffer
+ MOVQ 24(DI), CX // buffer_size
+ MOVQ 0(DI), DI // pid
+ CALL libc_proc_regionfilename(SB)
+ RET
TEXT runtime·issetugid_trampoline(SB),NOSPLIT,$0
BL libc_issetugid(SB)
RET
+
+// mach_vm_region_trampoline calls mach_vm_region from libc.
+TEXT runtime·mach_vm_region_trampoline(SB),NOSPLIT,$0
+ MOVD 0(R0), R1 // address
+ MOVD 8(R0), R2 // size
+ MOVW 16(R0), R3 // flavor
+ MOVD 24(R0), R4 // info
+ MOVD 32(R0), R5 // count
+ MOVD 40(R0), R6 // object_name
+ MOVD $libc_mach_task_self_(SB), R0
+ MOVW 0(R0), R0
+ BL libc_mach_vm_region(SB)
+ RET
+
+// proc_regionfilename_trampoline calls proc_regionfilename for
+// the current process.
+TEXT runtime·proc_regionfilename_trampoline(SB),NOSPLIT,$0
+ MOVD 8(R0), R1 // address
+ MOVD 16(R0), R2 // buffer
+ MOVD 24(R0), R3 // buffer_size
+ MOVD 0(R0), R0 // pid
+ BL libc_proc_regionfilename(SB)
+ RET