Skip to content

Commit 06ef541

Browse files
adonovangopherbot
authored andcommitted
internal/counter: record program counters in stack counter names
The frame-relative PC is appended to each stack frame. Also, deprecate Supported() function, which since go1.23 always returns true. Fixes golang/go#73268 Change-Id: If8959d0042ff6753aa1f4b58fbc1484b74db5ad8 Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/664175 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Alan Donovan <[email protected]>
1 parent dbf0ff6 commit 06ef541

File tree

7 files changed

+68
-112
lines changed

7 files changed

+68
-112
lines changed

crashmonitor/supported.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
package crashmonitor
66

7-
import ic "golang.org/x/telemetry/internal/crashmonitor"
8-
9-
// Supported reports whether the runtime supports [runtime.SetCrashOutput].
7+
// Deprecated: Supported returns true.
108
//
11-
// TODO(adonovan): eliminate once go1.23+ is assured.
12-
func Supported() bool { return ic.Supported() }
9+
//go:fix inline
10+
func Supported() bool { return true }

internal/counter/stackcounter.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,22 @@ func EncodeStack(pcs []uintptr, prefix string) string {
9393
// Use function-relative line numbering.
9494
// f:+2 means two lines into function f.
9595
// f:-1 should never happen, but be conservative.
96+
//
97+
// An inlined call is replaced by a NOP instruction
98+
// with the correct pclntab information.
9699
_, entryLine := fr.Func.FileLine(fr.Entry)
97-
loc = fmt.Sprintf("%s.%s:%+d", path, fname, fr.Line-entryLine)
100+
loc = fmt.Sprintf("%s.%s:%+d,+0x%x", path, fname, fr.Line-entryLine, fr.PC-fr.Entry)
98101
} else {
99102
// The function is non-Go code or is fully inlined:
100103
// use absolute line number within enclosing file.
101-
loc = fmt.Sprintf("%s.%s:=%d", path, fname, fr.Line)
104+
//
105+
// For inlined calls, the PC and Entry values
106+
// both refer to the enclosing combined function.
107+
// For example, both these PCs are relative to "caller":
108+
//
109+
// callee:=1,+0x12 ('=' means inlined)
110+
// caller:+2,+0x34
111+
loc = fmt.Sprintf("%s.%s:=%d,+0x%x", path, fname, fr.Line, fr.PC-fr.Entry)
102112
}
103113
locs = append(locs, loc)
104114
if !more {

internal/crashmonitor/crash_go123.go

-16
This file was deleted.

internal/crashmonitor/monitor.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,13 @@ import (
2121
"golang.org/x/telemetry/internal/counter"
2222
)
2323

24-
// Supported reports whether the runtime supports [runtime/debug.SetCrashOutput].
25-
//
26-
// TODO(adonovan): eliminate once go1.23+ is assured.
27-
func Supported() bool { return setCrashOutput != nil }
28-
29-
var setCrashOutput func(*os.File) error // = runtime/debug.SetCrashOutput on go1.23+
30-
3124
// Parent sets up the parent side of the crashmonitor. It requires
3225
// exclusive use of a writable pipe connected to the child process's stdin.
3326
func Parent(pipe *os.File) {
3427
writeSentinel(pipe)
3528
// Ensure that we get pc=0x%x values in the traceback.
3629
debug.SetTraceback("system")
37-
setCrashOutput(pipe)
30+
debug.SetCrashOutput(pipe, debug.CrashOptions{}) // ignore error
3831
}
3932

4033
// Child runs the part of the crashmonitor that runs in the child process.
@@ -284,7 +277,7 @@ func parseStackPCs(crash string) ([]uintptr, error) {
284277
continue
285278
}
286279

287-
pc = pc-parentSentinel+childSentinel
280+
pc = pc - parentSentinel + childSentinel
288281

289282
// If the previous frame was sigpanic, then this frame
290283
// was a trap (e.g., SIGSEGV).

internal/crashmonitor/monitor_test.go

+48-68
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"os/exec"
1212
"path/filepath"
13+
"regexp"
1314
"runtime/debug"
1415
"strings"
1516
"testing"
@@ -21,6 +22,11 @@ import (
2122
"golang.org/x/telemetry/internal/testenv"
2223
)
2324

25+
//
26+
// Add/removed lines from this comment to compensate for minor
27+
// perturbations in line numbers as the code below evolves.
28+
//
29+
2430
func TestMain(m *testing.M) {
2531
entry := os.Getenv("CRASHMONITOR_TEST_ENTRYPOINT")
2632
switch entry {
@@ -76,7 +82,7 @@ func childPanic() {
7682
}
7783

7884
func grandchildPanic() {
79-
panic("oops") // this line is "grandchildPanic:=79" (the call from child is inlined)
85+
panic("oops") // this line is "grandchildPanic:=85" (the call from child is inlined)
8086
}
8187

8288
var sinkPtr *int
@@ -87,7 +93,7 @@ func childTrap() {
8793
}
8894

8995
func grandchildTrap(i *int) {
90-
*i = 42 // this line is "grandchildTrap:=90" (the call from childTrap is inlined)
96+
*i = 42 // this line is "grandchildTrap:=96" (the call from childTrap is inlined)
9197
}
9298

9399
// TestViaStderr is an internal test that asserts that the telemetry
@@ -102,30 +108,16 @@ func TestViaStderr(t *testing.T) {
102108
t.Fatal(err)
103109
}
104110
got = sanitize(counter.DecodeStack(got))
105-
want := "crash/crash\n" +
106-
"runtime.gopanic:--\n" +
107-
"golang.org/x/telemetry/internal/crashmonitor_test.grandchildPanic:=79\n" +
108-
"golang.org/x/telemetry/internal/crashmonitor_test.childPanic:+2\n" +
109-
"golang.org/x/telemetry/internal/crashmonitor_test.TestMain:+10\n" +
110-
"main.main:--\n" +
111-
"runtime.main:--\n" +
112-
"runtime.goexit:--"
113-
114-
if !crashmonitor.Supported() { // !go1.23
115-
// Traceback excludes PCs for inlined frames. Before
116-
// go1.23 (https://go.dev/cl/571798 specifically),
117-
// passing the set of PCs in the traceback to
118-
// runtime.CallersFrames, would report only the
119-
// innermost inlined frame and none of the inline
120-
// "callers".
121-
//
122-
// Thus, here we must drop the caller of the inlined
123-
// frame.
124-
want = strings.ReplaceAll(want, "golang.org/x/telemetry/internal/crashmonitor_test.childPanic:+2\n", "")
125-
}
126-
127-
if got != want {
128-
t.Errorf("got counter name <<%s>>, want <<%s>>", got, want)
111+
wantRE := regexp.MustCompile(`(?m)crash/crash
112+
runtime.gopanic:--
113+
golang.org/x/telemetry/internal/crashmonitor_test\.grandchildPanic:=85,\+0x.*
114+
golang.org/x/telemetry/internal/crashmonitor_test\.childPanic:\+2,\+0x.*
115+
golang.org/x/telemetry/internal/crashmonitor_test\.TestMain:\+10,\+0x.*
116+
main.main:--
117+
runtime.main:--
118+
runtime.goexit:--`)
119+
if !wantRE.MatchString(got) {
120+
t.Errorf("got counter name <<%s>>, want match for <<%s>>", got, wantRE)
129121
}
130122
})
131123

@@ -137,25 +129,18 @@ func TestViaStderr(t *testing.T) {
137129
t.Fatal(err)
138130
}
139131
got = sanitize(counter.DecodeStack(got))
140-
want := "crash/crash\n" +
141-
"runtime.gopanic:--\n" +
142-
"runtime.panicmem:--\n" +
143-
"runtime.sigpanic:--\n" +
144-
"golang.org/x/telemetry/internal/crashmonitor_test.grandchildTrap:=90\n" +
145-
"golang.org/x/telemetry/internal/crashmonitor_test.childTrap:+2\n" +
146-
"golang.org/x/telemetry/internal/crashmonitor_test.TestMain:+12\n" +
147-
"main.main:--\n" +
148-
"runtime.main:--\n" +
149-
"runtime.goexit:--"
150-
151-
if !crashmonitor.Supported() { // !go1.23
152-
// See above.
153-
want = strings.ReplaceAll(want, "runtime.sigpanic:--\n", "")
154-
want = strings.ReplaceAll(want, "golang.org/x/telemetry/internal/crashmonitor_test.childTrap:+2\n", "")
155-
}
156-
157-
if got != want {
158-
t.Errorf("got counter name <<%s>>, want <<%s>>", got, want)
132+
wantRE := regexp.MustCompile(`(?m)crash/crash
133+
runtime.gopanic:--
134+
runtime.panicmem:--
135+
runtime.sigpanic:--
136+
golang.org/x/telemetry/internal/crashmonitor_test.grandchildTrap:=96,\+0x.*
137+
golang.org/x/telemetry/internal/crashmonitor_test.childTrap:\+2,\+0x.*
138+
golang.org/x/telemetry/internal/crashmonitor_test.TestMain:\+10,\+0x.*
139+
main.main:--
140+
runtime.main:--
141+
runtime.goexit:--`)
142+
if wantRE.MatchString(got) {
143+
t.Errorf("got counter name <<%s>>, want match for <<%s>>", got, wantRE)
159144
}
160145
})
161146
}
@@ -181,14 +166,9 @@ func waitForExitFile(t *testing.T, exitFile string) {
181166
}
182167

