Commit 4435fcfd authored by Than McIntosh's avatar Than McIntosh

compiler,linker: support for DWARF inlined instances

Compiler and linker changes to support DWARF inlined instances,
see https://go.googlesource.com/proposal/+/HEAD/design/22080-dwarf-inlining.md
for design details.

This functionality is gated via the cmd/compile option -gendwarfinl=N,
where N={0,1,2}, where a value of 0 disables dwarf inline generation,
a value of 1 turns on dwarf generation without tracking of formal/local
vars from inlined routines, and a value of 2 enables inlines with
variable tracking.

Updates #22080

Change-Id: I69309b3b815d9fed04aebddc0b8d33d0dbbfad6e
Reviewed-on: https://go-review.googlesource.com/75550
Run-TryBot: Than McIntosh <thanm@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarDavid Chase <drchase@google.com>
parent dbb1d198
......@@ -186,7 +186,7 @@ Diff:
t.Errorf(format, args...)
ok = false
}
obj.Flushplist(ctxt, pList, nil)
obj.Flushplist(ctxt, pList, nil, "")
for p := top; p != nil; p = p.Link {
if p.As == obj.ATEXT {
......@@ -290,7 +290,7 @@ func testErrors(t *testing.T, goarch, file string) {
errBuf.WriteString(s)
}
pList.Firstpc, ok = parser.Parse()
obj.Flushplist(ctxt, pList, nil)
obj.Flushplist(ctxt, pList, nil, "")
if ok && !failed {
t.Errorf("asm: %s had no errors", goarch)
}
......
......@@ -72,7 +72,7 @@ func main() {
break
}
// reports errors to parser.Errorf
obj.Flushplist(ctxt, pList, nil)
obj.Flushplist(ctxt, pList, nil, "")
}
if ok {
obj.WriteObjFile(ctxt, buf)
......
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gc
import (
"cmd/internal/dwarf"
"cmd/internal/obj"
"cmd/internal/src"
"sort"
"strings"
)
// To identify variables by original source position.
type varPos struct {
DeclFile string
DeclLine uint
DeclCol uint
}
// This is the main entry point for collection of raw material to
// drive generation of DWARF "inlined subroutine" DIEs. See proposal
// 22080 for more details and background info.
func assembleInlines(fnsym *obj.LSym, fn *Node, dwVars []*dwarf.Var) dwarf.InlCalls {
var inlcalls dwarf.InlCalls
if Debug_gendwarfinl != 0 {
Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name)
}
// This maps inline index (from Ctxt.InlTree) to index in inlcalls.Calls
imap := make(map[int]int)
// Walk progs to build up the InlCalls data structure
var prevpos src.XPos
for p := fnsym.Func.Text; p != nil; p = p.Link {
if p.Pos == prevpos {
continue
}
ii := posInlIndex(p.Pos)
if ii >= 0 {
insertInlCall(&inlcalls, ii, imap)
}
prevpos = p.Pos
}
// This is used to partition DWARF vars by inline index. Vars not
// produced by the inliner will wind up in the vmap[0] entry.
vmap := make(map[int32][]*dwarf.Var)
// Now walk the dwarf vars and partition them based on whether they
// were produced by the inliner (dwv.InlIndex > 0) or were original
// vars/params from the function (dwv.InlIndex == 0).
for _, dwv := range dwVars {
vmap[dwv.InlIndex] = append(vmap[dwv.InlIndex], dwv)
// Zero index => var was not produced by an inline
if dwv.InlIndex == 0 {
continue
}
// Look up index in our map, then tack the var in question
// onto the vars list for the correct inlined call.
ii := int(dwv.InlIndex) - 1
idx, ok := imap[ii]
if !ok {
// We can occasionally encounter a var produced by the
// inliner for which there is no remaining prog; add a new
// entry to the call list in this scenario.
idx = insertInlCall(&inlcalls, ii, imap)
}
inlcalls.Calls[idx].InlVars =
append(inlcalls.Calls[idx].InlVars, dwv)
}
// Post process the map above to assign child indices to vars. For
// variables that weren't produced by an inline, sort them
// according to class and name and assign indices that way. For
// vars produced by an inline, assign child index by looking up
// the var name in the origin pre-optimization dcl list for the
// inlined function.
for ii, sl := range vmap {
if ii == 0 {
sort.Sort(byClassThenName(sl))
for j := 0; j < len(sl); j++ {
sl[j].ChildIndex = int32(j)
}
} else {
// Assign child index based on pre-inlined decls
ifnlsym := Ctxt.InlTree.InlinedFunction(int(ii - 1))
dcl, _ := preInliningDcls(ifnlsym)
m := make(map[varPos]int)
for i := 0; i < len(dcl); i++ {
n := dcl[i]
pos := Ctxt.InnermostPos(n.Pos)
vp := varPos{
DeclFile: pos.Base().SymFilename(),
DeclLine: pos.Line(),
DeclCol: pos.Col(),
}
m[vp] = i
}
for j := 0; j < len(sl); j++ {
vp := varPos{
DeclFile: sl[j].DeclFile,
DeclLine: sl[j].DeclLine,
DeclCol: sl[j].DeclCol,
}
if idx, found := m[vp]; found {
sl[j].ChildIndex = int32(idx)
} else {
Fatalf("unexpected: can't find var %s in preInliningDcls for %v\n", sl[j].Name, Ctxt.InlTree.InlinedFunction(int(ii-1)))
}
}
}
}
// Make a second pass through the progs to compute PC ranges
// for the various inlined calls.
curii := -1
var crange *dwarf.Range
var prevp *obj.Prog
for p := fnsym.Func.Text; p != nil; prevp, p = p, p.Link {
if prevp != nil && p.Pos == prevp.Pos {
continue
}
ii := posInlIndex(p.Pos)
if ii == curii {
continue
} else {
// Close out the current range
endRange(crange, prevp)
// Begin new range
crange = beginRange(inlcalls.Calls, p, ii, imap)
curii = ii
}
}
if prevp != nil {
endRange(crange, prevp)
}
// Debugging
if Debug_gendwarfinl != 0 {
dumpInlCalls(inlcalls)
dumpInlVars(dwVars)
}
return inlcalls
}
// Secondary hook for DWARF inlined subroutine generation. This is called
// late in the compilation when it is determined that we need an
// abstract function DIE for an inlined routine imported from a
// previously compiled package.
func genAbstractFunc(fn *obj.LSym) {
ifn := Ctxt.DwFixups.GetPrecursorFunc(fn)
if ifn == nil {
Ctxt.Diag("failed to locate precursor fn for %v", fn)
return
}
if Debug_gendwarfinl != 0 {
Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name)
}
Ctxt.DwarfAbstractFunc(ifn, fn, myimportpath)
}
func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int {
callIdx, found := imap[inlIdx]
if found {
return callIdx
}
// Haven't seen this inline yet. Visit parent of inline if there
// is one. We do this first so that parents appear before their
// children in the resulting table.
parCallIdx := -1
parInlIdx := Ctxt.InlTree.Parent(inlIdx)
if parInlIdx >= 0 {
parCallIdx = insertInlCall(dwcalls, parInlIdx, imap)
}
// Create new entry for this inline
inlinedFn := Ctxt.InlTree.InlinedFunction(int(inlIdx))
callXPos := Ctxt.InlTree.CallPos(int(inlIdx))
absFnSym := Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn)
pb := Ctxt.PosTable.Pos(callXPos).Base()
callFileSym := Ctxt.Lookup(pb.SymFilename())
ic := dwarf.InlCall{
InlIndex: inlIdx,
CallFile: callFileSym,
CallLine: uint32(callXPos.Line()),
AbsFunSym: absFnSym,
Root: parCallIdx == -1,
}
dwcalls.Calls = append(dwcalls.Calls, ic)
callIdx = len(dwcalls.Calls) - 1
imap[inlIdx] = callIdx
if parCallIdx != -1 {
// Add this inline to parent's child list
dwcalls.Calls[parCallIdx].Children = append(dwcalls.Calls[parCallIdx].Children, callIdx)
}
return callIdx
}
// Given a src.XPos, return its associated inlining index if it
// corresponds to something created as a result of an inline, or -1 if
// there is no inline info. Note that the index returned will refer to
// the deepest call in the inlined stack, e.g. if you have "A calls B
// calls C calls D" and all three callees are inlined (B, C, and D),
// the index for a node from the inlined body of D will refer to the
// call to D from C. Whew.
func posInlIndex(xpos src.XPos) int {
pos := Ctxt.PosTable.Pos(xpos)
if b := pos.Base(); b != nil {
ii := b.InliningIndex()
if ii >= 0 {
return ii
}
}
return -1
}
func endRange(crange *dwarf.Range, p *obj.Prog) {
if crange == nil {
return
}
crange.End = p.Pc
}
func beginRange(calls []dwarf.InlCall, p *obj.Prog, ii int, imap map[int]int) *dwarf.Range {
if ii == -1 {
return nil
}
callIdx, found := imap[ii]
if !found {
Fatalf("internal error: can't find inlIndex %d in imap for prog at %d\n", ii, p.Pc)
}
call := &calls[callIdx]
// Set up range and append to correct inlined call
call.Ranges = append(call.Ranges, dwarf.Range{Start: p.Pc, End: -1})
return &call.Ranges[len(call.Ranges)-1]
}
func cmpDwarfVar(a, b *dwarf.Var) bool {
// named before artificial
aart := 0
if strings.HasPrefix(a.Name, "~r") {
aart = 1
}
bart := 0
if strings.HasPrefix(b.Name, "~r") {
bart = 1
}
if aart != bart {
return aart < bart
}
// otherwise sort by name
return a.Name < b.Name
}
// byClassThenName implements sort.Interface for []*dwarf.Var using cmpDwarfVar.
type byClassThenName []*dwarf.Var
func (s byClassThenName) Len() int { return len(s) }
func (s byClassThenName) Less(i, j int) bool { return cmpDwarfVar(s[i], s[j]) }
func (s byClassThenName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) {
for i := 0; i < ilevel; i += 1 {
Ctxt.Logf(" ")
}
ic := inlcalls.Calls[idx]
callee := Ctxt.InlTree.InlinedFunction(ic.InlIndex)
Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name)
for _, f := range ic.InlVars {
Ctxt.Logf(" %v", f.Name)
}
Ctxt.Logf(" ) C: (")
for _, k := range ic.Children {
Ctxt.Logf(" %v", k)
}
Ctxt.Logf(" ) R:")
for _, r := range ic.Ranges {
Ctxt.Logf(" [%d,%d)", r.Start, r.End)
}
Ctxt.Logf("\n")
for _, k := range ic.Children {
dumpInlCall(inlcalls, k, ilevel+1)
}
}
func dumpInlCalls(inlcalls dwarf.InlCalls) {
n := len(inlcalls.Calls)
for k := 0; k < n; k += 1 {
if inlcalls.Calls[k].Root {
dumpInlCall(inlcalls, k, 0)
}
}
}
func dumpInlVars(dwvars []*dwarf.Var) {
for i, dwv := range dwvars {
typ := "local"
if dwv.Abbrev == dwarf.DW_ABRV_PARAM_LOCLIST || dwv.Abbrev == dwarf.DW_ABRV_PARAM {
typ = "param"
}
Ctxt.Logf("V%d: %s CI:%d II:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, typ)
}
}
......@@ -219,6 +219,11 @@ var instrumenting bool
// Whether we are tracking lexical scopes for DWARF.
var trackScopes bool
// Controls generation of DWARF inlined instance records. Zero
// disables, 1 emits inlined routines but suppresses var info,
// and 2 emits inlined routines with tracking of formals/locals.
var genDwarfInline int
var debuglive int
var Ctxt *obj.Link
......
......@@ -84,7 +84,7 @@ func (pp *Progs) NewProg() *obj.Prog {
// Flush converts from pp to machine code.
func (pp *Progs) Flush() {
plist := &obj.Plist{Firstpc: pp.Text, Curfn: pp.curfn}
obj.Flushplist(Ctxt, plist, pp.NewProg)
obj.Flushplist(Ctxt, plist, pp.NewProg, myimportpath)
}
// Free clears pp and any associated resources.
......
......@@ -31,8 +31,11 @@ package gc
import (
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/src"
"fmt"
"sort"
"strings"
)
// Get the function's package. For ordinary functions it's on the ->sym, but for imported methods
......@@ -809,6 +812,9 @@ func mkinlcall1(n, fn *Node, isddd bool) *Node {
// Make temp names to use instead of the originals.
inlvars := make(map[*Node]*Node)
// record formals/locals for later post-processing
var inlfvars []*Node
// Find declarations corresponding to inlineable body.
var dcl []*Node
if fn.Name.Defn != nil {
......@@ -867,13 +873,25 @@ func mkinlcall1(n, fn *Node, isddd bool) *Node {
if ln.Class() == PPARAM || ln.Name.Param.Stackcopy != nil && ln.Name.Param.Stackcopy.Class() == PPARAM {
ninit.Append(nod(ODCL, inlvars[ln], nil))
}
if genDwarfInline > 0 {
inlf := inlvars[ln]
if ln.Class() == PPARAM {
inlf.SetInlFormal(true)
} else {
inlf.SetInlLocal(true)
}
inlf.Pos = ln.Pos
inlfvars = append(inlfvars, inlf)
}
}
// temporaries for return values.
var retvars []*Node
for i, t := range fn.Type.Results().Fields().Slice() {
var m *Node
var mpos src.XPos
if t != nil && asNode(t.Nname) != nil && !isblank(asNode(t.Nname)) {
mpos = asNode(t.Nname).Pos
m = inlvar(asNode(t.Nname))
m = typecheck(m, Erv)
inlvars[asNode(t.Nname)] = m
......@@ -882,6 +900,17 @@ func mkinlcall1(n, fn *Node, isddd bool) *Node {
m = retvar(t, i)
}
if genDwarfInline > 0 {
// Don't update the src.Pos on a return variable if it
// was manufactured by the inliner (e.g. "~r2"); such vars
// were not part of the original callee.
if !strings.HasPrefix(m.Sym.Name, "~r") {
m.SetInlFormal(true)
m.Pos = mpos
inlfvars = append(inlfvars, m)
}
}
ninit.Append(nod(ODCL, m, nil))
retvars = append(retvars, m)
}
......@@ -976,8 +1005,16 @@ func mkinlcall1(n, fn *Node, isddd bool) *Node {
if b := Ctxt.PosTable.Pos(n.Pos).Base(); b != nil {
parent = b.InliningIndex()
}
sort.Sort(byNodeName(dcl))
newIndex := Ctxt.InlTree.Add(parent, n.Pos, fn.Sym.Linksym())
if genDwarfInline > 0 {
if !fn.Sym.Linksym().WasInlined() {
Ctxt.DwFixups.SetPrecursorFunc(fn.Sym.Linksym(), fn)
fn.Sym.Linksym().Set(obj.AttrWasInlined, true)
}
}
subst := inlsubst{
retlabel: retlabel,
retvars: retvars,
......@@ -993,6 +1030,12 @@ func mkinlcall1(n, fn *Node, isddd bool) *Node {
typecheckslice(body, Etop)
if genDwarfInline > 0 {
for _, v := range inlfvars {
v.Pos = subst.updatedPos(v.Pos)
}
}
//dumplist("ninit post", ninit);
call := nod(OINLCALL, nil, nil)
......@@ -1192,3 +1235,28 @@ func (subst *inlsubst) updatedPos(xpos src.XPos) src.XPos {
pos.SetBase(newbase)
return Ctxt.PosTable.XPos(pos)
}
func cmpNodeName(a, b *Node) bool {
// named before artificial
aart := 0
if strings.HasPrefix(a.Sym.Name, "~r") {
aart = 1
}
bart := 0
if strings.HasPrefix(b.Sym.Name, "~r") {
bart = 1
}
if aart != bart {
return aart < bart
}
// otherwise sort by name
return a.Sym.Name < b.Sym.Name
}
// byNodeName implements sort.Interface for []*Node using cmpNodeName.
type byNodeName []*Node
func (s byNodeName) Len() int { return len(s) }
func (s byNodeName) Less(i, j int) bool { return cmpNodeName(s[i], s[j]) }
func (s byNodeName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
......@@ -48,6 +48,7 @@ var (
Debug_pctab string
Debug_locationlist int
Debug_typecheckinl int
Debug_gendwarfinl int
)
// Debug arguments.
......@@ -76,6 +77,7 @@ var debugtab = []struct {
{"pctab", "print named pc-value table", &Debug_pctab},
{"locationlists", "print information about DWARF location list creation", &Debug_locationlist},
{"typecheckinl", "eager typechecking of inline function bodies", &Debug_typecheckinl},
{"dwarfinl", "print information about DWARF inlined function creation", &Debug_gendwarfinl},
}
const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
......@@ -191,6 +193,7 @@ func Main(archInit func(*Arch)) {
flag.StringVar(&debugstr, "d", "", "print debug information about items in `list`; try -d help")
flag.BoolVar(&flagDWARF, "dwarf", true, "generate DWARF symbols")
flag.BoolVar(&Ctxt.Flag_locationlists, "dwarflocationlists", false, "add location lists to DWARF in optimized mode")
flag.IntVar(&genDwarfInline, "gendwarfinl", 2, "generate DWARF inline info records")
objabi.Flagcount("e", "no limit on number of errors reported", &Debug['e'])
objabi.Flagcount("f", "debug stack frames", &Debug['f'])
objabi.Flagcount("h", "halt on error", &Debug['h'])
......@@ -247,6 +250,11 @@ func Main(archInit func(*Arch)) {
Ctxt.Debugvlog = Debug_vlog
if flagDWARF {
Ctxt.DebugInfo = debuginfo
Ctxt.GenAbstractFunc = genAbstractFunc
Ctxt.DwFixups = obj.NewDwarfFixupTable(Ctxt)
} else {
// turn off inline generation if no dwarf at all
genDwarfInline = 0
}
if flag.NArg() < 1 && debugstr != "help" && debugstr != "ssa/help" {
......@@ -381,6 +389,9 @@ func Main(archInit func(*Arch)) {
// set via a -d flag
Ctxt.Debugpcln = Debug_pctab
if flagDWARF {
dwarf.EnableLogging(Debug_gendwarfinl != 0)
}
// enable inlining. for now:
// default: inlining on. (debug['l'] == 1)
......@@ -631,6 +642,15 @@ func Main(archInit func(*Arch)) {
nowritebarrierrecCheck = nil
}
// Finalize DWARF inline routine DIEs, then explicitly turn off
// DWARF inlining gen so as to avoid problems with generated
// method wrappers.
if Ctxt.DwFixups != nil {
Ctxt.DwFixups.Finalize(myimportpath, Debug_gendwarfinl != 0)
Ctxt.DwFixups = nil
genDwarfInline = 0
}
// Check whether any of the functions we have compiled have gigantic stack frames.
obj.SortSlice(largeStackFrames, func(i, j int) bool {
return largeStackFrames[i].Before(largeStackFrames[j])
......
......@@ -16,6 +16,7 @@ import (
"math"
"math/rand"
"sort"
"strings"
"sync"
"time"
)
......@@ -279,6 +280,7 @@ func compileFunctions() {
})
}
var wg sync.WaitGroup
Ctxt.InParallel = true
c := make(chan *Node, nBackendWorkers)
for i := 0; i < nBackendWorkers; i++ {
wg.Add(1)
......@@ -295,17 +297,20 @@ func compileFunctions() {
close(c)
compilequeue = nil
wg.Wait()
Ctxt.InParallel = false
sizeCalculationDisabled = false
}
}
func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope {
func debuginfo(fnsym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) {
fn := curfn.(*Node)
debugInfo := fn.Func.DebugInfo
fn.Func.DebugInfo = nil
if fn.Func.Nname != nil {
if expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect {
Fatalf("unexpected fnsym: %v != %v", fnsym, expect)
}
}
var automDecls []*Node
// Populate Automs for fn.
......@@ -335,13 +340,7 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope {
})
}
var dwarfVars []*dwarf.Var
var decls []*Node
if Ctxt.Flag_locationlists && Ctxt.Flag_optimize {
decls, dwarfVars = createComplexVars(fnsym, debugInfo, automDecls)
} else {
decls, dwarfVars = createSimpleVars(automDecls)
}
decls, dwarfVars := createDwarfVars(fnsym, debugInfo, automDecls)
var varScopes []ScopeID
for _, decl := range decls {
......@@ -365,14 +364,21 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope {
}
varScopes = append(varScopes, findScope(fn.Func.Marks, pos))
}
return assembleScopes(fnsym, fn, dwarfVars, varScopes)
scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes)
var inlcalls dwarf.InlCalls
if genDwarfInline > 0 {
inlcalls = assembleInlines(fnsym, fn, dwarfVars)
}
return scopes, inlcalls
}
// createSimpleVars creates a DWARF entry for every variable declared in the
// function, claiming that they are permanently on the stack.
func createSimpleVars(automDecls []*Node) ([]*Node, []*dwarf.Var) {
func createSimpleVars(automDecls []*Node) ([]*Node, []*dwarf.Var, map[*Node]bool) {
var vars []*dwarf.Var
var decls []*Node
selected := make(map[*Node]bool)
for _, n := range automDecls {
if n.IsAutoTmp() {
continue
......@@ -397,18 +403,31 @@ func createSimpleVars(automDecls []*Node) ([]*Node, []*dwarf.Var) {
Fatalf("createSimpleVars unexpected type %v for node %v", n.Class(), n)
}
selected[n] = true
typename := dwarf.InfoPrefix + typesymname(n.Type)
decls = append(decls, n)
inlIndex := 0
if genDwarfInline > 1 {
if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos) + 1
}
}
declpos := Ctxt.InnermostPos(n.Pos)
vars = append(vars, &dwarf.Var{
Name: n.Sym.Name,
IsReturnValue: n.Class() == PPARAMOUT,
IsInlFormal: n.InlFormal(),
Abbrev: abbrev,
StackOffset: int32(offs),
Type: Ctxt.Lookup(typename),
DeclLine: n.Pos.Line(),
DeclFile: declpos.Base().SymFilename(),
DeclLine: declpos.Line(),
DeclCol: declpos.Col(),
InlIndex: int32(inlIndex),
ChildIndex: -1,
})
}
return decls, vars
return decls, vars, selected
}
type varPart struct {
......@@ -416,7 +435,7 @@ type varPart struct {
slot ssa.SlotID
}
func createComplexVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*Node) ([]*Node, []*dwarf.Var) {
func createComplexVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*Node) ([]*Node, []*dwarf.Var, map[*Node]bool) {
for _, blockDebug := range debugInfo.Blocks {
for _, locList := range blockDebug.Variables {
for _, loc := range locList.Locations {
......@@ -475,20 +494,43 @@ func createComplexVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*
}
}
// The machinery above will create a dwarf.Var for only those
// variables that are decomposed into SSA names. Fill in the list
// with entries for the remaining variables (including things too
// big to decompose). Since optimization is enabled, the recipe
// below creates a conservative location. The idea here is that we
// want to communicate to the user that "yes, there is a variable
// named X in this function, but no, I don't have enough
// information to reliably report its contents."
for _, n := range automDecls {
if _, found := ssaVars[n]; found {
return decls, vars, ssaVars
}
func createDwarfVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*Node) ([]*Node, []*dwarf.Var) {
// Collect a raw list of DWARF vars.
var vars []*dwarf.Var
var decls []*Node
var selected map[*Node]bool
if Ctxt.Flag_locationlists && Ctxt.Flag_optimize && debugInfo != nil {
decls, vars, selected = createComplexVars(fnsym, debugInfo, automDecls)
} else {
decls, vars, selected = createSimpleVars(automDecls)
}
var dcl []*Node
var chopVersion bool
if fnsym.WasInlined() {
dcl, chopVersion = preInliningDcls(fnsym)
} else {
dcl = automDecls
}
// If optimization is enabled, the list above will typically be
// missing some of the original pre-optimization variables in the
// function (they may have been promoted to registers, folded into
// constants, dead-coded away, etc). Here we add back in entries
// for selected missing vars. Note that the recipe below creates a
// conservative location. The idea here is that we want to
// communicate to the user that "yes, there is a variable named X
// in this function, but no, I don't have enough information to
// reliably report its contents."
for _, n := range dcl {
if _, found := selected[n]; found {
continue
}
c := n.Sym.Name[0]
if c == '~' || c == '.' {
if c == '~' || c == '.' || n.Type.IsUntyped() {
continue
}
typename := dwarf.InfoPrefix + typesymname(n.Type)
......@@ -497,19 +539,70 @@ func createComplexVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*
if n.Class() == PPARAM || n.Class() == PPARAMOUT {
abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
}
inlIndex := 0
if genDwarfInline > 1 {
if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos) + 1
}
}
declpos := Ctxt.InnermostPos(n.Pos)
vars = append(vars, &dwarf.Var{
Name: n.Sym.Name,
IsReturnValue: n.Class() == PPARAMOUT,
Abbrev: abbrev,
StackOffset: int32(n.Xoffset),
Type: Ctxt.Lookup(typename),
DeclLine: n.Pos.Line(),
DeclFile: declpos.Base().SymFilename(),
DeclLine: declpos.Line(),
DeclCol: declpos.Col(),
InlIndex: int32(inlIndex),
ChildIndex: -1,
})
}
// Parameter and local variable names are given middle dot
// version numbers as part of the writing them out to export
// data (see issue 4326). If DWARF inlined routine generation
// is turned on, undo this versioning, since DWARF variables
// in question will be parented by the inlined routine and
// not the top-level caller.
if genDwarfInline > 1 && chopVersion {
for _, v := range vars {
if v.InlIndex != -1 {
if i := strings.Index(v.Name, "·"); i > 0 {
v.Name = v.Name[:i] // cut off Vargen
}
}
}
}
return decls, vars
}
// Given a function that was inlined at some point during the compilation,
// return a list of nodes corresponding to the autos/locals in that
// function prior to inlining. Untyped and compiler-synthesized vars are
// stripped out along the way.
func preInliningDcls(fnsym *obj.LSym) ([]*Node, bool) {
fn := Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*Node)
imported := false
var dcl, rdcl []*Node
if fn.Name.Defn != nil {
dcl = fn.Func.Inldcl.Slice() // local function
} else {
dcl = fn.Func.Dcl // imported function
imported = true
}
for _, n := range dcl {
c := n.Sym.Name[0]
if c == '~' || c == '.' || n.Type.IsUntyped() {
continue
}
rdcl = append(rdcl, n)
}
return rdcl, imported
}
// varOffset returns the offset of slot within the user variable it was
// decomposed from. This has nothing to do with its stack offset.
func varOffset(slot *ssa.LocalSlot) int64 {
......@@ -570,8 +663,17 @@ func createComplexVar(debugInfo *ssa.FuncDebug, n *Node, parts []varPart) *dwarf
gotype := ngotype(n).Linksym()
typename := dwarf.InfoPrefix + gotype.Name[len("type."):]
inlIndex := 0
if genDwarfInline > 1 {
if n.InlFormal() || n.InlLocal() {
inlIndex = posInlIndex(n.Pos) + 1
}
}
declpos := Ctxt.InnermostPos(n.Pos)
dvar := &dwarf.Var{
Name: n.Sym.Name,
IsReturnValue: n.Class() == PPARAMOUT,
IsInlFormal: n.InlFormal(),
Abbrev: abbrev,
Type: Ctxt.Lookup(typename),
// The stack offset is used as a sorting key, so for decomposed
......@@ -579,7 +681,11 @@ func createComplexVar(debugInfo *ssa.FuncDebug, n *Node, parts []varPart) *dwarf
// This won't work well if the first slot hasn't been assigned a stack
// location, but it's not obvious how to do better.
StackOffset: int32(stackOffset(slots[parts[0].slot])),
DeclLine: n.Pos.Line(),
DeclFile: declpos.Base().SymFilename(),
DeclLine: declpos.Line(),
DeclCol: declpos.Col(),
InlIndex: int32(inlIndex),
ChildIndex: -1,
}
if Debug_locationlist != 0 {
......
......@@ -97,6 +97,8 @@ const (
_, nodeHasVal // node.E contains a Val
_, nodeHasOpt // node.E contains an Opt
_, nodeEmbedded // ODCLFIELD embedded type
_, nodeInlFormal // OPAUTO created by inliner, derived from callee formal
_, nodeInlLocal // OPAUTO created by inliner, derived from callee local
)
func (n *Node) Class() Class { return Class(n.flags.get3(nodeClass)) }
......@@ -123,6 +125,8 @@ func (n *Node) Likely() bool { return n.flags&nodeLikely != 0 }
func (n *Node) HasVal() bool { return n.flags&nodeHasVal != 0 }
func (n *Node) HasOpt() bool { return n.flags&nodeHasOpt != 0 }
func (n *Node) Embedded() bool { return n.flags&nodeEmbedded != 0 }
func (n *Node) InlFormal() bool { return n.flags&nodeInlFormal != 0 }
func (n *Node) InlLocal() bool { return n.flags&nodeInlLocal != 0 }
func (n *Node) SetClass(b Class) { n.flags.set3(nodeClass, uint8(b)) }
func (n *Node) SetWalkdef(b uint8) { n.flags.set2(nodeWalkdef, b) }
......@@ -148,6 +152,8 @@ func (n *Node) SetLikely(b bool) { n.flags.set(nodeLikely, b) }
func (n *Node) SetHasVal(b bool) { n.flags.set(nodeHasVal, b) }
func (n *Node) SetHasOpt(b bool) { n.flags.set(nodeHasOpt, b) }
func (n *Node) SetEmbedded(b bool) { n.flags.set(nodeEmbedded, b) }
func (n *Node) SetInlFormal(b bool) { n.flags.set(nodeInlFormal, b) }
func (n *Node) SetInlLocal(b bool) { n.flags.set(nodeInlLocal, b) }
// Val returns the Val for the node.
func (n *Node) Val() Val {
......
This diff is collapsed.
......@@ -6,7 +6,7 @@ package obj
import "cmd/internal/src"
// InlTree s a collection of inlined calls. The Parent field of an
// InlTree is a collection of inlined calls. The Parent field of an
// InlinedCall is the index of another InlinedCall in InlTree.
//
// The compiler maintains a global inlining tree and adds a node to it
......@@ -64,6 +64,18 @@ func (tree *InlTree) Add(parent int, pos src.XPos, func_ *LSym) int {
return r
}
func (tree *InlTree) Parent(inlIndex int) int {
return tree.nodes[inlIndex].Parent
}
func (tree *InlTree) InlinedFunction(inlIndex int) *LSym {
return tree.nodes[inlIndex].Func
}
func (tree *InlTree) CallPos(inlIndex int) src.XPos {
return tree.nodes[inlIndex].Pos
}
// OutermostPos returns the outermost position corresponding to xpos,
// which is where xpos was ultimately inlined to. In the example for
// InlTree, main() contains inlined AST nodes from h(), but the
......
......@@ -389,6 +389,7 @@ type FuncInfo struct {
dwarfInfoSym *LSym
dwarfLocSym *LSym
dwarfRangesSym *LSym
dwarfAbsFnSym *LSym
GCArgs LSym
GCLocals LSym
......@@ -427,6 +428,10 @@ const (
// definition. (When not compiling to support Go shared libraries, all symbols are
// local in this sense unless there is a cgo_export_* directive).
AttrLocal
// For function symbols; indicates that the specified function was the
// target of an inline during compilation
AttrWasInlined
)
func (a Attribute) DuplicateOK() bool { return a&AttrDuplicateOK != 0 }
......@@ -442,6 +447,7 @@ func (a Attribute) Wrapper() bool { return a&AttrWrapper != 0 }
func (a Attribute) NeedCtxt() bool { return a&AttrNeedCtxt != 0 }
func (a Attribute) NoFrame() bool { return a&AttrNoFrame != 0 }
func (a Attribute) Static() bool { return a&AttrStatic != 0 }
func (a Attribute) WasInlined() bool { return a&AttrWasInlined != 0 }
func (a *Attribute) Set(flag Attribute, value bool) {
if value {
......@@ -468,6 +474,7 @@ var textAttrStrings = [...]struct {
{bit: AttrNeedCtxt, s: "NEEDCTXT"},
{bit: AttrNoFrame, s: "NOFRAME"},
{bit: AttrStatic, s: "STATIC"},
{bit: AttrWasInlined, s: ""},
}
// TextAttrString formats a for printing in as part of a TEXT prog.
......@@ -549,12 +556,15 @@ type Link struct {
statichash map[string]*LSym // name -> sym mapping for static syms
PosTable src.PosTable
InlTree InlTree // global inlining tree used by gc/inl.go
DwFixups *DwarfFixupTable
Imports []string
DiagFunc func(string, ...interface{})
DiagFlush func()
DebugInfo func(fn *LSym, curfn interface{}) []dwarf.Scope // if non-nil, curfn is a *gc.Node
DebugInfo func(fn *LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) // if non-nil, curfn is a *gc.Node
GenAbstractFunc func(fn *LSym)
Errors int
InParallel bool // parallel backend phase in effect
Framepointer_enabled bool
// state for writing objects
......
This diff is collapsed.
......@@ -19,7 +19,7 @@ type Plist struct {
// It is used to provide access to cached/bulk-allocated Progs to the assemblers.
type ProgAlloc func() *Prog
func Flushplist(ctxt *Link, plist *Plist, newprog ProgAlloc) {
func Flushplist(ctxt *Link, plist *Plist, newprog ProgAlloc, myimportpath string) {
// Build list of symbols, and assign instructions to lists.
var curtext *LSym
var etext *Prog
......@@ -106,7 +106,7 @@ func Flushplist(ctxt *Link, plist *Plist, newprog ProgAlloc) {
ctxt.Arch.Preprocess(ctxt, s, newprog)
ctxt.Arch.Assemble(ctxt, s, newprog)
linkpcln(ctxt, s)
ctxt.populateDWARF(plist.Curfn, s)
ctxt.populateDWARF(plist.Curfn, s, myimportpath)
}
}
......@@ -136,7 +136,7 @@ func (ctxt *Link) InitTextSym(s *LSym, flag int) {
ctxt.Text = append(ctxt.Text, s)
// Set up DWARF entries for s.
info, loc, ranges := ctxt.dwarfSym(s)
info, loc, ranges, _ := ctxt.dwarfSym(s)
info.Type = objabi.SDWARFINFO
info.Set(AttrDuplicateOK, s.DuplicateOK())
if loc != nil {
......
......@@ -72,10 +72,28 @@ func (c dwctxt) AddSectionOffset(s dwarf.Sym, size int, t interface{}, ofs int64
r.Add = ofs
}
func (c dwctxt) Logf(format string, args ...interface{}) {
c.linkctxt.Logf(format, args...)
}
// At the moment these interfaces are only used in the compiler.
func (c dwctxt) AddFileRef(s dwarf.Sym, f interface{}) {
panic("should be used only in the compiler")
}
func (c dwctxt) CurrentOffset(s dwarf.Sym) int64 {
panic("should be used only in the compiler")
}
func (c dwctxt) RecordDclReference(s dwarf.Sym, t dwarf.Sym, dclIdx int, inlIndex int) {
panic("should be used only in the compiler")
}
func (c dwctxt) RecordChildDieOffsets(s dwarf.Sym, vars []*dwarf.Var, offsets []int32) {
panic("should be used only in the compiler")
}
var gdbscript string
var dwarfp []*sym.Symbol
......@@ -132,8 +150,10 @@ func getattr(die *dwarf.DWDie, attr uint16) *dwarf.DWAttr {
return nil
}
// Every DIE has at least an AT_name attribute (but it will only be
// written out if it is listed in the abbrev).
// Every DIE manufactured by the linker has at least an AT_name
// attribute (but it will only be written out if it is listed in the abbrev).
// The compiler does create nameless DWARF DIEs (ex: concrete subprogram
// instance).
func newdie(ctxt *Link, parent *dwarf.DWDie, abbrev int, name string, version int) *dwarf.DWDie {
die := new(dwarf.DWDie)
die.Abbrev = abbrev
......@@ -854,6 +874,7 @@ type compilationUnit struct {
pcs []dwarf.Range // PC ranges, relative to textp[0]
dwinfo *dwarf.DWDie // CU root DIE
funcDIEs []*sym.Symbol // Function DIE subtrees
absFnDIEs []*sym.Symbol // Abstract function DIE subtrees
}
// getCompilationUnits divides the symbols in ctxt.Textp by package.
......@@ -1052,7 +1073,52 @@ func importInfoSymbol(ctxt *Link, dsym *sym.Symbol) {
}
}
func writelines(ctxt *Link, lib *sym.Library, textp []*sym.Symbol, ls *sym.Symbol) (dwinfo *dwarf.DWDie, funcs []*sym.Symbol) {
// For the specified function, collect symbols corresponding to any
// "abstract" subprogram DIEs referenced. The first case of interest
// is a concrete subprogram DIE, which will refer to its corresponding
// abstract subprogram DIE, and then there can be references from a
// non-abstract subprogram DIE to the abstract subprogram DIEs for any
// functions inlined into this one.
//
// A given abstract subprogram DIE can be referenced in numerous
// places (even within the same DIE), so it is important to make sure
// it gets imported and added to the absfuncs lists only once.
func collectAbstractFunctions(ctxt *Link, fn *sym.Symbol, dsym *sym.Symbol, absfuncs []*sym.Symbol) []*sym.Symbol {
var newabsfns []*sym.Symbol
// Walk the relocations on the primary subprogram DIE and look for
// references to abstract funcs.
for _, reloc := range dsym.R {
candsym := reloc.Sym
if reloc.Type != objabi.R_DWARFSECREF {
continue
}
if !strings.HasPrefix(candsym.Name, dwarf.InfoPrefix) {
continue
}
if !strings.HasSuffix(candsym.Name, dwarf.AbstractFuncSuffix) {
continue
}
if candsym.Attr.OnList() {
continue
}
candsym.Attr |= sym.AttrOnList
newabsfns = append(newabsfns, candsym)
}
// Import any new symbols that have turned up.
for _, absdsym := range newabsfns {
importInfoSymbol(ctxt, absdsym)
absfuncs = append(absfuncs, absdsym)
}
return absfuncs
}
func writelines(ctxt *Link, lib *sym.Library, textp []*sym.Symbol, ls *sym.Symbol) (dwinfo *dwarf.DWDie, funcs []*sym.Symbol, absfuncs []*sym.Symbol) {
var dwarfctxt dwarf.Context = dwctxt{ctxt}
unitstart := int64(-1)
......@@ -1125,6 +1191,27 @@ func writelines(ctxt *Link, lib *sym.Library, textp []*sym.Symbol, ls *sym.Symbo
ls.AddUint8(0)
ls.AddUint8(0)
}
// Look up the .debug_info sym for the function. We do this
// now so that we can walk the sym's relocations to discover
// files that aren't mentioned in S.FuncInfo.File (for
// example, files mentioned only in an inlined subroutine).
dsym := ctxt.Syms.Lookup(dwarf.InfoPrefix+s.Name, int(s.Version))
importInfoSymbol(ctxt, dsym)
for ri := 0; ri < len(dsym.R); ri++ {
r := &dsym.R[ri]
if r.Type != objabi.R_DWARFFILEREF {
continue
}
_, ok := fileNums[int(r.Sym.Value)]
if !ok {
fileNums[int(r.Sym.Value)] = len(fileNums) + 1
Addstring(ls, r.Sym.Name)
ls.AddUint8(0)
ls.AddUint8(0)
ls.AddUint8(0)
}
}
}
// 4 zeros: the string termination + 3 fields.
......@@ -1146,8 +1233,8 @@ func writelines(ctxt *Link, lib *sym.Library, textp []*sym.Symbol, ls *sym.Symbo
var pcline Pciter
for _, s := range textp {
dsym := ctxt.Syms.Lookup(dwarf.InfoPrefix+s.Name, int(s.Version))
importInfoSymbol(ctxt, dsym)
funcs = append(funcs, dsym)
absfuncs = collectAbstractFunctions(ctxt, s, dsym, absfuncs)
finddebugruntimepath(s)
......@@ -1201,6 +1288,7 @@ func writelines(ctxt *Link, lib *sym.Library, textp []*sym.Symbol, ls *sym.Symbo
// changed to generate DW_AT_decl_file attributes for other
// DIE flavors (ex: variables) then those DIEs would need to
// be included below.
missing := make(map[int]interface{})
for fidx := 0; fidx < len(funcs); fidx++ {
f := funcs[fidx]
for ri := 0; ri < len(f.R); ri++ {
......@@ -1224,12 +1312,16 @@ func writelines(ctxt *Link, lib *sym.Library, textp []*sym.Symbol, ls *sym.Symbo
}
ctxt.Arch.ByteOrder.PutUint32(f.P[r.Off:r.Off+4], uint32(idx))
} else {
Errorf(f, "R_DWARFFILEREF relocation file missing: %v", r.Sym)
_, found := missing[int(r.Sym.Value)]
if !found {
Errorf(f, "R_DWARFFILEREF relocation file missing: %v idx %d", r.Sym, r.Sym.Value)
missing[int(r.Sym.Value)] = nil
}
}
}
}
return dwinfo, funcs
return dwinfo, funcs, absfuncs
}
// writepcranges generates the DW_AT_ranges table for compilation unit cu.
......@@ -1434,6 +1526,7 @@ func writeinfo(ctxt *Link, syms []*sym.Symbol, units []*compilationUnit, abbrevs
dwarf.PutAttrs(dwarfctxt, s, compunit.Abbrev, compunit.Attr)
cu := []*sym.Symbol{s}
cu = append(cu, u.absFnDIEs...)
cu = append(cu, u.funcDIEs...)
if u.consts != nil {
cu = append(cu, u.consts)
......@@ -1621,7 +1714,7 @@ func dwarfgeneratedebugsyms(ctxt *Link) {
debugRanges.Attr |= sym.AttrReachable
syms = append(syms, debugLine)
for _, u := range units {
u.dwinfo, u.funcDIEs = writelines(ctxt, u.lib, u.lib.Textp, debugLine)
u.dwinfo, u.funcDIEs, u.absFnDIEs = writelines(ctxt, u.lib, u.lib.Textp, debugLine)
writepcranges(ctxt, u.dwinfo, u.lib.Textp[0], u.pcs, debugRanges)
}
......
......@@ -7,6 +7,8 @@ package ld
import (
objfilepkg "cmd/internal/objfile" // renamed to avoid conflict with objfile function
"debug/dwarf"
"errors"
"fmt"
"internal/testenv"
"io/ioutil"
"os"
......@@ -30,7 +32,7 @@ func TestRuntimeTypeDIEs(t *testing.T) {
}
defer os.RemoveAll(dir)
f := gobuild(t, dir, `package main; func main() { }`)
f := gobuild(t, dir, `package main; func main() { }`, false)
defer f.Close()
dwarf, err := f.DWARF()
......@@ -75,7 +77,7 @@ func findTypes(t *testing.T, dw *dwarf.Data, want map[string]bool) (found map[st
return
}
func gobuild(t *testing.T, dir string, testfile string) *objfilepkg.File {
func gobuild(t *testing.T, dir string, testfile string, opt bool) *objfilepkg.File {
src := filepath.Join(dir, "test.go")
dst := filepath.Join(dir, "out")
......@@ -83,7 +85,11 @@ func gobuild(t *testing.T, dir string, testfile string) *objfilepkg.File {
t.Fatal(err)
}
cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-N -l", "-o", dst, src)
gcflags := "-gcflags=-N -l"
if opt {
gcflags = "-gcflags=-l=4"
}
cmd := exec.Command(testenv.GoToolPath(t), "build", gcflags, "-o", dst, src)
if b, err := cmd.CombinedOutput(); err != nil {
t.Logf("build: %s\n", b)
t.Fatalf("build error: %v", err)
......@@ -136,7 +142,7 @@ func main() {
}
defer os.RemoveAll(dir)
f := gobuild(t, dir, prog)
f := gobuild(t, dir, prog, false)
defer f.Close()
......@@ -214,7 +220,7 @@ func main() {
t.Fatalf("could not create directory: %v", err)
}
defer os.RemoveAll(dir)
f := gobuild(t, dir, prog)
f := gobuild(t, dir, prog, false)
defer f.Close()
d, err := f.DWARF()
if err != nil {
......@@ -262,7 +268,7 @@ func main() {
}
defer os.RemoveAll(dir)
f := gobuild(t, dir, prog)
f := gobuild(t, dir, prog, false)
defer f.Close()
d, err := f.DWARF()
......@@ -320,7 +326,7 @@ func main() {
}
defer os.RemoveAll(dir)
f := gobuild(t, dir, prog)
f := gobuild(t, dir, prog, false)
d, err := f.DWARF()
if err != nil {
......@@ -328,37 +334,274 @@ func main() {
}
rdr := d.Reader()
var iEntry *dwarf.Entry
var pEntry *dwarf.Entry
foundMain := false
for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
if err != nil {
ex := examiner{}
if err := ex.populate(rdr); err != nil {
t.Fatalf("error reading DWARF: %v", err)
}
if entry.Tag == dwarf.TagSubprogram && entry.Val(dwarf.AttrName).(string) == "main.main" {
foundMain = true
pEntry = entry
continue
// Locate the main.main DIE
mains := ex.Named("main.main")
if len(mains) == 0 {
t.Fatalf("unable to locate DIE for main.main")
}
if !foundMain {
continue
if len(mains) != 1 {
t.Fatalf("more than one main.main DIE")
}
if entry.Tag == dwarf.TagSubprogram {
t.Fatalf("didn't find DW_TAG_variable for i in main.main")
maindie := mains[0]
// Vet the main.main DIE
if maindie.Tag != dwarf.TagSubprogram {
t.Fatalf("unexpected tag %v on main.main DIE", maindie.Tag)
}
if foundMain && entry.Tag == dwarf.TagVariable && entry.Val(dwarf.AttrName).(string) == "i" {
iEntry = entry
// Walk main's children and select variable "i".
mainIdx := ex.idxFromOffset(maindie.Offset)
childDies := ex.Children(mainIdx)
var iEntry *dwarf.Entry
for _, child := range childDies {
if child.Tag == dwarf.TagVariable && child.Val(dwarf.AttrName).(string) == "i" {
iEntry = child
break
}
}
if iEntry == nil {
t.Fatalf("didn't find DW_TAG_variable for i in main.main")
}
// Verify line/file attributes.
line := iEntry.Val(dwarf.AttrDeclLine)
if line == nil || line.(int64) != 5 {
t.Errorf("DW_AT_decl_line for i is %v, want 5", line)
}
file := pEntry.Val(dwarf.AttrDeclFile)
file := maindie.Val(dwarf.AttrDeclFile)
if file == nil || file.(int64) != 1 {
t.Errorf("DW_AT_decl_file for main is %v, want 1", file)
}
}
// Helper class for supporting queries on DIEs within a DWARF .debug_info
// section. Invoke the populate() method below passing in a dwarf.Reader,
// which will read in all DIEs and keep track of parent/child
// relationships. Queries can then be made to ask for DIEs by name or
// by offset. This will hopefully reduce boilerplate for future test
// writing.
type examiner struct {
dies []*dwarf.Entry
idxByOffset map[dwarf.Offset]int
kids map[int][]int
byname map[string][]int
}
// Populate the examiner using the DIEs read from rdr.
func (ex *examiner) populate(rdr *dwarf.Reader) error {
ex.idxByOffset = make(map[dwarf.Offset]int)
ex.kids = make(map[int][]int)
ex.byname = make(map[string][]int)
var nesting []int
for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
if err != nil {
return err
}
if entry.Tag == 0 {
// terminator
if len(nesting) == 0 {
return errors.New("nesting stack underflow")
}
nesting = nesting[:len(nesting)-1]
continue
}
idx := len(ex.dies)
ex.dies = append(ex.dies, entry)
if _, found := ex.idxByOffset[entry.Offset]; found {
return errors.New("DIE clash on offset")
}
ex.idxByOffset[entry.Offset] = idx
if name, ok := entry.Val(dwarf.AttrName).(string); ok {
ex.byname[name] = append(ex.byname[name], idx)
}
if len(nesting) > 0 {
parent := nesting[len(nesting)-1]
ex.kids[parent] = append(ex.kids[parent], idx)
}
if entry.Children {
nesting = append(nesting, idx)
}
}
if len(nesting) > 0 {
return errors.New("unterminated child sequence")
}
return nil
}
func indent(ilevel int) {
for i := 0; i < ilevel; i++ {
fmt.Printf(" ")
}
}
// For debugging new tests
func (ex *examiner) dumpEntry(idx int, dumpKids bool, ilevel int) error {
if idx >= len(ex.dies) {
msg := fmt.Sprintf("bad DIE %d: index out of range\n", idx)
return errors.New(msg)
}
entry := ex.dies[idx]
indent(ilevel)
fmt.Printf("%d: %v\n", idx, entry.Tag)
for _, f := range entry.Field {
indent(ilevel)
fmt.Printf("at=%v val=%v\n", f.Attr, f.Val)
}
if dumpKids {
ksl := ex.kids[idx]
for _, k := range ksl {
ex.dumpEntry(k, true, ilevel+2)
}
}
return nil
}
// Given a DIE offset, return the previously read dwarf.Entry, or nil
func (ex *examiner) entryFromOffset(off dwarf.Offset) *dwarf.Entry {
if idx, found := ex.idxByOffset[off]; found && idx != -1 {
return ex.entryFromIdx(idx)
}
return nil
}
// Return the ID that that examiner uses to refer to the DIE at offset off
func (ex *examiner) idxFromOffset(off dwarf.Offset) int {
if idx, found := ex.idxByOffset[off]; found {
return idx
}
return -1
}
// Return the dwarf.Entry pointer for the DIE with id 'idx'
func (ex *examiner) entryFromIdx(idx int) *dwarf.Entry {
if idx >= len(ex.dies) {
return nil
}
return ex.dies[idx]
}
// Returns a list of child entries for a die with ID 'idx'
func (ex *examiner) Children(idx int) []*dwarf.Entry {
sl := ex.kids[idx]
ret := make([]*dwarf.Entry, len(sl))
for i, k := range sl {
ret[i] = ex.entryFromIdx(k)
}
return ret
}
// Return a list of all DIEs with name 'name'. When searching for DIEs
// by name, keep in mind that the returned results will include child
// DIEs such as params/variables. For example, asking for all DIEs named
// "p" for even a small program will give you 400-500 entries.
func (ex *examiner) Named(name string) []*dwarf.Entry {
sl := ex.byname[name]
ret := make([]*dwarf.Entry, len(sl))
for i, k := range sl {
ret[i] = ex.entryFromIdx(k)
}
return ret
}
func TestInlinedRoutineRecords(t *testing.T) {
testenv.MustHaveGoBuild(t)
if runtime.GOOS == "plan9" {
t.Skip("skipping on plan9; no DWARF symbol table in executables")
}
const prog = `
package main
var G int
func cand(x, y int) int {
return (x + y) ^ (y - x)
}
func main() {
x := cand(G*G,G|7%G)
G = x
}
`
dir, err := ioutil.TempDir("", "TestInlinedRoutineRecords")
if err != nil {
t.Fatalf("could not create directory: %v", err)
}
defer os.RemoveAll(dir)
// Note: this is a regular go build here, without "-l -N". The
// test is intended to verify DWARF that is only generated when the
// inliner is active.
f := gobuild(t, dir, prog, true)
d, err := f.DWARF()
if err != nil {
t.Fatalf("error reading DWARF: %v", err)
}
// The inlined subroutines we expect to visit
expectedInl := []string{"main.cand"}
rdr := d.Reader()
ex := examiner{}
if err := ex.populate(rdr); err != nil {
t.Fatalf("error reading DWARF: %v", err)
}
// Locate the main.main DIE
mains := ex.Named("main.main")
if len(mains) == 0 {
t.Fatalf("unable to locate DIE for main.main")
}
if len(mains) != 1 {
t.Fatalf("more than one main.main DIE")
}
maindie := mains[0]
// Vet the main.main DIE
if maindie.Tag != dwarf.TagSubprogram {
t.Fatalf("unexpected tag %v on main.main DIE", maindie.Tag)
}
// Walk main's children and pick out the inlined subroutines
mainIdx := ex.idxFromOffset(maindie.Offset)
childDies := ex.Children(mainIdx)
exCount := 0
for _, child := range childDies {
if child.Tag == dwarf.TagInlinedSubroutine {
// Found an inlined subroutine, locate abstract origin.
ooff, originOK := child.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
if !originOK {
t.Fatalf("no abstract origin attr for inlined subroutine at offset %v", child.Offset)
}
originDIE := ex.entryFromOffset(ooff)
if originDIE == nil {
t.Fatalf("can't locate origin DIE at off %v", ooff)
}
if exCount >= len(expectedInl) {
t.Fatalf("too many inlined subroutines found in main.main")
}
// Name should check out.
expected := expectedInl[exCount]
if name, ok := originDIE.Val(dwarf.AttrName).(string); ok {
if name != expected {
t.Fatalf("expected inlined routine %s got %s", name, expected)
}
}
exCount++
}
}
if exCount != len(expectedInl) {
t.Fatalf("not enough inlined subroutines found in main.main")
}
}
......@@ -218,7 +218,7 @@ func testGdbPython(t *testing.T, cgo bool) {
// a collection of scalar vars holding the fields. In such cases
// the DWARF variable location expression should be of the
// form "var.field" and not just "field".
infoLocalsRe := regexp.MustCompile(`^slicevar.len = `)
infoLocalsRe := regexp.MustCompile(`.*\sslicevar.cap = `)
if bl := blocks["info locals"]; !infoLocalsRe.MatchString(bl) {
t.Fatalf("info locals failed: %s", bl)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment