From: Alessandro Arzilli Date: Tue, 7 Mar 2017 17:59:14 +0000 (+0100) Subject: cmd/compile: output DWARF lexical blocks for local variables X-Git-Tag: go1.9beta1~791 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=c8b889cc4824f4dbd64a51a3f7b5b6dce4b87ed2;p=gostls13.git cmd/compile: output DWARF lexical blocks for local variables Change compiler and linker to emit DWARF lexical blocks in debug_info. Version of debug_info is updated from DWARF v.2 to DWARF v.3 since version 2 does not allow lexical blocks with discontinuous ranges. Second attempt at https://go-review.googlesource.com/#/c/29591/ Remaining open problems: - scope information is removed from inlined functions - variables in debug_info do not have DW_AT_start_scope attributes so a variable will shadow other variables with the same name as soon as its containing scope begins, before its declaration. Updates golang/go#12899, golang/go#6913 Change-Id: I0e260a45b564d14a87b88974eb16c5387cb410a5 Reviewed-on: https://go-review.googlesource.com/36879 Run-TryBot: Matthew Dempsky TryBot-Result: Gobot Gobot Reviewed-by: Matthew Dempsky --- diff --git a/src/cmd/compile/internal/gc/alg.go b/src/cmd/compile/internal/gc/alg.go index 8ee158c5f7..f094e8e934 100644 --- a/src/cmd/compile/internal/gc/alg.go +++ b/src/cmd/compile/internal/gc/alg.go @@ -6,6 +6,7 @@ package gc import ( "cmd/compile/internal/types" + "cmd/internal/src" "fmt" ) @@ -191,7 +192,7 @@ func genhash(sym *types.Sym, t *types.Type) { lineno = autogeneratedPos // less confusing than end of input dclcontext = PEXTERN - markdcl() + markdcl(src.NoPos) // func sym(p *T, h uintptr) uintptr fn := nod(ODCLFUNC, nil, nil) @@ -210,7 +211,7 @@ func genhash(sym *types.Sym, t *types.Type) { n = anonfield(types.Types[TUINTPTR]) // return value tfn.Rlist.Append(n) - funchdr(fn) + funchdr(fn, src.NoPos) fn.Func.Nname.Name.Param.Ntype = typecheck(fn.Func.Nname.Name.Param.Ntype, Etype) // genhash is only called for types that have equality but @@ -299,13 +300,13 @@ func genhash(sym *types.Sym, t *types.Type) { dumplist("genhash body", fn.Nbody) } - funcbody(fn) + funcbody(fn, src.NoPos) Curfn = fn fn.Func.SetDupok(true) fn = typecheck(fn, Etop) typecheckslice(fn.Nbody.Slice(), Etop) + popdcl(src.NoPos) Curfn = nil - popdcl() if debug_dclstack != 0 { testdclstack() } @@ -369,7 +370,7 @@ func geneq(sym *types.Sym, t *types.Type) { lineno = autogeneratedPos // less confusing than end of input dclcontext = PEXTERN - markdcl() + markdcl(src.NoPos) // func sym(p, q *T) bool fn := nod(ODCLFUNC, nil, nil) @@ -388,7 +389,7 @@ func geneq(sym *types.Sym, t *types.Type) { n = anonfield(types.Types[TBOOL]) tfn.Rlist.Append(n) - funchdr(fn) + funchdr(fn, src.NoPos) fn.Func.Nname.Name.Param.Ntype = typecheck(fn.Func.Nname.Name.Param.Ntype, Etype) // geneq is only called for types that have equality but @@ -491,13 +492,13 @@ func geneq(sym *types.Sym, t *types.Type) { dumplist("geneq body", fn.Nbody) } - funcbody(fn) + funcbody(fn, src.NoPos) Curfn = fn fn.Func.SetDupok(true) fn = typecheck(fn, Etop) typecheckslice(fn.Nbody.Slice(), Etop) + popdcl(src.NoPos) Curfn = nil - popdcl() if debug_dclstack != 0 { testdclstack() } diff --git a/src/cmd/compile/internal/gc/bimport.go b/src/cmd/compile/internal/gc/bimport.go index 64f410fa80..5a33aec7b5 100644 --- a/src/cmd/compile/internal/gc/bimport.go +++ b/src/cmd/compile/internal/gc/bimport.go @@ -187,7 +187,7 @@ func Import(imp *types.Pkg, in *bufio.Reader) { if f := p.funcList[i]; f != nil { // function not yet imported - read body and set it - funchdr(f) + funchdr(f, src.NoPos) body := p.stmtList() if body == nil { // Make sure empty body is not interpreted as @@ -198,7 +198,7 @@ func Import(imp *types.Pkg, in *bufio.Reader) { body = []*Node{nod(OEMPTY, nil, nil)} } f.Func.Inl.Set(body) - funcbody(f) + funcbody(f, src.NoPos) } else { // function already imported - read body but discard declarations dclcontext = PDISCARD // throw away any declarations @@ -1091,54 +1091,54 @@ func (p *importer) node() *Node { return nodl(p.pos(), op, p.expr(), nil) case OIF: - markdcl() + markdcl(src.NoPos) n := nodl(p.pos(), OIF, nil, nil) n.Ninit.Set(p.stmtList()) n.Left = p.expr() n.Nbody.Set(p.stmtList()) n.Rlist.Set(p.stmtList()) - popdcl() + popdcl(src.NoPos) return n case OFOR: - markdcl() + markdcl(src.NoPos) n := nodl(p.pos(), OFOR, nil, nil) n.Ninit.Set(p.stmtList()) n.Left, n.Right = p.exprsOrNil() n.Nbody.Set(p.stmtList()) - popdcl() + popdcl(src.NoPos) return n case ORANGE: - markdcl() + markdcl(src.NoPos) n := nodl(p.pos(), ORANGE, nil, nil) n.List.Set(p.stmtList()) n.Right = p.expr() n.Nbody.Set(p.stmtList()) - popdcl() + popdcl(src.NoPos) return n case OSELECT, OSWITCH: - markdcl() + markdcl(src.NoPos) n := nodl(p.pos(), op, nil, nil) n.Ninit.Set(p.stmtList()) n.Left, _ = p.exprsOrNil() n.List.Set(p.stmtList()) - popdcl() + popdcl(src.NoPos) return n // case OCASE, OXCASE: // unreachable - mapped to OXCASE case below by exporter case OXCASE: - markdcl() + markdcl(src.NoPos) n := nodl(p.pos(), OXCASE, nil, nil) n.Xoffset = int64(block) n.List.Set(p.exprList()) // TODO(gri) eventually we must declare variables for type switch // statements (type switch statements are not yet exported) n.Nbody.Set(p.stmtList()) - popdcl() + popdcl(src.NoPos) return n // case OFALL: diff --git a/src/cmd/compile/internal/gc/closure.go b/src/cmd/compile/internal/gc/closure.go index cc6f8eb6a6..283d771c9c 100644 --- a/src/cmd/compile/internal/gc/closure.go +++ b/src/cmd/compile/internal/gc/closure.go @@ -6,6 +6,7 @@ package gc import ( "cmd/compile/internal/types" + "cmd/internal/src" "fmt" ) @@ -17,7 +18,7 @@ func closurehdr(ntype *Node) { n.Func.Depth = funcdepth n.Func.Outerfunc = Curfn - funchdr(n) + funchdr(n, src.NoPos) // steal ntype's argument names and // leave a fresh copy in their place. @@ -58,7 +59,7 @@ func closurebody(body []*Node) *Node { func_ := Curfn func_.Nbody.Set(body) func_.Func.Endlineno = lineno - funcbody(func_) + funcbody(func_, src.NoPos) // closure-specific variables are hanging off the // ordinary ones in the symbol table; see oldname. @@ -226,8 +227,10 @@ func makeclosure(func_ *Node) *Node { } xfunc.Nbody.Set(func_.Nbody.Slice()) + xfunc.Func.scopes = func_.Func.scopes xfunc.Func.Dcl = append(func_.Func.Dcl, xfunc.Func.Dcl...) func_.Func.Dcl = nil + func_.Func.scopes.Scopes = nil if xfunc.Nbody.Len() == 0 { Fatalf("empty body - won't generate any code") } @@ -609,6 +612,7 @@ func makepartialcall(fn *Node, t0 *types.Type, meth *types.Sym) *Node { xfunc.Func.Nname.Sym.SetExported(true) // disable export xfunc.Func.Nname.Name.Param.Ntype = xtype xfunc.Func.Nname.Name.Defn = xfunc + xfunc.Func.scopes.Scopes = []src.Scope{src.Scope{}} declare(xfunc.Func.Nname, PFUNC) // Declare and initialize variable holding receiver. diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index 0704d6ef5d..ecd5e9d806 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -33,6 +33,9 @@ var block int32 // current block number // Sym definitions, and only a subset of its fields are actually used. var dclstack *types.Sym +// lastPopdclPos is the last pos passed to popdcl +var lastPopdclPos src.Pos + func dcopy(a, b *types.Sym) { a.Pkg = b.Pkg a.Name = b.Name @@ -59,7 +62,7 @@ func pushdcl(s *types.Sym) *types.Sym { // popdcl pops the innermost block scope and restores all symbol declarations // to their previous state. -func popdcl() { +func popdcl(pos src.Pos) { d := dclstack for ; d != nil && d.Name != ""; d = d.Link { s := d.Pkg.Lookup(d.Name) @@ -74,16 +77,29 @@ func popdcl() { dclstack = d.Link // pop mark block = d.Block + + if Curfn != nil { + Curfn.Func.scopes.Close(&Ctxt.PosTable, pos) + lastPopdclPos = pos + } + + return } // markdcl records the start of a new block scope for declarations. -func markdcl() { +func markdcl(pos src.Pos) { d := push() d.Name = "" // used as a mark in fifo d.Block = block blockgen++ block = blockgen + + if Curfn != nil { + Curfn.Func.scopes.Open(&Ctxt.PosTable, pos) + } + + return } // keep around for debugging @@ -451,14 +467,14 @@ func ifacedcl(n *Node) { // and declare the arguments. // called in extern-declaration context // returns in auto-declaration context. -func funchdr(n *Node) { +func funchdr(n *Node, pos src.Pos) { // change the declaration context from extern to auto if funcdepth == 0 && dclcontext != PEXTERN { Fatalf("funchdr: dclcontext = %d", dclcontext) } dclcontext = PAUTO - funcstart(n) + funcstart(n, pos) if n.Func.Nname != nil { funcargs(n.Func.Nname.Name.Param.Ntype) @@ -596,22 +612,22 @@ var funcdepth int32 // len(funcstack) during parsing, but then forced to be th // start the function. // called before funcargs; undone at end of funcbody. -func funcstart(n *Node) { - markdcl() +func funcstart(n *Node, pos src.Pos) { funcstack = append(funcstack, Curfn) funcdepth++ Curfn = n + markdcl(pos) } // finish the body. // called in auto-declaration context. // returns in extern-declaration context. -func funcbody(n *Node) { +func funcbody(n *Node, pos src.Pos) { // change the declaration context from auto to extern if dclcontext != PAUTO { Fatalf("funcbody: unexpected dclcontext %d", dclcontext) } - popdcl() + popdcl(pos) funcstack, Curfn = funcstack[:len(funcstack)-1], funcstack[len(funcstack)-1] funcdepth-- if funcdepth == 0 { diff --git a/src/cmd/compile/internal/gc/init.go b/src/cmd/compile/internal/gc/init.go index e26718dfdc..9d11decc60 100644 --- a/src/cmd/compile/internal/gc/init.go +++ b/src/cmd/compile/internal/gc/init.go @@ -4,7 +4,10 @@ package gc -import "cmd/compile/internal/types" +import ( + "cmd/compile/internal/types" + "cmd/internal/src" +) // a function named init is a special case. // it is called by the initialization before @@ -92,7 +95,7 @@ func fninit(n []*Node) { fn.Func.Nname.Name.Defn = fn fn.Func.Nname.Name.Param.Ntype = nod(OTFUNC, nil, nil) declare(fn.Func.Nname, PFUNC) - funchdr(fn) + funchdr(fn, src.NoPos) // (3) a := nod(OIF, nil, nil) @@ -152,7 +155,7 @@ func fninit(n []*Node) { exportsym(fn.Func.Nname) fn.Nbody.Set(r) - funcbody(fn) + funcbody(fn, src.NoPos) Curfn = fn fn = typecheck(fn, Etop) diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index bd769b6126..61a338d214 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -326,7 +326,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { declare(f.Func.Nname, PFUNC) } - funchdr(f) + funchdr(f, fun.Pos()) if fun.Body != nil { if f.Noescape() { @@ -347,7 +347,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { } } - funcbody(f) + funcbody(f, fun.Pos()) return f } @@ -777,14 +777,14 @@ func (p *noder) stmt(stmt syntax.Stmt) *Node { } func (p *noder) blockStmt(stmt *syntax.BlockStmt) []*Node { - markdcl() + markdcl(stmt.Pos()) nodes := p.stmts(stmt.List) - popdcl() + popdcl(stmt.Rbrace) return nodes } func (p *noder) ifStmt(stmt *syntax.IfStmt) *Node { - markdcl() + markdcl(stmt.Pos()) n := p.nod(stmt, OIF, nil, nil) if stmt.Init != nil { n.Ninit.Set1(p.stmt(stmt.Init)) @@ -801,12 +801,12 @@ func (p *noder) ifStmt(stmt *syntax.IfStmt) *Node { n.Rlist.Set1(e) } } - popdcl() + popdcl(lastPopdclPos) return n } func (p *noder) forStmt(stmt *syntax.ForStmt) *Node { - markdcl() + markdcl(stmt.Pos()) var n *Node if r, ok := stmt.Init.(*syntax.RangeClause); ok { if stmt.Cond != nil || stmt.Post != nil { @@ -835,12 +835,12 @@ func (p *noder) forStmt(stmt *syntax.ForStmt) *Node { } } n.Nbody.Set(p.blockStmt(stmt.Body)) - popdcl() + popdcl(stmt.Body.Rbrace) return n } func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node { - markdcl() + markdcl(stmt.Pos()) n := p.nod(stmt, OSWITCH, nil, nil) if stmt.Init != nil { n.Ninit.Set1(p.stmt(stmt.Init)) @@ -854,17 +854,17 @@ func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node { tswitch = nil } - n.List.Set(p.caseClauses(stmt.Body, tswitch)) + n.List.Set(p.caseClauses(stmt.Body, tswitch, stmt.Rbrace)) - popdcl() + popdcl(stmt.Rbrace) return n } -func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node) []*Node { +func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node, rbrace src.Pos) []*Node { var nodes []*Node - for _, clause := range clauses { + for i, clause := range clauses { p.lineno(clause) - markdcl() + markdcl(clause.Pos()) n := p.nod(clause, OXCASE, nil, nil) if clause.Cases != nil { n.List.Set(p.exprList(clause.Cases)) @@ -878,7 +878,11 @@ func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node) []*Node } n.Xoffset = int64(block) n.Nbody.Set(p.stmts(clause.Body)) - popdcl() + if i+1 < len(clauses) { + popdcl(clauses[i+1].Pos()) + } else { + popdcl(rbrace) + } nodes = append(nodes, n) } return nodes @@ -886,22 +890,26 @@ func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node) []*Node func (p *noder) selectStmt(stmt *syntax.SelectStmt) *Node { n := p.nod(stmt, OSELECT, nil, nil) - n.List.Set(p.commClauses(stmt.Body)) + n.List.Set(p.commClauses(stmt.Body, stmt.Rbrace)) return n } -func (p *noder) commClauses(clauses []*syntax.CommClause) []*Node { +func (p *noder) commClauses(clauses []*syntax.CommClause, rbrace src.Pos) []*Node { var nodes []*Node - for _, clause := range clauses { + for i, clause := range clauses { p.lineno(clause) - markdcl() + markdcl(clause.Pos()) n := p.nod(clause, OXCASE, nil, nil) if clause.Comm != nil { n.List.Set1(p.stmt(clause.Comm)) } n.Xoffset = int64(block) n.Nbody.Set(p.stmts(clause.Body)) - popdcl() + if i+1 < len(clauses) { + popdcl(clauses[i+1].Pos()) + } else { + popdcl(rbrace) + } nodes = append(nodes, n) } return nodes diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index ec6483fe8e..c2fa7dfcae 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -310,13 +310,18 @@ func compile(fn *Node) { pp.Free() } -func debuginfo(fnsym *obj.LSym, curfn interface{}) []*dwarf.Var { +func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope { fn := curfn.(*Node) if expect := Linksym(fn.Func.Nname.Sym); fnsym != expect { Fatalf("unexpected fnsym: %v != %v", fnsym, expect) } - var vars []*dwarf.Var + scopes := make([]dwarf.Scope, len(fn.Func.scopes.Scopes)) + + for i := range scopes { + scopes[i] = dwarf.Scope{Scope: fn.Func.scopes.Scopes[i]} + } + for _, n := range fn.Func.Dcl { if n.Op != ONAME { // might be OTYPE or OLITERAL continue @@ -363,8 +368,14 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []*dwarf.Var { continue } + var scope int32 = 0 + // Only record scope information when inlining is disabled. + if Debug['l'] == 0 { + scope = findScope(n.Pos, scopes) + } + typename := dwarf.InfoPrefix + gotype.Name[len("type."):] - vars = append(vars, &dwarf.Var{ + scopes[scope].Vars = append(scopes[scope].Vars, &dwarf.Var{ Name: n.Sym.Name, Abbrev: abbrev, Offset: int32(offs), @@ -373,9 +384,13 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []*dwarf.Var { } // Stable sort so that ties are broken with declaration order. - sort.Stable(dwarf.VarsByOffset(vars)) + for i := range scopes { + sort.Stable(dwarf.VarsByOffset(scopes[i].Vars)) + } + + scopeRanges(fnsym, scopes) - return vars + return scopes } // fieldtrack adds R_USEFIELD relocations to fnsym to record any diff --git a/src/cmd/compile/internal/gc/scope.go b/src/cmd/compile/internal/gc/scope.go new file mode 100644 index 0000000000..82cc371016 --- /dev/null +++ b/src/cmd/compile/internal/gc/scope.go @@ -0,0 +1,91 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gc + +import ( + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/src" +) + +// findScope returns the most specific scope containing pos. +func findScope(pos src.XPos, scopes []dwarf.Scope) int32 { + if !pos.IsKnown() { + return 0 + } + for i := len(scopes) - 1; i > 0; i-- { + if pos.After(scopes[i].Start) && pos.Before(scopes[i].End) { + return int32(i) + } + } + return 0 +} + +type scopedProg struct { + p *obj.Prog + scope int32 +} + +// scopeRanges calculates scope ranges for symbol fnsym. +func scopeRanges(fnsym *obj.LSym, scopes []dwarf.Scope) { + var sp []scopedProg + + for p := fnsym.Text; p != nil; p = p.Link { + sp = append(sp, scopedProg{p, -1}) + } + + for scopeID := int32(len(scopes) - 1); scopeID >= 0; scopeID-- { + if scope := &scopes[scopeID]; scope.Start.IsKnown() && scope.End.IsKnown() { + scopeProgs(sp, scopeID, scope.Start, scope.End) + } + } + + scopedProgsToRanges(sp, scopes, fnsym.Size) + + // Propagate scope's pc ranges to parent + for i := len(scopes) - 1; i > 0; i-- { + cur := &scopes[i] + if scopes[i].Parent != 0 { + parent := &scopes[scopes[i].Parent] + parent.UnifyRanges(cur) + } + } +} + +// scopeProgs marks all scopedProgs between start and end that don't already +// belong to a scope as belonging to scopeId. +func scopeProgs(sp []scopedProg, scopeId int32, start, end src.XPos) { + for i := range sp { + if sp[i].scope >= 0 { + continue + } + if pos := sp[i].p.Pos; pos.After(start) && pos.Before(end) { + sp[i].scope = scopeId + } + } +} + +// scopedProgsToRanges scans sp and collects in the Ranges field of each +// scope the start and end instruction of the scope. +func scopedProgsToRanges(sp []scopedProg, scopes []dwarf.Scope, symSize int64) { + var curscope int32 = -1 + for i := range sp { + if sp[i].scope == curscope { + continue + } + if curscope >= 0 { + curranges := scopes[curscope].Ranges + curranges[len(curranges)-1].End = sp[i].p.Pc + } + curscope = sp[i].scope + if curscope >= 0 { + scopes[curscope].Ranges = append(scopes[curscope].Ranges, dwarf.Range{Start: sp[i].p.Pc, End: -1}) + } + } + if curscope >= 0 { + curranges := scopes[curscope].Ranges + curranges[len(curranges)-1].End = symSize + } +} diff --git a/src/cmd/compile/internal/gc/scope_test.go b/src/cmd/compile/internal/gc/scope_test.go new file mode 100644 index 0000000000..0d01abad09 --- /dev/null +++ b/src/cmd/compile/internal/gc/scope_test.go @@ -0,0 +1,410 @@ +// 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 gc_test + +import ( + "cmd/internal/objfile" + "debug/dwarf" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "sort" + "strconv" + "strings" + "testing" +) + +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 +} + +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 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"}, + {line: " f1(a)"}, + {line: " for i := 0; i < 5; i++ {", scopes: []int{1}, vars: []string{"var i int"}}, + {line: " f2(i)", scopes: []int{1}}, + {line: " for i := 0; i < 5; i++ {", scopes: []int{1, 2}, vars: []string{"var i int"}}, + {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}}, + {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}}, + {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}}, + {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:"}, + {line: " f1(x)", scopes: []int{1}, vars: []string{"var x int"}}, + {line: " case uint8:"}, + {line: " f1(int(x))", scopes: []int{2}, vars: []string{"var x uint8"}}, + {line: " case float64:"}, + {line: " f1(int(x)+1)", scopes: []int{3}, vars: []string{"var x float64"}}, + {line: " }"}, + {line: "}"}, + {line: "func TestSelectScope() {"}, + {line: " select {"}, + {line: " case i := <- ch:"}, + {line: " f1(i)", scopes: []int{1}, vars: []string{"var i int"}}, + {line: " case f := <- floatch:"}, + {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 a int", "var d int"}}, + {line: " d := 3"}, + {line: " f1(a); 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: " }"}, + {line: " }"}, + {line: " f(3); f1(b)"}, + {line: "}"}, + {line: "func main() {"}, + {line: " TestNestedFor()"}, + {line: " TestOas2()"}, + {line: " TestIfElse()"}, + {line: " TestSwitch()"}, + {line: " TestTypeSwitch()"}, + {line: " TestSelectScope()"}, + {line: " TestBlock()"}, + {line: " TestDiscontiguousRanges()"}, + {line: " TestClosureScope()"}, + {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) + 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, 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, testfile[i].line, out[len(out)-1].id, testfile[i].vars, out[len(out)-1].vars) + } + } + } + + 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, out []string) bool { + if len(tgt) != len(out) { + return false + } + for i := range tgt { + if tgt[i] != out[i] { + return false + } + } + return true +} + +type lexblock struct { + id int + ranges [][2]uint64 + vars []string + scopes []lexblock +} + +type line struct { + file string + lineno int +} + +type scopexplainContext struct { + dwarfData *dwarf.Data + dwarfReader *dwarf.Reader + scopegen int + lines map[line][]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) { + if lowpc, ok := entry.Val(dwarf.AttrLowpc).(uint64); ok { + highpc, _ := entry.Val(dwarf.AttrHighpc).(uint64) + scope.ranges = [][2]uint64{[2]uint64{lowpc, highpc}} + } else { + ranges, err := ctxt.dwarfData.Ranges(entry) + if err != nil { + panic(err) + } + scope.ranges = ranges + } + for { + e, err := ctxt.dwarfReader.Next() + if err != nil { + panic(err) + } + switch e.Tag { + case 0: + sort.Strings(scope.vars) + 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, "arg "+e.Val(dwarf.AttrName).(string)+" "+typ.String()) + case dwarf.TagVariable: + typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) + if err != nil { + panic(err) + } + scope.vars = append(scope.vars, "var "+e.Val(dwarf.AttrName).(string)+" "+typ.String()) + case dwarf.TagLexDwarfBlock: + scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen}) + ctxt.scopegen++ + readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e) + } + } +} + +// 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, 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() + + cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-N -l", "-o", dst, src) + 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 +} diff --git a/src/cmd/compile/internal/gc/sizeof_test.go b/src/cmd/compile/internal/gc/sizeof_test.go index bea25dde2b..f2d6728863 100644 --- a/src/cmd/compile/internal/gc/sizeof_test.go +++ b/src/cmd/compile/internal/gc/sizeof_test.go @@ -22,7 +22,7 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {Func{}, 96, 160}, + {Func{}, 112, 192}, {Name{}, 36, 56}, {Param{}, 28, 56}, {Node{}, 84, 136}, diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go index ddf6f77c07..a7d2adf4a8 100644 --- a/src/cmd/compile/internal/gc/subr.go +++ b/src/cmd/compile/internal/gc/subr.go @@ -1687,7 +1687,7 @@ func genwrapper(rcvr *types.Type, method *types.Field, newnam *types.Sym, iface lineno = autogeneratedPos dclcontext = PEXTERN - markdcl() + markdcl(src.NoPos) this := namedfield(".this", rcvr) this.Left.Name.Param.Ntype = this.Right @@ -1717,7 +1717,7 @@ func genwrapper(rcvr *types.Type, method *types.Field, newnam *types.Sym, iface fn.Func.Nname.Name.Param.Ntype = t fn.Func.Nname.Sym.SetExported(true) // prevent export; see closure.go declare(fn.Func.Nname, PFUNC) - funchdr(fn) + funchdr(fn, src.NoPos) // arg list var args []*Node @@ -1782,9 +1782,9 @@ func genwrapper(rcvr *types.Type, method *types.Field, newnam *types.Sym, iface dumplist("genwrapper body", fn.Nbody) } - funcbody(fn) + funcbody(fn, src.NoPos) Curfn = fn - popdcl() + popdcl(src.NoPos) if debug_dclstack != 0 { testdclstack() } diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index ef70e4b1f7..92f2a29288 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -321,9 +321,10 @@ type Func struct { Shortname *types.Sym Enter Nodes // for example, allocate and initialize memory for escaping parameters Exit Nodes - Cvars Nodes // closure params - Dcl []*Node // autodcl for this func/closure - Inldcl Nodes // copy of dcl for use in inlining + Cvars Nodes // closure params + Dcl []*Node // autodcl for this func/closure + Inldcl Nodes // copy of dcl for use in inlining + scopes src.Scopes // lexical scopes of the function Closgen int Outerfunc *Node // outer function (for closure) FieldTrack map[*types.Sym]struct{} diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index 7a8becb35a..95b72c873c 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -7,6 +7,7 @@ package gc import ( "cmd/compile/internal/types" "cmd/internal/obj" + "cmd/internal/src" "cmd/internal/sys" "fmt" "strings" @@ -3661,7 +3662,7 @@ func walkprintfunc(n *Node, init *Nodes) *Node { oldfn := Curfn Curfn = nil - funchdr(fn) + funchdr(fn, src.NoPos) a = nod(n.Op, nil, nil) a.List.Set(printargs) @@ -3670,7 +3671,7 @@ func walkprintfunc(n *Node, init *Nodes) *Node { fn.Nbody.Set1(a) - funcbody(fn) + funcbody(fn, src.NoPos) fn = typecheck(fn, Etop) typecheckslice(fn.Nbody.Slice(), Etop) diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index 7625149914..da419d8574 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -8,14 +8,20 @@ package dwarf import ( + "cmd/internal/src" + "errors" "fmt" ) // InfoPrefix is the prefix for all the symbols containing DWARF info entries. const InfoPrefix = "go.info." +// RangePrefix is the prefix for all the symbols containing DWARF range lists. +const RangePrefix = "go.range." + // Sym represents a symbol. type Sym interface { + Len() int64 } // A Var represents a local variable or a function parameter. @@ -26,6 +32,63 @@ type Var struct { Type Sym } +// A Scope represents a lexical scope, all variables contained in a scope +// will only be visible to instructions covered by the scope. +// Lexical scopes are contiguous in source files but can end up being +// compiled to discontiguous blocks of instructions in the executable, the +// Ranges field lists all the blocks of instructions that belong in this +// scope. +type Scope struct { + src.Scope + Ranges []Range + Vars []*Var +} + +// A Range represents a half-open interval [Start, End). +type Range struct { + Start, End int64 +} + +// UnifyRanges merges the list of ranges of c into the list of ranges of s +func (s *Scope) UnifyRanges(c *Scope) { + out := make([]Range, 0, len(s.Ranges)+len(c.Ranges)) + + i, j := 0, 0 + for { + var cur Range + if i < len(s.Ranges) && j < len(c.Ranges) { + if s.Ranges[i].Start < c.Ranges[j].Start { + cur = s.Ranges[i] + i++ + } else { + cur = c.Ranges[j] + j++ + } + } else if i < len(s.Ranges) { + cur = s.Ranges[i] + i++ + } else if j < len(c.Ranges) { + cur = c.Ranges[j] + j++ + } else { + break + } + + if len(out) == 0 { + out = append(out, cur) + } else { + last := &out[len(out)-1] + if cur.Start > last.End { + out = append(out, cur) + } else if cur.End > last.End { + last.End = cur.End + } + } + } + + s.Ranges = out +} + // A Context specifies how to add data to a Sym. type Context interface { PtrSize() int @@ -153,6 +216,8 @@ const ( DW_ABRV_VARIABLE DW_ABRV_AUTO DW_ABRV_PARAM + DW_ABRV_LEXICAL_BLOCK_RANGES + DW_ABRV_LEXICAL_BLOCK_SIMPLE DW_ABRV_STRUCTFIELD DW_ABRV_FUNCTYPEPARAM DW_ABRV_DOTDOTDOT @@ -243,6 +308,25 @@ var abbrevs = [DW_NABRV]dwAbbrev{ }, }, + /* LEXICAL_BLOCK_RANGES */ + { + DW_TAG_lexical_block, + DW_CHILDREN_yes, + []dwAttrForm{ + {DW_AT_ranges, DW_FORM_data4}, // replace with DW_FORM_sec_offset in DWARFv4. + }, + }, + + /* LEXICAL_BLOCK_SIMPLE */ + { + DW_TAG_lexical_block, + DW_CHILDREN_yes, + []dwAttrForm{ + {DW_AT_low_pc, DW_FORM_addr}, + {DW_AT_high_pc, DW_FORM_addr}, + }, + }, + /* STRUCTFIELD */ { DW_TAG_member, @@ -520,8 +604,8 @@ func putattr(ctxt Context, s Sym, abbrev int, form int, cls int, value int64, da ctxt.AddInt(s, 2, value) case DW_FORM_data4: // constant, {line,loclist,mac,rangelist}ptr - if cls == DW_CLS_PTR { // DW_AT_stmt_list - ctxt.AddSectionOffset(s, 4, data, 0) + if cls == DW_CLS_PTR { // DW_AT_stmt_list and DW_AT_ranges + ctxt.AddSectionOffset(s, 4, data, value) break } ctxt.AddInt(s, 4, value) @@ -550,15 +634,13 @@ func putattr(ctxt Context, s Sym, abbrev int, form int, cls int, value int64, da ctxt.AddInt(s, 1, 0) } - // In DWARF 2 (which is what we claim to generate), - // the ref_addr is the same size as a normal address. - // In DWARF 3 it is always 32 bits, unless emitting a large + // In DWARF 3 the ref_addr is always 32 bits, unless emitting a large // (> 4 GB of debug info aka "64-bit") unit, which we don't implement. case DW_FORM_ref_addr: // reference to a DIE in the .info section if data == nil { return fmt.Errorf("dwarf: null reference in %d", abbrev) } else { - ctxt.AddSectionOffset(s, ctxt.PtrSize(), data, 0) + ctxt.AddSectionOffset(s, 4, data, 0) } case DW_FORM_ref1, // reference within the compilation unit @@ -601,7 +683,7 @@ func HasChildren(die *DWDie) bool { // PutFunc writes a DIE for a function to s. // It also writes child DIEs for each variable in vars. -func PutFunc(ctxt Context, s Sym, name string, external bool, startPC Sym, size int64, vars []*Var) { +func PutFunc(ctxt Context, s, ranges Sym, name string, external bool, startPC Sym, size int64, scopes []Scope) error { Uleb128put(ctxt, s, DW_ABRV_FUNCTION) putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_string, DW_CLS_STRING, int64(len(name)), name) putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_addr, DW_CLS_ADDRESS, 0, startPC) @@ -611,29 +693,72 @@ func PutFunc(ctxt Context, s Sym, name string, external bool, startPC Sym, size ev = 1 } putattr(ctxt, s, DW_ABRV_FUNCTION, DW_FORM_flag, DW_CLS_FLAG, ev, 0) - names := make(map[string]bool) - var encbuf [20]byte - for _, v := range vars { - var n string - if names[v.Name] { - n = fmt.Sprintf("%s#%d", v.Name, len(names)) - } else { - n = v.Name - } - names[n] = true - - Uleb128put(ctxt, s, int64(v.Abbrev)) - putattr(ctxt, s, v.Abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) - loc := append(encbuf[:0], DW_OP_call_frame_cfa) - if v.Offset != 0 { - loc = append(loc, DW_OP_consts) - loc = AppendSleb128(loc, int64(v.Offset)) - loc = append(loc, DW_OP_plus) + if len(scopes) > 0 { + var encbuf [20]byte + if putscope(ctxt, s, ranges, startPC, 0, scopes, encbuf[:0]) < int32(len(scopes)) { + return errors.New("multiple toplevel scopes") } - putattr(ctxt, s, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) - putattr(ctxt, s, v.Abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) } + Uleb128put(ctxt, s, 0) + return nil +} + +func putvar(ctxt Context, s Sym, v *Var, encbuf []byte) { + n := v.Name + + Uleb128put(ctxt, s, int64(v.Abbrev)) + putattr(ctxt, s, v.Abbrev, DW_FORM_string, DW_CLS_STRING, int64(len(n)), n) + loc := append(encbuf[:0], DW_OP_call_frame_cfa) + if v.Offset != 0 { + loc = append(loc, DW_OP_consts) + loc = AppendSleb128(loc, int64(v.Offset)) + loc = append(loc, DW_OP_plus) + } + putattr(ctxt, s, v.Abbrev, DW_FORM_block1, DW_CLS_BLOCK, int64(len(loc)), loc) + putattr(ctxt, s, v.Abbrev, DW_FORM_ref_addr, DW_CLS_REFERENCE, 0, v.Type) +} + +func putscope(ctxt Context, s, ranges Sym, startPC Sym, curscope int32, scopes []Scope, encbuf []byte) int32 { + for _, v := range scopes[curscope].Vars { + putvar(ctxt, s, v, encbuf) + } + this := curscope + curscope++ + for curscope < int32(len(scopes)) { + scope := scopes[curscope] + if scope.Parent != this { + return curscope + } + + emptyscope := len(scope.Vars) == 0 || len(scope.Ranges) == 0 + + if !emptyscope { + if len(scope.Ranges) == 1 { + Uleb128put(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE) + putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].Start, startPC) + putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_SIMPLE, DW_FORM_addr, DW_CLS_ADDRESS, scope.Ranges[0].End, startPC) + } else { + Uleb128put(ctxt, s, DW_ABRV_LEXICAL_BLOCK_RANGES) + putattr(ctxt, s, DW_ABRV_LEXICAL_BLOCK_RANGES, DW_FORM_data4, DW_CLS_PTR, ranges.Len(), ranges) + ctxt.AddAddress(ranges, nil, -1) + ctxt.AddAddress(ranges, startPC, 0) + for _, pcrange := range scope.Ranges { + ctxt.AddAddress(ranges, nil, pcrange.Start) + ctxt.AddAddress(ranges, nil, pcrange.End) + } + ctxt.AddAddress(ranges, nil, 0) + ctxt.AddAddress(ranges, nil, 0) + } + } + + curscope = putscope(ctxt, s, ranges, startPC, curscope, scopes, encbuf) + + if !emptyscope { + Uleb128put(ctxt, s, 0) + } + } + return curscope } // VarsByOffset attaches the methods of sort.Interface to []*Var, diff --git a/src/cmd/internal/obj/data.go b/src/cmd/internal/obj/data.go index 114841dedb..7a19e3a38c 100644 --- a/src/cmd/internal/obj/data.go +++ b/src/cmd/internal/obj/data.go @@ -114,7 +114,7 @@ func (s *LSym) WriteInt(ctxt *Link, off int64, siz int, i int64) { // WriteAddr writes an address of size siz into s at offset off. // rsym and roff specify the relocation for the address. func (s *LSym) WriteAddr(ctxt *Link, off int64, siz int, rsym *LSym, roff int64) { - if siz != ctxt.Arch.PtrSize { + if siz != ctxt.Arch.PtrSize && siz != 4 { ctxt.Diag("WriteAddr: bad address size %d in %s", siz, s.Name) } s.prepwrite(ctxt, off, siz) diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index fd07851f3b..e08d7a0340 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -483,6 +483,7 @@ const ( SHOSTOBJ SDWARFSECT SDWARFINFO + SDWARFRANGE SSUB = SymKind(1 << 8) SMASK = SymKind(SSUB - 1) SHIDDEN = SymKind(1 << 9) @@ -739,7 +740,7 @@ type Link struct { Armsize int32 Pc int64 DiagFunc func(string, ...interface{}) - DebugInfo func(fn *LSym, curfn interface{}) []*dwarf.Var // if non-nil, curfn is a *gc.Node + DebugInfo func(fn *LSym, curfn interface{}) []dwarf.Scope // if non-nil, curfn is a *gc.Node Cursym *LSym Version int Errors int diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index 6858143674..314c7a9de6 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -542,10 +542,14 @@ func (c dwCtxt) SymValue(s dwarf.Sym) int64 { return 0 } func (c dwCtxt) AddAddress(s dwarf.Sym, data interface{}, value int64) { - rsym := data.(*LSym) ls := s.(*LSym) size := c.PtrSize() - ls.WriteAddr(c.Link, ls.Size, size, rsym, value) + if data != nil { + rsym := data.(*LSym) + ls.WriteAddr(c.Link, ls.Size, size, rsym, value) + } else { + ls.WriteInt(c.Link, ls.Size, size, value) + } } func (c dwCtxt) AddSectionOffset(s dwarf.Sym, size int, t interface{}, ofs int64) { ls := s.(*LSym) @@ -555,6 +559,10 @@ func (c dwCtxt) AddSectionOffset(s dwarf.Sym, size int, t interface{}, ofs int64 r.Type = R_DWARFREF } +func (s *LSym) Len() int64 { + return s.Size +} + // makeFuncDebugEntry makes a DWARF Debugging Information Entry // for TEXT symbol s. func makeFuncDebugEntry(ctxt *Link, curfn interface{}, s *LSym) { @@ -564,10 +572,21 @@ func makeFuncDebugEntry(ctxt *Link, curfn interface{}, s *LSym) { } dsym.Type = SDWARFINFO dsym.Set(AttrDuplicateOK, s.DuplicateOK()) - var vars []*dwarf.Var + + drsym := ctxt.Lookup(dwarf.RangePrefix+s.Name, int(s.Version)) + if drsym.Size != 0 { + return + } + drsym.Type = SDWARFRANGE + drsym.Set(AttrDuplicateOK, s.DuplicateOK()) + + var scopes []dwarf.Scope if ctxt.DebugInfo != nil { - vars = ctxt.DebugInfo(s, curfn) + scopes = ctxt.DebugInfo(s, curfn) + } + err := dwarf.PutFunc(dwCtxt{ctxt}, dsym, drsym, s.Name, s.Version == 0, s, s.Size, scopes) + if err != nil { + ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err) } - dwarf.PutFunc(dwCtxt{ctxt}, dsym, s.Name, s.Version == 0, s, s.Size, vars) - ctxt.Data = append(ctxt.Data, dsym) + ctxt.Data = append(ctxt.Data, dsym, drsym) } diff --git a/src/cmd/internal/src/scope.go b/src/cmd/internal/src/scope.go new file mode 100644 index 0000000000..d9ca1f5bfa --- /dev/null +++ b/src/cmd/internal/src/scope.go @@ -0,0 +1,36 @@ +// 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 src + +// Scope represents a lexical scope. +type Scope struct { + Start, End XPos + Parent int32 +} + +// Scopes represents a tree of lexical scopes +type Scopes struct { + Scopes []Scope // lexical scopes + curscope int32 // current scope index (during noding) +} + +// Open starts a new scope, scopes.Curscope is the parent. +func (scopes *Scopes) Open(posTable *PosTable, pos Pos) { + scope := Scope{Parent: scopes.curscope, Start: NoXPos, End: NoXPos} + if pos.IsKnown() { + scope.Start = posTable.XPos(pos) + } + scopes.Scopes = append(scopes.Scopes, scope) + scopes.curscope = int32(len(scopes.Scopes) - 1) +} + +// Close ends the current scope. +func (scopes *Scopes) Close(posTable *PosTable, pos Pos) { + scope := &scopes.Scopes[scopes.curscope] + if pos.IsKnown() { + scope.End = posTable.XPos(pos) + } + scopes.curscope = scope.Parent +} diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 094df86dce..6f45b2fa06 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -570,9 +570,18 @@ func relocsym(ctxt *Link, s *Symbol) { } case obj.R_DWARFREF: - if r.Sym.Sect == nil { + sectName := "" + vaddr := int64(0) + if r.Sym.Sect != nil { + sectName = r.Sym.Sect.Name + vaddr = int64(r.Sym.Sect.Vaddr) + } else if r.Sym.Type == obj.SDWARFRANGE { + sectName = ".debug_ranges" + vaddr = 0 + } else { Errorf(s, "missing DWARF section for relocation target %s", r.Sym.Name) } + if Linkmode == LinkExternal { r.Done = 0 // PE code emits IMAGE_REL_I386_SECREL and IMAGE_REL_AMD64_SECREL @@ -584,8 +593,9 @@ func relocsym(ctxt *Link, s *Symbol) { r.Type = obj.R_ADDR } - r.Xsym = ctxt.Syms.ROLookup(r.Sym.Sect.Name, 0) - r.Xadd = r.Add + Symaddr(r.Sym) - int64(r.Sym.Sect.Vaddr) + r.Xsym = ctxt.Syms.ROLookup(sectName, 0) + r.Xadd = r.Add + Symaddr(r.Sym) - vaddr + o = r.Xadd rs = r.Xsym if Iself && SysArch.Family == sys.AMD64 { @@ -593,7 +603,7 @@ func relocsym(ctxt *Link, s *Symbol) { } break } - o = Symaddr(r.Sym) + r.Add - int64(r.Sym.Sect.Vaddr) + o = Symaddr(r.Sym) + r.Add - vaddr case obj.R_WEAKADDROFF: if !r.Sym.Attr.Reachable() { @@ -1822,6 +1832,7 @@ func (ctxt *Link) dodata() { if s.Type != obj.SDWARFSECT { break } + sect = addsection(&Segdwarf, s.Name, 04) sect.Align = 1 datsize = Rnd(datsize, int64(sect.Align)) diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 696a80bcdc..579e13e2ab 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -8,7 +8,6 @@ // - assign global variables and types to their packages // - gdb uses c syntax, meaning clumsy quoting is needed for go identifiers. eg // ptype struct '[]uint8' and qualifiers need to be quoted away -// - lexical scoping is lost, so gdb gets confused as to which 'main.i' you mean. // - file:line info for variables // - make strings a typedef so prettyprinters can see the underlying string type @@ -76,6 +75,7 @@ var arangessec *Symbol var framesec *Symbol var infosec *Symbol var linesec *Symbol +var rangesec *Symbol var gdbscript string @@ -1288,6 +1288,34 @@ func writeframes(ctxt *Link, syms []*Symbol) []*Symbol { return syms } +func writeranges(ctxt *Link, syms []*Symbol) []*Symbol { + if rangesec == nil { + rangesec = ctxt.Syms.Lookup(".debug_ranges", 0) + } + rangesec.Type = obj.SDWARFSECT + rangesec.Attr |= AttrReachable + rangesec.R = rangesec.R[:0] + + for _, s := range ctxt.Textp { + rangeSym := ctxt.Syms.Lookup(dwarf.RangePrefix+s.Name, int(s.Version)) + rangeSym.Attr |= AttrHidden + rangeSym.Attr |= AttrReachable + rangeSym.Type = obj.SDWARFRANGE + rangeSym.Value = rangesec.Size + rangesec.P = append(rangesec.P, rangeSym.P...) + for _, r := range rangeSym.R { + r.Off += int32(rangesec.Size) + rangesec.R = append(rangesec.R, r) + } + rangesec.Size += rangeSym.Size + } + if rangesec.Size > 0 { + // PE does not like empty sections + syms = append(syms, rangesec) + } + return syms +} + /* * Walk DWarfDebugInfoEntries, and emit .debug_info */ @@ -1318,7 +1346,7 @@ func writeinfo(ctxt *Link, syms []*Symbol, funcs []*Symbol) []*Symbol { // Fields marked with (*) must be changed for 64-bit dwarf // This must match COMPUNITHEADERSIZE above. Adduint32(ctxt, s, 0) // unit_length (*), will be filled in later. - Adduint16(ctxt, s, 2) // dwarf version (appendix F) + Adduint16(ctxt, s, 3) // dwarf version (appendix F) // debug_abbrev_offset (*) adddwarfref(ctxt, s, abbrevsym, 4) @@ -1531,6 +1559,7 @@ func dwarfgeneratedebugsyms(ctxt *Link) { syms := writeabbrev(ctxt, nil) syms, funcs := writelines(ctxt, syms) syms = writeframes(ctxt, syms) + syms = writeranges(ctxt, syms) synthesizestringtypes(ctxt, dwtypes.Child) synthesizeslicetypes(ctxt, dwtypes.Child) @@ -1572,6 +1601,7 @@ func dwarfaddshstrings(ctxt *Link, shstrtab *Symbol) { Addstring(shstrtab, ".debug_pubnames") Addstring(shstrtab, ".debug_pubtypes") Addstring(shstrtab, ".debug_gdb_scripts") + Addstring(shstrtab, ".debug_ranges") if Linkmode == LinkExternal { Addstring(shstrtab, elfRelType+".debug_info") Addstring(shstrtab, elfRelType+".debug_aranges") @@ -1599,6 +1629,10 @@ func dwarfaddelfsectionsyms(ctxt *Link) { putelfsectionsym(sym, sym.Sect.Elfsect.shnum) sym = ctxt.Syms.Lookup(".debug_frame", 0) putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + sym = ctxt.Syms.Lookup(".debug_ranges", 0) + if sym.Sect != nil { + putelfsectionsym(sym, sym.Sect.Elfsect.shnum) + } } /* diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index 1c61835da6..5ed8e894b9 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -89,6 +89,10 @@ func (s *Symbol) ElfsymForReloc() int32 { } } +func (s *Symbol) Len() int64 { + return s.Size +} + // Attribute is a set of common symbol attributes. type Attribute int16