1
0
mirror of https://github.com/golang/go synced 2024-11-19 12:44:51 -07:00

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: David Chase <drchase@google.com>
This commit is contained in:
Than McIntosh 2017-10-06 11:32:28 -04:00
parent dbb1d198ab
commit 4435fcfd6c
17 changed files with 1950 additions and 152 deletions

View File

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

View File

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

View File

@ -0,0 +1,317 @@
// 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)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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,16 +297,19 @@ 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 expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect {
Fatalf("unexpected fnsym: %v != %v", fnsym, expect)
if fn.Func.Nname != nil {
if expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect {
Fatalf("unexpected fnsym: %v != %v", fnsym, expect)
}
}
var automDecls []*Node
@ -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,16 +663,29 @@ 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,
Abbrev: abbrev,
Type: Ctxt.Lookup(typename),
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
// variables just give it the lowest one. It's not used otherwise.
// 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 {

View File

@ -85,18 +85,20 @@ const (
_, nodeAssigned // is the variable ever assigned to
_, nodeAddrtaken // address taken, even if not moved to heap
_, nodeImplicit
_, nodeIsddd // is the argument variadic
_, nodeDiag // already printed error about this
_, nodeColas // OAS resulting from :=
_, nodeNonNil // guaranteed to be non-nil
_, nodeNoescape // func arguments do not escape; TODO(rsc): move Noescape to Func struct (see CL 7360)
_, nodeBounded // bounds check unnecessary
_, nodeAddable // addressable
_, nodeHasCall // expression contains a function call
_, nodeLikely // if statement condition likely
_, nodeHasVal // node.E contains a Val
_, nodeHasOpt // node.E contains an Opt
_, nodeEmbedded // ODCLFIELD embedded type
_, nodeIsddd // is the argument variadic
_, nodeDiag // already printed error about this
_, nodeColas // OAS resulting from :=
_, nodeNonNil // guaranteed to be non-nil
_, nodeNoescape // func arguments do not escape; TODO(rsc): move Noescape to Func struct (see CL 7360)
_, nodeBounded // bounds check unnecessary
_, nodeAddable // addressable
_, nodeHasCall // expression contains a function call
_, nodeLikely // if statement condition likely
_, 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 {

View File

@ -10,6 +10,8 @@ package dwarf
import (
"errors"
"fmt"
"sort"
"strings"
)
// InfoPrefix is the prefix for all the symbols containing DWARF info entries.
@ -29,6 +31,14 @@ const ConstInfoPrefix = "go.constinfo."
// populate the DWARF compilation unit info entries.
const CUInfoPrefix = "go.cuinfo."
// Used to form the symbol name assigned to the DWARF 'abstract subprogram"
// info entry for a function
const AbstractFuncSuffix = "$abstract"
// Controls logging/debugging for selected aspects of DWARF subprogram
// generation (functions, scopes).
var logDwarf bool
// Sym represents a symbol.
type Sym interface {
Len() int64
@ -56,11 +66,16 @@ type Var struct {
Name string
Abbrev int // Either DW_ABRV_AUTO[_LOCLIST] or DW_ABRV_PARAM[_LOCLIST]
IsReturnValue bool
IsInlFormal bool
StackOffset int32
LocationList []Location
Scope int32
Type Sym
DeclFile string
DeclLine uint
DeclCol uint
InlIndex int32 // subtract 1 to form real index into InlTree
ChildIndex int32 // child DIE index in abstract function
}
// A Scope represents a lexical scope. All variables declared within a
@ -80,6 +95,27 @@ type Range struct {
Start, End int64
}
// This container is used by the PutFunc* variants below when
// creating the DWARF subprogram DIE(s) for a function.
type FnState struct {
Name string
Importpath string
Info Sym
Filesym Sym
Loc Sym
Ranges Sym
Absfn Sym
StartPC Sym
Size int64
External bool
Scopes []Scope
InlCalls InlCalls
}
func EnableLogging(doit bool) {
logDwarf = doit
}
// UnifyRanges merges the list of ranges of c into the list of ranges of s
func (s *Scope) UnifyRanges(c *Scope) {
out := make([]Range, 0, len(s.Ranges)+len(c.Ranges))
@ -115,6 +151,37 @@ func (s *Scope) UnifyRanges(c *Scope) {
s.Ranges = out
}
type InlCalls struct {
Calls []InlCall
}
type InlCall struct {
// index into ctx.InlTree describing the call inlined here
InlIndex int
// Symbol of file containing inlined call site (really *obj.LSym).
CallFile Sym
// Line number of inlined call site.
CallLine uint32
// Dwarf abstract subroutine symbol (really *obj.LSym).
AbsFunSym Sym
// Indices of child inlines within Calls array above.
Children []int
// entries in this list are PAUTO's created by the inliner to
// capture the promoted formals and locals of the inlined callee.
InlVars []*Var
// PC ranges for this inlined call.
Ranges []Range
// Root call (not a child of some other call).
Root bool
}
// A Context specifies how to add data to a Sym.
type Context interface {
PtrSize() int
@ -123,8 +190,12 @@ type Context interface {
AddAddress(s Sym, t interface{}, ofs int64)
AddCURelativeAddress(s Sym, t interface{}, ofs int64)
AddSectionOffset(s Sym, size int, t interface{}, ofs int64)
CurrentOffset(s Sym) int64
RecordDclReference(from Sym, to Sym, dclIdx int, inlIndex int)
RecordChildDieOffsets(s Sym, vars []*Var, offsets []int32)
AddString(s Sym, v string)
AddFileRef(s Sym, f interface{})
Logf(format string, args ...interface{})
}
// AppendUleb128 appends v to b using DWARF's unsigned LEB128 encoding.
@ -243,12 +314,22 @@ const (
DW_ABRV_NULL = iota
DW_ABRV_COMPUNIT
DW_ABRV_FUNCTION
DW_ABRV_FUNCTION_ABSTRACT
DW_ABRV_FUNCTION_CONCRETE
DW_ABRV_INLINED_SUBROUTINE
DW_ABRV_INLINED_SUBROUTINE_RANGES
DW_ABRV_VARIABLE
DW_ABRV_INT_CONSTANT
DW_ABRV_AUTO
DW_ABRV_AUTO_LOCLIST
DW_ABRV_AUTO_ABSTRACT
DW_ABRV_AUTO_CONCRETE
DW_ABRV_AUTO_CONCRETE_LOCLIST
DW_ABRV_PARAM
DW_ABRV_PARAM_LOCLIST
DW_ABRV_PARAM_ABSTRACT
DW_ABRV_PARAM_CONCRETE
DW_ABRV_PARAM_CONCRETE_LOCLIST
DW_ABRV_LEXICAL_BLOCK_RANGES
DW_ABRV_LEXICAL_BLOCK_SIMPLE
DW_ABRV_STRUCTFIELD
@ -310,6 +391,54 @@ var abbrevs = [DW_NABRV]dwAbbrev{
},
},
/* FUNCTION_ABSTRACT */
{
DW_TAG_subprogram,
DW_CHILDREN_yes,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_inline, DW_FORM_data1},
{DW_AT_external, DW_FORM_flag},
},
},
/* FUNCTION_CONCRETE */
{
DW_TAG_subprogram,
DW_CHILDREN_yes,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_low_pc, DW_FORM_addr},
{DW_AT_high_pc, DW_FORM_addr},
{DW_AT_frame_base, DW_FORM_block1},
},
},
/* INLINED_SUBROUTINE */
{
DW_TAG_inlined_subroutine,
DW_CHILDREN_yes,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_low_pc, DW_FORM_addr},
{DW_AT_high_pc, DW_FORM_addr},
{DW_AT_call_file, DW_FORM_data4},
{DW_AT_call_line, DW_FORM_udata},
},
},
/* INLINED_SUBROUTINE_RANGES */
{
DW_TAG_inlined_subroutine,
DW_CHILDREN_yes,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_ranges, DW_FORM_sec_offset},
{DW_AT_call_file, DW_FORM_data4},
{DW_AT_call_line, DW_FORM_udata},
},
},
/* VARIABLE */
{
DW_TAG_variable,
@ -340,8 +469,8 @@ var abbrevs = [DW_NABRV]dwAbbrev{
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_location, DW_FORM_block1},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
@ -352,8 +481,39 @@ var abbrevs = [DW_NABRV]dwAbbrev{
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_location, DW_FORM_sec_offset},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
/* AUTO_ABSTRACT */
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
},
},
/* AUTO_CONCRETE */
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
/* AUTO_CONCRETE_LOCLIST */
{
DW_TAG_variable,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
@ -365,8 +525,8 @@ var abbrevs = [DW_NABRV]dwAbbrev{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_location, DW_FORM_block1},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
@ -378,8 +538,40 @@ var abbrevs = [DW_NABRV]dwAbbrev{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_location, DW_FORM_sec_offset},
{DW_AT_type, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
/* PARAM_ABSTRACT */
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_name, DW_FORM_string},
{DW_AT_variable_parameter, DW_FORM_flag},
{DW_AT_decl_line, DW_FORM_udata},
{DW_AT_type, DW_FORM_ref_addr},
},
},
/* PARAM_CONCRETE */
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_block1},
},
},
/* PARAM_CONCRETE_LOCLIST */
{
DW_TAG_formal_parameter,
DW_CHILDREN_no,
[]dwAttrForm{
{DW_AT_abstract_origin, DW_FORM_ref_addr},
{DW_AT_location, DW_FORM_sec_offset},
},
},
@ -792,34 +984,320 @@ func PutRanges(ctxt Context, sym Sym, base Sym, ranges []Range) {
ctxt.AddInt(sym, ps, 0)
}
// PutFunc writes a DIE for a function to s.
// It also writes child DIEs for each variable in vars.
func PutFunc(ctxt Context, info, loc, ranges, filesym Sym, name string, external bool, startPC Sym, size int64, scopes []Scope) error {
Uleb128put(ctxt, info, DW_ABRV_FUNCTION)
putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_string, DW_CLS_STRING, int64(len(name)), name)
putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, 0, startPC)
putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, size, startPC)
putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_block1, DW_CLS_BLOCK, 1, []byte{DW_OP_call_frame_cfa})
// DW_AT_decl_file attribute
ctxt.AddFileRef(info, filesym)
var ev int64
if external {
ev = 1
// Return TRUE if the inlined call in the specified slot is empty,
// meaning it has a zero-length range (no instructions), and all
// of its children are empty.
func isEmptyInlinedCall(slot int, calls *InlCalls) bool {
ic := &calls.Calls[slot]
if ic.InlIndex == -2 {
return true
}
putattr(ctxt, info, DW_ABRV_FUNCTION, DW_FORM_flag, DW_CLS_FLAG, ev, 0)
if len(scopes) > 0 {
var encbuf [20]byte
if putscope(ctxt, info, loc, ranges, startPC, 0, scopes, encbuf[:0]) < int32(len(scopes)) {
return errors.New("multiple toplevel scopes")
live := false
for _, k := range ic.Children {
if !isEmptyInlinedCall(k, calls) {
live = true
}
}
Uleb128put(ctxt, info, 0)
if len(ic.Ranges) > 0 {
live = true
}
if !live {
ic.InlIndex = -2
}
return !live
}
// Slot -1: return top-level inlines
// Slot >= 0: return children of that slot
func inlChildren(slot int, calls *InlCalls) []int {
var kids []int
if slot != -1 {
for _, k := range calls.Calls[slot].Children {
if !isEmptyInlinedCall(k, calls) {
kids = append(kids, k)
}
}
} else {
for k := 0; k < len(calls.Calls); k += 1 {
if calls.Calls[k].Root && !isEmptyInlinedCall(k, calls) {
kids = append(kids, k)
}
}
}
return kids
}
func inlinedVarTable(inlcalls *InlCalls) map[*Var]bool {
vars := make(map[*Var]bool)
for _, ic := range inlcalls.Calls {
for _, v := range ic.InlVars {
vars[v] = true
}
}
return vars
}
// The s.Scopes slice contains variables were originally part of the
// function being emitted, as well as variables that were imported
// from various callee functions during the inlining process. This
// function prunes out any variables from the latter category (since
// they will be emitted as part of DWARF inlined_subroutine DIEs) and
// then generates scopes for vars in the former category.
func putPrunedScopes(ctxt Context, s *FnState, fnabbrev int) error {
if len(s.Scopes) == 0 {
return nil
}
scopes := make([]Scope, len(s.Scopes), len(s.Scopes))
pvars := inlinedVarTable(&s.InlCalls)
for k, s := range s.Scopes {
var pruned Scope = Scope{Parent: s.Parent, Ranges: s.Ranges}
for i := 0; i < len(s.Vars); i++ {
_, found := pvars[s.Vars[i]]
if !found {
pruned.Vars = append(pruned.Vars, s.Vars[i])
}
}
sort.Sort(byChildIndex(pruned.Vars))
scopes[k] = pruned
}
var encbuf [20]byte
if putscope(ctxt, s, scopes, 0, fnabbrev, encbuf[:0]) < int32(len(scopes)) {
return errors.New("multiple toplevel scopes")
}
return nil
}
func putscope(ctxt Context, info, loc, ranges, startPC Sym, curscope int32, scopes []Scope, encbuf []byte) int32 {
// Emit DWARF attributes and child DIEs for an 'abstract' subprogram.
// The abstract subprogram DIE for a function contains its
// location-independent attributes (name, type, etc). Other instances
// of the function (any inlined copy of it, or the single out-of-line
// 'concrete' instance) will contain a pointer back to this abstract
// DIE (as a space-saving measure, so that name/type etc doesn't have
// to be repeated for each inlined copy).
func PutAbstractFunc(ctxt Context, s *FnState) error {
if logDwarf {
ctxt.Logf("PutAbstractFunc(%v)\n", s.Absfn)
}
abbrev := DW_ABRV_FUNCTION_ABSTRACT
Uleb128put(ctxt, s.Absfn, int64(abbrev))
fullname := s.Name
if strings.HasPrefix(s.Name, "\"\".") {
// Generate a fully qualified name for the function in the
// abstract case. This is so as to avoid the need for the
// linker to process the DIE with patchDWARFName(); we can't
// allow the name attribute of an abstract subprogram DIE to
// be rewritten, since it would change the offsets of the
// child DIEs (which we're relying on in order for abstract
// origin references to work).
fullname = s.Importpath + "." + s.Name[3:]
}
putattr(ctxt, s.Absfn, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(fullname)), fullname)
// DW_AT_inlined value
putattr(ctxt, s.Absfn, abbrev, DW_FORM_data1, DW_CLS_CONSTANT, int64(DW_INL_inlined), nil)
var ev int64
if s.External {
ev = 1
}
putattr(ctxt, s.Absfn, abbrev, DW_FORM_flag, DW_CLS_FLAG, ev, 0)
// Child variables (may be empty)
var flattened []*Var
// This slice will hold the offset in bytes for each child var DIE
// with respect to the start of the parent subprogram DIE.
var offsets []int32
// Scopes/vars
if len(s.Scopes) > 0 {
// For abstract subprogram DIEs we want to flatten out scope info:
// lexical scope DIEs contain range and/or hi/lo PC attributes,
// which we explicitly don't want for the abstract subprogram DIE.
pvars := inlinedVarTable(&s.InlCalls)
for _, scope := range s.Scopes {
for i := 0; i < len(scope.Vars); i++ {
_, found := pvars[scope.Vars[i]]
if !found {
flattened = append(flattened, scope.Vars[i])
}
}
}
if len(flattened) > 0 {
sort.Sort(byChildIndex(flattened))
// This slice will hold the offset in bytes for each child
// variable DIE with respect to the start of the parent
// subprogram DIE.
for _, v := range flattened {
offsets = append(offsets, int32(ctxt.CurrentOffset(s.Absfn)))
putAbstractVar(ctxt, s.Absfn, v)
}
}
}
ctxt.RecordChildDieOffsets(s.Absfn, flattened, offsets)
Uleb128put(ctxt, s.Absfn, 0)
return nil
}
// Emit DWARF attributes and child DIEs for an inlined subroutine. The
// first attribute of an inlined subroutine DIE is a reference back to
// its corresponding 'abstract' DIE (containing location-independent
// attributes such as name, type, etc). Inlined subroutine DIEs can
// have other inlined subroutine DIEs as children.
func PutInlinedFunc(ctxt Context, s *FnState, callersym Sym, callIdx int) error {
ic := s.InlCalls.Calls[callIdx]
callee := ic.AbsFunSym
abbrev := DW_ABRV_INLINED_SUBROUTINE_RANGES
if len(ic.Ranges) == 1 {
abbrev = DW_ABRV_INLINED_SUBROUTINE
}
Uleb128put(ctxt, s.Info, int64(abbrev))
if logDwarf {
ctxt.Logf("PutInlinedFunc(caller=%v,callee=%v,abbrev=%d)\n", callersym, callee, abbrev)
}
// Abstract origin.
putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, callee)
if abbrev == DW_ABRV_INLINED_SUBROUTINE_RANGES {
putattr(ctxt, s.Info, abbrev, DW_FORM_sec_offset, DW_CLS_PTR, s.Ranges.Len(), s.Ranges)
PutRanges(ctxt, s.Ranges, s.StartPC, ic.Ranges)
} else {
st := ic.Ranges[0].Start
en := ic.Ranges[0].End
putattr(ctxt, s.Info, abbrev, DW_FORM_addr, DW_CLS_ADDRESS, st, s.StartPC)
putattr(ctxt, s.Info, abbrev, DW_FORM_addr, DW_CLS_ADDRESS, en, s.StartPC)
}
// Emit call file, line attrs.
ctxt.AddFileRef(s.Info, ic.CallFile)
putattr(ctxt, s.Info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(ic.CallLine), nil)
// Variables associated with this inlined routine instance.
vars := ic.InlVars
sort.Sort(byChildIndex(vars))
inlIndex := ic.InlIndex
var encbuf [20]byte
for _, v := range vars {
putvar(ctxt, s, v, callee, abbrev, inlIndex, encbuf[:0])
}
// Children of this inline.
for _, sib := range inlChildren(callIdx, &s.InlCalls) {
absfn := s.InlCalls.Calls[sib].AbsFunSym
err := PutInlinedFunc(ctxt, s, absfn, sib)
if err != nil {
return err
}
}
Uleb128put(ctxt, s.Info, 0)
return nil
}
// Emit DWARF attributes and child DIEs for a 'concrete' subprogram,
// meaning the out-of-line copy of a function that was inlined at some
// point during the compilation of its containing package. The first
// attribute for a concrete DIE is a reference to the 'abstract' DIE
// for the function (which holds location-independent attributes such
// as name, type), then the remainder of the attributes are specific
// to this instance (location, frame base, etc).
func PutConcreteFunc(ctxt Context, s *FnState) error {
if logDwarf {
ctxt.Logf("PutConcreteFunc(%v)\n", s.Info)
}
abbrev := DW_ABRV_FUNCTION_CONCRETE
Uleb128put(ctxt, s.Info, int64(abbrev))
// Abstract origin.
putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, s.Absfn)
// Start/end PC.
putattr(ctxt, s.Info, abbrev, DW_FORM_addr, DW_CLS_ADDRESS, 0, s.StartPC)
putattr(ctxt, s.Info, abbrev, DW_FORM_addr, DW_CLS_ADDRESS, s.Size, s.StartPC)
// cfa / frame base
putattr(ctxt, s.Info, abbrev, DW_FORM_block1, DW_CLS_BLOCK, 1, []byte{DW_OP_call_frame_cfa})
// Scopes
if err := putPrunedScopes(ctxt, s, abbrev); err != nil {
return err
}
// Inlined subroutines.
for _, sib := range inlChildren(-1, &s.InlCalls) {
absfn := s.InlCalls.Calls[sib].AbsFunSym
err := PutInlinedFunc(ctxt, s, absfn, sib)
if err != nil {
return err
}
}
Uleb128put(ctxt, s.Info, 0)
return nil
}
// Emit DWARF attributes and child DIEs for a subprogram. Here
// 'default' implies that the function in question was not inlined
// when its containing package was compiled (hence there is no need to
// emit an abstract version for it to use as a base for inlined
// routine records).
func PutDefaultFunc(ctxt Context, s *FnState) error {
if logDwarf {
ctxt.Logf("PutDefaultFunc(%v)\n", s.Info)
}
abbrev := DW_ABRV_FUNCTION
Uleb128put(ctxt, s.Info, int64(abbrev))
putattr(ctxt, s.Info, DW_ABRV_FUNCTION, DW_FORM_string, DW_CLS_STRING, int64(len(s.Name)), s.Name)
putattr(ctxt, s.Info, abbrev, DW_FORM_addr, DW_CLS_ADDRESS, 0, s.StartPC)
putattr(ctxt, s.Info, abbrev, DW_FORM_addr, DW_CLS_ADDRESS, s.Size, s.StartPC)
putattr(ctxt, s.Info, abbrev, DW_FORM_block1, DW_CLS_BLOCK, 1, []byte{DW_OP_call_frame_cfa})
ctxt.AddFileRef(s.Info, s.Filesym)
var ev int64
if s.External {
ev = 1
}
putattr(ctxt, s.Info, abbrev, DW_FORM_flag, DW_CLS_FLAG, ev, 0)
// Scopes
if err := putPrunedScopes(ctxt, s, abbrev); err != nil {
return err
}
// Inlined subroutines.
for _, sib := range inlChildren(-1, &s.InlCalls) {
absfn := s.InlCalls.Calls[sib].AbsFunSym
err := PutInlinedFunc(ctxt, s, absfn, sib)
if err != nil {
return err
}
}
Uleb128put(ctxt, s.Info, 0)
return nil
}
func putscope(ctxt Context, s *FnState, scopes []Scope, curscope int32, fnabbrev int, encbuf []byte) int32 {
if logDwarf {
ctxt.Logf("putscope(%v,%d): vars:", s.Info, curscope)
for i, v := range scopes[curscope].Vars {
ctxt.Logf(" %d:%d:%s", i, v.ChildIndex, v.Name)
}
ctxt.Logf("\n")
}
for _, v := range scopes[curscope].Vars {
putvar(ctxt, info, loc, v, startPC, encbuf)
putvar(ctxt, s, v, s.Absfn, fnabbrev, -1, encbuf)
}
this := curscope
curscope++
@ -830,49 +1308,144 @@ func putscope(ctxt Context, info, loc, ranges, startPC Sym, curscope int32, scop
}
if len(scope.Ranges) == 1 {
Uleb128put(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE)
putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].Start, startPC)
putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].End, startPC)
Uleb128put(ctxt, s.Info, DW_ABRV_LEXICAL_BLOCK_SIMPLE)
putattr(ctxt, s.Info, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].Start, s.StartPC)
putattr(ctxt, s.Info, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].End, s.StartPC)
} else {
Uleb128put(ctxt, info, DW_ABRV_LEXICAL_BLOCK_RANGES)
putattr(ctxt, info, DW_ABRV_LEXICAL_BLOCK_RANGES, DW_FORM_sec_offset, DW_CLS_PTR, ranges.Len(), ranges)
Uleb128put(ctxt, s.Info, DW_ABRV_LEXICAL_BLOCK_RANGES)
putattr(ctxt, s.Info, DW_ABRV_LEXICAL_BLOCK_RANGES, DW_FORM_sec_offset, DW_CLS_PTR, s.Ranges.Len(), s.Ranges)
PutRanges(ctxt, ranges, startPC, scope.Ranges)
PutRanges(ctxt, s.Ranges, s.StartPC, scope.Ranges)
}
curscope = putscope(ctxt, info, loc, ranges, startPC, curscope, scopes, encbuf)
Uleb128put(ctxt, info, 0)
curscope = putscope(ctxt, s, scopes, curscope, fnabbrev, encbuf)
Uleb128put(ctxt, s.Info, 0)
}
return curscope
}
func putvar(ctxt Context, info, loc Sym, v *Var, startPC Sym, encbuf []byte) {
// Pick the correct abbrev code for variable or parameter DIE.
func determineVarAbbrev(v *Var, fnabbrev int) (int, bool, bool) {
abbrev := v.Abbrev
// If the variable was entirely optimized out, don't emit a location list;
// convert to an inline abbreviation and emit an empty location.
missing := false
switch {
case v.Abbrev == DW_ABRV_AUTO_LOCLIST && len(v.LocationList) == 0:
case abbrev == DW_ABRV_AUTO_LOCLIST && len(v.LocationList) == 0:
missing = true
v.Abbrev = DW_ABRV_AUTO
case v.Abbrev == DW_ABRV_PARAM_LOCLIST && len(v.LocationList) == 0:
abbrev = DW_ABRV_AUTO
case abbrev == DW_ABRV_PARAM_LOCLIST && len(v.LocationList) == 0:
missing = true
v.Abbrev = DW_ABRV_PARAM
abbrev = DW_ABRV_PARAM
}
Uleb128put(ctxt, info, int64(v.Abbrev))
putattr(ctxt, info, v.Abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(v.Name)), v.Name)
if v.Abbrev == DW_ABRV_PARAM || v.Abbrev == DW_ABRV_PARAM_LOCLIST {
concrete := true
switch fnabbrev {
case DW_ABRV_FUNCTION:
concrete = false
break
case DW_ABRV_FUNCTION_CONCRETE, DW_ABRV_INLINED_SUBROUTINE, DW_ABRV_INLINED_SUBROUTINE_RANGES:
switch abbrev {
case DW_ABRV_AUTO:
if v.IsInlFormal {
abbrev = DW_ABRV_PARAM_CONCRETE
} else {
abbrev = DW_ABRV_AUTO_CONCRETE
}
concrete = true
case DW_ABRV_AUTO_LOCLIST:
if v.IsInlFormal {
abbrev = DW_ABRV_PARAM_CONCRETE_LOCLIST
} else {
abbrev = DW_ABRV_AUTO_CONCRETE_LOCLIST
}
case DW_ABRV_PARAM:
abbrev = DW_ABRV_PARAM_CONCRETE
case DW_ABRV_PARAM_LOCLIST:
abbrev = DW_ABRV_PARAM_CONCRETE_LOCLIST
}
default:
panic("should never happen")
}
return abbrev, missing, concrete
}
func abbrevUsesLoclist(abbrev int) bool {
switch abbrev {
case DW_ABRV_AUTO_LOCLIST, DW_ABRV_AUTO_CONCRETE_LOCLIST,
DW_ABRV_PARAM_LOCLIST, DW_ABRV_PARAM_CONCRETE_LOCLIST:
return true
default:
return false
}
}
// Emit DWARF attributes for a variable belonging to an 'abstract' subprogram.
func putAbstractVar(ctxt Context, info Sym, v *Var) {
// Remap abbrev
abbrev := v.Abbrev
switch abbrev {
case DW_ABRV_AUTO, DW_ABRV_AUTO_LOCLIST:
abbrev = DW_ABRV_AUTO_ABSTRACT
case DW_ABRV_PARAM, DW_ABRV_PARAM_LOCLIST:
abbrev = DW_ABRV_PARAM_ABSTRACT
}
Uleb128put(ctxt, info, int64(abbrev))
putattr(ctxt, info, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(v.Name)), v.Name)
// Isreturn attribute if this is a param
if abbrev == DW_ABRV_PARAM_ABSTRACT {
var isReturn int64
if v.IsReturnValue {
isReturn = 1
}
putattr(ctxt, info, v.Abbrev, DW_FORM_flag, DW_CLS_FLAG, isReturn, nil)
putattr(ctxt, info, abbrev, DW_FORM_flag, DW_CLS_FLAG, isReturn, nil)
}
putattr(ctxt, info, v.Abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(v.DeclLine), nil)
if v.Abbrev == DW_ABRV_AUTO_LOCLIST || v.Abbrev == DW_ABRV_PARAM_LOCLIST {
putattr(ctxt, info, v.Abbrev, DW_FORM_sec_offset, DW_CLS_PTR, int64(loc.Len()), loc)
addLocList(ctxt, loc, startPC, v, encbuf)
// Line
putattr(ctxt, info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(v.DeclLine), nil)
// Type
putattr(ctxt, info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type)
// Var has no children => no terminator
}
func putvar(ctxt Context, s *FnState, v *Var, absfn Sym, fnabbrev, inlIndex int, encbuf []byte) {
// Remap abbrev according to parent DIE abbrev
abbrev, missing, concrete := determineVarAbbrev(v, fnabbrev)
Uleb128put(ctxt, s.Info, int64(abbrev))
// Abstract origin for concrete / inlined case
if concrete {
// Here we are making a reference to a child DIE of an abstract
// function subprogram DIE. The child DIE has no LSym, so instead
// after the call to 'putattr' below we make a call to register
// the child DIE reference.
putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, absfn)
ctxt.RecordDclReference(s.Info, absfn, int(v.ChildIndex), inlIndex)
} else {
// Var name, line for abstract and default cases
n := v.Name
putattr(ctxt, s.Info, abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n)
if abbrev == DW_ABRV_PARAM || abbrev == DW_ABRV_PARAM_LOCLIST || abbrev == DW_ABRV_PARAM_ABSTRACT {
var isReturn int64
if v.IsReturnValue {
isReturn = 1
}
putattr(ctxt, s.Info, abbrev, DW_FORM_flag, DW_CLS_FLAG, isReturn, nil)
}
putattr(ctxt, s.Info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(v.DeclLine), nil)
putattr(ctxt, s.Info, abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type)
}
if abbrevUsesLoclist(abbrev) {
putattr(ctxt, s.Info, abbrev, DW_FORM_sec_offset, DW_CLS_PTR, int64(s.Loc.Len()), s.Loc)
addLocList(ctxt, s.Loc, s.StartPC, v, encbuf)
} else {
loc := encbuf[:0]
switch {
@ -884,9 +1457,10 @@ func putvar(ctxt Context, info, loc Sym, v *Var, startPC Sym, encbuf []byte) {
loc = append(loc, DW_OP_fbreg)
loc = AppendSleb128(loc, int64(v.StackOffset))
}
putattr(ctxt, info, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc)
putattr(ctxt, s.Info, abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc)
}
putattr(ctxt, info, v.Abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type)
// Var has no children => no terminator
}
func addLocList(ctxt Context, listSym, startPC Sym, v *Var, encbuf []byte) {
@ -935,3 +1509,10 @@ type VarsByOffset []*Var
func (s VarsByOffset) Len() int { return len(s) }
func (s VarsByOffset) Less(i, j int) bool { return s[i].StackOffset < s[j].StackOffset }
func (s VarsByOffset) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// byChildIndex implements sort.Interface for []*dwarf.Var by child index.
type byChildIndex []*Var
func (s byChildIndex) Len() int { return len(s) }
func (s byChildIndex) Less(i, j int) bool { return s[i].ChildIndex < s[j].ChildIndex }
func (s byChildIndex) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

View File

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

View File

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

View File

@ -15,6 +15,7 @@ import (
"log"
"path/filepath"
"sort"
"sync"
)
// objWriter writes Go object files.
@ -473,8 +474,33 @@ func (c dwCtxt) AddFileRef(s dwarf.Sym, f interface{}) {
r.Type = objabi.R_DWARFFILEREF
}
// dwarfSym returns the DWARF symbols for TEXT symbol.
func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfLocSym, dwarfRangesSym *LSym) {
func (c dwCtxt) CurrentOffset(s dwarf.Sym) int64 {
ls := s.(*LSym)
return ls.Size
}
// Here "from" is a symbol corresponding to an inlined or concrete
// function, "to" is the symbol for the corresponding abstract
// function, and "dclIdx" is the index of the symbol of interest with
// respect to the Dcl slice of the original pre-optimization version
// of the inlined function.
func (c dwCtxt) RecordDclReference(from dwarf.Sym, to dwarf.Sym, dclIdx int, inlIndex int) {
ls := from.(*LSym)
tls := to.(*LSym)
ridx := len(ls.R) - 1
c.Link.DwFixups.ReferenceChildDIE(ls, ridx, tls, dclIdx, inlIndex)
}
func (c dwCtxt) RecordChildDieOffsets(s dwarf.Sym, vars []*dwarf.Var, offsets []int32) {
ls := s.(*LSym)
c.Link.DwFixups.RegisterChildDIEOffsets(ls, vars, offsets)
}
func (c dwCtxt) Logf(format string, args ...interface{}) {
c.Link.Logf(format, args...)
}
func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfLocSym, dwarfRangesSym, dwarfAbsFnSym *LSym) {
if s.Type != objabi.STEXT {
ctxt.Diag("dwarfSym of non-TEXT %v", s)
}
@ -484,8 +510,12 @@ func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfLocSym, dwarfRangesSym *
s.Func.dwarfLocSym = ctxt.LookupDerived(s, dwarf.LocPrefix+s.Name)
}
s.Func.dwarfRangesSym = ctxt.LookupDerived(s, dwarf.RangePrefix+s.Name)
if s.WasInlined() {
s.Func.dwarfAbsFnSym = ctxt.DwFixups.AbsFuncDwarfSym(s)
}
}
return s.Func.dwarfInfoSym, s.Func.dwarfLocSym, s.Func.dwarfRangesSym
return s.Func.dwarfInfoSym, s.Func.dwarfLocSym, s.Func.dwarfRangesSym, s.Func.dwarfAbsFnSym
}
func (s *LSym) Len() int64 {
@ -505,20 +535,45 @@ func (ctxt *Link) fileSymbol(fn *LSym) *LSym {
return nil
}
// populateDWARF fills in the DWARF Debugging Information Entries for TEXT symbol s.
// The DWARFs symbol must already have been initialized in InitTextSym.
func (ctxt *Link) populateDWARF(curfn interface{}, s *LSym) {
info, loc, ranges := ctxt.dwarfSym(s)
// populateDWARF fills in the DWARF Debugging Information Entries for
// TEXT symbol 's'. The various DWARF symbols must already have been
// initialized in InitTextSym.
func (ctxt *Link) populateDWARF(curfn interface{}, s *LSym, myimportpath string) {
info, loc, ranges, absfunc := ctxt.dwarfSym(s)
if info.Size != 0 {
ctxt.Diag("makeFuncDebugEntry double process %v", s)
}
var scopes []dwarf.Scope
var inlcalls dwarf.InlCalls
if ctxt.DebugInfo != nil {
scopes = ctxt.DebugInfo(s, curfn)
scopes, inlcalls = ctxt.DebugInfo(s, curfn)
}
var err error
dwctxt := dwCtxt{ctxt}
filesym := ctxt.fileSymbol(s)
fnstate := &dwarf.FnState{
Name: s.Name,
Importpath: myimportpath,
Info: info,
Filesym: filesym,
Loc: loc,
Ranges: ranges,
Absfn: absfunc,
StartPC: s,
Size: s.Size,
External: !s.Static(),
Scopes: scopes,
InlCalls: inlcalls,
}
if absfunc != nil {
err = dwarf.PutAbstractFunc(dwctxt, fnstate)
if err != nil {
ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err)
}
err = dwarf.PutConcreteFunc(dwctxt, fnstate)
} else {
err = dwarf.PutDefaultFunc(dwctxt, fnstate)
}
fs := ctxt.fileSymbol(s)
err := dwarf.PutFunc(dwCtxt{ctxt}, info, loc, ranges, fs, s.Name, !s.Static(), s, s.Size, scopes)
if err != nil {
ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err)
}
@ -536,3 +591,285 @@ func (ctxt *Link) DwarfIntConst(myimportpath, name, typename string, val int64)
})
dwarf.PutIntConst(dwCtxt{ctxt}, s, ctxt.Lookup(dwarf.InfoPrefix+typename), myimportpath+"."+name, val)
}
func (ctxt *Link) DwarfAbstractFunc(curfn interface{}, s *LSym, myimportpath string) {
absfn := ctxt.DwFixups.AbsFuncDwarfSym(s)
if absfn.Size != 0 {
ctxt.Diag("internal error: DwarfAbstractFunc double process %v", s)
}
if s.Func == nil {
s.Func = new(FuncInfo)
}
scopes, _ := ctxt.DebugInfo(s, curfn)
dwctxt := dwCtxt{ctxt}
filesym := ctxt.fileSymbol(s)
fnstate := dwarf.FnState{
Name: s.Name,
Importpath: myimportpath,
Info: absfn,
Filesym: filesym,
Absfn: absfn,
External: !s.Static(),
Scopes: scopes,
}
if err := dwarf.PutAbstractFunc(dwctxt, &fnstate); err != nil {
ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err)
}
}
// This table is designed to aid in the creation of references betweeen
// DWARF subprogram DIEs.
//
// In most cases when one DWARF DIE has to refer to another DWARF DIE,
// the target of the reference has an LSym, which makes it easy to use
// the existing relocation mechanism. For DWARF inlined routine DIEs,
// however, the subprogram DIE has to refer to a child
// parameter/variable DIE of the abstract subprogram. This child DIE
// doesn't have an LSym, and also of interest is the fact that when
// DWARF generation is happening for inlined function F within caller
// G, it's possible that DWARF generation hasn't happened yet for F,
// so there is no way to know the offset of a child DIE within F's
// abstract function. Making matters more complex, each inlined
// instance of F may refer to a subset of the original F's variables
// (depending on what happens with optimization, some vars may be
// eliminated).
//
// The fixup table below helps overcome this hurdle. At the point
// where a parameter/variable reference is made (via a call to
// "ReferenceChildDIE"), a fixup record is generate that records
// the relocation that is targeting that child variable. At a later
// point when the abstract function DIE is emitted, there will be
// a call to "RegisterChildDIEOffsets", at which point the offsets
// needed to apply fixups are captured. Finally, once the parallel
// portion of the compilation is done, fixups can actually be applied
// during the "Finalize" method (this can't be done during the
// parallel portion of the compile due to the possibility of data
// races).
//
// This table is also used to record the "precursor" function node for
// each function that is the target of an inline -- child DIE references
// have to be made with respect to the original pre-optimization
// version of the function (to allow for the fact that each inlined
// body may be optimized differently).
type DwarfFixupTable struct {
ctxt *Link
mu sync.Mutex
symtab map[*LSym]int // maps abstract fn LSYM to index in svec
svec []symFixups
precursor map[*LSym]fnState // maps fn Lsym to precursor Node, absfn sym
}
type symFixups struct {
fixups []relFixup
doffsets []declOffset
inlIndex int32
defseen bool
}
type declOffset struct {
// Index of variable within DCL list of pre-optimization function
dclIdx int32
// Offset of var's child DIE with respect to containing subprogram DIE
offset int32
}
type relFixup struct {
refsym *LSym
relidx int32
dclidx int32
}
type fnState struct {
// precursor function (really *gc.Node)
precursor interface{}
// abstract function symbol
absfn *LSym
}
func NewDwarfFixupTable(ctxt *Link) *DwarfFixupTable {
return &DwarfFixupTable{
ctxt: ctxt,
symtab: make(map[*LSym]int),
precursor: make(map[*LSym]fnState),
}
}
func (ft *DwarfFixupTable) GetPrecursorFunc(s *LSym) interface{} {
if fnstate, found := ft.precursor[s]; found {
return fnstate.precursor
}
return nil
}
func (ft *DwarfFixupTable) SetPrecursorFunc(s *LSym, fn interface{}) {
if _, found := ft.precursor[s]; found {
ft.ctxt.Diag("internal error: DwarfFixupTable.SetPrecursorFunc double call on %v", s)
}
// initialize abstract function symbol now. This is done here so
// as to avoid data races later on during the parallel portion of
// the back end.
absfn := ft.ctxt.LookupDerived(s, dwarf.InfoPrefix+s.Name+dwarf.AbstractFuncSuffix)
absfn.Set(AttrDuplicateOK, true)
absfn.Type = objabi.SDWARFINFO
ft.ctxt.Data = append(ft.ctxt.Data, absfn)
ft.precursor[s] = fnState{precursor: fn, absfn: absfn}
}
// Make a note of a child DIE reference: relocation 'ridx' within symbol 's'
// is targeting child 'c' of DIE with symbol 'tgt'.
func (ft *DwarfFixupTable) ReferenceChildDIE(s *LSym, ridx int, tgt *LSym, dclidx int, inlIndex int) {
// Protect against concurrent access if multiple backend workers
ft.mu.Lock()
defer ft.mu.Unlock()
// Create entry for symbol if not already present.
idx, found := ft.symtab[tgt]
if !found {
ft.svec = append(ft.svec, symFixups{inlIndex: int32(inlIndex)})
idx = len(ft.svec) - 1
ft.symtab[tgt] = idx
}
// Do we have child DIE offsets available? If so, then apply them,
// otherwise create a fixup record.
sf := &ft.svec[idx]
if len(sf.doffsets) > 0 {
found := false
for _, do := range sf.doffsets {
if do.dclIdx == int32(dclidx) {
off := do.offset
s.R[ridx].Add += int64(off)
found = true
break
}
}
if !found {
ft.ctxt.Diag("internal error: DwarfFixupTable.ReferenceChildDIE unable to locate child DIE offset for dclIdx=%d src=%v tgt=%v", dclidx, s, tgt)
}
} else {
sf.fixups = append(sf.fixups, relFixup{s, int32(ridx), int32(dclidx)})
}
}
// Called once DWARF generation is complete for a given abstract function,
// whose children might have been referenced via a call above. Stores
// the offsets for any child DIEs (vars, params) so that they can be
// consumed later in on DwarfFixupTable.Finalize, which applies any
// outstanding fixups.
func (ft *DwarfFixupTable) RegisterChildDIEOffsets(s *LSym, vars []*dwarf.Var, coffsets []int32) {
// Length of these two slices should agree
if len(vars) != len(coffsets) {
ft.ctxt.Diag("internal error: RegisterChildDIEOffsets vars/offsets length mismatch")
return
}
// Generate the slice of declOffset's based in vars/coffsets
doffsets := make([]declOffset, len(coffsets))
for i := 0; i < len(coffsets); i++ {
doffsets[i].dclIdx = vars[i].ChildIndex
doffsets[i].offset = coffsets[i]
}
ft.mu.Lock()
defer ft.mu.Unlock()
// Store offsets for this symbol.
idx, found := ft.symtab[s]
if !found {
sf := symFixups{inlIndex: -1, defseen: true, doffsets: doffsets}
ft.svec = append(ft.svec, sf)
ft.symtab[s] = len(ft.svec) - 1
} else {
sf := &ft.svec[idx]
sf.doffsets = doffsets
sf.defseen = true
}
}
func (ft *DwarfFixupTable) processFixups(slot int, s *LSym) {
sf := &ft.svec[slot]
for _, f := range sf.fixups {
dfound := false
for i := 0; i < len(sf.doffsets); i++ {
if sf.doffsets[i].dclIdx == f.dclidx {
f.refsym.R[f.relidx].Add += int64(sf.doffsets[i].offset)
dfound = true
break
}
}
if !dfound {
ft.ctxt.Diag("internal error: DwarfFixupTable has orphaned fixup on %v targeting %v relidx=%d dclidx=%d", f.refsym, s, f.relidx, f.dclidx)
}
}
}
// return the LSym corresponding to the 'abstract subprogram' DWARF
// info entry for a function.
func (ft *DwarfFixupTable) AbsFuncDwarfSym(fnsym *LSym) *LSym {
// Protect against concurrent access if multiple backend workers
ft.mu.Lock()
defer ft.mu.Unlock()
if fnstate, found := ft.precursor[fnsym]; found {
return fnstate.absfn
}
ft.ctxt.Diag("internal error: AbsFuncDwarfSym requested for %v, not seen during inlining", fnsym)
return nil
}
// Called after all functions have been compiled; the main job of this
// function is to identify cases where there are outstanding fixups.
// This scenario crops up when we have references to variables of an
// inlined routine, but that routine is defined in some other package.
// This helper walks through and locate these fixups, then invokes a
// helper to create an abstract subprogram DIE for each one.
func (ft *DwarfFixupTable) Finalize(myimportpath string, trace bool) {
if trace {
ft.ctxt.Logf("DwarfFixupTable.Finalize invoked for %s\n", myimportpath)
}
// Collect up the keys from the precursor map, then sort the
// resulting list (don't want to rely on map ordering here).
fns := make([]*LSym, len(ft.precursor))
idx := 0
for fn, _ := range ft.precursor {
fns[idx] = fn
idx++
}
sort.Sort(bySymName(fns))
// Should not be called during parallel portion of compilation.
if ft.ctxt.InParallel {
ft.ctxt.Diag("internal error: DwarfFixupTable.Finalize call during parallel backend")
}
// Generate any missing abstract functions.
for i := 0; i < len(fns); i++ {
s := fns[i]
absfn := ft.AbsFuncDwarfSym(s)
slot, found := ft.symtab[absfn]
if !found || !ft.svec[slot].defseen {
ft.ctxt.GenAbstractFunc(s)
}
}
// Apply fixups.
for i := 0; i < len(fns); i++ {
s := fns[i]
absfn := ft.AbsFuncDwarfSym(s)
slot, found := ft.symtab[absfn]
if !found {
ft.ctxt.Diag("internal error: DwarfFixupTable.Finalize orphan abstract function for %v", s)
} else {
ft.processFixups(slot, s)
}
}
}
type bySymName []*LSym
func (s bySymName) Len() int { return len(s) }
func (s bySymName) Less(i, j int) bool { return s[i].Name < s[j].Name }
func (s bySymName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

View File

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

View File

@ -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
@ -849,11 +869,12 @@ func defdwsymb(ctxt *Link, s *sym.Symbol, str string, t SymbolType, v int64, got
// compilationUnit is per-compilation unit (equivalently, per-package)
// debug-related data.
type compilationUnit struct {
lib *sym.Library
consts *sym.Symbol // Package constants DIEs
pcs []dwarf.Range // PC ranges, relative to textp[0]
dwinfo *dwarf.DWDie // CU root DIE
funcDIEs []*sym.Symbol // Function DIE subtrees
lib *sym.Library
consts *sym.Symbol // Package constants DIEs
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)
}

View File

@ -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()
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 select variable "i".
mainIdx := ex.idxFromOffset(maindie.Offset)
childDies := ex.Children(mainIdx)
var iEntry *dwarf.Entry
var pEntry *dwarf.Entry
foundMain := false
for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
if 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
}
if !foundMain {
continue
}
if entry.Tag == dwarf.TagSubprogram {
t.Fatalf("didn't find DW_TAG_variable for i in main.main")
}
if foundMain && entry.Tag == dwarf.TagVariable && entry.Val(dwarf.AttrName).(string) == "i" {
iEntry = 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")
}
}

View File

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