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:
parent
568251e43f
commit
49bfbe6d24
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user