Skip to content

Commit

Permalink
runtime: make FuncForPC return the innermost inlined frame
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
randall77 committed Jan 8, 2019
1 parent 033b650 commit 956879d
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/runtime/race.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func racecallback(cmd uintptr, ctx unsafe.Pointer) {
}

func raceSymbolizeCode(ctx *symbolizeCodeContext) {
f := FuncForPC(ctx.pc)
f := findfunc(ctx.pc)._Func()
if f != nil {
file, line := f.FileLine(ctx.pc)
if line != 0 {
Expand Down
11 changes: 11 additions & 0 deletions src/runtime/runtime2.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,17 @@ type _func struct {
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
// allocated in non-garbage-collected memory
// Needs to be in sync with
Expand Down
40 changes: 37 additions & 3 deletions src/runtime/symtab.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,29 +466,63 @@ func moduledataverify1(datap *moduledata) {
// given program counter address, or else nil.
//
// 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 {
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.
func (f *Func) Name() string {
if f == nil {
return ""
}
fn := f.raw()
if fn.entry == 0 { // inlined version
fi := (*funcinl)(unsafe.Pointer(fn))
return fi.name
}
return funcname(f.funcInfo())
}

// Entry returns the entry address of the function.
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
// source code corresponding to the program counter pc.
// The result will not be accurate if pc is not a program
// counter within f.
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,
// and they might just be wrong about targetpc belonging to f.
file, line32 := funcline1(f.funcInfo(), pc, false)
Expand Down
6 changes: 3 additions & 3 deletions test/inline_caller.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ type wantFrame struct {

// -1 means don't care
var expected = []wantFrame{
0: {"main.testCaller", 36},
1: {"main.testCaller", 31},
2: {"main.testCaller", 27},
0: {"main.h", 36},
1: {"main.g", 31},
2: {"main.f", 27},
3: {"main.testCaller", 42},
4: {"main.main", 68},
5: {"runtime.main", -1},
Expand Down
10 changes: 5 additions & 5 deletions test/inline_callers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func testCallers(skp int) (frames []string) {
skip = skp
f()
for i := 0; i < npcs; i++ {
fn := runtime.FuncForPC(pcs[i])
fn := runtime.FuncForPC(pcs[i] - 1)
frames = append(frames, fn.Name())
if fn.Name() == "main.main" {
break
Expand All @@ -56,10 +56,10 @@ func testCallersFrames(skp int) (frames []string) {
}

var expectedFrames [][]string = [][]string{
0: {"runtime.Callers", "main.testCallers", "main.testCallers", "main.testCallers", "main.testCallers", "main.main"},
1: {"main.testCallers", "main.testCallers", "main.testCallers", "main.testCallers", "main.main"},
2: {"main.testCallers", "main.testCallers", "main.testCallers", "main.main"},
3: {"main.testCallers", "main.testCallers", "main.main"},
0: {"runtime.Callers", "main.h", "main.g", "main.f", "main.testCallers", "main.main"},
1: {"main.h", "main.g", "main.f", "main.testCallers", "main.main"},
2: {"main.g", "main.f", "main.testCallers", "main.main"},
3: {"main.f", "main.testCallers", "main.main"},
4: {"main.testCallers", "main.main"},
5: {"main.main"},
}
Expand Down

0 comments on commit 956879d

Please sign in to comment.