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 cgo 6 7package runtime_test 8 9import ( 10 "bytes" 11 "fmt" 12 "internal/testenv" 13 "internal/trace" 14 "io" 15 "os" 16 "runtime" 17 "strings" 18 "testing" 19) 20 21// TestTraceUnwindCGO verifies that trace events emitted in cgo callbacks 22// produce the same stack traces and don't cause any crashes regardless of 23// tracefpunwindoff being set to 0 or 1. 24func TestTraceUnwindCGO(t *testing.T) { 25 if *flagQuick { 26 t.Skip("-quick") 27 } 28 testenv.MustHaveGoBuild(t) 29 t.Parallel() 30 31 exe, err := buildTestProg(t, "testprogcgo") 32 if err != nil { 33 t.Fatal(err) 34 } 35 36 wantLogs := []string{ 37 "goCalledFromC", 38 "goCalledFromCThread", 39 } 40 logs := make(map[string]*trace.Event) 41 for _, category := range wantLogs { 42 logs[category] = nil 43 } 44 for _, tracefpunwindoff := range []int{1, 0} { 45 env := fmt.Sprintf("GODEBUG=tracefpunwindoff=%d", tracefpunwindoff) 46 got := runBuiltTestProg(t, exe, "Trace", env) 47 prefix, tracePath, found := strings.Cut(got, ":") 48 if !found || prefix != "trace path" { 49 t.Fatalf("unexpected output:\n%s\n", got) 50 } 51 defer os.Remove(tracePath) 52 53 traceData, err := os.ReadFile(tracePath) 54 if err != nil { 55 t.Fatalf("failed to read trace: %s", err) 56 } 57 for category := range logs { 58 event := mustFindLogV2(t, bytes.NewReader(traceData), category) 59 if wantEvent := logs[category]; wantEvent == nil { 60 logs[category] = &event 61 } else if got, want := dumpStackV2(&event), dumpStackV2(wantEvent); got != want { 62 t.Errorf("%q: got stack:\n%s\nwant stack:\n%s\n", category, got, want) 63 } 64 } 65 } 66} 67 68func mustFindLogV2(t *testing.T, trc io.Reader, category string) trace.Event { 69 r, err := trace.NewReader(trc) 70 if err != nil { 71 t.Fatalf("bad trace: %v", err) 72 } 73 var candidates []trace.Event 74 for { 75 ev, err := r.ReadEvent() 76 if err == io.EOF { 77 break 78 } 79 if err != nil { 80 t.Fatalf("failed to parse trace: %v", err) 81 } 82 if ev.Kind() == trace.EventLog && ev.Log().Category == category { 83 candidates = append(candidates, ev) 84 } 85 } 86 if len(candidates) == 0 { 87 t.Fatalf("could not find log with category: %q", category) 88 } else if len(candidates) > 1 { 89 t.Fatalf("found more than one log with category: %q", category) 90 } 91 return candidates[0] 92} 93 94// dumpStack returns e.Stack() as a string. 95func dumpStackV2(e *trace.Event) string { 96 var buf bytes.Buffer 97 e.Stack().Frames(func(f trace.StackFrame) bool { 98 file := strings.TrimPrefix(f.File, runtime.GOROOT()) 99 fmt.Fprintf(&buf, "%s\n\t%s:%d\n", f.Func, file, f.Line) 100 return true 101 }) 102 return buf.String() 103} 104