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