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

View as plain text