Source file src/runtime/pprof/vminfo_darwin_test.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build !ios
     6  
     7  package pprof
     8  
     9  import (
    10  	"bufio"
    11  	"bytes"
    12  	"fmt"
    13  	"internal/abi"
    14  	"internal/testenv"
    15  	"os"
    16  	"os/exec"
    17  	"strconv"
    18  	"strings"
    19  	"testing"
    20  )
    21  
    22  func TestVMInfo(t *testing.T) {
    23  	var begin, end, offset uint64
    24  	var filename string
    25  	first := true
    26  	machVMInfo(func(lo, hi, off uint64, file, buildID string) {
    27  		if first {
    28  			begin = lo
    29  			end = hi
    30  			offset = off
    31  			filename = file
    32  		}
    33  		// May see multiple text segments if rosetta is used for running
    34  		// the go toolchain itself.
    35  		first = false
    36  	})
    37  	lo, hi, err := useVMMapWithRetry(t)
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	if got, want := begin, lo; got != want {
    42  		t.Errorf("got %x, want %x", got, want)
    43  	}
    44  	if got, want := end, hi; got != want {
    45  		t.Errorf("got %x, want %x", got, want)
    46  	}
    47  	if got, want := offset, uint64(0); got != want {
    48  		t.Errorf("got %x, want %x", got, want)
    49  	}
    50  	if !strings.HasSuffix(filename, "pprof.test") {
    51  		t.Errorf("got %s, want pprof.test", filename)
    52  	}
    53  	addr := uint64(abi.FuncPCABIInternal(TestVMInfo))
    54  	if addr < lo || addr > hi {
    55  		t.Errorf("%x..%x does not contain function %p (%x)", lo, hi, TestVMInfo, addr)
    56  	}
    57  }
    58  
    59  func useVMMapWithRetry(t *testing.T) (hi, lo uint64, err error) {
    60  	var retryable bool
    61  	for {
    62  		hi, lo, retryable, err = useVMMap(t)
    63  		if err == nil {
    64  			return hi, lo, nil
    65  		}
    66  		if !retryable {
    67  			return 0, 0, err
    68  		}
    69  		t.Logf("retrying vmmap after error: %v", err)
    70  	}
    71  }
    72  
    73  func useVMMap(t *testing.T) (hi, lo uint64, retryable bool, err error) {
    74  	pid := strconv.Itoa(os.Getpid())
    75  	testenv.MustHaveExecPath(t, "vmmap")
    76  	cmd := testenv.Command(t, "vmmap", pid)
    77  	out, cmdErr := cmd.Output()
    78  	if cmdErr != nil {
    79  		t.Logf("vmmap output: %s", out)
    80  		if ee, ok := cmdErr.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
    81  			t.Logf("%v: %v\n%s", cmd, cmdErr, ee.Stderr)
    82  			if testing.Short() && strings.Contains(string(ee.Stderr), "No process corpse slots currently available, waiting to get one") {
    83  				t.Skipf("Skipping knwn flake in short test mode")
    84  			}
    85  			retryable = bytes.Contains(ee.Stderr, []byte("resource shortage"))
    86  		}
    87  		t.Logf("%v: %v\n", cmd, cmdErr)
    88  		if retryable {
    89  			return 0, 0, true, cmdErr
    90  		}
    91  	}
    92  	// Always parse the output of vmmap since it may return an error
    93  	// code even if it successfully reports the text segment information
    94  	// required for this test.
    95  	hi, lo, err = parseVmmap(out)
    96  	if err != nil {
    97  		if cmdErr != nil {
    98  			return 0, 0, false, fmt.Errorf("failed to parse vmmap output, vmmap reported an error: %v", err)
    99  		}
   100  		t.Logf("vmmap output: %s", out)
   101  		return 0, 0, false, fmt.Errorf("failed to parse vmmap output, vmmap did not report an error: %v", err)
   102  	}
   103  	return hi, lo, false, nil
   104  }
   105  
   106  // parseVmmap parses the output of vmmap and calls addMapping for the first r-x TEXT segment in the output.
   107  func parseVmmap(data []byte) (hi, lo uint64, err error) {
   108  	// vmmap 53799
   109  	// Process:         gopls [53799]
   110  	// Path:            /Users/USER/*/gopls
   111  	// Load Address:    0x1029a0000
   112  	// Identifier:      gopls
   113  	// Version:         ???
   114  	// Code Type:       ARM64
   115  	// Platform:        macOS
   116  	// Parent Process:  Code Helper (Plugin) [53753]
   117  	//
   118  	// Date/Time:       2023-05-25 09:45:49.331 -0700
   119  	// Launch Time:     2023-05-23 09:35:37.514 -0700
   120  	// OS Version:      macOS 13.3.1 (22E261)
   121  	// Report Version:  7
   122  	// Analysis Tool:   /Applications/Xcode.app/Contents/Developer/usr/bin/vmmap
   123  	// Analysis Tool Version:  Xcode 14.3 (14E222b)
   124  	//
   125  	// Physical footprint:         1.2G
   126  	// Physical footprint (peak):  1.2G
   127  	// Idle exit:                  untracked
   128  	// ----
   129  	//
   130  	// Virtual Memory Map of process 53799 (gopls)
   131  	// Output report format:  2.4  -64-bit process
   132  	// VM page size:  16384 bytes
   133  	//
   134  	// ==== Non-writable regions for process 53799
   135  	// REGION TYPE                    START END         [ VSIZE  RSDNT  DIRTY   SWAP] PRT/MAX SHRMOD PURGE    REGION DETAIL
   136  	// __TEXT                      1029a0000-1033bc000    [ 10.1M  7360K     0K     0K] r-x/rwx SM=COW          /Users/USER/*/gopls
   137  	// __DATA_CONST                1033bc000-1035bc000    [ 2048K  2000K     0K     0K] r--/rwSM=COW          /Users/USER/*/gopls
   138  	// __DATA_CONST                1035bc000-103a48000    [ 4656K  3824K     0K     0K] r--/rwSM=COW          /Users/USER/*/gopls
   139  	// __LINKEDIT                  103b00000-103c98000    [ 1632K  1616K     0K     0K] r--/r-SM=COW          /Users/USER/*/gopls
   140  	// dyld private memory         103cd8000-103cdc000    [   16K     0K     0K     0K] ---/--SM=NUL
   141  	// shared memory               103ce4000-103ce8000    [   16K    16K    16K     0K] r--/r-SM=SHM
   142  	// MALLOC metadata             103ce8000-103cec000    [   16K    16K    16K     0K] r--/rwx SM=COW          DefaultMallocZone_0x103ce8000 zone structure
   143  	// MALLOC guard page           103cf0000-103cf4000    [   16K     0K     0K     0K] ---/rwx SM=COW
   144  	// MALLOC guard page           103cfc000-103d00000    [   16K     0K     0K     0K] ---/rwx SM=COW
   145  	// MALLOC guard page           103d00000-103d04000    [   16K     0K     0K     0K] ---/rwx SM=NUL
   146  
   147  	banner := "==== Non-writable regions for process"
   148  	grabbing := false
   149  	sc := bufio.NewScanner(bytes.NewReader(data))
   150  	for sc.Scan() {
   151  		l := sc.Text()
   152  		if grabbing {
   153  			p := strings.Fields(l)
   154  			if len(p) > 7 && p[0] == "__TEXT" && p[7] == "r-x/rwx" {
   155  				locs := strings.Split(p[1], "-")
   156  				start, _ := strconv.ParseUint(locs[0], 16, 64)
   157  				end, _ := strconv.ParseUint(locs[1], 16, 64)
   158  				return start, end, nil
   159  			}
   160  		}
   161  		if strings.HasPrefix(l, banner) {
   162  			grabbing = true
   163  		}
   164  	}
   165  	return 0, 0, fmt.Errorf("vmmap no text segment found")
   166  }
   167  

View as plain text