diff options
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/export_test.go | 10 | ||||
| -rw-r--r-- | src/runtime/metrics/doc.go | 5 | ||||
| -rw-r--r-- | src/runtime/runtime-gdb_test.go | 3 | ||||
| -rw-r--r-- | src/runtime/symtab.go | 5 | ||||
| -rw-r--r-- | src/runtime/sys_linux_s390x.s | 4 | ||||
| -rw-r--r-- | src/runtime/testdata/testprognet/signalexec.go | 43 | ||||
| -rw-r--r-- | src/runtime/trace.go | 2 | ||||
| -rw-r--r-- | src/runtime/traceevent.go | 2 | ||||
| -rw-r--r-- | src/runtime/tracestack.go | 8 | ||||
| -rw-r--r-- | src/runtime/tracestack_test.go | 46 |
10 files changed, 118 insertions, 10 deletions
diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index b3bb5d2c58..81542deb59 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -1919,3 +1919,13 @@ const ( BubbleAssocCurrentBubble = bubbleAssocCurrentBubble BubbleAssocOtherBubble = bubbleAssocOtherBubble ) + +type TraceStackTable traceStackTable + +func (t *TraceStackTable) Reset() { + t.tab.reset() +} + +func TraceStack(gp *G, tab *TraceStackTable) { + traceStack(0, gp, (*traceStackTable)(tab)) +} diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 32fc436e1a..a1902bc6d7 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -230,6 +230,11 @@ Below is the full list of supported metrics, ordered lexicographically. /gc/stack/starting-size:bytes The stack size of new goroutines. + /godebug/non-default-behavior/allowmultiplevcs:events + The number of non-default behaviors executed by the cmd/go + package due to a non-default GODEBUG=allowmultiplevcs=... + setting. + /godebug/non-default-behavior/asynctimerchan:events The number of non-default behaviors executed by the time package due to a non-default GODEBUG=asynctimerchan=... setting. diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index 19ad29c127..47c1fe5851 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -78,6 +78,9 @@ func checkGdbVersion(t *testing.T) { if major < 10 { t.Skipf("skipping: gdb version %d.%d too old", major, minor) } + if major < 12 || (major == 12 && minor < 1) { + t.Logf("gdb version <12.1 is known to crash due to a SIGWINCH recieved in non-interactive mode; if you see a crash, some test may be sending SIGWINCH to the whole process group. See go.dev/issue/58932.") + } t.Logf("gdb version %d.%d", major, minor) } diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index 8c6ef2b4fc..866c46a83d 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -981,6 +981,9 @@ func pcvalue(f funcInfo, off uint32, targetpc uintptr, strict bool) (int32, uint // matches the cached contents. const debugCheckCache = false + // If true, skip checking the cache entirely. + const skipCache = false + if off == 0 { return -1, 0 } @@ -991,7 +994,7 @@ func pcvalue(f funcInfo, off uint32, targetpc uintptr, strict bool) (int32, uint var checkVal int32 var checkPC uintptr ck := pcvalueCacheKey(targetpc) - { + if !skipCache { mp := acquirem() cache := &mp.pcvalueCache // The cache can be used by the signal handler on this M. Avoid diff --git a/src/runtime/sys_linux_s390x.s b/src/runtime/sys_linux_s390x.s index 2f9d4beda8..a3472a4508 100644 --- a/src/runtime/sys_linux_s390x.s +++ b/src/runtime/sys_linux_s390x.s @@ -226,7 +226,7 @@ TEXT runtime·walltime(SB),NOSPLIT,$32-12 MOVD R4, 24(R15) MOVD R14, R8 // Backup return address - MOVD $sec+0(FP), R4 // return parameter caller + MOVD $ret-8(FP), R4 // caller's SP MOVD R8, m_vdsoPC(R6) MOVD R4, m_vdsoSP(R6) @@ -312,7 +312,7 @@ TEXT runtime·nanotime1(SB),NOSPLIT,$32-8 MOVD R4, 24(R15) MOVD R14, R8 // Backup return address - MOVD $ret+0(FP), R4 // caller's SP + MOVD $ret-8(FP), R4 // caller's SP MOVD R8, m_vdsoPC(R6) MOVD R4, m_vdsoSP(R6) diff --git a/src/runtime/testdata/testprognet/signalexec.go b/src/runtime/testdata/testprognet/signalexec.go index 62ebce7176..7e7591e8f7 100644 --- a/src/runtime/testdata/testprognet/signalexec.go +++ b/src/runtime/testdata/testprognet/signalexec.go @@ -13,9 +13,11 @@ package main import ( "fmt" + "io" "os" "os/exec" "os/signal" + "runtime" "sync" "syscall" "time" @@ -23,10 +25,51 @@ import ( func init() { register("SignalDuringExec", SignalDuringExec) + register("SignalDuringExecPgrp", SignalDuringExecPgrp) register("Nop", Nop) } func SignalDuringExec() { + // Re-launch ourselves in a new process group. + cmd := exec.Command(os.Args[0], "SignalDuringExecPgrp") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + // Start the new process with an extra pipe. It will + // exit if the pipe is closed. + rp, wp, err := os.Pipe() + if err != nil { + fmt.Printf("Failed to create pipe: %v", err) + return + } + cmd.ExtraFiles = []*os.File{rp} + + // Run the command. + if err := cmd.Run(); err != nil { + fmt.Printf("Run failed: %v", err) + } + + // We don't actually need to write to the pipe, it just + // needs to get closed, which will happen on process + // exit. + runtime.KeepAlive(wp) +} + +func SignalDuringExecPgrp() { + // Grab fd 3 which is a pipe we need to read on. + f := os.NewFile(3, "pipe") + go func() { + // Nothing will ever get written to the pipe, so we'll + // just block on it. If it closes, ReadAll will return + // one way or another, at which point we'll exit. + io.ReadAll(f) + os.Exit(1) + }() + + // This is just for SignalDuringExec. pgrp := syscall.Getpgrp() const tries = 10 diff --git a/src/runtime/trace.go b/src/runtime/trace.go index b92e7b4e8e..0d71ad445c 100644 --- a/src/runtime/trace.go +++ b/src/runtime/trace.go @@ -396,7 +396,7 @@ func traceAdvance(stopTrace bool) { ug.status = readgstatus(s.g) &^ _Gscan ug.waitreason = s.g.waitreason ug.inMarkAssist = s.g.inMarkAssist - ug.stackID = traceStack(0, gp, gen) + ug.stackID = traceStack(0, gp, &trace.stackTab[gen%2]) } resumeG(s) casgstatus(me, _Gwaiting, _Grunning) diff --git a/src/runtime/traceevent.go b/src/runtime/traceevent.go index 9d1a93d3f9..263847be2e 100644 --- a/src/runtime/traceevent.go +++ b/src/runtime/traceevent.go @@ -56,7 +56,7 @@ func (e traceEventWriter) event(ev tracev2.EventType, args ...traceArg) { // It then returns a traceArg representing that stack which may be // passed to write. func (tl traceLocker) stack(skip int) traceArg { - return traceArg(traceStack(skip, nil, tl.gen)) + return traceArg(traceStack(skip, nil, &trace.stackTab[tl.gen%2])) } // startPC takes a start PC for a goroutine and produces a unique diff --git a/src/runtime/tracestack.go b/src/runtime/tracestack.go index 2ee68c85f0..76d6b05048 100644 --- a/src/runtime/tracestack.go +++ b/src/runtime/tracestack.go @@ -28,10 +28,8 @@ const ( // skip controls the number of leaf frames to omit in order to hide tracer internals // from stack traces, see CL 5523. // -// Avoid calling this function directly. gen needs to be the current generation -// that this stack trace is being written out for, which needs to be synchronized with -// generations moving forward. Prefer traceEventWriter.stack. -func traceStack(skip int, gp *g, gen uintptr) uint64 { +// Avoid calling this function directly. Prefer traceEventWriter.stack. +func traceStack(skip int, gp *g, tab *traceStackTable) uint64 { var pcBuf [tracev2.MaxFramesPerStack]uintptr // Figure out gp and mp for the backtrace. @@ -134,7 +132,7 @@ func traceStack(skip int, gp *g, gen uintptr) uint64 { if nstk > 0 && gp.goid == 1 { nstk-- // skip runtime.main } - id := trace.stackTab[gen%2].put(pcBuf[:nstk]) + id := tab.put(pcBuf[:nstk]) return id } diff --git a/src/runtime/tracestack_test.go b/src/runtime/tracestack_test.go new file mode 100644 index 0000000000..eaf4d906e3 --- /dev/null +++ b/src/runtime/tracestack_test.go @@ -0,0 +1,46 @@ +// Copyright 2025 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. + +package runtime_test + +import ( + "runtime" + "strconv" + "testing" +) + +func BenchmarkTraceStack(b *testing.B) { + for _, stackDepth := range []int{1, 10, 100} { + b.Run("stackDepth="+strconv.Itoa(stackDepth), func(b *testing.B) { + benchmarkTraceStack(b, stackDepth) + }) + } +} + +func benchmarkTraceStack(b *testing.B, stackDepth int) { + var tab runtime.TraceStackTable + defer tab.Reset() + + wait := make(chan struct{}) + ready := make(chan struct{}) + done := make(chan struct{}) + var gp *runtime.G + go func() { + gp = runtime.Getg() + useStackAndCall(stackDepth, func() { + ready <- struct{}{} + <-wait + }) + done <- struct{}{} + }() + <-ready + + for b.Loop() { + runtime.TraceStack(gp, &tab) + } + + // Clean up. + wait <- struct{}{} + <-done +} |
