]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: fixes for bad DWARF abstract origin references
authorThan McIntosh <thanm@google.com>
Mon, 11 Dec 2017 20:53:31 +0000 (15:53 -0500)
committerThan McIntosh <thanm@google.com>
Fri, 15 Dec 2017 17:59:32 +0000 (17:59 +0000)
Change the compiler's DWARF inline info generation to be more careful
about producing consistent instances of abstract function DIEs. The
new strategy is to insure that the only params/variables created in an
abstract subprogram DIE are those corresponding to declarations in the
original pre-inlining version of the code. If a concrete subprogram
winds up with other vars as part of the compilation process (return
temps, for example, or scalars generated by splitting a structure into
pieces) these are emitted as regular param/variable DIEs instead of
concrete DIEs.

The linker dwarf test now has a couple of new testpoints that include
checks to make sure that all abstract DIE references are
sane/resolvable; this will help catch similar problems in the future.

Fixes #23046.

Change-Id: I9b0030da8673fbb80b7ad50461fcf8c6ac823a37
Reviewed-on: https://go-review.googlesource.com/83675
Run-TryBot: Than McIntosh <thanm@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/cmd/compile/internal/gc/dwinl.go
src/cmd/compile/internal/gc/inl.go
src/cmd/compile/internal/gc/pgen.go
src/cmd/internal/dwarf/dwarf.go
src/cmd/link/internal/ld/dwarf_test.go

index dd91b6c0fc3b86d71a88dddb995e6ac4d46202f6..06eebc96e50d4d996b16d9cf0ab425696b7c84fd 100644 (file)
@@ -76,55 +76,82 @@ func assembleInlines(fnsym *obj.LSym, fn *Node, dwVars []*dwarf.Var) dwarf.InlCa
                        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.
