Skip to content

Commit

Permalink
runtime,runtime/pprof: get memory mappings on darwin.
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Cherry Mui <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: David Chase <[email protected]>
  • Loading branch information
cosnicolaou authored and cherrymui committed Aug 3, 2023
1 parent d50272a commit b7c826d
Show file tree
Hide file tree
Showing 17 changed files with 464 additions and 18 deletions.
3 changes: 3 additions & 0 deletions src/cmd/internal/objfile/macho.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ 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) {
if seg := f.macho.Segment("__TEXT"); seg != nil {
return seg.Addr, nil
}
return 0, fmt.Errorf("unknown load address")
}

Expand Down
3 changes: 1 addition & 2 deletions src/cmd/pprof/pprof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ func mustHaveDisasm(t *testing.T) {

// 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)
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/runtime/defs_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ const (
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
Expand Down Expand Up @@ -163,3 +166,11 @@ type PthreadCond C.pthread_cond_t
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
11 changes: 11 additions & 0 deletions src/runtime/defs_darwin_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ const (
_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 {
Expand Down Expand Up @@ -371,3 +374,11 @@ type machTimebaseInfo 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
11 changes: 11 additions & 0 deletions src/runtime/defs_darwin_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ const (
_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 {
Expand Down Expand Up @@ -238,3 +241,11 @@ type machTimebaseInfo 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
30 changes: 30 additions & 0 deletions src/runtime/pprof/defs_darwin.go
Original file line number Diff line number Diff line change
@@ -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
)
26 changes: 26 additions & 0 deletions src/runtime/pprof/defs_darwin_amd64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/runtime/pprof/defs_darwin_arm64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions src/runtime/pprof/proto_darwin.go
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 3 additions & 3 deletions src/runtime/pprof/proto_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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")
}
13 changes: 4 additions & 9 deletions src/runtime/pprof/proto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -120,15 +115,15 @@ 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{
ID: 1,
Start: start,
Limit: end,
File: exe,
BuildID: peBuildID(exe),
BuildID: buildID,
HasFunctions: true,
}
case "js", "wasip1":
Expand Down
13 changes: 9 additions & 4 deletions src/runtime/pprof/proto_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package pprof
import (
"errors"
"internal/syscall/windows"
"os"
"syscall"
)

Expand Down Expand Up @@ -42,21 +43,25 @@ 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) }()

var module windows.ModuleEntry32
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) {
Expand Down
71 changes: 71 additions & 0 deletions src/runtime/pprof/vminfo_darwin.go
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit b7c826d

Please sign in to comment.