Skip to content

Commit 0ea120a

Browse files
committed
runtime: skip logical frames in runtime.Caller
This rewrites runtime.Caller in terms of stackExpander, which already handles inlined frames and partially skipped frames. This also has the effect of making runtime.Caller understand cgo frames if there is a cgo symbolizer. Updates #19348. Change-Id: Icdf4df921aab5aa394d4d92e3becc4dd169c9a6e Reviewed-on: https://go-review.googlesource.com/40270 Run-TryBot: David Lazar <lazard@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com>
1 parent 5e15497 commit 0ea120a

File tree

2 files changed

+92
-22
lines changed

2 files changed

+92
-22
lines changed

src/runtime/extern.go

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -166,33 +166,26 @@ import "runtime/internal/sys"
166166
// program counter, file name, and line number within the file of the corresponding
167167
// call. The boolean ok is false if it was not possible to recover the information.
168168
func Caller(skip int) (pc uintptr, file string, line int, ok bool) {
169-
// Ask for two PCs: the one we were asked for
170-
// and what it called, so that we can see if it
171-
// "called" sigpanic.
172-
var rpc [2]uintptr
169+
// Make room for three PCs: the one we were asked for,
170+
// what it called, so that CallersFrames can see if it "called"
171+
// sigpanic, and possibly a PC for skipPleaseUseCallersFrames.
172+
var rpc [3]uintptr
173173
if callers(1+skip-1, rpc[:]) < 2 {
174174
return
175175
}
176-
f := findfunc(rpc[1])
177-
if !f.valid() {
178-
// TODO(rsc): Probably a bug?
179-
// The C version said "have retpc at least"
180-
// but actually returned pc=0.
181-
ok = true
176+
var stackExpander stackExpander
177+
callers := stackExpander.init(rpc[:])
178+
// We asked for one extra, so skip that one. If this is sigpanic,
179+
// stepping over this frame will set up state in Frames so the
180+
// next frame is correct.
181+
callers, _, ok = stackExpander.next(callers)
182+
if !ok {
182183
return
183184
}
184-
pc = rpc[1]
185-
xpc := pc
186-
g := findfunc(rpc[0])
187-
// All architectures turn faults into apparent calls to sigpanic.
188-
// If we see a call to sigpanic, we do not back up the PC to find
189-
// the line number of the call instruction, because there is no call.
190-
if xpc > f.entry && (!g.valid() || g.entry != funcPC(sigpanic)) {
191-
xpc--
192-
}
193-
file, line32 := funcline(f, xpc)
194-
line = int(line32)
195-
ok = true
185+
_, frame, _ := stackExpander.next(callers)
186+
pc = frame.PC
187+
file = frame.File
188+
line = frame.Line
196189
return
197190
}
198191

test/inline_caller.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// run -gcflags -l=4
2+
3+
// Copyright 2017 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package main
8+
9+
import (
10+
"fmt"
11+
"runtime"
12+
)
13+
14+
type frame struct {
15+
pc uintptr
16+
file string
17+
line int
18+
ok bool
19+
}
20+
21+
var (
22+
skip int
23+
globalFrame frame
24+
)
25+
26+
func f() {
27+
g() // line 27
28+
}
29+
30+
func g() {
31+
h() // line 31
32+
}
33+
34+
func h() {
35+
x := &globalFrame
36+
x.pc, x.file, x.line, x.ok = runtime.Caller(skip) // line 36
37+
}
38+
39+
//go:noinline
40+
func testCaller(skp int) frame {
41+
skip = skp
42+
f() // line 42
43+
frame := globalFrame
44+
if !frame.ok {
45+
panic(fmt.Sprintf("skip=%d runtime.Caller failed", skp))
46+
}
47+
return frame
48+
}
49+
50+
type wantFrame struct {
51+
funcName string
52+
line int
53+
}
54+
55+
// -1 means don't care
56+
var expected = []wantFrame{
57+
0: {"main.testCaller", 36},
58+
1: {"main.testCaller", 31},
59+
2: {"main.testCaller", 27},
60+
3: {"main.testCaller", 42},
61+
4: {"main.main", 68},
62+
5: {"runtime.main", -1},
63+
6: {"runtime.goexit", -1},
64+
}
65+
66+
func main() {
67+
for i := 0; i <= 6; i++ {
68+
frame := testCaller(i) // line 68
69+
fn := runtime.FuncForPC(frame.pc)
70+
if expected[i].line >= 0 && frame.line != expected[i].line {
71+
panic(fmt.Sprintf("skip=%d expected line %d, got line %d", i, expected[i].line, frame.line))
72+
}
73+
if fn.Name() != expected[i].funcName {
74+
panic(fmt.Sprintf("skip=%d expected function %s, got %s", i, expected[i].funcName, fn.Name()))
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)