Source file src/cmd/vendor/golang.org/x/telemetry/internal/crashmonitor/monitor.go

     1  // Copyright 2024 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  package crashmonitor
     6  
     7  // This file defines a monitor that reports arbitrary Go runtime
     8  // crashes to telemetry.
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"io"
    14  	"log"
    15  	"os"
    16  	"reflect"
    17  	"runtime/debug"
    18  	"strconv"
    19  	"strings"
    20  
    21  	"golang.org/x/telemetry/internal/counter"
    22  )
    23  
    24  // Parent sets up the parent side of the crashmonitor. It requires
    25  // exclusive use of a writable pipe connected to the child process's stdin.
    26  func Parent(pipe *os.File) {
    27  	writeSentinel(pipe)
    28  	// Ensure that we get pc=0x%x values in the traceback.
    29  	debug.SetTraceback("system")
    30  	debug.SetCrashOutput(pipe, debug.CrashOptions{}) // ignore error
    31  }
    32  
    33  // Child runs the part of the crashmonitor that runs in the child process.
    34  // It expects its stdin to be connected via a pipe to the parent which has
    35  // run Parent.
    36  func Child() {
    37  	// Wait for parent process's dying gasp.
    38  	// If the parent dies for any reason this read will return.
    39  	data, err := io.ReadAll(os.Stdin)
    40  	if err != nil {
    41  		log.Fatalf("failed to read from input pipe: %v", err)
    42  	}
    43  
    44  	// If the only line is the sentinel, it wasn't a crash.
    45  	if bytes.Count(data, []byte("\n")) < 2 {
    46  		childExitHook()
    47  		os.Exit(0) // parent exited without crash report
    48  	}
    49  
    50  	log.Printf("parent reported crash:\n%s", data)
    51  
    52  	// Parse the stack out of the crash report
    53  	// and record a telemetry count for it.
    54  	name, err := telemetryCounterName(data)
    55  	if err != nil {
    56  		// Keep count of how often this happens
    57  		// so that we can investigate if necessary.
    58  		incrementCounter("crash/malformed")
    59  
    60  		// Something went wrong.
    61  		// Save the crash securely in the file system.
    62  		f, err := os.CreateTemp(os.TempDir(), "*.crash")
    63  		if err != nil {
    64  			log.Fatal(err)
    65  		}
    66  		if _, err := f.Write(data); err != nil {
    67  			log.Fatal(err)
    68  		}
    69  		if err := f.Close(); err != nil {
    70  			log.Fatal(err)
    71  		}
    72  		log.Printf("failed to report crash to telemetry: %v", err)
    73  		log.Fatalf("crash report saved at %s", f.Name())
    74  	}
    75  
    76  	incrementCounter(name)
    77  
    78  	childExitHook()
    79  	log.Fatalf("telemetry crash recorded")
    80  }
    81  
    82  // (stubbed by test)
    83  var (
    84  	incrementCounter = func(name string) { counter.New(name).Inc() }
    85  	childExitHook    = func() {}
    86  )
    87  
    88  // The sentinel function returns its address. The difference between
    89  // this value as observed by calls in two different processes of the
    90  // same executable tells us the relative offset of their text segments.
    91  //
    92  // It would be nice if SetCrashOutput took care of this as it's fiddly
    93  // and likely to confuse every user at first.
    94  func sentinel() uint64 {
    95  	return uint64(reflect.ValueOf(sentinel).Pointer())
    96  }
    97  
    98  func writeSentinel(out io.Writer) {
    99  	fmt.Fprintf(out, "sentinel %x\n", sentinel())
   100  }
   101  
   102  // telemetryCounterName parses a crash report produced by the Go
   103  // runtime, extracts the stack of the first runnable goroutine,
   104  // converts each line into telemetry form ("symbol:relative-line"),
   105  // and returns this as the name of a counter.
   106  func telemetryCounterName(crash []byte) (string, error) {
   107  	pcs, err := parseStackPCs(string(crash))
   108  	if err != nil {
   109  		return "", err
   110  	}
   111  
   112  	// Limit the number of frames we request.
   113  	pcs = pcs[:min(len(pcs), 16)]
   114  
   115  	if len(pcs) == 0 {
   116  		// This can occur if all goroutines are idle, as when
   117  		// caught in a deadlock, or killed by an async signal
   118  		// while blocked.
   119  		//
   120  		// TODO(adonovan): consider how to report such
   121  		// situations. Reporting a goroutine in [sleep] or
   122  		// [select] state could be quite confusing without
   123  		// further information about the nature of the crash,
   124  		// as the problem is not local to the code location.
   125  		//
   126  		// For now, we keep count of this situation so that we
   127  		// can access whether it needs a more involved solution.
   128  		return "crash/no-running-goroutine", nil
   129  	}
   130  
   131  	// This string appears at the start of all
   132  	// crashmonitor-generated counter names.
   133  	//
   134  	// It is tempting to expose this as a parameter of Start, but
   135  	// it is not without risk. What value should most programs
   136  	// provide? There's no point giving the name of the executable
   137  	// as this is already recorded by telemetry. What if the
   138  	// application runs in multiple modes? Then it might be useful
   139  	// to record the mode. The problem is that an application with
   140  	// multiple modes probably doesn't know its mode by line 1 of
   141  	// main.main: it might require flag or argument parsing, or
   142  	// even validation of an environment variable, and we really
   143  	// want to steer users aware from any logic before Start. The
   144  	// flags and arguments will be wrong in the child process, and
   145  	// every extra conditional branch creates a risk that the
   146  	// recursively executed child program will behave not like the
   147  	// monitor but like the application. If the child process
   148  	// exits before calling Start, then the parent application
   149  	// will not have a monitor, and its crash reports will be
   150  	// discarded (written in to a pipe that is never read).
   151  	//
   152  	// So for now, we use this constant string.
   153  	const prefix = "crash/crash"
   154  	return counter.EncodeStack(pcs, prefix), nil
   155  }
   156  
   157  // parseStackPCs parses the parent process's program counters for the
   158  // first running goroutine out of a GOTRACEBACK=system traceback,
   159  // adjusting them so that they are valid for the child process's text
   160  // segment.
   161  //
   162  // This function returns only program counter values, ensuring that
   163  // there is no possibility of strings from the crash report (which may
   164  // contain PII) leaking into the telemetry system.
   165  func parseStackPCs(crash string) ([]uintptr, error) {
   166  	// getSymbol parses the symbol name out of a line of the form:
   167  	// SYMBOL(ARGS)
   168  	//
   169  	// Note: SYMBOL may contain parens "pkg.(*T).method". However, type
   170  	// parameters are always replaced with ..., so they cannot introduce
   171  	// more parens. e.g., "pkg.(*T[...]).method".
   172  	//
   173  	// ARGS can contain parens. We want the first paren that is not
   174  	// immediately preceded by a ".".
   175  	//
   176  	// TODO(prattmic): This is mildly complicated and is only used to find
   177  	// runtime.sigpanic, so perhaps simplify this by checking explicitly
   178  	// for sigpanic.
   179  	getSymbol := func(line string) (string, error) {
   180  		var prev rune
   181  		for i, c := range line {
   182  			if line[i] != '(' {
   183  				prev = c
   184  				continue
   185  			}
   186  			if prev == '.' {
   187  				prev = c
   188  				continue
   189  			}
   190  			return line[:i], nil
   191  		}
   192  		return "", fmt.Errorf("no symbol for stack frame: %s", line)
   193  	}
   194  
   195  	// getPC parses the PC out of a line of the form:
   196  	//     \tFILE:LINE +0xRELPC sp=... fp=... pc=...
   197  	getPC := func(line string) (uint64, error) {
   198  		_, pcstr, ok := strings.Cut(line, " pc=") // e.g. pc=0x%x
   199  		if !ok {
   200  			return 0, fmt.Errorf("no pc= for stack frame: %s", line)
   201  		}
   202  		return strconv.ParseUint(pcstr, 0, 64) // 0 => allow 0x prefix
   203  	}
   204  
   205  	type goroutine struct {
   206  		id     uint64
   207  		pcs    []uintptr
   208  		system bool // stack starts with runtime.systemstack_switch
   209  	}
   210  
   211  	var (
   212  		glist          []*goroutine
   213  		parentSentinel uint64
   214  		childSentinel  = sentinel()
   215  		lines          = strings.Split(crash, "\n")
   216  
   217  		// Parsing state for the current goroutine.
   218  		g          *goroutine
   219  		symLine    = true // a symbol (not a filename) is expected on this line
   220  		currSymbol string
   221  		prevSymbol string
   222  	)
   223  
   224  	for _, line := range lines {
   225  		// Read sentinel value.
   226  		if parentSentinel == 0 && strings.HasPrefix(line, "sentinel ") {
   227  			_, err := fmt.Sscanf(line, "sentinel %x", &parentSentinel)
   228  			if err != nil {
   229  				return nil, fmt.Errorf("can't read sentinel line")
   230  			}
   231  			continue
   232  		}
   233  
   234  		// Check for a "goroutine GID [STATUS]" header.
   235  		if strings.HasPrefix(line, "goroutine ") {
   236  			if parentSentinel == 0 {
   237  				return nil, fmt.Errorf("no sentinel value in crash report")
   238  			}
   239  			isG0 := strings.HasPrefix(line, "goroutine 0 ")
   240  			isRunning := strings.Contains(line, " [running]:")
   241  			if isG0 || isRunning {
   242  				var id uint64
   243  				if parts := strings.Fields(line); len(parts) > 1 {
   244  					id, _ = strconv.ParseUint(parts[1], 10, 64)
   245  				}
   246  				g = &goroutine{id: id}
   247  				glist = append(glist, g)
   248  				symLine = true
   249  				currSymbol = ""
   250  				prevSymbol = ""
   251  			} else {
   252  				g = nil
   253  			}
   254  			continue
   255  		}
   256  
   257  		if g == nil {
   258  			continue
   259  		}
   260  
   261  		// A blank line or "created by " marks the end of the goroutine stack.
   262  		if line == "" || strings.HasPrefix(line, "created by ") {
   263  			g = nil
   264  			continue
   265  		}
   266  
   267  		// Expect a pair of lines:
   268  		//   SYMBOL(ARGS)
   269  		//   \tFILE:LINE +0xRELPC sp=0x%x fp=0x%x pc=0x%x
   270  		// Note: SYMBOL may contain parens "pkg.(*T).method"
   271  		// The RELPC is sometimes missing.
   272  
   273  		if symLine {
   274  			var err error
   275  			currSymbol, err = getSymbol(line)
   276  			if err != nil {
   277  				return nil, fmt.Errorf("error extracting symbol: %v", err)
   278  			}
   279  
   280  			symLine = false // Next line is FILE:LINE.
   281  		} else {
   282  			// Parse the PC, and correct for the parent and child's
   283  			// different mappings of the text section.
   284  			pc, err := getPC(line)
   285  			if err != nil {
   286  				// Inlined frame, perhaps; skip it.
   287  
   288  				// Done with this frame. Next line is a new frame.
   289  				//
   290  				// Don't update prevSymbol; we only want to
   291  				// track frames with a PC.
   292  				currSymbol = ""
   293  				symLine = true
   294  				continue
   295  			}
   296  
   297  			pc = pc - parentSentinel + childSentinel
   298  
   299  			// If the previous frame was sigpanic, then this frame
   300  			// was a trap (e.g., SIGSEGV).
   301  			//
   302  			// Typically all middle frames are calls, and report
   303  			// the "return PC". That is, the instruction following
   304  			// the CALL where the callee will eventually return to.
   305  			//
   306  			// runtime.CallersFrames is aware of this property and
   307  			// will decrement each PC by 1 to "back up" to the
   308  			// location of the CALL, which is the actual line
   309  			// number the user expects.
   310  			//
   311  			// This does not work for traps, as a trap is not a
   312  			// call, so the reported PC is not the return PC, but
   313  			// the actual PC of the trap.
   314  			//
   315  			// runtime.Callers is aware of this and will
   316  			// intentionally increment trap PCs in order to correct
   317  			// for the decrement performed by
   318  			// runtime.CallersFrames. See runtime.tracebackPCs and
   319  			// runtume.(*unwinder).symPC.
   320  			//
   321  			// We must emulate the same behavior, otherwise we will
   322  			// report the location of the instruction immediately
   323  			// prior to the trap, which may be on a different line,
   324  			// or even a different inlined functions.
   325  			//
   326  			// TODO(prattmic): The runtime applies the same trap
   327  			// behavior for other "injected calls", see injectCall
   328  			// in runtime.(*unwinder).next. Do we want to handle
   329  			// those as well? I don't believe we'd ever see
   330  			// runtime.asyncPreempt or runtime.debugCallV2 in a
   331  			// typical crash.
   332  			if prevSymbol == "runtime.sigpanic" {
   333  				pc++
   334  			}
   335  
   336  			if len(g.pcs) == 0 && currSymbol == "runtime.systemstack_switch" {
   337  				g.system = true
   338  			}
   339  			g.pcs = append(g.pcs, uintptr(pc))
   340  
   341  			// Done with this frame. Next line is a new frame.
   342  			prevSymbol = currSymbol
   343  			currSymbol = ""
   344  			symLine = true
   345  		}
   346  	}
   347  
   348  	if len(glist) == 0 {
   349  		return nil, nil
   350  	}
   351  
   352  	// The first goroutine in the dump is the one that crashed.
   353  	firstG := glist[0]
   354  
   355  	// If the first goroutine is g0 (the system stack), we want to find the user
   356  	// goroutine that called it, which will start with systemstack_switch.
   357  	if firstG.id == 0 {
   358  		for _, g := range glist[1:] {
   359  			if g.system && len(g.pcs) > 0 {
   360  				// Stitch the g0 stack and user stack (skipping systemstack_switch).
   361  				return append(firstG.pcs, g.pcs[1:]...), nil
   362  			}
   363  		}
   364  	}
   365  
   366  	return firstG.pcs, nil
   367  }
   368  

View as plain text