proc: fix inlined stack reading for midstack inlined calls
Due to a bug in the Go compiler midstack inlined calls do not report their ranges correctly. We can't check if an address is in the range of a DIE by simply looking at that DIE's range, we should also recursively check the DIE's children's ranges. Also fixes the way stacktraces of midstack inlined calls are reported (they used to be inverted, with the deepest inlined stack frame reported last). Fixes #1795
This commit is contained in:
parent
a8606afb0b
commit
adb1746c60
14
_fixtures/issue1795.go
Normal file
14
_fixtures/issue1795.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := regexp.MustCompile("ab")
|
||||||
|
runtime.Breakpoint()
|
||||||
|
out := r.MatchString("blah")
|
||||||
|
fmt.Printf("%v\n", out)
|
||||||
|
}
|
@ -371,63 +371,93 @@ func LoadAbstractOrigin(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.O
|
|||||||
type InlineStackReader struct {
|
type InlineStackReader struct {
|
||||||
dwarf *dwarf.Data
|
dwarf *dwarf.Data
|
||||||
reader *dwarf.Reader
|
reader *dwarf.Reader
|
||||||
entry *dwarf.Entry
|
|
||||||
depth int
|
|
||||||
pc uint64
|
pc uint64
|
||||||
|
entry *dwarf.Entry
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
// stack contains the list of DIEs that will be returned by Next.
|
||||||
|
stack []*dwarf.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
// InlineStack returns an InlineStackReader for the specified function and
|
// InlineStack returns an InlineStackReader for the specified function and
|
||||||
// PC address.
|
// PC address.
|
||||||
// If pc is 0 then all inlined calls will be returned.
|
// If pc is 0 then all inlined calls will be returned.
|
||||||
func InlineStack(dwarf *dwarf.Data, fnoff dwarf.Offset, pc RelAddr) *InlineStackReader {
|
func InlineStack(dw *dwarf.Data, fnoff dwarf.Offset, pc RelAddr) *InlineStackReader {
|
||||||
reader := dwarf.Reader()
|
reader := dw.Reader()
|
||||||
reader.Seek(fnoff)
|
reader.Seek(fnoff)
|
||||||
return &InlineStackReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, pc: uint64(pc)}
|
r := &InlineStackReader{dwarf: dw, reader: reader, pc: uint64(pc)}
|
||||||
|
r.precalcStack(nil)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// precalcStack precalculates the inlined call stack for irdr.pc.
|
||||||
|
// If irdr.pc == 0 then all inlined calls will be saved in irdr.stack.
|
||||||
|
// Otherwise an inlined call will be saved in irdr.stack if its range, or
|
||||||
|
// the range of one of its child entries contains irdr.pc.
|
||||||
|
// The recursive calculation of range inclusion is necessary because
|
||||||
|
// sometimes when doing midstack inlining the Go compiler emits the toplevel
|
||||||
|
// inlined call with ranges that do not cover the inlining of nested inlined
|
||||||
|
// calls.
|
||||||
|
// For example if a function A calls B which calls C and both the calls to
|
||||||
|
// B and C are inlined the DW_AT_inlined_subroutine entry for A might have
|
||||||
|
// ranges that do not cover the ranges of the inlined call to C.
|
||||||
|
// This is probably a violation of the DWARF standard (it's unclear) but we
|
||||||
|
// might as well support it as best as possible anyway.
|
||||||
|
func (irdr *InlineStackReader) precalcStack(rentry *dwarf.Entry) bool {
|
||||||
|
var contains bool
|
||||||
|
|
||||||
|
childLoop:
|
||||||
|
for {
|
||||||
|
if irdr.err != nil {
|
||||||
|
return contains
|
||||||
|
}
|
||||||
|
e2, err := irdr.reader.Next()
|
||||||
|
if e2 == nil || err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e2.Tag {
|
||||||
|
case 0:
|
||||||
|
break childLoop
|
||||||
|
case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram, dwarf.TagInlinedSubroutine:
|
||||||
|
if irdr.precalcStack(e2) {
|
||||||
|
contains = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
irdr.reader.SkipChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rentry != nil && rentry.Tag == dwarf.TagInlinedSubroutine {
|
||||||
|
if !contains {
|
||||||
|
if irdr.pc != 0 {
|
||||||
|
contains, irdr.err = entryRangesContains(irdr.dwarf, rentry, irdr.pc)
|
||||||
|
} else {
|
||||||
|
contains = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if contains {
|
||||||
|
irdr.stack = append(irdr.stack, rentry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contains
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next reads next inlined call in the stack, returns false if there aren't any.
|
// Next reads next inlined call in the stack, returns false if there aren't any.
|
||||||
|
// Next reads the inlined stack of calls backwards, starting with the
|
||||||
|
// deepest inlined call and moving back out, like a normal stacktrace works.
|
||||||
func (irdr *InlineStackReader) Next() bool {
|
func (irdr *InlineStackReader) Next() bool {
|
||||||
if irdr.err != nil {
|
if irdr.err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
if len(irdr.stack) == 0 {
|
||||||
irdr.entry, irdr.err = irdr.reader.Next()
|
return false
|
||||||
if irdr.entry == nil || irdr.err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch irdr.entry.Tag {
|
|
||||||
case 0:
|
|
||||||
irdr.depth--
|
|
||||||
if irdr.depth == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram, dwarf.TagInlinedSubroutine:
|
|
||||||
var recur bool
|
|
||||||
if irdr.pc != 0 {
|
|
||||||
recur, irdr.err = entryRangesContains(irdr.dwarf, irdr.entry, irdr.pc)
|
|
||||||
} else {
|
|
||||||
recur = true
|
|
||||||
}
|
|
||||||
if recur {
|
|
||||||
irdr.depth++
|
|
||||||
if irdr.entry.Tag == dwarf.TagInlinedSubroutine {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if irdr.depth == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
irdr.reader.SkipChildren()
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
irdr.reader.SkipChildren()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
irdr.entry = irdr.stack[0]
|
||||||
|
irdr.stack = irdr.stack[1:]
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry returns the DIE for the current inlined call.
|
// Entry returns the DIE for the current inlined call.
|
||||||
|
@ -440,7 +440,7 @@ func (bi *BinaryInfo) PCToInlineFunc(pc uint64) *Function {
|
|||||||
fn := bi.PCToFunc(pc)
|
fn := bi.PCToFunc(pc)
|
||||||
irdr := reader.InlineStack(fn.cu.image.dwarf, fn.offset, reader.ToRelAddr(pc, fn.cu.image.StaticBase))
|
irdr := reader.InlineStack(fn.cu.image.dwarf, fn.offset, reader.ToRelAddr(pc, fn.cu.image.StaticBase))
|
||||||
var inlineFnEntry *dwarf.Entry
|
var inlineFnEntry *dwarf.Entry
|
||||||
for irdr.Next() {
|
if irdr.Next() {
|
||||||
inlineFnEntry = irdr.Entry()
|
inlineFnEntry = irdr.Entry()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3177,6 +3177,9 @@ func logStacktrace(t *testing.T, bi *proc.BinaryInfo, frames []proc.Stackframe)
|
|||||||
if frames[j].Current.Fn != nil {
|
if frames[j].Current.Fn != nil {
|
||||||
name = frames[j].Current.Fn.Name
|
name = frames[j].Current.Fn.Name
|
||||||
}
|
}
|
||||||
|
if frames[j].Call.Fn != nil && frames[j].Current.Fn != frames[j].Call.Fn {
|
||||||
|
name = fmt.Sprintf("%s inlined in %s", frames[j].Call.Fn.Name, frames[j].Current.Fn.Name)
|
||||||
|
}
|
||||||
|
|
||||||
t.Logf("\t%#x %#x %#x %s at %s:%d\n", frames[j].Call.PC, frames[j].FrameOffset(), frames[j].FramePointerOffset(), name, filepath.Base(frames[j].Call.File), frames[j].Call.Line)
|
t.Logf("\t%#x %#x %#x %s at %s:%d\n", frames[j].Call.PC, frames[j].FrameOffset(), frames[j].FramePointerOffset(), name, filepath.Base(frames[j].Call.File), frames[j].Call.Line)
|
||||||
if frames[j].TopmostDefer != nil {
|
if frames[j].TopmostDefer != nil {
|
||||||
@ -3613,8 +3616,10 @@ func checkFrame(frame proc.Stackframe, fnname, file string, line int, inlined bo
|
|||||||
if frame.Call.Fn == nil || frame.Call.Fn.Name != fnname {
|
if frame.Call.Fn == nil || frame.Call.Fn.Name != fnname {
|
||||||
return fmt.Errorf("wrong function name: %s", fnname)
|
return fmt.Errorf("wrong function name: %s", fnname)
|
||||||
}
|
}
|
||||||
if frame.Call.File != file || frame.Call.Line != line {
|
if file != "" {
|
||||||
return fmt.Errorf("wrong file:line %s:%d", frame.Call.File, frame.Call.Line)
|
if frame.Call.File != file || frame.Call.Line != line {
|
||||||
|
return fmt.Errorf("wrong file:line %s:%d", frame.Call.File, frame.Call.Line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if frame.Inlined != inlined {
|
if frame.Inlined != inlined {
|
||||||
if inlined {
|
if inlined {
|
||||||
@ -4546,3 +4551,44 @@ func TestListPackagesBuildInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue1795(t *testing.T) {
|
||||||
|
// When doing midstack inlining the Go compiler sometimes (always?) emits
|
||||||
|
// the toplevel inlined call with ranges that do not cover the inlining of
|
||||||
|
// other nested inlined calls.
|
||||||
|
// For example if a function A calls B which calls C and both the calls to
|
||||||
|
// B and C are inlined the DW_AT_inlined_subroutine entry for A might have
|
||||||
|
// ranges that do not cover the ranges of the inlined call to C.
|
||||||
|
// This is probably a violation of the DWARF standard (it's unclear) but we
|
||||||
|
// might as well support it as best as possible anyway.
|
||||||
|
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
|
||||||
|
t.Skip("Test not relevant to Go < 1.13")
|
||||||
|
}
|
||||||
|
withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p proc.Process, fixture protest.Fixture) {
|
||||||
|
assertNoError(proc.Continue(p), t, "Continue()")
|
||||||
|
assertLineNumber(p, t, 12, "wrong line number after Continue,")
|
||||||
|
assertNoError(proc.Next(p), t, "Next()")
|
||||||
|
assertLineNumber(p, t, 13, "wrong line number after Next,")
|
||||||
|
})
|
||||||
|
withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p proc.Process, fixture protest.Fixture) {
|
||||||
|
setFunctionBreakpoint(p, t, "regexp.(*Regexp).doExecute")
|
||||||
|
assertNoError(proc.Continue(p), t, "Continue()")
|
||||||
|
assertLineNumber(p, t, 12, "wrong line number after Continue (1),")
|
||||||
|
assertNoError(proc.Continue(p), t, "Continue()")
|
||||||
|
frames, err := proc.ThreadStacktrace(p.CurrentThread(), 40)
|
||||||
|
assertNoError(err, t, "ThreadStacktrace()")
|
||||||
|
logStacktrace(t, p.BinInfo(), frames)
|
||||||
|
if err := checkFrame(frames[0], "regexp.(*Regexp).doExecute", "", 0, false); err != nil {
|
||||||
|
t.Errorf("Wrong frame 0: %v", err)
|
||||||
|
}
|
||||||
|
if err := checkFrame(frames[1], "regexp.(*Regexp).doMatch", "", 0, true); err != nil {
|
||||||
|
t.Errorf("Wrong frame 1: %v", err)
|
||||||
|
}
|
||||||
|
if err := checkFrame(frames[2], "regexp.(*Regexp).MatchString", "", 0, true); err != nil {
|
||||||
|
t.Errorf("Wrong frame 2: %v", err)
|
||||||
|
}
|
||||||
|
if err := checkFrame(frames[3], "main.main", fixture.Source, 12, false); err != nil {
|
||||||
|
t.Errorf("Wrong frame 3: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user