183168
// TestStart is an integration test of the crashmonitor feature of [telemetry.Start].
184-
// Requires go1.23+.
185169
func TestStart(t *testing.T) {
186170
testenv.SkipIfUnsupportedPlatform(t)
187171

188-
if !crashmonitor.Supported() {
189-
t.Skip("crashmonitor not supported")
190-
}
191-
192172
// Assert that the crash monitor does nothing when the child
193173
// process merely exits.
194174
t.Run("exit", func(t *testing.T) {
@@ -211,14 +191,14 @@ func TestStart(t *testing.T) {
211191
t.Fatalf("failed to read file: %v", err)
212192
}
213193
got := sanitize(counter.DecodeStack(string(data)))
214-
want := "crash/crash\n" +
215-
"runtime.gopanic:--\n" +
216-
"golang.org/x/telemetry/internal/crashmonitor_test.grandchildPanic:=79\n" +
217-
"golang.org/x/telemetry/internal/crashmonitor_test.childPanic:+2\n" +
218-
"golang.org/x/telemetry/internal/crashmonitor_test.TestMain.func3:+1\n" +
219-
"runtime.goexit:--"
220-
if got != want {
221-
t.Errorf("got counter name <<%s>>, want <<%s>>", got, want)
194+
wantRE := regexp.MustCompile(`(?m)crash/crash
195+
runtime.gopanic:--
196+
golang.org/x/telemetry/internal/crashmonitor_test.grandchildPanic:=85,.*
197+
golang.org/x/telemetry/internal/crashmonitor_test.childPanic:\+2,.*
198+
golang.org/x/telemetry/internal/crashmonitor_test.TestMain.func3:\+1,.*
199+
runtime.goexit:--`)
200+
if !wantRE.MatchString(got) {
201+
t.Errorf("got counter name <<%s>>, want match for <<%s>>", got, wantRE)
222202
}
223203
})
224204

@@ -233,16 +213,16 @@ func TestStart(t *testing.T) {
233213
t.Fatalf("failed to read file: %v", err)
234214
}
235215
got := sanitize(counter.DecodeStack(string(data)))
236-
want := "crash/crash\n" +
237-
"runtime.gopanic:--\n" +
238-
"runtime.panicmem:--\n" +
239-
"runtime.sigpanic:--\n" +
240-
"golang.org/x/telemetry/internal/crashmonitor_test.grandchildTrap:=90\n" +
241-
"golang.org/x/telemetry/internal/crashmonitor_test.childTrap:+2\n" +
242-
"golang.org/x/telemetry/internal/crashmonitor_test.TestMain.func4:+1\n" +
243-
"runtime.goexit:--"
244-
if got != want {
245-
t.Errorf("got counter name <<%s>>, want <<%s>>", got, want)
216+
wantRE := regexp.MustCompile(`(?m)crash/crash
217+
runtime.gopanic:--
218+
runtime.panicmem:--
219+
runtime.sigpanic:--
220+
golang.org/x/telemetry/internal/crashmonitor_test.grandchildTrap:=96,.*
221+
golang.org/x/telemetry/internal/crashmonitor_test.childTrap:\+2,.*
222+
golang.org/x/telemetry/internal/crashmonitor_test.TestMain.func4:\+1,.*
223+
runtime.goexit:--`)
224+
if !wantRE.MatchString(got) {
225+
t.Errorf("got counter name <<%s>>, want match for <<%s>>", got, wantRE)
246226
}
247227
})
248228
}

