Skip to content

Commit 956879d

Browse files
committed
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 <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 033b650 commit 956879d

File tree

5 files changed

+57
-12
lines changed

5 files changed

+57
-12
lines changed

src/runtime/race.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func racecallback(cmd uintptr, ctx unsafe.Pointer) {
156156
}
157157

158158
func raceSymbolizeCode(ctx *symbolizeCodeContext) {
159-
f := FuncForPC(ctx.pc)
159+
f := findfunc(ctx.pc)._Func()
160160
if f != nil {
161161
file, line := f.FileLine(ctx.pc)
162162
if line != 0 {

src/runtime/runtime2.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,17 @@ type _func struct {
663663
nfuncdata uint8 // must be last
664664
}
665665

666+
// Pseudo-Func that is returned for PCs that occur in inlined code.
667+
// A *Func can be either a *_func or a *funcinl, and they are distinguished
668+
// by the first uintptr.
669+
type funcinl struct {
670+
zero uintptr // set to 0 to distinguish from _func
671+
entry uintptr // entry of the real (the "outermost") frame.
672+
name string
673+
file string
674+
line int
675+
}
676+
666677
// layout of Itab known to compilers
667678
// allocated in non-garbage-collected memory
668679
// Needs to be in sync with

src/runtime/symtab.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -466,29 +466,63 @@ func moduledataverify1(datap *moduledata) {
466466
// given program counter address, or else nil.
467467
//
468468
// If pc represents multiple functions because of inlining, it returns
469-
// the *Func describing the outermost function.
469+
// the a *Func describing the innermost function, but with an entry
470+
// of the outermost function.
470471
func FuncForPC(pc uintptr) *Func {
471-
return findfunc(pc)._Func()
472+
f := findfunc(pc)
473+
if !f.valid() {
474+
return nil
475+
}
476+
if inldata := funcdata(f, _FUNCDATA_InlTree); inldata != nil {
477+
if ix := pcdatavalue(f, _PCDATA_InlTreeIndex, pc, nil); ix >= 0 {
478+
inltree := (*[1 << 20]inlinedCall)(inldata)
479+
name := funcnameFromNameoff(f, inltree[ix].func_)
480+
file, line := funcline(f, pc)
481+
fi := &funcinl{
482+
entry: f.entry, // entry of the real (the outermost) function.
483+
name: name,
484+
file: file,
485+
line: int(line),
486+
}
487+
return (*Func)(unsafe.Pointer(fi))
488+
}
489+
}
490+
return f._Func()
472491
}
473492

474493
// Name returns the name of the function.
475494
func (f *Func) Name() string {
476495
if f == nil {
477496
return ""
478497
}
498+
fn := f.raw()
499+
if fn.entry == 0 { // inlined version
500+
fi := (*funcinl)(unsafe.Pointer(fn))
501+
return fi.name
502+
}
479503
return funcname(f.funcInfo())
480504
}
481505

482506
// Entry returns the entry address of the function.
483507
func (f *Func) Entry() uintptr {
484-
return f.raw().entry
508+
fn := f.raw()
509+
if fn.entry == 0 { // inlined version
510+
fi := (*funcinl)(unsafe.Pointer(fn))
511+
return fi.entry
512+
}
513+
return fn.entry
485514
}
486515

487516
// FileLine returns the file name and line number of the
488517
// source code corresponding to the program counter pc.
489518
// The result will not be accurate if pc is not a program
490519
// counter within f.
491520
func (f *Func) FileLine(pc uintptr) (file string, line int) {
521+
fn := f.raw()
522+
if fn.entry == 0 { // inlined version
523+
fi := (*funcinl)(unsafe.Pointer(fn))
524+
return fi.file, fi.line
525+
}
492526
// Pass strict=false here, because anyone can call this function,
493527
// and they might just be wrong about targetpc belonging to f.
494528
file, line32 := funcline1(f.funcInfo(), pc, false)

test/inline_caller.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ type wantFrame struct {
5454

5555
// -1 means don't care
5656
var expected = []wantFrame{
57-
0: {"main.testCaller", 36},
58-
1: {"main.testCaller", 31},
59-
2: {"main.testCaller", 27},
57+
0: {"main.h", 36},
58+
1: {"main.g", 31},
59+
2: {"main.f", 27},
6060
3: {"main.testCaller", 42},
6161
4: {"main.main", 68},
6262
5: {"runtime.main", -1},

test/inline_callers.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func testCallers(skp int) (frames []string) {
3131
skip = skp
3232
f()
3333
for i := 0; i < npcs; i++ {
34-
fn := runtime.FuncForPC(pcs[i])
34+
fn := runtime.FuncForPC(pcs[i] - 1)
3535
frames = append(frames, fn.Name())
3636
if fn.Name() == "main.main" {
3737
break
@@ -56,10 +56,10 @@ func testCallersFrames(skp int) (frames []string) {
5656
}
5757

5858
var expectedFrames [][]string = [][]string{
59-
0: {"runtime.Callers", "main.testCallers", "main.testCallers", "main.testCallers", "main.testCallers", "main.main"},
60-
1: {"main.testCallers", "main.testCallers", "main.testCallers", "main.testCallers", "main.main"},
61-
2: {"main.testCallers", "main.testCallers", "main.testCallers", "main.main"},
62-
3: {"main.testCallers", "main.testCallers", "main.main"},
59+
0: {"runtime.Callers", "main.h", "main.g", "main.f", "main.testCallers", "main.main"},
60+
1: {"main.h", "main.g", "main.f", "main.testCallers", "main.main"},
61+
2: {"main.g", "main.f", "main.testCallers", "main.main"},
62+
3: {"main.f", "main.testCallers", "main.main"},
6363
4: {"main.testCallers", "main.main"},
6464
5: {"main.main"},
6565
}

0 commit comments

Comments
 (0)