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/testenv"
    13  	"internal/trace"
    14  	tracev2 "internal/trace/v2"
    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  	t.Parallel()
    31  
    32  	exe, err := buildTestProg(t, "testprogcgo")
    33  	if err != nil {
    34  		t.Fatal(err)
    35  	}
    36  
    37  	wantLogs := []string{
    38  		"goCalledFromC",
    39  		"goCalledFromCThread",
    40  	}
    41  	logs := make(map[string]*trace.Event)
    42  	for _, category := range wantLogs {
    43  		logs[category] = nil
    44  	}
    45  	logsV2 := make(map[string]*tracev2.Event)
    46  	for _, category := range wantLogs {
    47  		logsV2[category] = nil
    48  	}
    49  	for _, tracefpunwindoff := range []int{1, 0} {
    50  		env := fmt.Sprintf("GODEBUG=tracefpunwindoff=%d", tracefpunwindoff)
    51  		got := runBuiltTestProg(t, exe, "Trace", env)
    52  		prefix, tracePath, found := strings.Cut(got, ":")
    53  		if !found || prefix != "trace path" {
    54  			t.Fatalf("unexpected output:\n%s\n", got)
    55  		}
    56  		defer os.Remove(tracePath)
    57  
    58  		traceData, err := os.ReadFile(tracePath)
    59  		if err != nil {
    60  			t.Fatalf("failed to read trace: %s", err)
    61  		}
    62  		for category := range logs {
    63  			event := mustFindLogV2(t, bytes.NewReader(traceData), category)
    64  			if wantEvent := logsV2[category]; wantEvent == nil {
    65  				logsV2[category] = &event
    66  			} else if got, want := dumpStackV2(&event), dumpStackV2(wantEvent); got != want {
    67  				t.Errorf("%q: got stack:\n%s\nwant stack:\n%s\n", category, got, want)
    68  			}
    69  		}
    70  	}
    71  }
    72  
    73  // mustFindLog returns the EvUserLog event with the given category in events. It
    74  // fails if no event or multiple events match the category.
    75  func mustFindLog(t *testing.T, events []*trace.Event, category string) *trace.Event {
    76  	t.Helper()
    77  	var candidates []*trace.Event
    78  	for _, e := range events {
    79  		if e.Type == trace.EvUserLog && len(e.SArgs) >= 1 && e.SArgs[0] == category {
    80  			candidates = append(candidates, e)
    81  		}
    82  	}
    83  	if len(candidates) == 0 {
    84  		t.Errorf("could not find log with category: %q", category)
    85  	} else if len(candidates) > 1 {
    86  		t.Errorf("found more than one log with category: %q", category)
    87  	}
    88  	return candidates[0]
    89  }
    90  
    91  // dumpStack returns e.Stk as a string.
    92  func dumpStack(e *trace.Event) string {
    93  	var buf bytes.Buffer
    94  	for _, f := range e.Stk {
    95  		file := strings.TrimPrefix(f.File, runtime.GOROOT())
    96  		fmt.Fprintf(&buf, "%s\n\t%s:%d\n", f.Fn, file, f.Line)
    97  	}
    98  	return buf.String()
    99  }
   100  
   101  // parseTrace parses the given trace or skips the test if the trace is broken
   102  // due to known issues. Partially copied from runtime/trace/trace_test.go.
   103  func parseTrace(t *testing.T, r io.Reader) []*trace.Event {
   104  	res, err := trace.Parse(r, "")
   105  	if err == trace.ErrTimeOrder {
   106  		t.Skipf("skipping trace: %v", err)
   107  	}
   108  	if err != nil {
   109  		t.Fatalf("failed to parse trace: %v", err)
   110  	}
   111  	return res.Events
   112  }
   113  
   114  func mustFindLogV2(t *testing.T, trace io.Reader, category string) tracev2.Event {
   115  	r, err := tracev2.NewReader(trace)
   116  	if err != nil {
   117  		t.Fatalf("bad trace: %v", err)
   118  	}
   119  	var candidates []tracev2.Event
   120  	for {
   121  		ev, err := r.ReadEvent()
   122  		if err == io.EOF {
   123  			break
   124  		}
   125  		if err != nil {
   126  			t.Fatalf("failed to parse trace: %v", err)
   127  		}
   128  		if ev.Kind() == tracev2.EventLog && ev.Log().Category == category {
   129  			candidates = append(candidates, ev)
   130  		}
   131  	}
   132  	if len(candidates) == 0 {
   133  		t.Fatalf("could not find log with category: %q", category)
   134  	} else if len(candidates) > 1 {
   135  		t.Fatalf("found more than one log with category: %q", category)
   136  	}
   137  	return candidates[0]
   138  }
   139  
   140  // dumpStack returns e.Stack() as a string.
   141  func dumpStackV2(e *tracev2.Event) string {
   142  	var buf bytes.Buffer
   143  	e.Stack().Frames(func(f tracev2.StackFrame) bool {
   144  		file := strings.TrimPrefix(f.File, runtime.GOROOT())
   145  		fmt.Fprintf(&buf, "%s\n\t%s:%d\n", f.Func, file, f.Line)
   146  		return true
   147  	})
   148  	return buf.String()
   149  }
   150  

View as plain text