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 crash() {
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 func child1() {
41 child2()
42 }
43
44 func child2() {
45 child3()
46 }
47
48 func child3() {
49 child4()
50 }
51
52 func child4() {
53 child5()
54 }
55
56
57 func child5() {
58 child6bad()
59 child6()
60 }
61
62
63 func child6bad() {
64 }
65
66
67 func child6() {
68 child7()
69 child7bad()
70 }
71
72
73 func child7bad() {
74 }
75
76
77 func child7() {
78
79 var pcs [16]uintptr
80 n := runtime.Callers(1, pcs[:])
81 fmt.Fprintf(os.Stderr, "Callers: %#x\n", pcs[:n])
82 io.WriteString(os.Stderr, formatStack(pcs[:n]))
83
84
85 panic("oops")
86 }
87
88
89
90
91
92
93
94
95
96
97 func TestTracebackSystem(t *testing.T) {
98 testenv.MustHaveExec(t)
99 if runtime.GOOS == "android" {
100 t.Skip("Can't read source code for this file on Android")
101 }
102
103
104 exe, err := os.Executable()
105 if err != nil {
106 t.Fatal(err)
107 }
108 cmd := testenv.Command(t, exe)
109 cmd.Env = append(cmd.Environ(), entrypointVar+"=crash")
110 var stdout, stderr bytes.Buffer
111 cmd.Stdout = &stdout
112 cmd.Stderr = &stderr
113 cmd.Run()
114 t.Logf("stderr:\n%s\nstdout: %s\n", stderr.Bytes(), stdout.Bytes())
115 crash := stdout.String()
116
117
118 if strings.Count(crash, "\n") < 2 {
119 t.Fatalf("child process did not produce a crash report")
120 }
121
122
123 pcs, err := parseStackPCs(crash)
124 if err != nil {
125 t.Fatal(err)
126 }
127
128
129 got := formatStack(pcs)
130 want := `redacted.go:0: runtime.gopanic
131 traceback_system_test.go:85: runtime_test.child7: panic("oops")
132 traceback_system_test.go:68: runtime_test.child6: child7() // appears in stack trace
133 traceback_system_test.go:59: runtime_test.child5: child6() // appears in stack trace
134 traceback_system_test.go:53: runtime_test.child4: child5()
135 traceback_system_test.go:49: runtime_test.child3: child4()
136 traceback_system_test.go:45: runtime_test.child2: child3()
137 traceback_system_test.go:41: runtime_test.child1: child2()
138 traceback_system_test.go:35: runtime_test.crash.func1: child1()
139 redacted.go:0: runtime.goexit
140 `
141 if strings.TrimSpace(got) != strings.TrimSpace(want) {
142 t.Errorf("got:\n%swant:\n%s", got, want)
143 }
144 }
145
146
147
148
149
150
151
152
153
154
155
156 func parseStackPCs(crash string) ([]uintptr, error) {
157
158
159 getPC := func(line string) (uint64, error) {
160 _, pcstr, ok := strings.Cut(line, " pc=")
161 if !ok {
162 return 0, fmt.Errorf("no pc= for stack frame: %s", line)
163 }
164 return strconv.ParseUint(pcstr, 0, 64)
165 }
166
167 var (
168 pcs []uintptr
169 parentSentinel uint64
170 childSentinel = sentinel()
171 on = false
172 lines = strings.Split(crash, "\n")
173 )
174 for i := 0; i < len(lines); i++ {
175 line := lines[i]
176
177
178 if parentSentinel == 0 && strings.HasPrefix(line, "sentinel ") {
179 _, err := fmt.Sscanf(line, "sentinel %x", &parentSentinel)
180 if err != nil {
181 return nil, fmt.Errorf("can't read sentinel line")
182 }
183 continue
184 }
185
186
187 if !on {
188 if strings.HasPrefix(line, "goroutine ") &&
189 strings.Contains(line, " [running]:") {
190 on = true
191
192 if parentSentinel == 0 {
193 return nil, fmt.Errorf("no sentinel value in crash report")
194 }
195 }
196 continue
197 }
198
199
200 if line == "" {
201 break
202 }
203
204
205 if strings.HasPrefix(line, "created by ") {
206 break
207 }
208
209
210
211
212
213
214
215
216 i++
217 if i == len(lines) {
218 break
219 }
220 line = lines[i]
221
222
223
224 pc, err := getPC(line)
225 if err != nil {
226
227 continue
228 }
229 pcs = append(pcs, uintptr(pc-parentSentinel+childSentinel))
230 }
231 return pcs, nil
232 }
233
234
235
236
237
238
239
240 func sentinel() uint64 {
241 return uint64(reflect.ValueOf(sentinel).Pointer())
242 }
243
244 func writeSentinel(out io.Writer) {
245 fmt.Fprintf(out, "sentinel %x\n", sentinel())
246 }
247
248
249
250 func formatStack(pcs []uintptr) string {
251
252 const debug = false
253
254 var buf strings.Builder
255 i := 0
256 frames := runtime.CallersFrames(pcs)
257 for {
258 fr, more := frames.Next()
259 if debug {
260 fmt.Fprintf(&buf, "pc=%x ", pcs[i])
261 i++
262 }
263 if base := filepath.Base(fr.File); base == "traceback_system_test.go" || debug {
264 content, err := os.ReadFile(fr.File)
265 if err != nil {
266 panic(err)
267 }
268 lines := bytes.Split(content, []byte("\n"))
269 fmt.Fprintf(&buf, "%s:%d: %s: %s\n", base, fr.Line, fr.Function, lines[fr.Line-1])
270 } else {
271
272 fmt.Fprintf(&buf, "redacted.go:0: %s\n", fr.Function)
273 }
274
275 if !more {
276 break
277 }
278 }
279 return buf.String()
280 }
281
View as plain text