Source file 
src/runtime/traceback_system_test.go
     1  
     2  
     3  
     4  
     5  package runtime_test
     6  
     7  
     8  
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"internal/testenv"
    14  	"io"
    15  	"os"
    16  	"path/filepath"
    17  	"reflect"
    18  	"runtime"
    19  	"runtime/debug"
    20  	"strconv"
    21  	"strings"
    22  	"testing"
    23  )
    24  
    25  
    26  
    27  func crashViaPanic() {
    28  	
    29  	debug.SetTraceback("system")
    30  	writeSentinel(os.Stdout)
    31  	debug.SetCrashOutput(os.Stdout, debug.CrashOptions{})
    32  
    33  	go func() {
    34  		
    35  		child1()
    36  	}()
    37  	select {}
    38  }
    39  
    40  
    41  
    42  func crashViaTrap() {
    43  	
    44  	debug.SetTraceback("system")
    45  	writeSentinel(os.Stdout)
    46  	debug.SetCrashOutput(os.Stdout, debug.CrashOptions{})
    47  
    48  	go func() {
    49  		
    50  		trap1()
    51  	}()
    52  	select {}
    53  }
    54  
    55  func child1() {
    56  	child2()
    57  }
    58  
    59  func child2() {
    60  	child3()
    61  }
    62  
    63  func child3() {
    64  	child4()
    65  }
    66  
    67  func child4() {
    68  	child5()
    69  }
    70  
    71  
    72  func child5() { 
    73  	child6bad()
    74  	child6() 
    75  }
    76  
    77  
    78  func child6bad() {
    79  }
    80  
    81  
    82  func child6() { 
    83  	child7() 
    84  	child7bad()
    85  }
    86  
    87  
    88  func child7bad() {
    89  }
    90  
    91  
    92  func child7() {
    93  	
    94  	var pcs [16]uintptr
    95  	n := runtime.Callers(1, pcs[:])
    96  	fmt.Fprintf(os.Stderr, "Callers: %#x\n", pcs[:n])
    97  	io.WriteString(os.Stderr, formatStack(pcs[:n]))
    98  
    99  	
   100  	panic("oops")
   101  }
   102  
   103  func trap1() {
   104  	trap2()
   105  }
   106  
   107  var sinkPtr *int
   108  
   109  func trap2() {
   110  	trap3(sinkPtr)
   111  }
   112  
   113  func trap3(i *int) {
   114  	*i = 42
   115  }
   116  
   117  
   118  
   119  
   120  
   121  
   122  
   123  
   124  
   125  
   126  func TestTracebackSystem(t *testing.T) {
   127  	testenv.MustHaveExec(t)
   128  	if runtime.GOOS == "android" {
   129  		t.Skip("Can't read source code for this file on Android")
   130  	}
   131  
   132  	tests := []struct {
   133  		name string
   134  		want string
   135  	}{
   136  		{
   137  			name: "panic",
   138  			want: `redacted.go:0: runtime.gopanic
   139  traceback_system_test.go:100: runtime_test.child7: 	panic("oops")
   140  traceback_system_test.go:83: runtime_test.child6: 	child7() // appears in stack trace
   141  traceback_system_test.go:74: runtime_test.child5: 	child6() // appears in stack trace
   142  traceback_system_test.go:68: runtime_test.child4: 	child5()
   143  traceback_system_test.go:64: runtime_test.child3: 	child4()
   144  traceback_system_test.go:60: runtime_test.child2: 	child3()
   145  traceback_system_test.go:56: runtime_test.child1: 	child2()
   146  traceback_system_test.go:35: runtime_test.crashViaPanic.func1: 		child1()
   147  redacted.go:0: runtime.goexit
   148  `,
   149  		},
   150  		{
   151  			
   152  			
   153  			
   154  			
   155  			name: "trap",
   156  			want: `redacted.go:0: runtime.gopanic
   157  redacted.go:0: runtime.panicmem
   158  redacted.go:0: runtime.sigpanic
   159  traceback_system_test.go:114: runtime_test.trap3: 	*i = 42
   160  traceback_system_test.go:110: runtime_test.trap2: 	trap3(sinkPtr)
   161  traceback_system_test.go:104: runtime_test.trap1: 	trap2()
   162  traceback_system_test.go:50: runtime_test.crashViaTrap.func1: 		trap1()
   163  redacted.go:0: runtime.goexit
   164  `,
   165  		},
   166  	}
   167  
   168  	for _, tc := range tests {
   169  		t.Run(tc.name, func(t *testing.T) {
   170  			
   171  			exe, err := os.Executable()
   172  			if err != nil {
   173  				t.Fatal(err)
   174  			}
   175  			cmd := testenv.Command(t, exe)
   176  			cmd.Env = append(cmd.Environ(), entrypointVar+"="+tc.name)
   177  			var stdout, stderr bytes.Buffer
   178  			cmd.Stdout = &stdout
   179  			cmd.Stderr = &stderr
   180  			cmd.Run() 
   181  			t.Logf("stderr:\n%s\nstdout: %s\n", stderr.Bytes(), stdout.Bytes())
   182  			crash := stdout.String()
   183  
   184  			
   185  			if strings.Count(crash, "\n") < 2 {
   186  				t.Fatalf("child process did not produce a crash report")
   187  			}
   188  
   189  			
   190  			pcs, err := parseStackPCs(crash)
   191  			if err != nil {
   192  				t.Fatal(err)
   193  			}
   194  
   195  			
   196  			got := formatStack(pcs)
   197  			if strings.TrimSpace(got) != strings.TrimSpace(tc.want) {
   198  				t.Errorf("got:\n%swant:\n%s", got, tc.want)
   199  			}
   200  		})
   201  	}
   202  }
   203  
   204  
   205  
   206  
   207  
   208  
   209  
   210  
   211  
   212  
   213  
   214  func parseStackPCs(crash string) ([]uintptr, error) {
   215  	
   216  	
   217  	
   218  	
   219  	
   220  	
   221  	
   222  	
   223  	
   224  	
   225  	
   226  	
   227  	
   228  	getSymbol := func(line string) (string, error) {
   229  		var prev rune
   230  		for i, c := range line {
   231  			if line[i] != '(' {
   232  				prev = c
   233  				continue
   234  			}
   235  			if prev == '.' {
   236  				prev = c
   237  				continue
   238  			}
   239  			return line[:i], nil
   240  		}
   241  		return "", fmt.Errorf("no symbol for stack frame: %s", line)
   242  	}
   243  
   244  	
   245  	
   246  	getPC := func(line string) (uint64, error) {
   247  		_, pcstr, ok := strings.Cut(line, " pc=") 
   248  		if !ok {
   249  			return 0, fmt.Errorf("no pc= for stack frame: %s", line)
   250  		}
   251  		return strconv.ParseUint(pcstr, 0, 64) 
   252  	}
   253  
   254  	var (
   255  		pcs            []uintptr
   256  		parentSentinel uint64
   257  		childSentinel  = sentinel()
   258  		on             = false 
   259  		lines          = strings.Split(crash, "\n")
   260  		symLine        = true 
   261  		currSymbol     string
   262  		prevSymbol     string 
   263  	)
   264  	for i := 0; i < len(lines); i++ {
   265  		line := lines[i]
   266  
   267  		
   268  		if parentSentinel == 0 && strings.HasPrefix(line, "sentinel ") {
   269  			_, err := fmt.Sscanf(line, "sentinel %x", &parentSentinel)
   270  			if err != nil {
   271  				return nil, fmt.Errorf("can't read sentinel line")
   272  			}
   273  			continue
   274  		}
   275  
   276  		
   277  		if !on {
   278  			if strings.HasPrefix(line, "goroutine ") &&
   279  				strings.Contains(line, " [running]:") {
   280  				on = true
   281  
   282  				if parentSentinel == 0 {
   283  					return nil, fmt.Errorf("no sentinel value in crash report")
   284  				}
   285  			}
   286  			continue
   287  		}
   288  
   289  		
   290  		if line == "" {
   291  			break
   292  		}
   293  
   294  		
   295  		if strings.HasPrefix(line, "created by ") {
   296  			break
   297  		}
   298  
   299  		
   300  		
   301  		
   302  		
   303  		
   304  
   305  		if symLine {
   306  			var err error
   307  			currSymbol, err = getSymbol(line)
   308  			if err != nil {
   309  				return nil, fmt.Errorf("error extracting symbol: %v", err)
   310  			}
   311  
   312  			symLine = false 
   313  		} else {
   314  			
   315  			
   316  			pc, err := getPC(line)
   317  			if err != nil {
   318  				
   319  
   320  				
   321  				
   322  				
   323  				
   324  				currSymbol = ""
   325  				symLine = true
   326  				continue
   327  			}
   328  
   329  			pc = pc - parentSentinel + childSentinel
   330  
   331  			
   332  			
   333  			
   334  			
   335  			
   336  			
   337  			
   338  			
   339  			
   340  			
   341  			
   342  			
   343  			
   344  			
   345  			
   346  			
   347  			
   348  			
   349  			
   350  			
   351  			
   352  			
   353  			
   354  			
   355  			
   356  			
   357  			
   358  			
   359  			
   360  			
   361  			
   362  			
   363  			
   364  			if prevSymbol == "runtime.sigpanic" {
   365  				pc++
   366  			}
   367  
   368  			pcs = append(pcs, uintptr(pc))
   369  
   370  			
   371  			prevSymbol = currSymbol
   372  			currSymbol = ""
   373  			symLine = true
   374  		}
   375  	}
   376  	return pcs, nil
   377  }
   378  
   379  
   380  
   381  
   382  
   383  
   384  
   385  func sentinel() uint64 {
   386  	return uint64(reflect.ValueOf(sentinel).Pointer())
   387  }
   388  
   389  func writeSentinel(out io.Writer) {
   390  	fmt.Fprintf(out, "sentinel %x\n", sentinel())
   391  }
   392  
   393  
   394  
   395  func formatStack(pcs []uintptr) string {
   396  	
   397  	const debug = false
   398  
   399  	var buf strings.Builder
   400  	i := 0
   401  	frames := runtime.CallersFrames(pcs)
   402  	for {
   403  		fr, more := frames.Next()
   404  		if debug {
   405  			fmt.Fprintf(&buf, "pc=%x ", pcs[i])
   406  			i++
   407  		}
   408  		if base := filepath.Base(fr.File); base == "traceback_system_test.go" || debug {
   409  			content, err := os.ReadFile(fr.File)
   410  			if err != nil {
   411  				panic(err)
   412  			}
   413  			lines := bytes.Split(content, []byte("\n"))
   414  			fmt.Fprintf(&buf, "%s:%d: %s: %s\n", base, fr.Line, fr.Function, lines[fr.Line-1])
   415  		} else {
   416  			
   417  			fmt.Fprintf(&buf, "redacted.go:0: %s\n", fr.Function)
   418  		}
   419  
   420  		if !more {
   421  			break
   422  		}
   423  	}
   424  	return buf.String()
   425  }
   426  
View as plain text