diff options
Diffstat (limited to 'src/cmd/compile/internal/dwarfgen')
-rw-r--r-- | src/cmd/compile/internal/dwarfgen/dwarf.go | 585 | ||||
-rw-r--r-- | src/cmd/compile/internal/dwarfgen/dwinl.go | 454 | ||||
-rw-r--r-- | src/cmd/compile/internal/dwarfgen/marker.go | 94 | ||||
-rw-r--r-- | src/cmd/compile/internal/dwarfgen/scope.go | 136 | ||||
-rw-r--r-- | src/cmd/compile/internal/dwarfgen/scope_test.go | 539 |
5 files changed, 1808 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go new file mode 100644 index 0000000..8ae03d7 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -0,0 +1,585 @@ +// Copyright 2011 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 dwarfgen + +import ( + "bytes" + "flag" + "fmt" + "internal/buildcfg" + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/ssa" + "cmd/compile/internal/ssagen" + "cmd/compile/internal/types" + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) { + fn := curfn.(*ir.Func) + + if fn.Nname != nil { + expect := fn.Linksym() + if fnsym.ABI() == obj.ABI0 { + expect = fn.LinksymABI(obj.ABI0) + } + if fnsym != expect { + base.Fatalf("unexpected fnsym: %v != %v", fnsym, expect) + } + } + + // Back when there were two different *Funcs for a function, this code + // was not consistent about whether a particular *Node being processed + // was an ODCLFUNC or ONAME node. Partly this is because inlined function + // bodies have no ODCLFUNC node, which was it's own inconsistency. + // In any event, the handling of the two different nodes for DWARF purposes + // was subtly different, likely in unintended ways. CL 272253 merged the + // two nodes' Func fields, so that code sees the same *Func whether it is + // holding the ODCLFUNC or the ONAME. This resulted in changes in the + // DWARF output. To preserve the existing DWARF output and leave an + // intentional change for a future CL, this code does the following when + // fn.Op == ONAME: + // + // 1. Disallow use of createComplexVars in createDwarfVars. + // It was not possible to reach that code for an ONAME before, + // because the DebugInfo was set only on the ODCLFUNC Func. + // Calling into it in the ONAME case causes an index out of bounds panic. + // + // 2. Do not populate apdecls. fn.Func.Dcl was in the ODCLFUNC Func, + // not the ONAME Func. Populating apdecls for the ONAME case results + // in selected being populated after createSimpleVars is called in + // createDwarfVars, and then that causes the loop to skip all the entries + // in dcl, meaning that the RecordAutoType calls don't happen. + // + // These two adjustments keep toolstash -cmp working for now. + // Deciding the right answer is, as they say, future work. + // + // We can tell the difference between the old ODCLFUNC and ONAME + // cases by looking at the infosym.Name. If it's empty, DebugInfo is + // being called from (*obj.Link).populateDWARF, which used to use + // the ODCLFUNC. If it's non-empty (the name will end in $abstract), + // DebugInfo is being called from (*obj.Link).DwarfAbstractFunc, + // which used to use the ONAME form. + isODCLFUNC := infosym.Name == "" + + var apdecls []*ir.Name + // Populate decls for fn. + if isODCLFUNC { + for _, n := range fn.Dcl { + if n.Op() != ir.ONAME { // might be OTYPE or OLITERAL + continue + } + switch n.Class { + case ir.PAUTO: + if !n.Used() { + // Text == nil -> generating abstract function + if fnsym.Func().Text != nil { + base.Fatalf("debuginfo unused node (AllocFrame should truncate fn.Func.Dcl)") + } + continue + } + case ir.PPARAM, ir.PPARAMOUT: + default: + continue + } + apdecls = append(apdecls, n) + if n.Type().Kind() == types.TSSA { + // Can happen for TypeInt128 types. This only happens for + // spill locations, so not a huge deal. + continue + } + fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type())) + } + } + + decls, dwarfVars := createDwarfVars(fnsym, isODCLFUNC, fn, apdecls) + + // For each type referenced by the functions auto vars but not + // already referenced by a dwarf var, attach an R_USETYPE relocation to + // the function symbol to insure that the type included in DWARF + // processing during linking. + typesyms := []*obj.LSym{} + for t, _ := range fnsym.Func().Autot { + typesyms = append(typesyms, t) + } + sort.Sort(obj.BySymName(typesyms)) + for _, sym := range typesyms { + r := obj.Addrel(infosym) + r.Sym = sym + r.Type = objabi.R_USETYPE + } + fnsym.Func().Autot = nil + + var varScopes []ir.ScopeID + for _, decl := range decls { + pos := declPos(decl) + varScopes = append(varScopes, findScope(fn.Marks, pos)) + } + + scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes) + var inlcalls dwarf.InlCalls + if base.Flag.GenDwarfInl > 0 { + inlcalls = assembleInlines(fnsym, dwarfVars) + } + return scopes, inlcalls +} + +func declPos(decl *ir.Name) src.XPos { + return decl.Canonical().Pos() +} + +// createDwarfVars process fn, returning a list of DWARF variables and the +// Nodes they represent. +func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var) { + // Collect a raw list of DWARF vars. + var vars []*dwarf.Var + var decls []*ir.Name + var selected ir.NameSet + + if base.Ctxt.Flag_locationlists && base.Ctxt.Flag_optimize && fn.DebugInfo != nil && complexOK { + decls, vars, selected = createComplexVars(fnsym, fn) + } else if fn.ABI == obj.ABIInternal && base.Flag.N != 0 && complexOK { + decls, vars, selected = createABIVars(fnsym, fn, apDecls) + } else { + decls, vars, selected = createSimpleVars(fnsym, apDecls) + } + + dcl := apDecls + if fnsym.WasInlined() { + dcl = preInliningDcls(fnsym) + } else { + // The backend's stackframe pass prunes away entries from the + // fn's Dcl list, including PARAMOUT nodes that correspond to + // output params passed in registers. Add back in these + // entries here so that we can process them properly during + // DWARF-gen. See issue 48573 for more details. + debugInfo := fn.DebugInfo.(*ssa.FuncDebug) + for _, n := range debugInfo.RegOutputParams { + if n.Class != ir.PPARAMOUT || !n.IsOutputParamInRegisters() { + panic("invalid ir.Name on debugInfo.RegOutputParams list") + } + dcl = append(dcl, n) + } + } + + // 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). Input arguments not eligible + // for SSA optimization are also missing. 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 non-SSA-able arguments, however, the correct information + // is known -- they have a single home on the stack. + for _, n := range dcl { + if selected.Has(n) { + continue + } + c := n.Sym().Name[0] + if c == '.' || n.Type().IsUntyped() { + continue + } + if n.Class == ir.PPARAM && !ssagen.TypeOK(n.Type()) { + // SSA-able args get location lists, and may move in and + // out of registers, so those are handled elsewhere. + // Autos and named output params seem to get handled + // with VARDEF, which creates location lists. + // Args not of SSA-able type are treated here; they + // are homed on the stack in a single place for the + // entire call. + vars = append(vars, createSimpleVar(fnsym, n)) + decls = append(decls, n) + continue + } + typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) + decls = append(decls, n) + abbrev := dwarf.DW_ABRV_AUTO_LOCLIST + isReturnValue := (n.Class == ir.PPARAMOUT) + if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } + if n.Esc() == ir.EscHeap { + // The variable in question has been promoted to the heap. + // Its address is in n.Heapaddr. + // TODO(thanm): generate a better location expression + } + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.InlFormal() || n.InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } + } + } + declpos := base.Ctxt.InnermostPos(n.Pos()) + vars = append(vars, &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: isReturnValue, + Abbrev: abbrev, + StackOffset: int32(n.FrameOffset()), + Type: base.Ctxt.Lookup(typename), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.RelCol(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + DictIndex: n.DictIndex, + }) + // Record go type of to insure that it gets emitted by the linker. + fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type())) + } + + // Sort decls and vars. + sortDeclsAndVars(fn, decls, vars) + + return decls, vars +} + +// sortDeclsAndVars sorts the decl and dwarf var lists according to +// parameter declaration order, so as to insure that when a subprogram +// DIE is emitted, its parameter children appear in declaration order. +// Prior to the advent of the register ABI, sorting by frame offset +// would achieve this; with the register we now need to go back to the +// original function signature. +func sortDeclsAndVars(fn *ir.Func, decls []*ir.Name, vars []*dwarf.Var) { + paramOrder := make(map[*ir.Name]int) + idx := 1 + for _, selfn := range types.RecvsParamsResults { + fsl := selfn(fn.Type()).FieldSlice() + for _, f := range fsl { + if n, ok := f.Nname.(*ir.Name); ok { + paramOrder[n] = idx + idx++ + } + } + } + sort.Stable(varsAndDecls{decls, vars, paramOrder}) +} + +type varsAndDecls struct { + decls []*ir.Name + vars []*dwarf.Var + paramOrder map[*ir.Name]int +} + +func (v varsAndDecls) Len() int { + return len(v.decls) +} + +func (v varsAndDecls) Less(i, j int) bool { + nameLT := func(ni, nj *ir.Name) bool { + oi, foundi := v.paramOrder[ni] + oj, foundj := v.paramOrder[nj] + if foundi { + if foundj { + return oi < oj + } else { + return true + } + } + return false + } + return nameLT(v.decls[i], v.decls[j]) +} + +func (v varsAndDecls) Swap(i, j int) { + v.vars[i], v.vars[j] = v.vars[j], v.vars[i] + v.decls[i], v.decls[j] = v.decls[j], v.decls[i] +} + +// 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) []*ir.Name { + fn := base.Ctxt.DwFixups.GetPrecursorFunc(fnsym).(*ir.Func) + var rdcl []*ir.Name + for _, n := range fn.Inl.Dcl { + c := n.Sym().Name[0] + // Avoid reporting "_" parameters, since if there are more than + // one, it can result in a collision later on, as in #23179. + if unversion(n.Sym().Name) == "_" || c == '.' || n.Type().IsUntyped() { + continue + } + rdcl = append(rdcl, n) + } + return rdcl +} + +// createSimpleVars creates a DWARF entry for every variable declared in the +// function, claiming that they are permanently on the stack. +func createSimpleVars(fnsym *obj.LSym, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) { + var vars []*dwarf.Var + var decls []*ir.Name + var selected ir.NameSet + for _, n := range apDecls { + if ir.IsAutoTmp(n) { + continue + } + + decls = append(decls, n) + vars = append(vars, createSimpleVar(fnsym, n)) + selected.Add(n) + } + return decls, vars, selected +} + +func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var { + var abbrev int + var offs int64 + + localAutoOffset := func() int64 { + offs = n.FrameOffset() + if base.Ctxt.FixedFrameSize() == 0 { + offs -= int64(types.PtrSize) + } + if buildcfg.FramePointerEnabled { + offs -= int64(types.PtrSize) + } + return offs + } + + switch n.Class { + case ir.PAUTO: + offs = localAutoOffset() + abbrev = dwarf.DW_ABRV_AUTO + case ir.PPARAM, ir.PPARAMOUT: + abbrev = dwarf.DW_ABRV_PARAM + if n.IsOutputParamInRegisters() { + offs = localAutoOffset() + } else { + offs = n.FrameOffset() + base.Ctxt.FixedFrameSize() + } + + default: + base.Fatalf("createSimpleVar unexpected class %v for node %v", n.Class, n) + } + + typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) + delete(fnsym.Func().Autot, reflectdata.TypeLinksym(n.Type())) + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.InlFormal() || n.InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM + } + } + } + declpos := base.Ctxt.InnermostPos(declPos(n)) + return &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: n.Class == ir.PPARAMOUT, + IsInlFormal: n.InlFormal(), + Abbrev: abbrev, + StackOffset: int32(offs), + Type: base.Ctxt.Lookup(typename), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.RelCol(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + DictIndex: n.DictIndex, + } +} + +// createABIVars creates DWARF variables for functions in which the +// register ABI is enabled but optimization is turned off. It uses a +// hybrid approach in which register-resident input params are +// captured with location lists, and all other vars use the "simple" +// strategy. +func createABIVars(fnsym *obj.LSym, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) { + + // Invoke createComplexVars to generate dwarf vars for input parameters + // that are register-allocated according to the ABI rules. + decls, vars, selected := createComplexVars(fnsym, fn) + + // Now fill in the remainder of the variables: input parameters + // that are not register-resident, output parameters, and local + // variables. + for _, n := range apDecls { + if ir.IsAutoTmp(n) { + continue + } + if _, ok := selected[n]; ok { + // already handled + continue + } + + decls = append(decls, n) + vars = append(vars, createSimpleVar(fnsym, n)) + selected.Add(n) + } + + return decls, vars, selected +} + +// createComplexVars creates recomposed DWARF vars with location lists, +// suitable for describing optimized code. +func createComplexVars(fnsym *obj.LSym, fn *ir.Func) ([]*ir.Name, []*dwarf.Var, ir.NameSet) { + debugInfo := fn.DebugInfo.(*ssa.FuncDebug) + + // Produce a DWARF variable entry for each user variable. + var decls []*ir.Name + var vars []*dwarf.Var + var ssaVars ir.NameSet + + for varID, dvar := range debugInfo.Vars { + n := dvar + ssaVars.Add(n) + for _, slot := range debugInfo.VarSlots[varID] { + ssaVars.Add(debugInfo.Slots[slot].N) + } + + if dvar := createComplexVar(fnsym, fn, ssa.VarID(varID)); dvar != nil { + decls = append(decls, n) + vars = append(vars, dvar) + } + } + + return decls, vars, ssaVars +} + +// createComplexVar builds a single DWARF variable entry and location list. +func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID) *dwarf.Var { + debug := fn.DebugInfo.(*ssa.FuncDebug) + n := debug.Vars[varID] + + var abbrev int + switch n.Class { + case ir.PAUTO: + abbrev = dwarf.DW_ABRV_AUTO_LOCLIST + case ir.PPARAM, ir.PPARAMOUT: + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + default: + return nil + } + + gotype := reflectdata.TypeLinksym(n.Type()) + delete(fnsym.Func().Autot, gotype) + typename := dwarf.InfoPrefix + gotype.Name[len("type."):] + inlIndex := 0 + if base.Flag.GenDwarfInl > 1 { + if n.InlFormal() || n.InlLocal() { + inlIndex = posInlIndex(n.Pos()) + 1 + if n.InlFormal() { + abbrev = dwarf.DW_ABRV_PARAM_LOCLIST + } + } + } + declpos := base.Ctxt.InnermostPos(n.Pos()) + dvar := &dwarf.Var{ + Name: n.Sym().Name, + IsReturnValue: n.Class == ir.PPARAMOUT, + IsInlFormal: n.InlFormal(), + Abbrev: abbrev, + Type: base.Ctxt.Lookup(typename), + // The stack offset is used as a sorting key, so for decomposed + // variables just give it the first 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: ssagen.StackOffset(debug.Slots[debug.VarSlots[varID][0]]), + DeclFile: declpos.RelFilename(), + DeclLine: declpos.RelLine(), + DeclCol: declpos.RelCol(), + InlIndex: int32(inlIndex), + ChildIndex: -1, + DictIndex: n.DictIndex, + } + list := debug.LocationLists[varID] + if len(list) != 0 { + dvar.PutLocationList = func(listSym, startPC dwarf.Sym) { + debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym)) + } + } + return dvar +} + +// RecordFlags records the specified command-line flags to be placed +// in the DWARF info. +func RecordFlags(flags ...string) { + if base.Ctxt.Pkgpath == "" { + // We can't record the flags if we don't know what the + // package name is. + return + } + + type BoolFlag interface { + IsBoolFlag() bool + } + type CountFlag interface { + IsCountFlag() bool + } + var cmd bytes.Buffer + for _, name := range flags { + f := flag.Lookup(name) + if f == nil { + continue + } + getter := f.Value.(flag.Getter) + if getter.String() == f.DefValue { + // Flag has default value, so omit it. + continue + } + if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() { + val, ok := getter.Get().(bool) + if ok && val { + fmt.Fprintf(&cmd, " -%s", f.Name) + continue + } + } + if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() { + val, ok := getter.Get().(int) + if ok && val == 1 { + fmt.Fprintf(&cmd, " -%s", f.Name) + continue + } + } + fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get()) + } + + // Adds flag to producer string singalling whether regabi is turned on or + // off. + // Once regabi is turned on across the board and the relative GOEXPERIMENT + // knobs no longer exist this code should be removed. + if buildcfg.Experiment.RegabiArgs { + cmd.Write([]byte(" regabi")) + } + + if cmd.Len() == 0 { + return + } + s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + base.Ctxt.Pkgpath) + s.Type = objabi.SDWARFCUINFO + // Sometimes (for example when building tests) we can link + // together two package main archives. So allow dups. + s.Set(obj.AttrDuplicateOK, true) + base.Ctxt.Data = append(base.Ctxt.Data, s) + s.P = cmd.Bytes()[1:] +} + +// RecordPackageName records the name of the package being +// compiled, so that the linker can save it in the compile unit's DIE. +func RecordPackageName() { + s := base.Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + base.Ctxt.Pkgpath) + s.Type = objabi.SDWARFCUINFO + // Sometimes (for example when building tests) we can link + // together two package main archives. So allow dups. + s.Set(obj.AttrDuplicateOK, true) + base.Ctxt.Data = append(base.Ctxt.Data, s) + s.P = []byte(types.LocalPkg.Name) +} diff --git a/src/cmd/compile/internal/dwarfgen/dwinl.go b/src/cmd/compile/internal/dwarfgen/dwinl.go new file mode 100644 index 0000000..c785e06 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/dwinl.go @@ -0,0 +1,454 @@ +// 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 dwarfgen + +import ( + "fmt" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/src" +) + +// To identify variables by original source position. +type varPos struct { + DeclName string + 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, dwVars []*dwarf.Var) dwarf.InlCalls { + var inlcalls dwarf.InlCalls + + if base.Debug.DwarfInl != 0 { + base.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. + // + // 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 { + var m map[varPos]int + if ii == 0 { + if !fnsym.WasInlined() { + for j, v := range sl { + v.ChildIndex = int32(j) + } + continue + } + m = makePreinlineDclMap(fnsym) + } else { + ifnlsym := base.Ctxt.InlTree.InlinedFunction(int(ii - 1)) + 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 _, v := range sl { + canonName := unversion(v.Name) + vp := varPos{ + DeclName: canonName, + DeclFile: v.DeclFile, + DeclLine: v.DeclLine, + DeclCol: v.DeclCol, + } + synthesized := strings.HasPrefix(v.Name, "~r") || canonName == "_" || strings.HasPrefix(v.Name, "~b") + if idx, found := m[vp]; found { + v.ChildIndex = int32(idx) + v.IsInAbstract = !synthesized + v.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, or unnamed params ("_"). + v.ChildIndex = int32(synthCount) + synthCount++ + } + } + } + + // Make a second pass through the progs to compute PC ranges for + // the various inlined calls. + start := int64(-1) + curii := -1 + 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 + } + // Close out the current range + if start != -1 { + addRange(inlcalls.Calls, start, p.Pc, curii, imap) + } + // Begin new range + start = p.Pc + curii = ii + } + if start != -1 { + addRange(inlcalls.Calls, start, fnsym.Size, curii, imap) + } + + // Issue 33188: if II foo is a child of II bar, then ensure that + // bar's ranges include the ranges of foo (the loop above will produce + // disjoint ranges). + for k, c := range inlcalls.Calls { + if c.Root { + unifyCallRanges(inlcalls, k) + } + } + + // Debugging + if base.Debug.DwarfInl != 0 { + dumpInlCalls(inlcalls) + dumpInlVars(dwVars) + } + + // Perform a consistency check on inlined routine PC ranges + // produced by unifyCallRanges above. In particular, complain in + // cases where you have A -> B -> C (e.g. C is inlined into B, and + // B is inlined into A) and the ranges for B are not enclosed + // within the ranges for A, or C within B. + for k, c := range inlcalls.Calls { + if c.Root { + checkInlCall(fnsym.Name, inlcalls, fnsym.Size, k, -1) + } + } + + 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 AbstractFunc(fn *obj.LSym) { + ifn := base.Ctxt.DwFixups.GetPrecursorFunc(fn) + if ifn == nil { + base.Ctxt.Diag("failed to locate precursor fn for %v", fn) + return + } + _ = ifn.(*ir.Func) + if base.Debug.DwarfInl != 0 { + base.Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name) + } + base.Ctxt.DwarfAbstractFunc(ifn, fn, base.Ctxt.Pkgpath) +} + +// 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. NB: the recipe for computing variable pos/file/line +// needs to be kept in sync with the similar code in gc.createSimpleVars +// and related functions. +func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int { + dcl := preInliningDcls(fnsym) + m := make(map[varPos]int) + for i, n := range dcl { + pos := base.Ctxt.InnermostPos(n.Pos()) + vp := varPos{ + DeclName: unversion(n.Sym().Name), + DeclFile: pos.RelFilename(), + DeclLine: pos.RelLine(), + DeclCol: pos.RelCol(), + } + if _, found := m[vp]; found { + // We can see collisions (variables with the same name/file/line/col) in obfuscated or machine-generated code -- see issue 44378 for an example. Skip duplicates in such cases, since it is unlikely that a human will be debugging such code. + continue + } + m[vp] = i + } + return m +} + +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 := base.Ctxt.InlTree.Parent(inlIdx) + if parInlIdx >= 0 { + parCallIdx = insertInlCall(dwcalls, parInlIdx, imap) + } + + // Create new entry for this inline + inlinedFn := base.Ctxt.InlTree.InlinedFunction(inlIdx) + callXPos := base.Ctxt.InlTree.CallPos(inlIdx) + absFnSym := base.Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn) + pb := base.Ctxt.PosTable.Pos(callXPos).Base() + callFileSym := base.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 := base.Ctxt.PosTable.Pos(xpos) + if b := pos.Base(); b != nil { + ii := b.InliningIndex() + if ii >= 0 { + return ii + } + } + return -1 +} + +func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) { + if start == -1 { + panic("bad range start") + } + if end == -1 { + panic("bad range end") + } + if ii == -1 { + return + } + if start == end { + return + } + // Append range to correct inlined call + callIdx, found := imap[ii] + if !found { + base.Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start) + } + call := &calls[callIdx] + call.Ranges = append(call.Ranges, dwarf.Range{Start: start, End: end}) +} + +func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) { + for i := 0; i < ilevel; i++ { + base.Ctxt.Logf(" ") + } + ic := inlcalls.Calls[idx] + callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex) + base.Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name) + for _, f := range ic.InlVars { + base.Ctxt.Logf(" %v", f.Name) + } + base.Ctxt.Logf(" ) C: (") + for _, k := range ic.Children { + base.Ctxt.Logf(" %v", k) + } + base.Ctxt.Logf(" ) R:") + for _, r := range ic.Ranges { + base.Ctxt.Logf(" [%d,%d)", r.Start, r.End) + } + base.Ctxt.Logf("\n") + for _, k := range ic.Children { + dumpInlCall(inlcalls, k, ilevel+1) + } + +} + +func dumpInlCalls(inlcalls dwarf.InlCalls) { + for k, c := range inlcalls.Calls { + if c.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" + } + ia := 0 + if dwv.IsInAbstract { + ia = 1 + } + base.Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ) + } +} + +func rangesContains(par []dwarf.Range, rng dwarf.Range) (bool, string) { + for _, r := range par { + if rng.Start >= r.Start && rng.End <= r.End { + return true, "" + } + } + msg := fmt.Sprintf("range [%d,%d) not contained in {", rng.Start, rng.End) + for _, r := range par { + msg += fmt.Sprintf(" [%d,%d)", r.Start, r.End) + } + msg += " }" + return false, msg +} + +func rangesContainsAll(parent, child []dwarf.Range) (bool, string) { + for _, r := range child { + c, m := rangesContains(parent, r) + if !c { + return false, m + } + } + return true, "" +} + +// checkInlCall verifies that the PC ranges for inline info 'idx' are +// enclosed/contained within the ranges of its parent inline (or if +// this is a root/toplevel inline, checks that the ranges fall within +// the extent of the top level function). A panic is issued if a +// malformed range is found. +func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, parentIdx int) { + + // Callee + ic := inlCalls.Calls[idx] + callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name + calleeRanges := ic.Ranges + + // Caller + caller := funcName + parentRanges := []dwarf.Range{dwarf.Range{Start: int64(0), End: funcSize}} + if parentIdx != -1 { + pic := inlCalls.Calls[parentIdx] + caller = base.Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name + parentRanges = pic.Ranges + } + + // Callee ranges contained in caller ranges? + c, m := rangesContainsAll(parentRanges, calleeRanges) + if !c { + base.Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m) + } + + // Now visit kids + for _, k := range ic.Children { + checkInlCall(funcName, inlCalls, funcSize, k, idx) + } +} + +// unifyCallRanges ensures that the ranges for a given inline +// transitively include all of the ranges for its child inlines. +func unifyCallRanges(inlcalls dwarf.InlCalls, idx int) { + ic := &inlcalls.Calls[idx] + for _, childIdx := range ic.Children { + // First make sure child ranges are unified. + unifyCallRanges(inlcalls, childIdx) + + // Then merge child ranges into ranges for this inline. + cic := inlcalls.Calls[childIdx] + ic.Ranges = dwarf.MergeRanges(ic.Ranges, cic.Ranges) + } +} diff --git a/src/cmd/compile/internal/dwarfgen/marker.go b/src/cmd/compile/internal/dwarfgen/marker.go new file mode 100644 index 0000000..ec6ce45 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/marker.go @@ -0,0 +1,94 @@ +// Copyright 2021 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 dwarfgen + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/src" +) + +// A ScopeMarker tracks scope nesting and boundaries for later use +// during DWARF generation. +type ScopeMarker struct { + parents []ir.ScopeID + marks []ir.Mark +} + +// checkPos validates the given position and returns the current scope. +func (m *ScopeMarker) checkPos(pos src.XPos) ir.ScopeID { + if !pos.IsKnown() { + base.Fatalf("unknown scope position") + } + + if len(m.marks) == 0 { + return 0 + } + + last := &m.marks[len(m.marks)-1] + if xposBefore(pos, last.Pos) { + base.FatalfAt(pos, "non-monotonic scope positions\n\t%v: previous scope position", base.FmtPos(last.Pos)) + } + return last.Scope +} + +// Push records a transition to a new child scope of the current scope. +func (m *ScopeMarker) Push(pos src.XPos) { + current := m.checkPos(pos) + + m.parents = append(m.parents, current) + child := ir.ScopeID(len(m.parents)) + + m.marks = append(m.marks, ir.Mark{Pos: pos, Scope: child}) +} + +// Pop records a transition back to the current scope's parent. +func (m *ScopeMarker) Pop(pos src.XPos) { + current := m.checkPos(pos) + + parent := m.parents[current-1] + + m.marks = append(m.marks, ir.Mark{Pos: pos, Scope: parent}) +} + +// Unpush removes the current scope, which must be empty. +func (m *ScopeMarker) Unpush() { + i := len(m.marks) - 1 + current := m.marks[i].Scope + + if current != ir.ScopeID(len(m.parents)) { + base.FatalfAt(m.marks[i].Pos, "current scope is not empty") + } + + m.parents = m.parents[:current-1] + m.marks = m.marks[:i] +} + +// WriteTo writes the recorded scope marks to the given function, +// and resets the marker for reuse. +func (m *ScopeMarker) WriteTo(fn *ir.Func) { + m.compactMarks() + + fn.Parents = make([]ir.ScopeID, len(m.parents)) + copy(fn.Parents, m.parents) + m.parents = m.parents[:0] + + fn.Marks = make([]ir.Mark, len(m.marks)) + copy(fn.Marks, m.marks) + m.marks = m.marks[:0] +} + +func (m *ScopeMarker) compactMarks() { + n := 0 + for _, next := range m.marks { + if n > 0 && next.Pos == m.marks[n-1].Pos { + m.marks[n-1].Scope = next.Scope + continue + } + m.marks[n] = next + n++ + } + m.marks = m.marks[:n] +} diff --git a/src/cmd/compile/internal/dwarfgen/scope.go b/src/cmd/compile/internal/dwarfgen/scope.go new file mode 100644 index 0000000..b4ae69e --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/scope.go @@ -0,0 +1,136 @@ +// 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 dwarfgen + +import ( + "sort" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/src" +) + +// See golang.org/issue/20390. +func xposBefore(p, q src.XPos) bool { + return base.Ctxt.PosTable.Pos(p).Before(base.Ctxt.PosTable.Pos(q)) +} + +func findScope(marks []ir.Mark, pos src.XPos) ir.ScopeID { + i := sort.Search(len(marks), func(i int) bool { + return xposBefore(pos, marks[i].Pos) + }) + if i == 0 { + return 0 + } + return marks[i-1].Scope +} + +func assembleScopes(fnsym *obj.LSym, fn *ir.Func, dwarfVars []*dwarf.Var, varScopes []ir.ScopeID) []dwarf.Scope { + // Initialize the DWARF scope tree based on lexical scopes. + dwarfScopes := make([]dwarf.Scope, 1+len(fn.Parents)) + for i, parent := range fn.Parents { + dwarfScopes[i+1].Parent = int32(parent) + } + + scopeVariables(dwarfVars, varScopes, dwarfScopes, fnsym.ABI() != obj.ABI0) + if fnsym.Func().Text != nil { + scopePCs(fnsym, fn.Marks, dwarfScopes) + } + return compactScopes(dwarfScopes) +} + +// scopeVariables assigns DWARF variable records to their scopes. +func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ir.ScopeID, dwarfScopes []dwarf.Scope, regabi bool) { + if regabi { + sort.Stable(varsByScope{dwarfVars, varScopes}) + } else { + sort.Stable(varsByScopeAndOffset{dwarfVars, varScopes}) + } + + i0 := 0 + for i := range dwarfVars { + if varScopes[i] == varScopes[i0] { + continue + } + dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:i] + i0 = i + } + if i0 < len(dwarfVars) { + dwarfScopes[varScopes[i0]].Vars = dwarfVars[i0:] + } +} + +// scopePCs assigns PC ranges to their scopes. +func scopePCs(fnsym *obj.LSym, marks []ir.Mark, dwarfScopes []dwarf.Scope) { + // If there aren't any child scopes (in particular, when scope + // tracking is disabled), we can skip a whole lot of work. + if len(marks) == 0 { + return + } + p0 := fnsym.Func().Text + scope := findScope(marks, p0.Pos) + for p := p0; p != nil; p = p.Link { + if p.Pos == p0.Pos { + continue + } + dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: p.Pc}) + p0 = p + scope = findScope(marks, p0.Pos) + } + if p0.Pc < fnsym.Size { + dwarfScopes[scope].AppendRange(dwarf.Range{Start: p0.Pc, End: fnsym.Size}) + } +} + +func compactScopes(dwarfScopes []dwarf.Scope) []dwarf.Scope { + // Reverse pass to propagate PC ranges to parent scopes. + for i := len(dwarfScopes) - 1; i > 0; i-- { + s := &dwarfScopes[i] + dwarfScopes[s.Parent].UnifyRanges(s) + } + + return dwarfScopes +} + +type varsByScopeAndOffset struct { + vars []*dwarf.Var + scopes []ir.ScopeID +} + +func (v varsByScopeAndOffset) Len() int { + return len(v.vars) +} + +func (v varsByScopeAndOffset) Less(i, j int) bool { + if v.scopes[i] != v.scopes[j] { + return v.scopes[i] < v.scopes[j] + } + return v.vars[i].StackOffset < v.vars[j].StackOffset +} + +func (v varsByScopeAndOffset) Swap(i, j int) { + v.vars[i], v.vars[j] = v.vars[j], v.vars[i] + v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i] +} + +type varsByScope struct { + vars []*dwarf.Var + scopes []ir.ScopeID +} + +func (v varsByScope) Len() int { + return len(v.vars) +} + +func (v varsByScope) Less(i, j int) bool { + return v.scopes[i] < v.scopes[j] +} + +func (v varsByScope) Swap(i, j int) { + v.vars[i], v.vars[j] = v.vars[j], v.vars[i] + v.scopes[i], v.scopes[j] = v.scopes[j], v.scopes[i] +} diff --git a/src/cmd/compile/internal/dwarfgen/scope_test.go b/src/cmd/compile/internal/dwarfgen/scope_test.go new file mode 100644 index 0000000..3df4c34 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/scope_test.go @@ -0,0 +1,539 @@ +// Copyright 2016 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 dwarfgen + +import ( + "debug/dwarf" + "fmt" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "testing" + + "cmd/internal/objfile" +) + +type testline struct { + // line is one line of go source + line string + + // scopes is a list of scope IDs of all the lexical scopes that this line + // of code belongs to. + // Scope IDs are assigned by traversing the tree of lexical blocks of a + // function in pre-order + // Scope IDs are function specific, i.e. scope 0 is always the root scope + // of the function that this line belongs to. Empty scopes are not assigned + // an ID (because they are not saved in debug_info). + // Scope 0 is always omitted from this list since all lines always belong + // to it. + scopes []int + + // vars is the list of variables that belong in scopes[len(scopes)-1]. + // Local variables are prefixed with "var ", formal parameters with "arg ". + // Must be ordered alphabetically. + // Set to nil to skip the check. + vars []string + + // decl is the list of variables declared at this line. + decl []string + + // declBefore is the list of variables declared at or before this line. + declBefore []string +} + +var testfile = []testline{ + {line: "package main"}, + {line: "func f1(x int) { }"}, + {line: "func f2(x int) { }"}, + {line: "func f3(x int) { }"}, + {line: "func f4(x int) { }"}, + {line: "func f5(x int) { }"}, + {line: "func f6(x int) { }"}, + {line: "func fi(x interface{}) { if a, ok := x.(error); ok { a.Error() } }"}, + {line: "func gret1() int { return 2 }"}, + {line: "func gretbool() bool { return true }"}, + {line: "func gret3() (int, int, int) { return 0, 1, 2 }"}, + {line: "var v = []int{ 0, 1, 2 }"}, + {line: "var ch = make(chan int)"}, + {line: "var floatch = make(chan float64)"}, + {line: "var iface interface{}"}, + {line: "func TestNestedFor() {", vars: []string{"var a int"}}, + {line: " a := 0", decl: []string{"a"}}, + {line: " f1(a)"}, + {line: " for i := 0; i < 5; i++ {", scopes: []int{1}, vars: []string{"var i int"}, decl: []string{"i"}}, + {line: " f2(i)", scopes: []int{1}}, + {line: " for i := 0; i < 5; i++ {", scopes: []int{1, 2}, vars: []string{"var i int"}, decl: []string{"i"}}, + {line: " f3(i)", scopes: []int{1, 2}}, + {line: " }"}, + {line: " f4(i)", scopes: []int{1}}, + {line: " }"}, + {line: " f5(a)"}, + {line: "}"}, + {line: "func TestOas2() {", vars: []string{}}, + {line: " if a, b, c := gret3(); a != 1 {", scopes: []int{1}, vars: []string{"var a int", "var b int", "var c int"}}, + {line: " f1(a)", scopes: []int{1}}, + {line: " f1(b)", scopes: []int{1}}, + {line: " f1(c)", scopes: []int{1}}, + {line: " }"}, + {line: " for i, x := range v {", scopes: []int{2}, vars: []string{"var i int", "var x int"}}, + {line: " f1(i)", scopes: []int{2}}, + {line: " f1(x)", scopes: []int{2}}, + {line: " }"}, + {line: " if a, ok := <- ch; ok {", scopes: []int{3}, vars: []string{"var a int", "var ok bool"}}, + {line: " f1(a)", scopes: []int{3}}, + {line: " }"}, + {line: " if a, ok := iface.(int); ok {", scopes: []int{4}, vars: []string{"var a int", "var ok bool"}}, + {line: " f1(a)", scopes: []int{4}}, + {line: " }"}, + {line: "}"}, + {line: "func TestIfElse() {"}, + {line: " if x := gret1(); x != 0 {", scopes: []int{1}, vars: []string{"var x int"}}, + {line: " a := 0", scopes: []int{1, 2}, vars: []string{"var a int"}}, + {line: " f1(a); f1(x)", scopes: []int{1, 2}}, + {line: " } else {"}, + {line: " b := 1", scopes: []int{1, 3}, vars: []string{"var b int"}}, + {line: " f1(b); f1(x+1)", scopes: []int{1, 3}}, + {line: " }"}, + {line: "}"}, + {line: "func TestSwitch() {", vars: []string{}}, + {line: " switch x := gret1(); x {", scopes: []int{1}, vars: []string{"var x int"}}, + {line: " case 0:", scopes: []int{1, 2}}, + {line: " i := x + 5", scopes: []int{1, 2}, vars: []string{"var i int"}}, + {line: " f1(x); f1(i)", scopes: []int{1, 2}}, + {line: " case 1:", scopes: []int{1, 3}}, + {line: " j := x + 10", scopes: []int{1, 3}, vars: []string{"var j int"}}, + {line: " f1(x); f1(j)", scopes: []int{1, 3}}, + {line: " case 2:", scopes: []int{1, 4}}, + {line: " k := x + 2", scopes: []int{1, 4}, vars: []string{"var k int"}}, + {line: " f1(x); f1(k)", scopes: []int{1, 4}}, + {line: " }"}, + {line: "}"}, + {line: "func TestTypeSwitch() {", vars: []string{}}, + {line: " switch x := iface.(type) {"}, + {line: " case int:", scopes: []int{1}}, + {line: " f1(x)", scopes: []int{1}, vars: []string{"var x int"}}, + {line: " case uint8:", scopes: []int{2}}, + {line: " f1(int(x))", scopes: []int{2}, vars: []string{"var x uint8"}}, + {line: " case float64:", scopes: []int{3}}, + {line: " f1(int(x)+1)", scopes: []int{3}, vars: []string{"var x float64"}}, + {line: " }"}, + {line: "}"}, + {line: "func TestSelectScope() {"}, + {line: " select {"}, + {line: " case i := <- ch:", scopes: []int{1}}, + {line: " f1(i)", scopes: []int{1}, vars: []string{"var i int"}}, + {line: " case f := <- floatch:", scopes: []int{2}}, + {line: " f1(int(f))", scopes: []int{2}, vars: []string{"var f float64"}}, + {line: " }"}, + {line: "}"}, + {line: "func TestBlock() {", vars: []string{"var a int"}}, + {line: " a := 1"}, + {line: " {"}, + {line: " b := 2", scopes: []int{1}, vars: []string{"var b int"}}, + {line: " f1(b)", scopes: []int{1}}, + {line: " f1(a)", scopes: []int{1}}, + {line: " }"}, + {line: "}"}, + {line: "func TestDiscontiguousRanges() {", vars: []string{"var a int"}}, + {line: " a := 0"}, + {line: " f1(a)"}, + {line: " {"}, + {line: " b := 0", scopes: []int{1}, vars: []string{"var b int"}}, + {line: " f2(b)", scopes: []int{1}}, + {line: " if gretbool() {", scopes: []int{1}}, + {line: " c := 0", scopes: []int{1, 2}, vars: []string{"var c int"}}, + {line: " f3(c)", scopes: []int{1, 2}}, + {line: " } else {"}, + {line: " c := 1.1", scopes: []int{1, 3}, vars: []string{"var c float64"}}, + {line: " f4(int(c))", scopes: []int{1, 3}}, + {line: " }"}, + {line: " f5(b)", scopes: []int{1}}, + {line: " }"}, + {line: " f6(a)"}, + {line: "}"}, + {line: "func TestClosureScope() {", vars: []string{"var a int", "var b int", "var f func(int)"}}, + {line: " a := 1; b := 1"}, + {line: " f := func(c int) {", scopes: []int{0}, vars: []string{"arg c int", "var &b *int", "var a int", "var d int"}, declBefore: []string{"&b", "a"}}, + {line: " d := 3"}, + {line: " f1(c); f1(d)"}, + {line: " if e := 3; e != 0 {", scopes: []int{1}, vars: []string{"var e int"}}, + {line: " f1(e)", scopes: []int{1}}, + {line: " f1(a)", scopes: []int{1}}, + {line: " b = 2", scopes: []int{1}}, + {line: " }"}, + {line: " }"}, + {line: " f(3); f1(b)"}, + {line: "}"}, + {line: "func TestEscape() {"}, + {line: " a := 1", vars: []string{"var a int"}}, + {line: " {"}, + {line: " b := 2", scopes: []int{1}, vars: []string{"var &b *int", "var p *int"}}, + {line: " p := &b", scopes: []int{1}}, + {line: " f1(a)", scopes: []int{1}}, + {line: " fi(p)", scopes: []int{1}}, + {line: " }"}, + {line: "}"}, + {line: "var fglob func() int"}, + {line: "func TestCaptureVar(flag bool) {"}, + {line: " a := 1", vars: []string{"arg flag bool", "var a int"}}, // TODO(register args) restore "arg ~r1 func() int", + {line: " if flag {"}, + {line: " b := 2", scopes: []int{1}, vars: []string{"var b int", "var f func() int"}}, + {line: " f := func() int {", scopes: []int{1, 0}}, + {line: " return b + 1"}, + {line: " }"}, + {line: " fglob = f", scopes: []int{1}}, + {line: " }"}, + {line: " f1(a)"}, + {line: "}"}, + {line: "func main() {"}, + {line: " TestNestedFor()"}, + {line: " TestOas2()"}, + {line: " TestIfElse()"}, + {line: " TestSwitch()"}, + {line: " TestTypeSwitch()"}, + {line: " TestSelectScope()"}, + {line: " TestBlock()"}, + {line: " TestDiscontiguousRanges()"}, + {line: " TestClosureScope()"}, + {line: " TestEscape()"}, + {line: " TestCaptureVar(true)"}, + {line: "}"}, +} + +const detailOutput = false + +// Compiles testfile checks that the description of lexical blocks emitted +// by the linker in debug_info, for each function in the main package, +// corresponds to what we expect it to be. +func TestScopeRanges(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + if runtime.GOOS == "plan9" { + t.Skip("skipping on plan9; no DWARF symbol table in executables") + } + + dir, err := ioutil.TempDir("", "TestScopeRanges") + if err != nil { + t.Fatalf("could not create directory: %v", err) + } + defer os.RemoveAll(dir) + + src, f := gobuild(t, dir, false, testfile) + defer f.Close() + + // the compiler uses forward slashes for paths even on windows + src = strings.Replace(src, "\\", "/", -1) + + pcln, err := f.PCLineTable() + if err != nil { + t.Fatal(err) + } + dwarfData, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + dwarfReader := dwarfData.Reader() + + lines := make(map[line][]*lexblock) + + for { + entry, err := dwarfReader.Next() + if err != nil { + t.Fatal(err) + } + if entry == nil { + break + } + + if entry.Tag != dwarf.TagSubprogram { + continue + } + + name, ok := entry.Val(dwarf.AttrName).(string) + if !ok || !strings.HasPrefix(name, "main.Test") { + continue + } + + var scope lexblock + ctxt := scopexplainContext{ + dwarfData: dwarfData, + dwarfReader: dwarfReader, + scopegen: 1, + } + + readScope(&ctxt, &scope, entry) + + scope.markLines(pcln, lines) + } + + anyerror := false + for i := range testfile { + tgt := testfile[i].scopes + out := lines[line{src, i + 1}] + + if detailOutput { + t.Logf("%s // %v", testfile[i].line, out) + } + + scopesok := checkScopes(tgt, out) + if !scopesok { + t.Logf("mismatch at line %d %q: expected: %v got: %v\n", i, testfile[i].line, tgt, scopesToString(out)) + } + + varsok := true + if testfile[i].vars != nil { + if len(out) > 0 { + varsok = checkVars(testfile[i].vars, out[len(out)-1].vars) + if !varsok { + t.Logf("variable mismatch at line %d %q for scope %d: expected: %v got: %v\n", i+1, testfile[i].line, out[len(out)-1].id, testfile[i].vars, out[len(out)-1].vars) + } + for j := range testfile[i].decl { + if line := declLineForVar(out[len(out)-1].vars, testfile[i].decl[j]); line != i+1 { + t.Errorf("wrong declaration line for variable %s, expected %d got: %d", testfile[i].decl[j], i+1, line) + } + } + + for j := range testfile[i].declBefore { + if line := declLineForVar(out[len(out)-1].vars, testfile[i].declBefore[j]); line > i+1 { + t.Errorf("wrong declaration line for variable %s, expected %d (or less) got: %d", testfile[i].declBefore[j], i+1, line) + } + } + } + } + + anyerror = anyerror || !scopesok || !varsok + } + + if anyerror { + t.Fatalf("mismatched output") + } +} + +func scopesToString(v []*lexblock) string { + r := make([]string, len(v)) + for i, s := range v { + r[i] = strconv.Itoa(s.id) + } + return "[ " + strings.Join(r, ", ") + " ]" +} + +func checkScopes(tgt []int, out []*lexblock) bool { + if len(out) > 0 { + // omit scope 0 + out = out[1:] + } + if len(tgt) != len(out) { + return false + } + for i := range tgt { + if tgt[i] != out[i].id { + return false + } + } + return true +} + +func checkVars(tgt []string, out []variable) bool { + if len(tgt) != len(out) { + return false + } + for i := range tgt { + if tgt[i] != out[i].expr { + return false + } + } + return true +} + +func declLineForVar(scope []variable, name string) int { + for i := range scope { + if scope[i].name() == name { + return scope[i].declLine + } + } + return -1 +} + +type lexblock struct { + id int + ranges [][2]uint64 + vars []variable + scopes []lexblock +} + +type variable struct { + expr string + declLine int +} + +func (v *variable) name() string { + return strings.Split(v.expr, " ")[1] +} + +type line struct { + file string + lineno int +} + +type scopexplainContext struct { + dwarfData *dwarf.Data + dwarfReader *dwarf.Reader + scopegen int +} + +// readScope reads the DW_TAG_lexical_block or the DW_TAG_subprogram in +// entry and writes a description in scope. +// Nested DW_TAG_lexical_block entries are read recursively. +func readScope(ctxt *scopexplainContext, scope *lexblock, entry *dwarf.Entry) { + var err error + scope.ranges, err = ctxt.dwarfData.Ranges(entry) + if err != nil { + panic(err) + } + for { + e, err := ctxt.dwarfReader.Next() + if err != nil { + panic(err) + } + switch e.Tag { + case 0: + sort.Slice(scope.vars, func(i, j int) bool { + return scope.vars[i].expr < scope.vars[j].expr + }) + return + case dwarf.TagFormalParameter: + typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) + if err != nil { + panic(err) + } + scope.vars = append(scope.vars, entryToVar(e, "arg", typ)) + case dwarf.TagVariable: + typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) + if err != nil { + panic(err) + } + scope.vars = append(scope.vars, entryToVar(e, "var", typ)) + case dwarf.TagLexDwarfBlock: + scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen}) + ctxt.scopegen++ + readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e) + } + } +} + +func entryToVar(e *dwarf.Entry, kind string, typ dwarf.Type) variable { + return variable{ + fmt.Sprintf("%s %s %s", kind, e.Val(dwarf.AttrName).(string), typ.String()), + int(e.Val(dwarf.AttrDeclLine).(int64)), + } +} + +// markLines marks all lines that belong to this scope with this scope +// Recursively calls markLines for all children scopes. +func (scope *lexblock) markLines(pcln objfile.Liner, lines map[line][]*lexblock) { + for _, r := range scope.ranges { + for pc := r[0]; pc < r[1]; pc++ { + file, lineno, _ := pcln.PCToLine(pc) + l := line{file, lineno} + if len(lines[l]) == 0 || lines[l][len(lines[l])-1] != scope { + lines[l] = append(lines[l], scope) + } + } + } + + for i := range scope.scopes { + scope.scopes[i].markLines(pcln, lines) + } +} + +func gobuild(t *testing.T, dir string, optimized bool, testfile []testline) (string, *objfile.File) { + src := filepath.Join(dir, "test.go") + dst := filepath.Join(dir, "out.o") + + f, err := os.Create(src) + if err != nil { + t.Fatal(err) + } + for i := range testfile { + f.Write([]byte(testfile[i].line)) + f.Write([]byte{'\n'}) + } + f.Close() + + args := []string{"build"} + if !optimized { + args = append(args, "-gcflags=-N -l") + } + args = append(args, "-o", dst, src) + + cmd := exec.Command(testenv.GoToolPath(t), args...) + if b, err := cmd.CombinedOutput(); err != nil { + t.Logf("build: %s\n", string(b)) + t.Fatal(err) + } + + pkg, err := objfile.Open(dst) + if err != nil { + t.Fatal(err) + } + return src, pkg +} + +// TestEmptyDwarfRanges tests that no list entry in debug_ranges has start == end. +// See issue #23928. +func TestEmptyDwarfRanges(t *testing.T) { + testenv.MustHaveGoRun(t) + t.Parallel() + + if runtime.GOOS == "plan9" { + t.Skip("skipping on plan9; no DWARF symbol table in executables") + } + + dir, err := ioutil.TempDir("", "TestEmptyDwarfRanges") + if err != nil { + t.Fatalf("could not create directory: %v", err) + } + defer os.RemoveAll(dir) + + _, f := gobuild(t, dir, true, []testline{{line: "package main"}, {line: "func main(){ println(\"hello\") }"}}) + defer f.Close() + + dwarfData, err := f.DWARF() + if err != nil { + t.Fatal(err) + } + dwarfReader := dwarfData.Reader() + + for { + entry, err := dwarfReader.Next() + if err != nil { + t.Fatal(err) + } + if entry == nil { + break + } + + ranges, err := dwarfData.Ranges(entry) + if err != nil { + t.Fatal(err) + } + if ranges == nil { + continue + } + + for _, rng := range ranges { + if rng[0] == rng[1] { + t.Errorf("range entry with start == end: %v", rng) + } + } + } +} |