Source file src/runtime/trace_cgo_test.go

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

View as plain text