mirror of https://github.com/golang/go.git
422 lines
12 KiB
Go
422 lines
12 KiB
Go
// Copyright 2022 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 (
|
|
"cmd/internal/obj"
|
|
"cmd/internal/objabi"
|
|
"cmd/link/internal/loader"
|
|
"fmt"
|
|
"internal/buildcfg"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type stackCheck struct {
|
|
ctxt *Link
|
|
ldr *loader.Loader
|
|
morestack loader.Sym
|
|
callSize int // The number of bytes added by a CALL
|
|
|
|
// height records the maximum number of bytes a function and
|
|
// its callees can add to the stack without a split check.
|
|
height map[loader.Sym]int16
|
|
|
|
// graph records the out-edges from each symbol. This is only
|
|
// populated on a second pass if the first pass reveals an
|
|
// over-limit function.
|
|
graph map[loader.Sym][]stackCheckEdge
|
|
}
|
|
|
|
type stackCheckEdge struct {
|
|
growth int // Stack growth in bytes at call to target
|
|
target loader.Sym // 0 for stack growth without a call
|
|
}
|
|
|
|
// stackCheckCycle is a sentinel stored in the height map to detect if
|
|
// we've found a cycle. This is effectively an "infinite" stack
|
|
// height, so we use the closest value to infinity that we can.
|
|
const stackCheckCycle int16 = 1<<15 - 1
|
|
|
|
// stackCheckIndirect is a sentinel Sym value used to represent the
|
|
// target of an indirect/closure call.
|
|
const stackCheckIndirect loader.Sym = -1
|
|
|
|
// doStackCheck walks the call tree to check that there is always
|
|
// enough stack space for call frames, especially for a chain of
|
|
// nosplit functions.
|
|
//
|
|
// It walks all functions to accumulate the number of bytes they can
|
|
// grow the stack by without a split check and checks this against the
|
|
// limit.
|
|
func (ctxt *Link) doStackCheck() {
|
|
sc := newStackCheck(ctxt, false)
|
|
|
|
// limit is number of bytes a splittable function ensures are
|
|
// available on the stack. If any call chain exceeds this
|
|
// depth, the stack check test fails.
|
|
//
|
|
// The call to morestack in every splittable function ensures
|
|
// that there are at least StackLimit bytes available below SP
|
|
// when morestack returns.
|
|
limit := objabi.StackNosplit(*flagRace) - sc.callSize
|
|
if buildcfg.GOARCH == "arm64" {
|
|
// Need an extra 8 bytes below SP to save FP.
|
|
limit -= 8
|
|
}
|
|
|
|
// Compute stack heights without any back-tracking information.
|
|
// This will almost certainly succeed and we can simply
|
|
// return. If it fails, we do a second pass with back-tracking
|
|
// to produce a good error message.
|
|
//
|
|
// This accumulates stack heights bottom-up so it only has to
|
|
// visit every function once.
|
|
var failed []loader.Sym
|
|
for _, s := range ctxt.Textp {
|
|
if sc.check(s) > limit {
|
|
failed = append(failed, s)
|
|
}
|
|
}
|
|
|
|
if len(failed) > 0 {
|
|
// Something was over-limit, so now we do the more
|
|
// expensive work to report a good error. First, for
|
|
// the over-limit functions, redo the stack check but
|
|
// record the graph this time.
|
|
sc = newStackCheck(ctxt, true)
|
|
for _, s := range failed {
|
|
sc.check(s)
|
|
}
|
|
|
|
// Find the roots of the graph (functions that are not
|
|
// called by any other function).
|
|
roots := sc.findRoots()
|
|
|
|
// Find and report all paths that go over the limit.
|
|
// This accumulates stack depths top-down. This is
|
|
// much less efficient because we may have to visit
|
|
// the same function multiple times at different
|
|
// depths, but lets us find all paths.
|
|
for _, root := range roots {
|
|
ctxt.Errorf(root, "nosplit stack over %d byte limit", limit)
|
|
chain := []stackCheckChain{{stackCheckEdge{0, root}, false}}
|
|
sc.report(root, limit, &chain)
|
|
}
|
|
}
|
|
}
|
|
|
|
func newStackCheck(ctxt *Link, graph bool) *stackCheck {
|
|
sc := &stackCheck{
|
|
ctxt: ctxt,
|
|
ldr: ctxt.loader,
|
|
morestack: ctxt.loader.Lookup("runtime.morestack", 0),
|
|
height: make(map[loader.Sym]int16, len(ctxt.Textp)),
|
|
}
|
|
// Compute stack effect of a CALL operation. 0 on LR machines.
|
|
// 1 register pushed on non-LR machines.
|
|
if !ctxt.Arch.HasLR {
|
|
sc.callSize = ctxt.Arch.RegSize
|
|
}
|
|
|
|
if graph {
|
|
// We're going to record the call graph.
|
|
sc.graph = make(map[loader.Sym][]stackCheckEdge)
|
|
}
|
|
|
|
return sc
|
|
}
|
|
|
|
func (sc *stackCheck) symName(sym loader.Sym) string {
|
|
switch sym {
|
|
case stackCheckIndirect:
|
|
return "indirect"
|
|
case 0:
|
|
return "leaf"
|
|
}
|
|
return fmt.Sprintf("%s<%d>", sc.ldr.SymName(sym), sc.ldr.SymVersion(sym))
|
|
}
|
|
|
|
// check returns the stack height of sym. It populates sc.height and
|
|
// sc.graph for sym and every function in its call tree.
|
|
func (sc *stackCheck) check(sym loader.Sym) int {
|
|
if h, ok := sc.height[sym]; ok {
|
|
// We've already visited this symbol or we're in a cycle.
|
|
return int(h)
|
|
}
|
|
// Store the sentinel so we can detect cycles.
|
|
sc.height[sym] = stackCheckCycle
|
|
// Compute and record the height and optionally edges.
|
|
h, edges := sc.computeHeight(sym, *flagDebugNosplit || sc.graph != nil)
|
|
if h > int(stackCheckCycle) { // Prevent integer overflow
|
|
h = int(stackCheckCycle)
|
|
}
|
|
sc.height[sym] = int16(h)
|
|
if sc.graph != nil {
|
|
sc.graph[sym] = edges
|
|
}
|
|
|
|
if *flagDebugNosplit {
|
|
for _, edge := range edges {
|
|
fmt.Printf("nosplit: %s +%d", sc.symName(sym), edge.growth)
|
|
if edge.target == 0 {
|
|
// Local stack growth or leaf function.
|
|
fmt.Printf("\n")
|
|
} else {
|
|
fmt.Printf(" -> %s\n", sc.symName(edge.target))
|
|
}
|
|
}
|
|
}
|
|
|
|
return h
|
|
}
|
|
|
|
// computeHeight returns the stack height of sym. If graph is true, it
|
|
// also returns the out-edges of sym.
|
|
//
|
|
// Caching is applied to this in check. Call check instead of calling
|
|
// this directly.
|
|
func (sc *stackCheck) computeHeight(sym loader.Sym, graph bool) (int, []stackCheckEdge) {
|
|
ldr := sc.ldr
|
|
|
|
// Check special cases.
|
|
if sym == sc.morestack {
|
|
// morestack looks like it calls functions, but they
|
|
// either happen only when already on the system stack
|
|
// (where there is ~infinite space), or after
|
|
// switching to the system stack. Hence, its stack
|
|
// height on the user stack is 0.
|
|
return 0, nil
|
|
}
|
|
if sym == stackCheckIndirect {
|
|
// Assume that indirect/closure calls are always to
|
|
// splittable functions, so they just need enough room
|
|
// to call morestack.
|
|
return sc.callSize, []stackCheckEdge{{sc.callSize, sc.morestack}}
|
|
}
|
|
|
|
// Ignore calls to external functions. Assume that these calls
|
|
// are only ever happening on the system stack, where there's
|
|
// plenty of room.
|
|
if ldr.AttrExternal(sym) {
|
|
return 0, nil
|
|
}
|
|
if info := ldr.FuncInfo(sym); !info.Valid() { // also external
|
|
return 0, nil
|
|
}
|
|
|
|
// Track the maximum height of this function and, if we're
|
|
// recording the graph, its out-edges.
|
|
var edges []stackCheckEdge
|
|
maxHeight := 0
|
|
ctxt := sc.ctxt
|
|
// addEdge adds a stack growth out of this function to
|
|
// function "target" or, if target == 0, a local stack growth
|
|
// within the function.
|
|
addEdge := func(growth int, target loader.Sym) {
|
|
if graph {
|
|
edges = append(edges, stackCheckEdge{growth, target})
|
|
}
|
|
height := growth
|
|
if target != 0 { // Don't walk into the leaf "edge"
|
|
height += sc.check(target)
|
|
}
|
|
if height > maxHeight {
|
|
maxHeight = height
|
|
}
|
|
}
|
|
|
|
if !ldr.IsNoSplit(sym) {
|
|
// Splittable functions start with a call to
|
|
// morestack, after which their height is 0. Account
|
|
// for the height of the call to morestack.
|
|
addEdge(sc.callSize, sc.morestack)
|
|
return maxHeight, edges
|
|
}
|
|
|
|
// This function is nosplit, so it adjusts SP without a split
|
|
// check.
|
|
//
|
|
// Walk through SP adjustments in function, consuming relocs
|
|
// and following calls.
|
|
maxLocalHeight := 0
|
|
relocs, ri := ldr.Relocs(sym), 0
|
|
pcsp := obj.NewPCIter(uint32(ctxt.Arch.MinLC))
|
|
for pcsp.Init(ldr.Data(ldr.Pcsp(sym))); !pcsp.Done; pcsp.Next() {
|
|
// pcsp.value is in effect for [pcsp.pc, pcsp.nextpc).
|
|
height := int(pcsp.Value)
|
|
if height > maxLocalHeight {
|
|
maxLocalHeight = height
|
|
}
|
|
|
|
// Process calls in this span.
|
|
for ; ri < relocs.Count(); ri++ {
|
|
r := relocs.At(ri)
|
|
if uint32(r.Off()) >= pcsp.NextPC {
|
|
break
|
|
}
|
|
t := r.Type()
|
|
if t.IsDirectCall() || t == objabi.R_CALLIND {
|
|
growth := height + sc.callSize
|
|
var target loader.Sym
|
|
if t == objabi.R_CALLIND {
|
|
target = stackCheckIndirect
|
|
} else {
|
|
target = r.Sym()
|
|
}
|
|
addEdge(growth, target)
|
|
}
|
|
}
|
|
}
|
|
if maxLocalHeight > maxHeight {
|
|
// This is either a leaf function, or the function
|
|
// grew its stack to larger than the maximum call
|
|
// height between calls. Either way, record that local
|
|
// stack growth.
|
|
addEdge(maxLocalHeight, 0)
|
|
}
|
|
|
|
return maxHeight, edges
|
|
}
|
|
|
|
func (sc *stackCheck) findRoots() []loader.Sym {
|
|
// Collect all nodes.
|
|
nodes := make(map[loader.Sym]struct{})
|
|
for k := range sc.graph {
|
|
nodes[k] = struct{}{}
|
|
}
|
|
|
|
// Start a DFS from each node and delete all reachable
|
|
// children. If we encounter an unrooted cycle, this will
|
|
// delete everything in that cycle, so we detect this case and
|
|
// track the lowest-numbered node encountered in the cycle and
|
|
// put that node back as a root.
|
|
var walk func(origin, sym loader.Sym) (cycle bool, lowest loader.Sym)
|
|
walk = func(origin, sym loader.Sym) (cycle bool, lowest loader.Sym) {
|
|
if _, ok := nodes[sym]; !ok {
|
|
// We already deleted this node.
|
|
return false, 0
|
|
}
|
|
delete(nodes, sym)
|
|
|
|
if origin == sym {
|
|
// We found an unrooted cycle. We already
|
|
// deleted all children of this node. Walk
|
|
// back up, tracking the lowest numbered
|
|
// symbol in this cycle.
|
|
return true, sym
|
|
}
|
|
|
|
// Delete children of this node.
|
|
for _, out := range sc.graph[sym] {
|
|
if c, l := walk(origin, out.target); c {
|
|
cycle = true
|
|
if lowest == 0 {
|
|
// On first cycle detection,
|
|
// add sym to the set of
|
|
// lowest-numbered candidates.
|
|
lowest = sym
|
|
}
|
|
if l < lowest {
|
|
lowest = l
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
for k := range nodes {
|
|
// Delete all children of k.
|
|
for _, out := range sc.graph[k] {
|
|
if cycle, lowest := walk(k, out.target); cycle {
|
|
// This is an unrooted cycle so we
|
|
// just deleted everything. Put back
|
|
// the lowest-numbered symbol.
|
|
nodes[lowest] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort roots by height. This makes the result deterministic
|
|
// and also improves the error reporting.
|
|
var roots []loader.Sym
|
|
for k := range nodes {
|
|
roots = append(roots, k)
|
|
}
|
|
sort.Slice(roots, func(i, j int) bool {
|
|
h1, h2 := sc.height[roots[i]], sc.height[roots[j]]
|
|
if h1 != h2 {
|
|
return h1 > h2
|
|
}
|
|
// Secondary sort by Sym.
|
|
return roots[i] < roots[j]
|
|
})
|
|
return roots
|
|
}
|
|
|
|
type stackCheckChain struct {
|
|
stackCheckEdge
|
|
printed bool
|
|
}
|
|
|
|
func (sc *stackCheck) report(sym loader.Sym, depth int, chain *[]stackCheckChain) {
|
|
// Walk the out-edges of sym. We temporarily pull the edges
|
|
// out of the graph to detect cycles and prevent infinite
|
|
// recursion.
|
|
edges, ok := sc.graph[sym]
|
|
isCycle := !(ok || sym == 0)
|
|
delete(sc.graph, sym)
|
|
for _, out := range edges {
|
|
*chain = append(*chain, stackCheckChain{out, false})
|
|
sc.report(out.target, depth-out.growth, chain)
|
|
*chain = (*chain)[:len(*chain)-1]
|
|
}
|
|
sc.graph[sym] = edges
|
|
|
|
// If we've reached the end of a chain and it went over the
|
|
// stack limit or was a cycle that would eventually go over,
|
|
// print the whole chain.
|
|
//
|
|
// We should either be in morestack (which has no out-edges)
|
|
// or the sentinel 0 Sym "called" from a leaf function (which
|
|
// has no out-edges), or we came back around a cycle (possibly
|
|
// to ourselves) and edges was temporarily nil'd.
|
|
if len(edges) == 0 && (depth < 0 || isCycle) {
|
|
var indent string
|
|
for i := range *chain {
|
|
ent := &(*chain)[i]
|
|
if ent.printed {
|
|
// Already printed on an earlier part
|
|
// of this call tree.
|
|
continue
|
|
}
|
|
ent.printed = true
|
|
|
|
if i == 0 {
|
|
// chain[0] is just the root function,
|
|
// not a stack growth.
|
|
fmt.Printf("%s\n", sc.symName(ent.target))
|
|
continue
|
|
}
|
|
|
|
indent = strings.Repeat(" ", i)
|
|
fmt.Print(indent)
|
|
// Grows the stack X bytes and (maybe) calls Y.
|
|
fmt.Printf("grows %d bytes", ent.growth)
|
|
if ent.target == 0 {
|
|
// Not a call, just a leaf. Print nothing.
|
|
} else {
|
|
fmt.Printf(", calls %s", sc.symName(ent.target))
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
// Print how far over this chain went.
|
|
if isCycle {
|
|
fmt.Printf("%sinfinite cycle\n", indent)
|
|
} else {
|
|
fmt.Printf("%s%d bytes over limit\n", indent, -depth)
|
|
}
|
|
}
|
|
}
|