mirror of https://go.googlesource.com/go
104 lines
2.6 KiB
Go
104 lines
2.6 KiB
Go
// Copyright 2023 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build cgo
|
|
|
|
package runtime_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"internal/testenv"
|
|
"internal/trace"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestTraceUnwindCGO verifies that trace events emitted in cgo callbacks
|
|
// produce the same stack traces and don't cause any crashes regardless of
|
|
// tracefpunwindoff being set to 0 or 1.
|
|
func TestTraceUnwindCGO(t *testing.T) {
|
|
if *flagQuick {
|
|
t.Skip("-quick")
|
|
}
|
|
testenv.MustHaveGoBuild(t)
|
|
t.Parallel()
|
|
|
|
exe, err := buildTestProg(t, "testprogcgo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
wantLogs := []string{
|
|
"goCalledFromC",
|
|
"goCalledFromCThread",
|
|
}
|
|
logs := make(map[string]*trace.Event)
|
|
for _, category := range wantLogs {
|
|
logs[category] = nil
|
|
}
|
|
for _, tracefpunwindoff := range []int{1, 0} {
|
|
env := fmt.Sprintf("GODEBUG=tracefpunwindoff=%d", tracefpunwindoff)
|
|
got := runBuiltTestProg(t, exe, "Trace", env)
|
|
prefix, tracePath, found := strings.Cut(got, ":")
|
|
if !found || prefix != "trace path" {
|
|
t.Fatalf("unexpected output:\n%s\n", got)
|
|
}
|
|
defer os.Remove(tracePath)
|
|
|
|
traceData, err := os.ReadFile(tracePath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read trace: %s", err)
|
|
}
|
|
for category := range logs {
|
|
event := mustFindLogV2(t, bytes.NewReader(traceData), category)
|
|
if wantEvent := logs[category]; wantEvent == nil {
|
|
logs[category] = &event
|
|
} else if got, want := dumpStackV2(&event), dumpStackV2(wantEvent); got != want {
|
|
t.Errorf("%q: got stack:\n%s\nwant stack:\n%s\n", category, got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustFindLogV2(t *testing.T, trc io.Reader, category string) trace.Event {
|
|
r, err := trace.NewReader(trc)
|
|
if err != nil {
|
|
t.Fatalf("bad trace: %v", err)
|
|
}
|
|
var candidates []trace.Event
|
|
for {
|
|
ev, err := r.ReadEvent()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("failed to parse trace: %v", err)
|
|
}
|
|
if ev.Kind() == trace.EventLog && ev.Log().Category == category {
|
|
candidates = append(candidates, ev)
|
|
}
|
|
}
|
|
if len(candidates) == 0 {
|
|
t.Fatalf("could not find log with category: %q", category)
|
|
} else if len(candidates) > 1 {
|
|
t.Fatalf("found more than one log with category: %q", category)
|
|
}
|
|
return candidates[0]
|
|
}
|
|
|
|
// dumpStack returns e.Stack() as a string.
|
|
func dumpStackV2(e *trace.Event) string {
|
|
var buf bytes.Buffer
|
|
e.Stack().Frames(func(f trace.StackFrame) bool {
|
|
file := strings.TrimPrefix(f.File, runtime.GOROOT())
|
|
fmt.Fprintf(&buf, "%s\n\t%s:%d\n", f.Func, file, f.Line)
|
|
return true
|
|
})
|
|
return buf.String()
|
|
}
|