// 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" "slices" "sort" "strings" "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/reflectdata" "cmd/compile/internal/ssa" "cmd/compile/internal/ssagen" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/dwarf" "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/src" ) func Info(ctxt *obj.Link, fnsym *obj.LSym, infosym *obj.LSym, curfn obj.Func) (scopes []dwarf.Scope, inlcalls 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 } if !shouldEmitDwarfVar(n) { 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())) } } var closureVars map[*ir.Name]int64 if fn.Needctxt() { closureVars = make(map[*ir.Name]int64) csiter := typecheck.NewClosureStructIter(fn.ClosureVars) for { n, _, offset := csiter.Next() if n == nil { break } closureVars[n] = offset if n.Heapaddr != nil { closureVars[n.Heapaddr] = offset } } } decls, dwarfVars := createDwarfVars(fnsym, isODCLFUNC, fn, apdecls, closureVars) // 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. // Do the same with R_USEIFACE relocations from the function symbol for the // same reason. // All these R_USETYPE relocations are only looked at if the function // survives deadcode elimination in the linker. typesyms := []*obj.LSym{} for t := range fnsym.Func().Autot { typesyms = append(typesyms, t) } for i := range fnsym.R { if fnsym.R[i].Type == objabi.R_USEIFACE && !strings.HasPrefix(fnsym.R[i].Sym.Name, "go:itab.") { // Types referenced through itab will be referenced from somewhere else typesyms = append(typesyms, fnsym.R[i].Sym) } } slices.SortFunc(typesyms, func(a, b *obj.LSym) int { return strings.Compare(a.Name, b.Name) }) var lastsym *obj.LSym for _, sym := range typesyms { if sym == lastsym { continue } lastsym = sym infosym.AddRel(ctxt, obj.Reloc{Type: objabi.R_USETYPE, Sym: sym}) } 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) 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, closureVars map[*ir.Name]int64) ([]*ir.Name, []*dwarf.Var) { // Collect a raw list of DWARF vars. var vars []*dwarf.Var var decls []*ir.Name // Build a VarID lookup map for SSA debug info if available. var debug *ssa.FuncDebug var varIDMap map[*ir.Name]ssa.VarID if fn.DebugInfo != nil { debug = fn.DebugInfo.(*ssa.FuncDebug) varIDMap = make(map[*ir.Name]ssa.VarID, len(debug.Vars)) for i, n := range debug.Vars { varIDMap[n] = ssa.VarID(i) } } canUseComplex := complexOK && debug != nil // markVarSeen marks a variable and all its associated slot names as seen. // This is needed because decomposed variables may have slots whose ir.Name // differs from the variable itself (e.g., PAUTO vs PPARAMOUT for the same // logical variable). Without this, the dcl loop could create duplicate // conservative entries for names that are already covered by a complex var. seen := make(map[*ir.Name]bool) markVarSeen := func(n *ir.Name, varID ssa.VarID) { seen[n] = true if debug != nil && int(varID) < len(debug.VarSlots) { for _, slot := range debug.VarSlots[varID] { seen[debug.Slots[slot].N] = true } } } // Unified loop: for each variable in apDecls, try createComplexVar // (SSA debug info) first, then fall back to createSimpleVar. for _, n := range apDecls { if !shouldEmitDwarfVar(n) { continue } if canUseComplex { if vid, ok := varIDMap[n]; ok { if dvar := createComplexVar(fnsym, fn, vid, closureVars); dvar != nil { decls = append(decls, n) vars = append(vars, dvar) markVarSeen(n, vid) continue } } } seen[n] = true decls = append(decls, n) vars = append(vars, createSimpleVar(fnsym, n, closureVars)) } // Add SSA-tracked vars not in apDecls. if canUseComplex { for i, n := range debug.Vars { if seen[n] { continue } if !shouldEmitDwarfVar(n) { continue } if dvar := createComplexVar(fnsym, fn, ssa.VarID(i), closureVars); dvar != nil { decls = append(decls, n) vars = append(vars, dvar) markVarSeen(n, ssa.VarID(i)) } } } // Recover zero-sized variables eliminated by the stackframe pass. if debug != nil { for _, n := range debug.OptDcl { if seen[n] { continue } if n.Class != ir.PAUTO { continue } types.CalcSize(n.Type()) if n.Type().Size() == 0 { decls = append(decls, n) vars = append(vars, createSimpleVar(fnsym, n, closureVars)) vars[len(vars)-1].StackOffset = 0 fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type())) seen[n] = true } } } // For inlined functions or functions with register output params, // collect additional declarations that may not be in apDecls. dcl := apDecls if fnsym.WasInlined() { dcl = preInliningDcls(fnsym) } else if debug != nil { // 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. for _, n := range debug.RegOutputParams { if !ssa.IsVarWantedForDebug(n) { continue } if n.Class != ir.PPARAMOUT || !n.IsOutputParamInRegisters() { base.Fatalf("invalid ir.Name on debugInfo.RegOutputParams list") } dcl = append(dcl, n) } } // Process remaining variables not yet handled. For each variable, // try createComplexVar first, then fall back to createSimpleVar // for non-SSA-able params, or createConservativeVar for the rest. for _, n := range dcl { if seen[n] { continue } if !shouldEmitDwarfVar(n) { continue } seen[n] = true if canUseComplex { if vid, ok := varIDMap[n]; ok { if dvar := createComplexVar(fnsym, fn, vid, closureVars); dvar != nil { decls = append(decls, n) vars = append(vars, dvar) continue } } } if n.Class == ir.PPARAM && !ssa.CanSSA(n.Type()) { decls = append(decls, n) vars = append(vars, createSimpleVar(fnsym, n, closureVars)) continue } decls = append(decls, n) vars = append(vars, createConservativeVar(fnsym, fn, n, closureVars)) } // Sort decls and vars. sortDeclsAndVars(fn, decls, vars) return decls, vars } // createConservativeVar creates a DWARF variable with a conservative location // description. This is used for variables that were optimized away or otherwise // don't have precise location info. The intent is to communicate 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 heap-escaped variables, a location list is created that describes // dereferencing the pointer at the stack offset. func createConservativeVar(fnsym *obj.LSym, fn *ir.Func, n *ir.Name, closureVars map[*ir.Name]int64) *dwarf.Var { typename := dwarf.InfoPrefix + types.TypeSymName(n.Type()) tag := dwarf.DW_TAG_variable isReturnValue := (n.Class == ir.PPARAMOUT) if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT { tag = dwarf.DW_TAG_formal_parameter } inlIndex := 0 if base.Flag.GenDwarfInl > 1 { if n.InlFormal() || n.InlLocal() { inlIndex = posInlIndex(n.Pos()) + 1 if n.InlFormal() { tag = dwarf.DW_TAG_formal_parameter } } } declpos := base.Ctxt.InnermostPos(n.Pos()) dvar := &dwarf.Var{ Name: n.Sym().Name, IsReturnValue: isReturnValue, Tag: tag, WithLoclist: true, 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, ClosureOffset: closureOffset(n, closureVars), } if n.Esc() == ir.EscHeap && n.Heapaddr != nil { // The variable was promoted to the heap and has a known heap // address, so describe its location by dereferencing the pointer // stored at its stack offset. A heap-escaped variable may have no // Heapaddr if it was declared in unreachable code: escape analysis // marks it as heap-allocated, but SSA generation skips the dead // declaration and never allocates the address. In that case fall // through and emit a conservative variable with no location list. debug := fn.DebugInfo.(*ssa.FuncDebug) list := createHeapDerefLocationList(n, debug.EntryID) dvar.PutLocationList = func(listSym, startPC dwarf.Sym) { debug.PutLocationList(list, base.Ctxt, listSym.(*obj.LSym), startPC.(*obj.LSym)) } } // Record go type to ensure that it gets emitted by the linker. fnsym.Func().RecordAutoType(reflectdata.TypeLinksym(n.Type())) return dvar } // 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 _, f := range fn.Type().RecvParamsResults() { 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 { if n.Sym().Name[0] == '.' || !shouldEmitDwarfVarSafe(n) { continue } rdcl = append(rdcl, n) } return rdcl } func createSimpleVar(fnsym *obj.LSym, n *ir.Name, closureVars map[*ir.Name]int64) *dwarf.Var { var tag int var offs int64 localAutoOffset := func() int64 { offs = n.FrameOffset() if base.Ctxt.Arch.FixedFrameSize == 0 { offs -= int64(types.PtrSize) } if buildcfg.FramePointerEnabled { offs -= int64(types.PtrSize) } return offs } switch n.Class { case ir.PAUTO: offs = localAutoOffset() tag = dwarf.DW_TAG_variable case ir.PPARAM, ir.PPARAMOUT: tag = dwarf.DW_TAG_formal_parameter if n.IsOutputParamInRegisters() { offs = localAutoOffset() } else { offs = n.FrameOffset() + base.Ctxt.Arch.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() { tag = dwarf.DW_TAG_formal_parameter } } } declpos := base.Ctxt.InnermostPos(declPos(n)) return &dwarf.Var{ Name: n.Sym().Name, IsReturnValue: n.Class == ir.PPARAMOUT, IsInlFormal: n.InlFormal(), Tag: tag, 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, ClosureOffset: closureOffset(n, closureVars), } } // createComplexVar builds a single DWARF variable entry and location list. func createComplexVar(fnsym *obj.LSym, fn *ir.Func, varID ssa.VarID, closureVars map[*ir.Name]int64) *dwarf.Var { debug := fn.DebugInfo.(*ssa.FuncDebug) n := debug.Vars[varID] var tag int switch n.Class { case ir.PAUTO: tag = dwarf.DW_TAG_variable case ir.PPARAM, ir.PPARAMOUT: tag = dwarf.DW_TAG_formal_parameter 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() { tag = dwarf.DW_TAG_formal_parameter } } } declpos := base.Ctxt.InnermostPos(n.Pos()) dvar := &dwarf.Var{ Name: n.Sym().Name, IsReturnValue: n.Class == ir.PPARAMOUT, IsInlFormal: n.InlFormal(), Tag: tag, WithLoclist: true, 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, ClosureOffset: closureOffset(n, closureVars), } 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 } // createHeapDerefLocationList creates a location list for a heap-escaped variable // that describes "dereference pointer at stack offset" func createHeapDerefLocationList(n *ir.Name, entryID ssa.ID) []ssa.LocListEntry { // Get the stack offset where the heap pointer is stored heapPtrOffset := n.Heapaddr.FrameOffset() if base.Ctxt.Arch.FixedFrameSize == 0 { heapPtrOffset -= int64(types.PtrSize) } if buildcfg.FramePointerEnabled { heapPtrOffset -= int64(types.PtrSize) } // Create a location expression: DW_OP_fbreg DW_OP_deref var expr []byte expr = append(expr, dwarf.DW_OP_fbreg) expr = dwarf.AppendSleb128(expr, heapPtrOffset) expr = append(expr, dwarf.DW_OP_deref) return []ssa.LocListEntry{{ StartBlock: entryID, StartValue: ssa.BlockStart.ID, EndBlock: entryID, EndValue: ssa.FuncEnd.ID, Expr: expr, }} } // RecordFlags records the specified command-line flags to be placed // in the DWARF info. func RecordFlags(flags ...string) { if base.Ctxt.Pkgpath == "" { base.Fatalf("missing pkgpath") } 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 signaling 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) } // shouldEmitDwarfVar reports whether n should have a DWARF variable entry. // This consolidates filtering that was previously spread across IR (AutoTemp), // SSA (IsVarWantedForDebug), and dwarfgen (symbol name checks). func shouldEmitDwarfVar(n *ir.Name) bool { if ir.IsAutoTmp(n) { return false } return shouldEmitDwarfVarSafe(n) } // shouldEmitDwarfVarSafe is like shouldEmitDwarfVar but omits the ir.IsAutoTmp // check, making it safe to call during parallel compilation on shared ir.Name // nodes (e.g., in preInliningDcls). ir.IsAutoTmp reads the mutable flags bitset, // which can race with other goroutines writing different flags during compilation. // Auto temps have names starting with "." so callers must filter those separately. func shouldEmitDwarfVarSafe(n *ir.Name) bool { if !ssa.IsVarWantedForDebug(n) { return false } if n.Sym().Name == "_" { return false } if n.Type().IsUntyped() { return false } return true } func closureOffset(n *ir.Name, closureVars map[*ir.Name]int64) int64 { return closureVars[n] }