1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
// 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.
//go:build !ios
package pprof
import (
"bufio"
"bytes"
"fmt"
"internal/abi"
"internal/testenv"
"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, err := useVMMapWithRetry(t)
if err != nil {
t.Fatal(err)
}
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 useVMMapWithRetry(t *testing.T) (hi, lo uint64, err error) {
var retryable bool
for {
hi, lo, retryable, err = useVMMap(t)
if err == nil {
return hi, lo, nil
}
if !retryable {
return 0, 0, err
}
t.Logf("retrying vmmap after error: %v", err)
}
}
func useVMMap(t *testing.T) (hi, lo uint64, retryable bool, err error) {
pid := strconv.Itoa(os.Getpid())
testenv.MustHaveExecPath(t, "vmmap")
cmd := testenv.Command(t, "vmmap", pid)
out, cmdErr := cmd.Output()
if cmdErr != nil {
t.Logf("vmmap output: %s", out)
if ee, ok := cmdErr.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
t.Logf("%v: %v\n%s", cmd, cmdErr, ee.Stderr)
}
retryable = bytes.Contains(out, []byte("resource shortage"))
t.Logf("%v: %v", cmd, cmdErr)
if retryable {
return 0, 0, true, cmdErr
}
}
// Always parse the output of vmmap since it may return an error
// code even if it successfully reports the text segment information
// required for this test.
hi, lo, err = parseVmmap(out)
if err != nil {
if cmdErr != nil {
return 0, 0, false, fmt.Errorf("failed to parse vmmap output, vmmap reported an error: %v", err)
}
t.Logf("vmmap output: %s", out)
return 0, 0, false, fmt.Errorf("failed to parse vmmap output, vmmap did not report an error: %v", err)
}
return hi, lo, false, nil
}
// parseVmmap parses the output of vmmap and calls addMapping for the first r-x TEXT segment in the output.
func parseVmmap(data []byte) (hi, lo uint64, err error) {
// 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, nil
}
}
if strings.HasPrefix(l, banner) {
grabbing = true
}
}
return 0, 0, fmt.Errorf("vmmap no text segment found")
}
|