+       // Post process the map above to assign child indices to vars.
+       //
+       // A given variable is treated differently depending on whether it
+       // is part of the top-level function (ii == 0) or if it was
+       // produced as a result of an inline (ii != 0).
+       //
+       // If a variable was not produced by an inline and its containing
+       // function was not inlined, then we just assign an ordering of
+       // based on variable name.
+       //
+       // If a variable was not produced by an inline and its containing
+       // function was inlined, then we need to assign a child index
+       // based on the order of vars in the abstract function (in
+       // addition, those vars that don't appear in the abstract
+       // function, such as "~r1", are flagged as such).
+       //
+       // If a variable was produced by an inline, then we locate it in
+       // the pre-inlining decls for the target function and assign child
+       // index accordingly.
        for ii, sl := range vmap {
+               sort.Sort(byClassThenName(sl))
+               var m map[varPos]int
                if ii == 0 {
-                       sort.Sort(byClassThenName(sl))
-                       for j := 0; j < len(sl); j++ {
-                               sl[j].ChildIndex = int32(j)
+                       if !fnsym.WasInlined() {
+                               for j := 0; j < len(sl); j++ {
+                                       sl[j].ChildIndex = int32(j)
+                               }
+                               continue
                        }
+                       m = makePreinlineDclMap(fnsym)
                } 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{
-                                       DeclName: n.Sym.Name,
-                                       DeclFile: pos.Base().SymFilename(),
-                                       DeclLine: pos.Line(),
-                                       DeclCol:  pos.Col(),
-                               }
-                               if _, found := m[vp]; found {
-                                       Fatalf("child dcl collision on symbol %s within %v\n", n.Sym.Name, fnsym.Name)
-                               }
-                               m[vp] = i
+                       m = makePreinlineDclMap(ifnlsym)
+               }
+
+               // Here we assign child indices to variables based on
+               // pre-inlined decls, and set the "IsInAbstract" flag
+               // appropriately. In addition: 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, we want to undo
+               // this versioning, since DWARF variables in question will be
+               // parented by the inlined routine and not the top-level
+               // caller.
+               synthCount := len(m)
+               for j := 0; j < len(sl); j++ {
+                       canonName := unversion(sl[j].Name)
+                       vp := varPos{
+                               DeclName: canonName,
+                               DeclFile: sl[j].DeclFile,
+                               DeclLine: sl[j].DeclLine,
+                               DeclCol:  sl[j].DeclCol,
                        }
-                       for j := 0; j < len(sl); j++ {
-                               vp := varPos{
-                                       DeclName: sl[j].Name,
-                                       DeclFile: sl[j].DeclFile,
-                                       DeclLine: sl[j].DeclLine,
-                                       DeclCol:  sl[j].DeclCol,
-                               }
-                               if idx, found := m[vp]; found {
-                                       sl[j].ChildIndex = int32(idx)
-                               } else {
+                       returnTmp := strings.HasPrefix(sl[j].Name, "~r")
+                       if idx, found := m[vp]; found {
+                               sl[j].ChildIndex = int32(idx)
+                               sl[j].IsInAbstract = !returnTmp
+                               sl[j].Name = canonName
+                       } else {
+                               // Variable can't be found in the pre-inline dcl list.
+                               // In the top-level case (ii=0) this can happen
+                               // because a composite variable was split into pieces,
+                               // and we're looking at a piece. We can also see
+                               // return temps (~r%d) that were created during
+                               // lowering.
+                               if ii != 0 && !returnTmp {
                                        Fatalf("unexpected: can't find var %s in preInliningDcls for %v\n", sl[j].Name, Ctxt.InlTree.InlinedFunction(int(ii-1)))
                                }
+                               sl[j].ChildIndex = int32(synthCount)
+                               synthCount += 1
                        }
                }
        }
 
-       // Make a second pass through the progs to compute PC ranges
-       // for the various inlined calls.
+       // 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
@@ -173,6 +200,39 @@ func genAbstractFunc(fn *obj.LSym) {
        Ctxt.DwarfAbstractFunc(ifn, fn, myimportpath)
 }
 
+// Undo any versioning performed when a name was written
+// out as part of export data.
+func unversion(name string) string {
+       if i := strings.Index(name, "ยท"); i > 0 {
+               name = name[:i]
+       }
+       return name
+}
+
+// Given a function that was inlined as part of the compilation, dig
+// up the pre-inlining DCL list for the function and create a map that
+// supports lookup of pre-inline dcl index, based on variable
+// position/name.
+func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int {
+       dcl := preInliningDcls(fnsym)
+       m := make(map[varPos]int)
+       for i := 0; i < len(dcl); i++ {
+               n := dcl[i]
+               pos := Ctxt.InnermostPos(n.Pos)
+               vp := varPos{
+                       DeclName: unversion(n.Sym.Name),
+                       DeclFile: pos.Base().SymFilename(),
+                       DeclLine: pos.Line(),
+                       DeclCol:  pos.Col(),
+               }
+               if _, found := m[vp]; found {
+                       Fatalf("child dcl collision on symbol %s within %v\n", n.Sym.Name, fnsym.Name)
+               }
+               m[vp] = i
+       }
+       return m
+}
+
 func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int {
        callIdx, found := imap[inlIdx]
        if found {
@@ -318,6 +378,10 @@ func dumpInlVars(dwvars []*dwarf.Var) {
                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)
+               ia := 0
+               if dwv.IsInAbstract {
+                       ia = 1
+               }
+               Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ)
        }
 }
index c5e1f1390d8f45ba25a581c3639075197651289b..2f96b46f2be35d084166bdd886f4309de63c59ae 100644 (file)
@@ -34,7 +34,6 @@ import (
        "cmd/internal/obj"
        "cmd/internal/src"
        "fmt"
-       "sort"
        "strings"
 )
 
@@ -883,9 +882,9 @@ func mkinlcall1(n, fn *Node, isddd bool) *Node {
 
                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
+                       // was manufactured by the inliner (e.g. "~R2"); such vars
                        // were not part of the original callee.
-                       if !strings.HasPrefix(m.Sym.Name, "~r") {
+                       if !strings.HasPrefix(m.Sym.Name, "~R") {
                                m.SetInlFormal(true)
                                m.Pos = mpos
                                inlfvars = append(inlfvars, m)
@@ -986,7 +985,6 @@ 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 {
@@ -1067,7 +1065,7 @@ func inlvar(var_ *Node) *Node {
 
 // Synthesize a variable to store the inlined function's results in.
 func retvar(t *types.Field, i int) *Node {
-       n := newname(lookupN("~r", i))
+       n := newname(lookupN("~R", i))
        n.Type = t.Type
        n.SetClass(PAUTO)
        n.Name.SetUsed(true)
@@ -1216,28 +1214,3 @@ 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] }
index cf99931bb5f28e2250055f23e1ca1f7ed534f038..07e4f9d2e9e6fd2bc5a791d2590326941bca3393 100644 (file)
@@ -414,6 +414,9 @@ func createSimpleVars(automDecls []*Node) ([]*Node, []*dwarf.Var, map[*Node]bool
                if genDwarfInline > 1 {
                        if n.InlFormal() || n.InlLocal() {
                                inlIndex = posInlIndex(n.Pos) + 1
+                               if n.InlFormal() {
+                                       abbrev = dwarf.DW_ABRV_PARAM
+                               }
                        }
                }
                declpos := Ctxt.InnermostPos(n.Pos)
@@ -513,9 +516,8 @@ func createDwarfVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*No
        }
 
        var dcl []*Node
-       var chopVersion bool
        if fnsym.WasInlined() {
-               dcl, chopVersion = preInliningDcls(fnsym)
+               dcl = preInliningDcls(fnsym)
        } else {
                dcl = automDecls
        }
@@ -534,7 +536,7 @@ func createDwarfVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*No
                        continue
                }
                c := n.Sym.Name[0]
-               if c == '~' || c == '.' || n.Type.IsUntyped() {
+               if c == '.' || n.Type.IsUntyped() {
                        continue
                }
                typename := dwarf.InfoPrefix + typesymname(n.Type)
@@ -547,6 +549,9 @@ func createDwarfVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*No
                if genDwarfInline > 1 {
                        if n.InlFormal() || n.InlLocal() {
                                inlIndex = posInlIndex(n.Pos) + 1
+                               if n.InlFormal() {
+                                       abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+                               }
                        }
                }
                declpos := Ctxt.InnermostPos(n.Pos)
@@ -575,49 +580,59 @@ func createDwarfVars(fnsym *obj.LSym, debugInfo *ssa.FuncDebug, automDecls []*No
 
        }
 
-       // 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) {
+// Given a function that was inlined at some point during the
+// compilation, return a sorted list of nodes corresponding to the
+// autos/locals in that function prior to inlining. If this is a
+// function that is not local to the package being compiled, then the
+// names of the variables may have been "versioned" to avoid conflicts
+// with local vars; disregard this versioning when sorting.
+func preInliningDcls(fnsym *obj.LSym) []*Node {
        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() {
+               if c == '.' || n.Type.IsUntyped() {
                        continue
                }
                rdcl = append(rdcl, n)
        }
-       return rdcl, imported
+       sort.Sort(byNodeName(rdcl))
+       return rdcl
+}
+
+func cmpNodeName(a, b *Node) bool {
+       aart := 0
+       if strings.HasPrefix(a.Sym.Name, "~") {
+               aart = 1
+       }
+       bart := 0
+       if strings.HasPrefix(b.Sym.Name, "~") {
+               bart = 1
+       }
+       if aart != bart {
+               return aart < bart
+       }
+
+       aname := unversion(a.Sym.Name)
+       bname := unversion(b.Sym.Name)
+       return aname < bname
 }
 
+// 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] }
+
 // 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 {
@@ -682,6 +697,9 @@ func createComplexVar(debugInfo *ssa.FuncDebug, n *Node, parts []varPart) *dwarf
        if genDwarfInline > 1 {
                if n.InlFormal() || n.InlLocal() {
                        inlIndex = posInlIndex(n.Pos) + 1
+                       if n.InlFormal() {
+                               abbrev = dwarf.DW_ABRV_PARAM_LOCLIST
+                       }
                }
        }
        declpos := Ctxt.InnermostPos(n.Pos)
index 8997fbf41edcfa8ac7992811568530a94ea2b5a1..b6a0604590583e37ea37d27f130f0a507819f059 100644 (file)
@@ -76,6 +76,7 @@ type Var struct {
        DeclCol       uint
        InlIndex      int32 // subtract 1 to form real index into InlTree
        ChildIndex    int32 // child DIE index in abstract function
+       IsInAbstract  bool  // variable exists in abstract function
 }
 
 // A Scope represents a lexical scope. All variables declared within a
@@ -1121,14 +1122,23 @@ func PutAbstractFunc(ctxt Context, s *FnState) error {
                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 found || !scope.Vars[i].IsInAbstract {
+                                       continue
                                }
+                               flattened = append(flattened, scope.Vars[i])
                        }
                }
                if len(flattened) > 0 {
                        sort.Sort(byChildIndex(flattened))
 
+                       if logDwarf {
+                               ctxt.Logf("putAbstractScope(%v): vars:", s.Info)
+                               for i, v := range flattened {
+                                       ctxt.Logf(" %d:%s", i, v.Name)
+                               }
+                               ctxt.Logf("\n")
+                       }
+
                        // This slice will hold the offset in bytes for each child
                        // variable DIE with respect to the start of the parent
                        // subprogram DIE.
@@ -1186,6 +1196,9 @@ func PutInlinedFunc(ctxt Context, s *FnState, callersym Sym, callIdx int) error
        inlIndex := ic.InlIndex
        var encbuf [20]byte
        for _, v := range vars {
+               if !v.IsInAbstract {
+                       continue
+               }
                putvar(ctxt, s, v, callee, abbrev, inlIndex, encbuf[:0])
        }
 
@@ -1324,6 +1337,22 @@ func putscope(ctxt Context, s *FnState, scopes []Scope, curscope int32, fnabbrev
        return curscope
 }
 
+// Given a default var abbrev code, select corresponding concrete code.
+func concreteVarAbbrev(varAbbrev int) int {
+       switch varAbbrev {
+       case DW_ABRV_AUTO:
+               return DW_ABRV_AUTO_CONCRETE
+       case DW_ABRV_PARAM:
+               return DW_ABRV_PARAM_CONCRETE
+       case DW_ABRV_AUTO_LOCLIST:
+               return DW_ABRV_AUTO_CONCRETE_LOCLIST
+       case DW_ABRV_PARAM_LOCLIST:
+               return DW_ABRV_PARAM_CONCRETE_LOCLIST
+       default:
+               panic("should never happen")
+       }
+}
+
 // Pick the correct abbrev code for variable or parameter DIE.
 func determineVarAbbrev(v *Var, fnabbrev int) (int, bool, bool) {
        abbrev := v.Abbrev
@@ -1340,35 +1369,29 @@ func determineVarAbbrev(v *Var, fnabbrev int) (int, bool, bool) {
                abbrev = DW_ABRV_PARAM
        }
 
+       // Determine whether to use a concrete variable or regular variable DIE.
        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
+       case DW_ABRV_FUNCTION_CONCRETE:
+               // If we're emitting a concrete subprogram DIE and the variable
+               // in question is not part of the corresponding abstract function DIE,
+               // then use the default (non-concrete) abbrev for this param.
+               if !v.IsInAbstract {
+                       concrete = false
                }
+       case DW_ABRV_INLINED_SUBROUTINE, DW_ABRV_INLINED_SUBROUTINE_RANGES:
        default:
                panic("should never happen")
        }
 
+       // Select proper abbrev based on concrete/non-concrete
+       if concrete {
+               abbrev = concreteVarAbbrev(abbrev)
+       }
+
        return abbrev, missing, concrete
 }
 
index 4332a3dfba4dd8feb4e9d6e5300c74b314d7a6e6..e959aeed654509409e1029927a83fdf884f1a017 100644 (file)
@@ -19,6 +19,13 @@ import (
        "testing"
 )
 
+const (
+       NoOpt        = "-gcflags=-l -N"
+       Opt          = ""
+       OptInl4      = "-gcflags=all=-l=4"
+       OptInl4DwLoc = "-gcflags=all=-l=4 -dwarflocationlists"
+)
+
 func TestRuntimeTypeDIEs(t *testing.T) {
        testenv.MustHaveGoBuild(t)
 
@@ -32,7 +39,7 @@ func TestRuntimeTypeDIEs(t *testing.T) {
        }
        defer os.RemoveAll(dir)
 
-       f := gobuild(t, dir, `package main; func main() { }`, false)
+       f := gobuild(t, dir, `package main; func main() { }`, NoOpt)
        defer f.Close()
 
        dwarf, err := f.DWARF()
@@ -77,7 +84,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, opt bool) *objfilepkg.File {
+func gobuild(t *testing.T, dir string, testfile string, gcflags string) *objfilepkg.File {
        src := filepath.Join(dir, "test.go")
        dst := filepath.Join(dir, "out")
 
@@ -85,10 +92,6 @@ func gobuild(t *testing.T, dir string, testfile string, opt bool) *objfilepkg.Fi
                t.Fatal(err)
        }
 
-       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)
@@ -142,7 +145,7 @@ func main() {
        }
        defer os.RemoveAll(dir)
 
-       f := gobuild(t, dir, prog, false)
+       f := gobuild(t, dir, prog, NoOpt)
 
        defer f.Close()
 
@@ -220,7 +223,7 @@ func main() {
                t.Fatalf("could not create directory: %v", err)
        }
        defer os.RemoveAll(dir)
-       f := gobuild(t, dir, prog, false)
+       f := gobuild(t, dir, prog, NoOpt)
        defer f.Close()
        d, err := f.DWARF()
        if err != nil {
@@ -268,7 +271,7 @@ func main() {
        }
        defer os.RemoveAll(dir)
 
-       f := gobuild(t, dir, prog, false)
+       f := gobuild(t, dir, prog, NoOpt)
        defer f.Close()
 
        d, err := f.DWARF()
@@ -326,7 +329,7 @@ func main() {
        }
        defer os.RemoveAll(dir)
 
-       f := gobuild(t, dir, prog, false)
+       f := gobuild(t, dir, prog, NoOpt)
 
        d, err := f.DWARF()
        if err != nil {
@@ -391,6 +394,7 @@ type examiner struct {
        dies        []*dwarf.Entry
        idxByOffset map[dwarf.Offset]int
        kids        map[int][]int
+       parent      map[int]int
        byname      map[string][]int
 }
 
@@ -398,6 +402,7 @@ type examiner struct {
 func (ex *examiner) populate(rdr *dwarf.Reader) error {
        ex.idxByOffset = make(map[dwarf.Offset]int)
        ex.kids = make(map[int][]int)
+       ex.parent = make(map[int]int)
        ex.byname = make(map[string][]int)
        var nesting []int
        for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() {
@@ -424,6 +429,7 @@ func (ex *examiner) populate(rdr *dwarf.Reader) error {
                if len(nesting) > 0 {
                        parent := nesting[len(nesting)-1]
                        ex.kids[parent] = append(ex.kids[parent], idx)
+                       ex.parent[idx] = parent
                }
                if entry.Children {
                        nesting = append(nesting, idx)
@@ -449,10 +455,10 @@ func (ex *examiner) dumpEntry(idx int, dumpKids bool, ilevel int) error {
        }
        entry := ex.dies[idx]
        indent(ilevel)
-       fmt.Printf("%d: %v\n", idx, entry.Tag)
+       fmt.Printf("0x%x: %v\n", idx, entry.Tag)
        for _, f := range entry.Field {
                indent(ilevel)
-               fmt.Printf("at=%v val=%v\n", f.Attr, f.Val)
+               fmt.Printf("at=%v val=0x%x\n", f.Attr, f.Val)
        }
        if dumpKids {
                ksl := ex.kids[idx]
@@ -481,7 +487,7 @@ func (ex *examiner) idxFromOffset(off dwarf.Offset) int {
 
 // Return the dwarf.Entry pointer for the DIE with id 'idx'
 func (ex *examiner) entryFromIdx(idx int) *dwarf.Entry {
-       if idx >= len(ex.dies) {
+       if idx >= len(ex.dies) || idx < 0 {
                return nil
        }
        return ex.dies[idx]
@@ -497,6 +503,15 @@ func (ex *examiner) Children(idx int) []*dwarf.Entry {
        return ret
 }
 
+// Returns parent DIE for DIE 'idx', or nil if the DIE is top level
+func (ex *examiner) Parent(idx int) *dwarf.Entry {
+       p, found := ex.parent[idx]
+       if !found {
+               return nil
+       }
+       return ex.entryFromIdx(p)
+}
+
 // 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
@@ -542,10 +557,10 @@ func main() {
        }
        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)
+       // Note: this is a build with "-l=4", as opposed to "-l -N". The
+       // test is intended to verify DWARF that is only generated when
+       // the inliner is active.
+       f := gobuild(t, dir, prog, OptInl4)
 
        d, err := f.DWARF()
        if err != nil {
@@ -628,3 +643,121 @@ func main() {
                t.Fatalf("not enough inlined subroutines found in main.main")
        }
 }
+
+func abstractOriginSanity(t *testing.T, flags string) {
+
+       // Nothing special about net/http here, this is just a convenient
+       // way to pull in a lot of code.
+       const prog = `
+package main
+
+import (
+       "net/http"
+       "net/http/httptest"
+)
+
+type statusHandler int
+
+func (h *statusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+       w.WriteHeader(int(*h))
+}
+
+func main() {
+       status := statusHandler(http.StatusNotFound)
+       s := httptest.NewServer(&status)
+       defer s.Close()
+}
+`
+       dir, err := ioutil.TempDir("", "TestAbstractOriginSanity")
+       if err != nil {
+               t.Fatalf("could not create directory: %v", err)
+       }
+       defer os.RemoveAll(dir)
+
+       // Build with inlining, to exercise DWARF inlining support.
+       f := gobuild(t, dir, prog, flags)
+
+       d, err := f.DWARF()
+       if err != nil {
+               t.Fatalf("error reading DWARF: %v", err)
+       }
+       rdr := d.Reader()
+       ex := examiner{}
+       if err := ex.populate(rdr); err != nil {
+               t.Fatalf("error reading DWARF: %v", err)
+       }
+
+       // Make a pass through all DIEs looking for abstract origin
+       // references.
+       abscount := 0
+       for i, die := range ex.dies {
+
+               // Does it have an abstract origin?
+               ooff, originOK := die.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
+               if !originOK {
+                       continue
+               }
+
+               // All abstract origin references should be resolvable.
+               abscount += 1
+               originDIE := ex.entryFromOffset(ooff)
+               if originDIE == nil {
+                       ex.dumpEntry(i, false, 0)
+                       t.Fatalf("unresolved abstract origin ref in DIE at offset 0x%x\n", die.Offset)
+               }
+
+               // Suppose that DIE X has parameter/variable children {K1,
+               // K2, ... KN}. If X has an abstract origin of A, then for
+               // each KJ, the abstract origin of KJ should be a child of A.
+               // Note that this same rule doesn't hold for non-variable DIEs.
+               pidx := ex.idxFromOffset(die.Offset)
+               if pidx < 0 {
+                       t.Fatalf("can't locate DIE id")
+               }
+               kids := ex.Children(pidx)
+               for _, kid := range kids {
+                       if kid.Tag != dwarf.TagVariable &&
+                               kid.Tag != dwarf.TagFormalParameter {
+                               continue
+                       }
+                       kooff, originOK := kid.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset)
+                       if !originOK {
+                               continue
+                       }
+                       childOriginDIE := ex.entryFromOffset(kooff)
+                       if childOriginDIE == nil {
+                               ex.dumpEntry(i, false, 0)
+                               t.Fatalf("unresolved abstract origin ref in DIE at offset %x", kid.Offset)
+                       }
+                       coidx := ex.idxFromOffset(childOriginDIE.Offset)
+                       childOriginParent := ex.Parent(coidx)
+                       if childOriginParent != originDIE {
+                               ex.dumpEntry(i, false, 0)
+                               t.Fatalf("unexpected parent of abstract origin DIE at offset %v", childOriginDIE.Offset)
+                       }
+               }
+       }
+       if abscount == 0 {
+               t.Fatalf("no abstract origin refs found, something is wrong")
+       }
+}
+
+func TestAbstractOriginSanity(t *testing.T) {
+       testenv.MustHaveGoBuild(t)
+
+       if runtime.GOOS == "plan9" {
+               t.Skip("skipping on plan9; no DWARF symbol table in executables")
+       }
+
+       abstractOriginSanity(t, OptInl4)
+}
+
+func TestAbstractOriginSanityWithLocationLists(t *testing.T) {
+       testenv.MustHaveGoBuild(t)
+
+       if runtime.GOOS == "plan9" {
+               t.Skip("skipping on plan9; no DWARF symbol table in executables")
+       }
+
+       abstractOriginSanity(t, OptInl4DwLoc)
+}