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
This commit is contained in:
Derek Parker 2018-08-06 18:08:25 -07:00 committed by Alessandro Arzilli
parent 568251e43f
commit 49bfbe6d24
3 changed files with 122 additions and 13 deletions

@ -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

@ -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")

@ -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))