From 49bfbe6d24a41ae3aaa3adca059a94bb167bf7ac Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Mon, 6 Aug 2018 18:08:25 -0700 Subject: [PATCH] proc: Increase inline function support This patch makes it so inlined functions are returned in the function list, and also allows users to set breakpoints on the call site of inlined functions. Fixes #1261 --- pkg/proc/bininfo.go | 35 +++++++++++++++++++++----- pkg/proc/proc_test.go | 42 +++++++++++++++++++++++++++++++ pkg/proc/types.go | 58 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 122 insertions(+), 13 deletions(-) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 332ae6a6..d3f020c9 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -79,13 +79,15 @@ var UnsupportedDarwinArchErr = errors.New("unsupported architecture - only darwi const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231) type compileUnit struct { - entry *dwarf.Entry // debug_info entry describing this compile unit - isgo bool // true if this is the go compile unit - Name string // univocal name for non-go compile units - lineInfo *line.DebugLineInfo // debug_line segment associated with this compile unit + Name string // univocal name for non-go compile units LowPC, HighPC uint64 - optimized bool // this compile unit is optimized - producer string // producer attribute + + entry *dwarf.Entry // debug_info entry describing this compile unit + isgo bool // true if this is the go compile unit + lineInfo *line.DebugLineInfo // debug_line segment associated with this compile unit + concreteInlinedFns []inlinedFn // list of concrete inlined functions within this compile unit + optimized bool // this compile unit is optimized + producer string // producer attribute } type partialUnitConstant struct { @@ -102,6 +104,16 @@ type partialUnit struct { functions []Function } +// inlinedFn represents a concrete inlined function, e.g. +// an entry for the generated code of an inlined function. +type inlinedFn struct { + Name string // Name of the function that was inlined + LowPC, HighPC uint64 // Address range of the generated inlined instructions + CallFile string // File of the call site of the inlined function + CallLine int64 // Line of the call site of the inlined function + Parent *Function // The function that contains this inlined function +} + // Function describes a function in the target program. type Function struct { Name string @@ -320,6 +332,17 @@ func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pc uint64, fn *Func for _, cu := range bi.compileUnits { if cu.lineInfo.Lookup[filename] != nil { pc = cu.lineInfo.LineToPC(filename, lineno) + if pc == 0 { + // Check to see if this file:line belongs to the call site + // of an inlined function. + for _, ifn := range cu.concreteInlinedFns { + if strings.Contains(ifn.CallFile, filename) && ifn.CallLine == int64(lineno) { + pc = ifn.LowPC + fn = ifn.Parent + return + } + } + } fn = bi.PCToFunc(pc) if fn != nil { return diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index bde19ffb..5e2e591c 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -3756,6 +3756,48 @@ func TestInlineStepOut(t *testing.T) { }) } +func TestInlineFunctionList(t *testing.T) { + // We should be able to list all functions, even inlined ones. + if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}) { + // Versions of go before 1.10 do not have DWARF information for inlined calls + t.Skip("inlining not supported") + } + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p proc.Process, fixture protest.Fixture) { + var found bool + for _, fn := range p.BinInfo().Functions { + if strings.Contains(fn.Name, "inlineThis") { + found = true + break + } + } + if !found { + t.Fatal("inline function not returned") + } + }) +} + +func TestInlineBreakpoint(t *testing.T) { + // We should be able to set a breakpoint on the call site of an inlined function. + if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}) { + // Versions of go before 1.10 do not have DWARF information for inlined calls + t.Skip("inlining not supported") + } + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p proc.Process, fixture protest.Fixture) { + pc, fn, err := p.BinInfo().LineToPC(fixture.Source, 17) + if pc == 0 { + t.Fatal("unable to get PC for inlined function call") + } + expectedFn := "main.main" + if fn.Name != expectedFn { + t.Fatalf("incorrect function returned, expected %s, got %s", expectedFn, fn.Name) + } + _, err = p.SetBreakpoint(pc, proc.UserBreakpoint, nil) + if err != nil { + t.Fatalf("unable to set breakpoint: %v", err) + } + }) +} + func TestIssue951(t *testing.T) { if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) { t.Skip("scopes not implemented in <=go1.8") diff --git a/pkg/proc/types.go b/pkg/proc/types.go index bfc96571..788cf9ac 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -195,6 +195,8 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou var cu *compileUnit = nil var pu *partialUnit = nil var partialUnits = make(map[dwarf.Offset]*partialUnit) + abstractOriginNameTable := make(map[dwarf.Offset]string) +outer: for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { if err != nil { break @@ -365,35 +367,77 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou case dwarf.TagSubprogram: ok1 := false + inlined := false var lowpc, highpc uint64 + if inval, ok := entry.Val(dwarf.AttrInline).(int64); ok { + inlined = inval == 1 + } if ranges, _ := bi.dwarf.Ranges(entry); len(ranges) == 1 { ok1 = true lowpc = ranges[0][0] highpc = ranges[0][1] } name, ok2 := entry.Val(dwarf.AttrName).(string) - if ok1 && ok2 { + var fn Function + if (ok1 == !inlined) && ok2 { + if inlined { + abstractOriginNameTable[entry.Offset] = name + } if pu != nil { - pu.functions = append(pu.functions, Function{ + fn = Function{ Name: name, Entry: lowpc, End: highpc, offset: entry.Offset, cu: &compileUnit{}, - }) + } + pu.functions = append(pu.functions, fn) } else { if !cu.isgo { name = "C." + name } - bi.Functions = append(bi.Functions, Function{ + fn = Function{ Name: name, Entry: lowpc, End: highpc, offset: entry.Offset, cu: cu, - }) + } + bi.Functions = append(bi.Functions, fn) + } + } + if entry.Children { + for { + entry, err = reader.Next() + if err != nil { + break outer + } + if entry.Tag == 0 { + break + } + if entry.Tag == dwarf.TagInlinedSubroutine { + originOffset := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset) + name := abstractOriginNameTable[originOffset] + if ranges, _ := bi.dwarf.Ranges(entry); len(ranges) == 1 { + ok1 = true + lowpc = ranges[0][0] + highpc = ranges[0][1] + } + callfileidx, ok1 := entry.Val(dwarf.AttrCallFile).(int64) + callline, ok2 := entry.Val(dwarf.AttrCallLine).(int64) + if ok1 && ok2 { + callfile := cu.lineInfo.FileNames[callfileidx-1].Path + cu.concreteInlinedFns = append(cu.concreteInlinedFns, inlinedFn{ + Name: name, + LowPC: lowpc, + HighPC: highpc, + CallFile: callfile, + CallLine: callline, + Parent: &fn, + }) + } + } + reader.SkipChildren() } } - reader.SkipChildren() - } } sort.Sort(compileUnitsByLowpc(bi.compileUnits)) -- GitLab