summaryrefslogtreecommitdiffstats
path: root/src/runtime/pprof/vminfo_darwin_test.go
blob: 8749a13390245d759e85952dec71e02d0cd00e70 (plain)
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")
}