start.go

+3-7
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func parent(config Config) *StartResult {
166166
}
167167

168168
childShouldUpload := config.Upload && acquireUploadToken()
169-
reportCrashes := config.ReportCrashes && crashmonitor.Supported()
169+
reportCrashes := config.ReportCrashes
170170

171171
if reportCrashes || childShouldUpload {
172172
startChild(reportCrashes, childShouldUpload, result)
@@ -267,10 +267,6 @@ func child(config Config) {
267267
os.Setenv(telemetryChildVar, "2")
268268
upload := os.Getenv(telemetryUploadVar) == "1"
269269

270-
reportCrashes := config.ReportCrashes && crashmonitor.Supported()
271-
uploadStartTime := config.UploadStartTime
272-
uploadURL := config.UploadURL
273-
274270
// The crashmonitor and/or upload process may themselves record counters.
275271
counter.Open()
276272

@@ -280,15 +276,15 @@ func child(config Config) {
280276
// upload to finish before exiting
281277
var g errgroup.Group
282278

283-
if reportCrashes {
279+
if config.ReportCrashes {
284280
g.Go(func() error {
285281
crashmonitor.Child()
286282
return nil
287283
})
288284
}
289285
if upload {
290286
g.Go(func() error {
291-
uploaderChild(uploadStartTime, uploadURL)
287+
uploaderChild(config.UploadStartTime, config.UploadURL)
292288
return nil
293289
})
294290
}

start_test.go

-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"golang.org/x/telemetry/counter/countertest"
2222
"golang.org/x/telemetry/internal/configtest"
2323
ic "golang.org/x/telemetry/internal/counter"
24-
"golang.org/x/telemetry/internal/crashmonitor"
2524
"golang.org/x/telemetry/internal/regtest"
2625
it "golang.org/x/telemetry/internal/telemetry"
2726
"golang.org/x/telemetry/internal/testenv"
@@ -135,10 +134,6 @@ func TestStart(t *testing.T) {
135134
testenv.SkipIfUnsupportedPlatform(t)
136135
testenv.MustHaveExec(t)
137136

138-
if !crashmonitor.Supported() {
139-
t.Skip("crashmonitor not supported")
140-
}
141-
142137
// This test sets up a telemetry environment, and then runs a test program
143138
// that increments a counter, and uploads via telemetry.Start.
144139

0 commit comments

Comments
 (0)