1
0
mirror of https://github.com/golang/go synced 2024-11-11 19:11:35 -07:00

runtime: make FuncForPC return the innermost inlined frame

Returning the innermost frame instead of the outermost
makes code that walks the results of runtime.Caller{,s}
still work correctly in the presence of mid-stack inlining.

Fixes #29582

Change-Id: I2392e3dd5636eb8c6f58620a61cef2194fe660a7
Reviewed-on: https://go-review.googlesource.com/c/156364
Run-TryBot: Keith Randall <khr@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Keith Randall 2019-01-05 14:31:23 -08:00 committed by Keith Randall
parent 033b650181
commit 956879dd0b
5 changed files with 57 additions and 12 deletions

View File

@ -156,7 +156,7 @@ func racecallback(cmd uintptr, ctx unsafe.Pointer) {
} }
func raceSymbolizeCode(ctx *symbolizeCodeContext) { func raceSymbolizeCode(ctx *symbolizeCodeContext) {
f := FuncForPC(ctx.pc) f := findfunc(ctx.pc)._Func()
if f != nil { if f != nil {
file, line := f.FileLine(ctx.pc) file, line := f.FileLine(ctx.pc)
if line != 0 { if line != 0 {

View File

@ -663,6 +663,17 @@ type _func struct {
nfuncdata uint8 // must be last nfuncdata uint8 // must be last
} }
// Pseudo-Func that is returned for PCs that occur in inlined code.
// A *Func can be either a *_func or a *funcinl, and they are distinguished
// by the first uintptr.
type funcinl struct {
zero uintptr // set to 0 to distinguish from _func
entry uintptr // entry of the real (the "outermost") frame.
name string
file string
line int
}
// layout of Itab known to compilers // layout of Itab known to compilers
// allocated in non-garbage-collected memory // allocated in non-garbage-collected memory
// Needs to be in sync with // Needs to be in sync with

View File

@ -466,9 +466,28 @@ func moduledataverify1(datap *moduledata) {
// given program counter address, or else nil. // given program counter address, or else nil.
// //
// If pc represents multiple functions because of inlining, it returns // If pc represents multiple functions because of inlining, it returns
// the *Func describing the outermost function. // the a *Func describing the innermost function, but with an entry
// of the outermost function.
func FuncForPC(pc uintptr) *Func { func FuncForPC(pc uintptr) *Func {
return findfunc(pc)._Func() f := findfunc(pc)
if !f.valid() {
return nil
}
if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
if ix := pcdatavalue(f, _PCDATA_InlTreeIndex, pc, nil); ix >= 0 {
inltree := (*[1 << 20]inlinedCall)(inldata)
name := funcnameFromNameoff(f, inltree[ix].func_)
file, line := funcline(f, pc)
fi := &funcinl{
entry: f.entry, // entry of the real (the outermost) function.
name: name,
file: file,
line: int(line),
}
return (*Func)(unsafe.Pointer(fi))
}
}
return f._Func()
} }
// Name returns the name of the function. // Name returns the name of the function.
@ -476,12 +495,22 @@ func (f *Func) Name() string {
if f == nil { if f == nil {
return "" return ""
} }
fn := f.raw()
if fn.entry == 0 { // inlined version
fi := (*funcinl)(unsafe.Pointer(fn))
return fi.name
}
return funcname(f.funcInfo()) return funcname(f.funcInfo())
} }
// Entry returns the entry address of the function. // Entry returns the entry address of the function.
func (f *Func) Entry() uintptr { func (f *Func) Entry() uintptr {
return f.raw().entry fn := f.raw()
if fn.entry == 0 { // inlined version
fi := (*funcinl)(unsafe.Pointer(fn))
return fi.entry
}
return fn.entry
} }
// FileLine returns the file name and line number of the // FileLine returns the file name and line number of the
@ -489,6 +518,11 @@ func (f *Func) Entry() uintptr {
// The result will not be accurate if pc is not a program // The result will not be accurate if pc is not a program
// counter within f. // counter within f.
func (f *Func) FileLine(pc uintptr) (file string, line int) { func (f *Func) FileLine(pc uintptr) (file string, line int) {
fn := f.raw()
if fn.entry == 0 { // inlined version
fi := (*funcinl)(unsafe.Pointer(fn))
return fi.file, fi.line
}
// Pass strict=false here, because anyone can call this function, // Pass strict=false here, because anyone can call this function,
// and they might just be wrong about targetpc belonging to f. // and they might just be wrong about targetpc belonging to f.
file, line32 := funcline1(f.funcInfo(), pc, false) file, line32 := funcline1(f.funcInfo(), pc, false)

View File

@ -54,9 +54,9 @@ type wantFrame struct {
// -1 means don't care // -1 means don't care
var expected = []wantFrame{ var expected = []wantFrame{
0: {"main.testCaller", 36}, 0: {"main.h", 36},
1: {"main.testCaller", 31}, 1: {"main.g", 31},
2: {"main.testCaller", 27}, 2: {"main.f", 27},
3: {"main.testCaller", 42}, 3: {"main.testCaller", 42},
4: {"main.main", 68}, 4: {"main.main", 68},
5: {"runtime.main", -1}, 5: {"runtime.main", -1},

View File

@ -31,7 +31,7 @@ func testCallers(skp int) (frames []string) {
skip = skp skip = skp
f() f()
for i := 0; i < npcs; i++ { for i := 0; i < npcs; i++ {
fn := runtime.FuncForPC(pcs[i]) fn := runtime.FuncForPC(pcs[i] - 1)
frames = append(frames, fn.Name()) frames = append(frames, fn.Name())
if fn.Name() == "main.main" { if fn.Name() == "main.main" {
break break
@ -56,10 +56,10 @@ func testCallersFrames(skp int) (frames []string) {
} }
var expectedFrames [][]string = [][]string{ var expectedFrames [][]string = [][]string{
0: {"runtime.Callers", "main.testCallers", "main.testCallers", "main.testCallers", "main.testCallers", "main.main"}, 0: {"runtime.Callers", "main.h", "main.g", "main.f", "main.testCallers", "main.main"},
1: {"main.testCallers", "main.testCallers", "main.testCallers", "main.testCallers", "main.main"}, 1: {"main.h", "main.g", "main.f", "main.testCallers", "main.main"},
2: {"main.testCallers", "main.testCallers", "main.testCallers", "main.main"}, 2: {"main.g", "main.f", "main.testCallers", "main.main"},
3: {"main.testCallers", "main.testCallers", "main.main"}, 3: {"main.f", "main.testCallers", "main.main"},
4: {"main.testCallers", "main.main"}, 4: {"main.testCallers", "main.main"},
5: {"main.main"}, 5: {"main.main"},
} }