Skip to content

Commit ad03af6

Browse files
runtime, runtime/pprof: add Frames to get file/line for Callers
This indirectly implements a small fix for runtime/pprof: it used to look for runtime.gopanic when it should have been looking for runtime.sigpanic. Update #11432. Change-Id: I5e3f5203b2ac5463efd85adf6636e64174aacb1d Reviewed-on: https://go-review.googlesource.com/19869 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent 14113b3 commit ad03af6

File tree

4 files changed

+173
-24
lines changed

4 files changed

+173
-24
lines changed

src/runtime/callers_test.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2016 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+
package runtime_test
6+
7+
import (
8+
"runtime"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func f1(pan bool) []uintptr {
14+
return f2(pan) // line 14
15+
}
16+
17+
func f2(pan bool) []uintptr {
18+
return f3(pan) // line 18
19+
}
20+
21+
func f3(pan bool) []uintptr {
22+
if pan {
23+
panic("f3") // line 23
24+
}
25+
ret := make([]uintptr, 20)
26+
return ret[:runtime.Callers(0, ret)] // line 26
27+
}
28+
29+
func testCallers(t *testing.T, pcs []uintptr, pan bool) {
30+
m := make(map[string]int, len(pcs))
31+
frames := runtime.CallersFrames(pcs)
32+
for {
33+
frame, more := frames.Next()
34+
if frame.Function != "" {
35+
m[frame.Function] = frame.Line
36+
}
37+
if !more {
38+
break
39+
}
40+
}
41+
42+
var seen []string
43+
for k := range m {
44+
seen = append(seen, k)
45+
}
46+
t.Logf("functions seen: %s", strings.Join(seen, " "))
47+
48+
var f3Line int
49+
if pan {
50+
f3Line = 23
51+
} else {
52+
f3Line = 26
53+
}
54+
want := []struct {
55+
name string
56+
line int
57+
}{
58+
{"f1", 14},
59+
{"f2", 18},
60+
{"f3", f3Line},
61+
}
62+
for _, w := range want {
63+
if got := m["runtime_test."+w.name]; got != w.line {
64+
t.Errorf("%s is line %d, want %d", w.name, got, w.line)
65+
}
66+
}
67+
}
68+
69+
func TestCallers(t *testing.T) {
70+
testCallers(t, f1(false), false)
71+
}
72+
73+
func TestCallersPanic(t *testing.T) {
74+
defer func() {
75+
if r := recover(); r == nil {
76+
t.Fatal("did not panic")
77+
}
78+
pcs := make([]uintptr, 20)
79+
pcs = pcs[:runtime.Callers(0, pcs)]
80+
testCallers(t, pcs, true)
81+
}()
82+
f1(true)
83+
}

src/runtime/extern.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,9 @@ func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
191191
//
192192
// Note that since each slice entry pc[i] is a return program counter,
193193
// looking up the file and line for pc[i] (for example, using (*Func).FileLine)
194-
// will return the file and line number of the instruction immediately
194+
// will normally return the file and line number of the instruction immediately
195195
// following the call.
196-
// To look up the file and line number of the call itself, use pc[i]-1.
197-
// As an exception to this rule, if pc[i-1] corresponds to the function
198-
// runtime.sigpanic, then pc[i] is the program counter of a faulting
199-
// instruction and should be used without any subtraction.
196+
// To easily look up file/line information for the call sequence, use Frames.
200197
func Callers(skip int, pc []uintptr) int {
201198
// runtime.callers uses pc.array==nil as a signal
202199
// to print a stack trace. Pick off 0-length pc here

src/runtime/pprof/pprof.go

+10-19
Original file line numberDiff line numberDiff line change
@@ -325,33 +325,24 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro
325325
// for a single stack trace.
326326
func printStackRecord(w io.Writer, stk []uintptr, allFrames bool) {
327327
show := allFrames
328-
wasPanic := false
329-
for i, pc := range stk {
330-
f := runtime.FuncForPC(pc)
331-
if f == nil {
328+
frames := runtime.CallersFrames(stk)
329+
for {
330+
frame, more := frames.Next()
331+
name := frame.Function
332+
if name == "" {
332333
show = true
333-
fmt.Fprintf(w, "#\t%#x\n", pc)
334-
wasPanic = false
334+
fmt.Fprintf(w, "#\t%#x\n", frame.PC)
335335
} else {
336-
tracepc := pc
337-
// Back up to call instruction.
338-
if i > 0 && pc > f.Entry() && !wasPanic {
339-
if runtime.GOARCH == "386" || runtime.GOARCH == "amd64" {
340-
tracepc--
341-
} else {
342-
tracepc -= 4 // arm, etc
343-
}
344-
}
345-
file, line := f.FileLine(tracepc)
346-
name := f.Name()
347336
// Hide runtime.goexit and any runtime functions at the beginning.
348337
// This is useful mainly for allocation traces.
349-
wasPanic = name == "runtime.gopanic"
350338
if name == "runtime.goexit" || !show && strings.HasPrefix(name, "runtime.") {
351339
continue
352340
}
353341
show = true
354-
fmt.Fprintf(w, "#\t%#x\t%s+%#x\t%s:%d\n", pc, name, pc-f.Entry(), file, line)
342+
fmt.Fprintf(w, "#\t%#x\t%s+%#x\t%s:%d\n", frame.PC, name, frame.PC-frame.Entry, frame.File, frame.Line)
343+
}
344+
if !more {
345+
break
355346
}
356347
}
357348
if !show {

src/runtime/symtab.go

+78
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,84 @@ import (
99
"unsafe"
1010
)
1111

12+
// Frames may be used to get function/file/line information for a
13+
// slice of PC values returned by Callers.
14+
type Frames struct {
15+
callers []uintptr
16+
17+
// If previous caller in iteration was a panic, then
18+
// ci.callers[0] is the address of the faulting instruction
19+
// instead of the return address of the call.
20+
wasPanic bool
21+
}
22+
23+
// Frame is the information returned by Frames for each call frame.
24+
type Frame struct {
25+
// Program counter for this frame; multiple frames may have
26+
// the same PC value.
27+
PC uintptr
28+
29+
// Func for this frame; may be nil for non-Go code or fully
30+
// inlined functions.
31+
Func *Func
32+
33+
// Function name, file name, and line number for this call frame.
34+
// May be the empty string or zero if not known.
35+
// If Func is not nil then Function == Func.Name().
36+
Function string
37+
File string
38+
Line int
39+
40+
// Entry point for the function; may be zero if not known.
41+
// If Func is not nil then Entry == Func.Entry().
42+
Entry uintptr
43+
}
44+
45+
// CallersFrames takes a slice of PC values returned by Callers and
46+
// prepares to return function/file/line information.
47+
// Do not change the slice until you are done with the Frames.
48+
func CallersFrames(callers []uintptr) *Frames {
49+
return &Frames{callers, false}
50+
}
51+
52+
// Next returns frame information for the next caller.
53+
// If more is false, there are no more callers (the Frame value is valid).
54+
func (ci *Frames) Next() (frame Frame, more bool) {
55+
if len(ci.callers) == 0 {
56+
ci.wasPanic = false
57+
return Frame{}, false
58+
}
59+
pc := ci.callers[0]
60+
ci.callers = ci.callers[1:]
61+
more = len(ci.callers) > 0
62+
f := FuncForPC(pc)
63+
if f == nil {
64+
ci.wasPanic = false
65+
return Frame{}, more
66+
}
67+
68+
entry := f.Entry()
69+
xpc := pc
70+
if xpc > entry && !ci.wasPanic {
71+
xpc--
72+
}
73+
file, line := f.FileLine(xpc)
74+
75+
function := f.Name()
76+
ci.wasPanic = entry == sigpanicPC
77+
78+
frame = Frame{
79+
PC: xpc,
80+
Func: f,
81+
Function: function,
82+
File: file,
83+
Line: line,
84+
Entry: entry,
85+
}
86+
87+
return frame, more
88+
}
89+
1290
// NOTE: Func does not expose the actual unexported fields, because we return *Func
1391
// values to users, and we want to keep them from being able to overwrite the data
1492
// with (say) *f = Func{}.

0 commit comments

Comments
 (0)