diff --git a/src/cmd/compile/internal/gc/closure.go b/src/cmd/compile/internal/gc/closure.go index 04f3e669e27bee824aa68dff3ec30f285e7b31c5..143e1969c7d796ae79949b7dd78d40394d1df854 100644 --- a/src/cmd/compile/internal/gc/closure.go +++ b/src/cmd/compile/internal/gc/closure.go @@ -5,19 +5,21 @@ package gc import ( + "cmd/compile/internal/syntax" "cmd/compile/internal/types" "fmt" ) -// function literals aka closures -func closurehdr(ntype *Node) { - n := nod(OCLOSURE, nil, nil) +func (p *noder) funcLit(expr *syntax.FuncLit) *Node { + ntype := p.typeExpr(expr.Type) + + n := p.nod(expr, OCLOSURE, nil, nil) n.Func.SetIsHiddenClosure(Curfn != nil) n.Func.Ntype = ntype n.Func.Depth = funcdepth n.Func.Outerfunc = Curfn - funchdr(n) + old := p.funchdr(n, expr.Pos()) // steal ntype's argument names and // leave a fresh copy in their place. @@ -25,8 +27,8 @@ func closurehdr(ntype *Node) { // refer to the variables in the external // function declared below; see walkclosure. n.List.Set(ntype.List.Slice()) - n.Rlist.Set(ntype.Rlist.Slice()) + ntype.List.Set(nil) ntype.Rlist.Set(nil) for _, n1 := range n.List.Slice() { @@ -48,23 +50,23 @@ func closurehdr(ntype *Node) { } ntype.Rlist.Append(nod(ODCLFIELD, name, n2.Right)) } -} -func closurebody(body []*Node) *Node { + body := p.stmts(expr.Body.List) + + lineno = Ctxt.PosTable.XPos(expr.Body.Rbrace) if len(body) == 0 { body = []*Node{nod(OEMPTY, nil, nil)} } - func_ := Curfn - func_.Nbody.Set(body) - func_.Func.Endlineno = lineno - funcbody(func_) + n.Nbody.Set(body) + n.Func.Endlineno = lineno + p.funcbody(n, expr.Body.Rbrace, old) // closure-specific variables are hanging off the // ordinary ones in the symbol table; see oldname. // unhook them. // make the list of pointers for the closure call. - for _, v := range func_.Func.Cvars.Slice() { + for _, v := range n.Func.Cvars.Slice() { // Unlink from v1; see comment in syntax.go type Param for these fields. v1 := v.Name.Defn v1.Name.Param.Innermost = v.Name.Param.Outer @@ -100,7 +102,7 @@ func closurebody(body []*Node) *Node { v.Name.Param.Outer = oldname(v.Sym) } - return func_ + return n } func typecheckclosure(func_ *Node, top int) { @@ -227,7 +229,11 @@ func makeclosure(func_ *Node) *Node { xfunc.Nbody.Set(func_.Nbody.Slice()) xfunc.Func.Dcl = append(func_.Func.Dcl, xfunc.Func.Dcl...) + xfunc.Func.Parents = func_.Func.Parents + xfunc.Func.Marks = func_.Func.Marks func_.Func.Dcl = nil + func_.Func.Parents = nil + func_.Func.Marks = nil if xfunc.Nbody.Len() == 0 { Fatalf("empty body - won't generate any code") } diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index 0bf349b33af825c7d07c8b9b85072e9e27361ffd..44dd3058308012ac390aa9d673d161d692f9062e 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -216,6 +216,9 @@ var flagDWARF bool // when the race detector is enabled. var instrumenting bool +// Whether we are tracking lexical scopes for DWARF. +var trackScopes bool + var debuglive int var Ctxt *obj.Link diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index a2472fa08bb35cf3db8da1ab76472e97d7586681..d8d061136ed8fa2bee2b76e7b42dab8cd52c761f 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -380,6 +380,8 @@ func Main(archInit func(*Arch)) { Debug['l'] = 1 - Debug['l'] } + trackScopes = flagDWARF && Debug['l'] == 0 && Debug['N'] != 0 + Widthptr = thearch.LinkArch.PtrSize Widthreg = thearch.LinkArch.RegSize diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 77a7a8a436ed991ff0b8bda4f2de3791d2aaddeb..f00095c866ac6c4d7a7ddb11b9d1b352c25b2f25 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -75,6 +75,62 @@ type noder struct { linknames []linkname pragcgobuf string err chan syntax.Error + scope ScopeID +} + +func (p *noder) funchdr(n *Node, pos src.Pos) ScopeID { + old := p.scope + p.scope = 0 + funchdr(n) + return old +} + +func (p *noder) funcbody(n *Node, pos src.Pos, old ScopeID) { + funcbody(n) + p.scope = old +} + +func (p *noder) openScope(pos src.Pos) { + types.Markdcl() + + if trackScopes { + Curfn.Func.Parents = append(Curfn.Func.Parents, p.scope) + p.scope = ScopeID(len(Curfn.Func.Parents)) + + p.markScope(pos) + } +} + +func (p *noder) closeScope(pos src.Pos) { + types.Popdcl() + + if trackScopes { + p.scope = Curfn.Func.Parents[p.scope-1] + + p.markScope(pos) + } +} + +func (p *noder) markScope(pos src.Pos) { + xpos := Ctxt.PosTable.XPos(pos) + if i := len(Curfn.Func.Marks); i > 0 && Curfn.Func.Marks[i-1].Pos == xpos { + Curfn.Func.Marks[i-1].Scope = p.scope + } else { + Curfn.Func.Marks = append(Curfn.Func.Marks, Mark{xpos, p.scope}) + } +} + +// closeAnotherScope is like closeScope, but it reuses the same mark +// position as the last closeScope call. This is useful for "for" and +// "if" statements, as their implicit blocks always end at the same +// position as an explicit block. +func (p *noder) closeAnotherScope() { + types.Popdcl() + + if trackScopes { + p.scope = Curfn.Func.Parents[p.scope-1] + Curfn.Func.Marks[len(Curfn.Func.Marks)-1].Scope = p.scope + } } // linkname records a //go:linkname directive. @@ -326,8 +382,9 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { declare(f.Func.Nname, PFUNC) } - funchdr(f) + oldScope := p.funchdr(f, fun.Pos()) + endPos := fun.Pos() if fun.Body != nil { if f.Noescape() { yyerrorl(f.Pos, "can only use //go:noescape with external func implementations") @@ -339,6 +396,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { } f.Nbody.Set(body) + endPos = fun.Body.Rbrace lineno = Ctxt.PosTable.XPos(fun.Body.Rbrace) f.Func.Endlineno = lineno } else { @@ -347,8 +405,7 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node { } } - funcbody(f) - + p.funcbody(f, endPos, oldScope) return f } @@ -439,10 +496,7 @@ func (p *noder) expr(expr syntax.Expr) *Node { case *syntax.KeyValueExpr: return p.nod(expr, OKEY, p.expr(expr.Key), p.wrapname(expr.Value, p.expr(expr.Value))) case *syntax.FuncLit: - closurehdr(p.typeExpr(expr.Type)) - body := p.stmts(expr.Body.List) - lineno = Ctxt.PosTable.XPos(expr.Body.Rbrace) - return p.setlineno(expr, closurebody(body)) + return p.funcLit(expr) case *syntax.ParenExpr: return p.nod(expr, OPAREN, p.expr(expr.X), nil) case *syntax.SelectorExpr: @@ -774,14 +828,14 @@ func (p *noder) stmt(stmt syntax.Stmt) *Node { } func (p *noder) blockStmt(stmt *syntax.BlockStmt) []*Node { - types.Markdcl() + p.openScope(stmt.Pos()) nodes := p.stmts(stmt.List) - types.Popdcl() + p.closeScope(stmt.Rbrace) return nodes } func (p *noder) ifStmt(stmt *syntax.IfStmt) *Node { - types.Markdcl() + p.openScope(stmt.Pos()) n := p.nod(stmt, OIF, nil, nil) if stmt.Init != nil { n.Ninit.Set1(p.stmt(stmt.Init)) @@ -798,12 +852,12 @@ func (p *noder) ifStmt(stmt *syntax.IfStmt) *Node { n.Rlist.Set1(e) } } - types.Popdcl() + p.closeAnotherScope() return n } func (p *noder) forStmt(stmt *syntax.ForStmt) *Node { - types.Markdcl() + p.openScope(stmt.Pos()) var n *Node if r, ok := stmt.Init.(*syntax.RangeClause); ok { if stmt.Cond != nil || stmt.Post != nil { @@ -832,12 +886,12 @@ func (p *noder) forStmt(stmt *syntax.ForStmt) *Node { } } n.Nbody.Set(p.blockStmt(stmt.Body)) - types.Popdcl() + p.closeAnotherScope() return n } func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node { - types.Markdcl() + p.openScope(stmt.Pos()) n := p.nod(stmt, OSWITCH, nil, nil) if stmt.Init != nil { n.Ninit.Set1(p.stmt(stmt.Init)) @@ -850,18 +904,21 @@ func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node { if tswitch != nil && (tswitch.Op != OTYPESW || tswitch.Left == nil) { tswitch = nil } + n.List.Set(p.caseClauses(stmt.Body, tswitch, stmt.Rbrace)) - n.List.Set(p.caseClauses(stmt.Body, tswitch)) - - types.Popdcl() + p.closeScope(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) - types.Markdcl() + if i > 0 { + p.closeScope(clause.Pos()) + } + p.openScope(clause.Pos()) + n := p.nod(clause, OXCASE, nil, nil) if clause.Cases != nil { n.List.Set(p.exprList(clause.Cases)) @@ -875,32 +932,40 @@ func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node) []*Node } n.Xoffset = int64(types.Block) n.Nbody.Set(p.stmts(clause.Body)) - types.Popdcl() nodes = append(nodes, n) } + if len(clauses) > 0 { + p.closeScope(rbrace) + } return nodes } 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) - types.Markdcl() + if i > 0 { + p.closeScope(clause.Pos()) + } + p.openScope(clause.Pos()) + n := p.nod(clause, OXCASE, nil, nil) if clause.Comm != nil { n.List.Set1(p.stmt(clause.Comm)) } n.Xoffset = int64(types.Block) n.Nbody.Set(p.stmts(clause.Body)) - types.Popdcl() nodes = append(nodes, n) } + if len(clauses) > 0 { + p.closeScope(rbrace) + } return nodes } diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index 355df9d32660bf5db943cc581dc785879672ef1d..427146ffb0bc60be534cd360076a07828e304980 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -299,13 +299,15 @@ func compileFunctions() { } } -func debuginfo(fnsym *obj.LSym, curfn interface{}) []*dwarf.Var { +func debuginfo(fnsym *obj.LSym, curfn interface{}) []dwarf.Scope { fn := curfn.(*Node) if expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect { Fatalf("unexpected fnsym: %v != %v", fnsym, expect) } - var vars []*dwarf.Var + var dwarfVars []*dwarf.Var + var varScopes []ScopeID + for _, n := range fn.Func.Dcl { if n.Op != ONAME { // might be OTYPE or OLITERAL continue @@ -353,18 +355,26 @@ func debuginfo(fnsym *obj.LSym, curfn interface{}) []*dwarf.Var { } typename := dwarf.InfoPrefix + gotype.Name[len("type."):] - vars = append(vars, &dwarf.Var{ + dwarfVars = append(dwarfVars, &dwarf.Var{ Name: n.Sym.Name, Abbrev: abbrev, Offset: int32(offs), Type: Ctxt.Lookup(typename), }) - } - // Stable sort so that ties are broken with declaration order. - sort.Stable(dwarf.VarsByOffset(vars)) + var scope ScopeID + if !n.Name.Captured() && !n.Name.Byval() { + // n.Pos of captured variables is their first + // use in the closure but they should always + // be assigned to scope 0 instead. + // TODO(mdempsky): Verify this. + scope = findScope(fn.Func.Marks, n.Pos) + } + + varScopes = append(varScopes, scope) + } - return vars + return assembleScopes(fnsym, fn, dwarfVars, varScopes) } // 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 0000000000000000000000000000000000000000..b0bc7f6908632cad2115da0bb6d184d5903dfb12 --- /dev/null +++ b/src/cmd/compile/internal/gc/scope.go @@ -0,0 +1,177 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gc + +import ( + "cmd/internal/dwarf" + "cmd/internal/obj" + "cmd/internal/src" + "sort" +) + +// See golang.org/issue/20390. +func xposBefore(p, q src.XPos) bool { + return Ctxt.PosTable.Pos(p).Before(Ctxt.PosTable.Pos(q)) +} + +func findScope(marks []Mark, pos src.XPos) 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 *Node, dwarfVars []*dwarf.Var, varScopes []ScopeID) []dwarf.Scope { + // Initialize the DWARF scope tree based on lexical scopes. + dwarfScopes := make([]dwarf.Scope, 1+len(fn.Func.Parents)) + for i, parent := range fn.Func.Parents { + dwarfScopes[i+1].Parent = int32(parent) + } + + scopeVariables(dwarfVars, varScopes, dwarfScopes) + scopePCs(fnsym, fn.Func.Marks, dwarfScopes) + return compactScopes(dwarfScopes) +} + +// scopeVariables assigns DWARF variable records to their scopes. +func scopeVariables(dwarfVars []*dwarf.Var, varScopes []ScopeID, dwarfScopes []dwarf.Scope) { + 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:] + } +} + +// A scopedPCs represents a non-empty half-open interval of PCs that +// share a common source position. +type scopedPCs struct { + start, end int64 + pos src.XPos + scope ScopeID +} + +// scopePCs assigns PC ranges to their scopes. +func scopePCs(fnsym *obj.LSym, marks []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 + } + + // Break function text into scopedPCs. + var pcs []scopedPCs + p0 := fnsym.Func.Text + for p := fnsym.Func.Text; p != nil; p = p.Link { + if p.Pos == p0.Pos { + continue + } + if p0.Pc < p.Pc { + pcs = append(pcs, scopedPCs{start: p0.Pc, end: p.Pc, pos: p0.Pos}) + } + p0 = p + } + if p0.Pc < fnsym.Size { + pcs = append(pcs, scopedPCs{start: p0.Pc, end: fnsym.Size, pos: p0.Pos}) + } + + // Sort PCs by source position, and walk in parallel with + // scope marks to assign a lexical scope to each PC interval. + sort.Sort(pcsByPos(pcs)) + var marki int + var scope ScopeID + for i := range pcs { + for marki < len(marks) && !xposBefore(pcs[i].pos, marks[marki].Pos) { + scope = marks[marki].Scope + marki++ + } + pcs[i].scope = scope + } + + // Re-sort to create sorted PC ranges for each DWARF scope. + sort.Sort(pcsByPC(pcs)) + for _, pc := range pcs { + r := &dwarfScopes[pc.scope].Ranges + if i := len(*r); i > 0 && (*r)[i-1].End == pc.start { + (*r)[i-1].End = pc.end + } else { + *r = append(*r, dwarf.Range{Start: pc.start, End: pc.end}) + } + } +} + +func compactScopes(dwarfScopes []dwarf.Scope) []dwarf.Scope { + // Forward pass to collapse empty scopes into parents. + remap := make([]int32, len(dwarfScopes)) + j := int32(1) + for i := 1; i < len(dwarfScopes); i++ { + s := &dwarfScopes[i] + s.Parent = remap[s.Parent] + if len(s.Vars) == 0 { + dwarfScopes[s.Parent].UnifyRanges(s) + remap[i] = s.Parent + continue + } + remap[i] = j + dwarfScopes[j] = *s + j++ + } + dwarfScopes = dwarfScopes[:j] + + // 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 pcsByPC []scopedPCs + +func (s pcsByPC) Len() int { return len(s) } +func (s pcsByPC) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s pcsByPC) Less(i, j int) bool { + return s[i].start < s[j].start +} + +type pcsByPos []scopedPCs + +func (s pcsByPos) Len() int { return len(s) } +func (s pcsByPos) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s pcsByPos) Less(i, j int) bool { + return xposBefore(s[i].pos, s[j].pos) +} + +type varsByScopeAndOffset struct { + vars []*dwarf.Var + scopes []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].Offset < v.vars[j].Offset +} + +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] +} 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 0000000000000000000000000000000000000000..5833b1e64990ea712361404ebfd0b98002f9a730 --- /dev/null +++ b/src/cmd/compile/internal/gc/scope_test.go @@ -0,0 +1,407 @@ +// 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, 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"}}, + {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 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) { + 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.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 e25b117338135be2511ca2f86f1866173eea384d..1ca0a615353afe1f3962bc7a573591f0806143e9 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{}, 100, 168}, + {Func{}, 124, 216}, {Name{}, 36, 56}, {Param{}, 28, 56}, {Node{}, 76, 128}, diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index 9b1eeaf43e83c7bd0d1a237304e506219c585af8..0fd146bca26deb57258173aa37e0f9d9c15785ce 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -351,12 +351,21 @@ type Param struct { // Func holds Node fields used only with function-like nodes. 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 + 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 + + // Parents records the parent scope of each scope within a + // function. The root scope (0) has no parent, so the i'th + // scope's parent is stored at Parents[i-1]. + Parents []ScopeID + + // Marks records scope boundary changes. + Marks []Mark + Closgen int Outerfunc *Node // outer function (for closure) FieldTrack map[*types.Sym]struct{} @@ -380,6 +389,19 @@ type Func struct { flags bitset8 } +// A Mark represents a scope boundary. +type Mark struct { + // Pos is the position of the token that marks the scope + // change. + Pos src.XPos + + // Scope identifies the innermost scope to the right of Pos. + Scope ScopeID +} + +// A ScopeID represents a lexical scope within a function. +type ScopeID int32 + const ( funcDupok = 1 << iota // duplicate definitions ok funcWrapper // is method wrapper diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go index 042a79b5a6824d102668a5fbf6ffb934b7baf05d..2974bf50921d4818d982348f13b2dac977c5a86c 100644 --- a/src/cmd/internal/dwarf/dwarf.go +++ b/src/cmd/internal/dwarf/dwarf.go @@ -8,14 +8,19 @@ package dwarf import ( + "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. @@ -23,9 +28,62 @@ type Var struct { Name string Abbrev int // Either DW_ABRV_AUTO or DW_ABRV_PARAM Offset int32 + Scope int32 Type Sym } +// A Scope represents a lexical scope. All variables declared within 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 { + Parent int32 + 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 n := len(out); n > 0 && cur.Start <= out[n-1].End { + out[n-1].End = cur.End + } else { + out = append(out, cur) + } + } + + s.Ranges = out +} + // A Context specifies how to add data to a Sym. type Context interface { PtrSize() int @@ -156,6 +214,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 @@ -247,6 +307,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, @@ -525,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) @@ -555,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 @@ -606,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) @@ -616,29 +693,67 @@ 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 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 + } + + 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 _, r := range scope.Ranges { + ctxt.AddAddress(ranges, nil, r.Start) + ctxt.AddAddress(ranges, nil, r.End) + } + ctxt.AddAddress(ranges, nil, 0) + ctxt.AddAddress(ranges, nil, 0) + } + + curscope = putscope(ctxt, s, ranges, startPC, curscope, scopes, encbuf) + + Uleb128put(ctxt, s, 0) + } + return curscope +} + +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) } // 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 8b1bdb1056fc17dcf572557857d5cbec96a3baf6..23d1809e0c146386a44341a7d0a2d9d0dd0a1831 100644 --- a/src/cmd/internal/obj/data.go +++ b/src/cmd/internal/obj/data.go @@ -120,7 +120,8 @@ 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 { + // Allow 4-byte addresses for DWARF. + 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 8bdc3f55e99e4b4a6783dca4831cd582c56448e4..d49bc8c5644c78ad75c96e2bbd130effd73e81cd 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -324,12 +324,15 @@ type LSym struct { // A FuncInfo contains extra fields for STEXT symbols. type FuncInfo struct { - Args int32 - Locals int32 - Text *Prog - Autom []*Auto - Pcln Pcln - dwarfSym *LSym + Args int32 + Locals int32 + Text *Prog + Autom []*Auto + Pcln Pcln + + dwarfSym *LSym + dwarfRangesSym *LSym + GCArgs LSym GCLocals LSym } @@ -490,7 +493,7 @@ type Link struct { InlTree InlTree // global inlining tree used by gc/inl.go Imports []string 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 Errors int Framepointer_enabled bool diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index dc22eacdf48bfe75f94bfcb2546b3c8a7fe67313..e309c5f7e785122a37edc74fad443ddc83872725 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -447,10 +447,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) @@ -460,27 +464,35 @@ func (c dwCtxt) AddSectionOffset(s dwarf.Sym, size int, t interface{}, ofs int64 r.Type = objabi.R_DWARFREF } -// dwarfSym returns the DWARF symbol for TEXT symbol. -func (ctxt *Link) dwarfSym(s *LSym) *LSym { +// dwarfSym returns the DWARF symbols for TEXT symbol. +func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfRangesSym *LSym) { if s.Type != objabi.STEXT { ctxt.Diag("dwarfSym of non-TEXT %v", s) } if s.Func.dwarfSym == nil { s.Func.dwarfSym = ctxt.LookupDerived(s, dwarf.InfoPrefix+s.Name) + s.Func.dwarfRangesSym = ctxt.LookupDerived(s, dwarf.RangePrefix+s.Name) } - return s.Func.dwarfSym + return s.Func.dwarfSym, s.Func.dwarfRangesSym } -// populateDWARF fills in the DWARF Debugging Information Entry for TEXT symbol s. -// The DWARF symbol must already have been initialized in InitTextSym. +func (s *LSym) Len() int64 { + return s.Size +} + +// populateDWARF fills in the DWARF Debugging Information Entries for TEXT symbol s. +// The DWARFs symbol must already have been initialized in InitTextSym. func (ctxt *Link) populateDWARF(curfn interface{}, s *LSym) { - dsym := ctxt.dwarfSym(s) + dsym, drsym := ctxt.dwarfSym(s) if dsym.Size != 0 { ctxt.Diag("makeFuncDebugEntry double process %v", s) } - var vars []*dwarf.Var + 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.Static(), 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.Static(), s, s.Size, vars) } diff --git a/src/cmd/internal/obj/plist.go b/src/cmd/internal/obj/plist.go index 5c86c20e73db5c75c6453d28a5078e552d3cc0b0..861da88703c859f6e27d166776d7820b320e4456 100644 --- a/src/cmd/internal/obj/plist.go +++ b/src/cmd/internal/obj/plist.go @@ -135,11 +135,14 @@ func (ctxt *Link) InitTextSym(s *LSym, flag int) { s.Type = objabi.STEXT ctxt.Text = append(ctxt.Text, s) - // Set up DWARF entry for s. - dsym := ctxt.dwarfSym(s) + // Set up DWARF entries for s. + dsym, drsym := ctxt.dwarfSym(s) dsym.Type = objabi.SDWARFINFO dsym.Set(AttrDuplicateOK, s.DuplicateOK()) + drsym.Type = objabi.SDWARFRANGE + drsym.Set(AttrDuplicateOK, s.DuplicateOK()) ctxt.Data = append(ctxt.Data, dsym) + ctxt.Data = append(ctxt.Data, drsym) // Set up the function's gcargs and gclocals. // They will be filled in later if needed. diff --git a/src/cmd/internal/objabi/symkind.go b/src/cmd/internal/objabi/symkind.go index 62a7efd964084f3f355b5dfa2df9db4e395e0007..b037e9e4ed17c6ecfef1d136d3b226f484ff2618 100644 --- a/src/cmd/internal/objabi/symkind.go +++ b/src/cmd/internal/objabi/symkind.go @@ -56,4 +56,5 @@ const ( STLSBSS // Debugging data SDWARFINFO + SDWARFRANGE ) diff --git a/src/cmd/internal/objabi/symkind_string.go b/src/cmd/internal/objabi/symkind_string.go index aabcfd2d54c1aee8caa84c133310402edc8f52cf..5123dc7097f49ad8aa917425af8f8d7f0d207ae0 100644 --- a/src/cmd/internal/objabi/symkind_string.go +++ b/src/cmd/internal/objabi/symkind_string.go @@ -4,9 +4,9 @@ package objabi import "fmt" -const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFINFO" +const _SymKind_name = "SxxxSTEXTSRODATASNOPTRDATASDATASBSSSNOPTRBSSSTLSBSSSDWARFINFOSDWARFRANGE" -var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 61} +var _SymKind_index = [...]uint8{0, 4, 9, 16, 26, 31, 35, 44, 51, 61, 72} func (i SymKind) String() string { if i >= SymKind(len(_SymKind_index)-1) { diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index d724c596c138758a53838de6249d43e6f2c0c74b..def7055707abc9b720869ae8f38eade09fc99361 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -574,9 +574,18 @@ func relocsym(ctxt *Link, s *Symbol) { } case objabi.R_DWARFREF: - if r.Sym.Sect == nil { + var sectName string + var vaddr int64 + switch { + case r.Sym.Sect != nil: + sectName = r.Sym.Sect.Name + vaddr = int64(r.Sym.Sect.Vaddr) + case r.Sym.Type == SDWARFRANGE: + sectName = ".debug_ranges" + default: 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 @@ -588,8 +597,9 @@ func relocsym(ctxt *Link, s *Symbol) { r.Type = objabi.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 { @@ -597,7 +607,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 objabi.R_WEAKADDROFF: if !r.Sym.Attr.Reachable() { @@ -1821,6 +1831,7 @@ func (ctxt *Link) dodata() { if s.Type != 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 785fe374d210c6e9acff91b98e69a9c28127a508..c705bf3ba55401f84046bb026341c118e282d4a8 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 @@ -1291,6 +1291,33 @@ 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 = 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 |= AttrReachable + rangeSym.Type = 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 */ @@ -1321,7 +1348,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) @@ -1553,6 +1580,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) @@ -1594,6 +1622,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") @@ -1601,6 +1630,7 @@ func dwarfaddshstrings(ctxt *Link, shstrtab *Symbol) { Addstring(shstrtab, elfRelType+".debug_frame") Addstring(shstrtab, elfRelType+".debug_pubnames") Addstring(shstrtab, elfRelType+".debug_pubtypes") + Addstring(shstrtab, elfRelType+".debug_ranges") } } @@ -1621,6 +1651,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 6a1a47e213a740e29f3f7ad815d929c923538646..8d9e4a7cf3a019d1db0d3f3a9cf95e7bf9cef68e 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 diff --git a/src/cmd/link/internal/ld/nooptcgolink_test.go b/src/cmd/link/internal/ld/nooptcgolink_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1df29652b2016b81c4097cb32ac2e86332040035 --- /dev/null +++ b/src/cmd/link/internal/ld/nooptcgolink_test.go @@ -0,0 +1,32 @@ +// 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 ld + +import ( + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" +) + +func TestNooptCgoBuild(t *testing.T) { + testenv.MustHaveGoBuild(t) + testenv.MustHaveCGO(t) + dir, err := ioutil.TempDir("", "go-build") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + cmd := exec.Command("go", "build", "-gcflags=-N -l", "-o", filepath.Join(dir, "a.out")) + cmd.Dir = filepath.Join(runtime.GOROOT(), "src", "runtime", "testdata", "testprogcgo") + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("go build output: %s", out) + t.Fatal(err) + } +} diff --git a/src/cmd/link/internal/ld/symkind.go b/src/cmd/link/internal/ld/symkind.go index 1f95f8afa8b1bca9b15a027e88c9d826233f1962..c057f6cd0c937a70c061d68ca757d4bd742acca1 100644 --- a/src/cmd/link/internal/ld/symkind.go +++ b/src/cmd/link/internal/ld/symkind.go @@ -104,6 +104,7 @@ const ( SHOSTOBJ SDWARFSECT SDWARFINFO + SDWARFRANGE SSUB = SymKind(1 << 8) SMASK = SymKind(SSUB - 1) SHIDDEN = SymKind(1 << 9) @@ -122,6 +123,7 @@ var abiSymKindToSymKind = [...]SymKind{ SNOPTRBSS, STLSBSS, SDWARFINFO, + SDWARFRANGE, } // readOnly are the symbol kinds that form read-only sections. In some diff --git a/src/cmd/link/internal/ld/symkind_string.go b/src/cmd/link/internal/ld/symkind_string.go index b9aaa72f3486d68db14cee81d78ebe306557f42d..2178b50c36654f3bc82b3f6db4c39bd4cf28a3dc 100644 --- a/src/cmd/link/internal/ld/symkind_string.go +++ b/src/cmd/link/internal/ld/symkind_string.go @@ -4,9 +4,9 @@ package ld import "fmt" -const _SymKind_name = "SxxxSTEXTSELFRXSECTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSMACHOPLTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASBSSSNOPTRBSSSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILESFILEPATHSCONSTSDYNIMPORTSHOSTOBJSDWARFSECTSDWARFINFO" +const _SymKind_name = "SxxxSTEXTSELFRXSECTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSMACHOPLTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASBSSSNOPTRBSSSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILESFILEPATHSCONSTSDYNIMPORTSHOSTOBJSDWARFSECTSDWARFINFOSDWARFRANGE" -var _SymKind_index = [...]uint16{0, 4, 9, 19, 24, 31, 40, 47, 54, 61, 69, 79, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 214, 220, 229, 237, 244, 254, 262, 267, 271, 280, 287, 292, 304, 316, 333, 350, 355, 364, 370, 380, 388, 398, 408} +var _SymKind_index = [...]uint16{0, 4, 9, 19, 24, 31, 40, 47, 54, 61, 69, 79, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 214, 220, 229, 237, 244, 254, 262, 267, 271, 280, 287, 292, 304, 316, 333, 350, 355, 364, 370, 380, 388, 398, 408, 419} func (i SymKind) String() string { if i < 0 || i >= SymKind(len(_SymKind_index)-1) {