"bytes"
"cmd/internal/bio"
"cmd/internal/goobj"
- "cmd/internal/obj"
"cmd/internal/objabi"
"cmd/internal/sys"
"cmd/link/internal/loadelf"
return sect
}
-type chain struct {
- sym loader.Sym
- up *chain
- limit int // limit on entry to sym
-}
-
-func callsize(ctxt *Link) int {
- if ctxt.Arch.HasLR {
- return 0
- }
- return ctxt.Arch.RegSize
-}
-
-type stkChk struct {
- ldr *loader.Loader
- ctxt *Link
- morestack loader.Sym
- done loader.Bitmap
-}
-
-// Walk the call tree and check that there is always enough stack space
-// for the call frames, especially for a chain of nosplit functions.
-func (ctxt *Link) dostkcheck() {
- ldr := ctxt.loader
- sc := stkChk{
- ldr: ldr,
- ctxt: ctxt,
- morestack: ldr.Lookup("runtime.morestack", 0),
- done: loader.MakeBitmap(ldr.NSym()),
- }
-
- // Every splitting function ensures that there are at least StackLimit
- // bytes available below SP when the splitting prologue finishes.
- // If the splitting function calls F, then F begins execution with
- // at least StackLimit - callsize() bytes available.
- // Check that every function behaves correctly with this amount
- // of stack, following direct calls in order to piece together chains
- // of non-splitting functions.
- var ch chain
- ch.limit = objabi.StackLimit - callsize(ctxt)
- if buildcfg.GOARCH == "arm64" {
- // need extra 8 bytes below SP to save FP
- ch.limit -= 8
- }
-
- // Check every function, but do the nosplit functions in a first pass,
- // to make the printed failure chains as short as possible.
- for _, s := range ctxt.Textp {
- if ldr.IsNoSplit(s) {
- ch.sym = s
- sc.check(&ch, 0)
- }
- }
-
- for _, s := range ctxt.Textp {
- if !ldr.IsNoSplit(s) {
- ch.sym = s
- sc.check(&ch, 0)
- }
- }
-}
-
-func (sc *stkChk) check(up *chain, depth int) int {
- limit := up.limit
- s := up.sym
- ldr := sc.ldr
- ctxt := sc.ctxt
-
- // Don't duplicate work: only need to consider each
- // function at top of safe zone once.
- top := limit == objabi.StackLimit-callsize(ctxt)
- if top {
- if sc.done.Has(s) {
- return 0
- }
- sc.done.Set(s)
- }
-
- if depth > 500 {
- sc.ctxt.Errorf(s, "nosplit stack check too deep")
- sc.broke(up, 0)
- return -1
- }
-
- if ldr.AttrExternal(s) {
- // external function.
- // should never be called directly.
- // onlyctxt.Diagnose the direct caller.
- // TODO(mwhudson): actually think about this.
- // TODO(khr): disabled for now. Calls to external functions can only happen on the g0 stack.
- // See the trampolines in src/runtime/sys_darwin_$ARCH.go.
- //if depth == 1 && ldr.SymType(s) != sym.SXREF && !ctxt.DynlinkingGo() &&
- // ctxt.BuildMode != BuildModeCArchive && ctxt.BuildMode != BuildModePIE && ctxt.BuildMode != BuildModeCShared && ctxt.BuildMode != BuildModePlugin {
- // Errorf(s, "call to external function")
- //}
- return -1
- }
- info := ldr.FuncInfo(s)
- if !info.Valid() { // external function. see above.
- return -1
- }
-
- if limit < 0 {
- sc.broke(up, limit)
- return -1
- }
-
- // morestack looks like it calls functions,
- // but it switches the stack pointer first.
- if s == sc.morestack {
- return 0
- }
-
- var ch chain
- ch.up = up
-
- if !ldr.IsNoSplit(s) {
- // Ensure we have enough stack to call morestack.
- ch.limit = limit - callsize(ctxt)
- ch.sym = sc.morestack
- if sc.check(&ch, depth+1) < 0 {
- return -1
- }
- if !top {
- return 0
- }
- // Raise limit to allow frame.
- locals := info.Locals()
- limit = objabi.StackLimit + int(locals) + int(ctxt.Arch.FixedFrameSize)
- }
-
- // Walk through sp adjustments in function, consuming relocs.
- relocs := ldr.Relocs(s)
- var ch1 chain
- pcsp := obj.NewPCIter(uint32(ctxt.Arch.MinLC))
- ri := 0
- for pcsp.Init(ldr.Data(ldr.Pcsp(s))); !pcsp.Done; pcsp.Next() {
- // pcsp.value is in effect for [pcsp.pc, pcsp.nextpc).
-
- // Check stack size in effect for this span.
- if int32(limit)-pcsp.Value < 0 {
- sc.broke(up, int(int32(limit)-pcsp.Value))
- return -1
- }
-
- // Process calls in this span.
- for ; ri < relocs.Count(); ri++ {
- r := relocs.At(ri)
- if uint32(r.Off()) >= pcsp.NextPC {
- break
- }
- t := r.Type()
- switch {
- case t.IsDirectCall():
- ch.limit = int(int32(limit) - pcsp.Value - int32(callsize(ctxt)))
- ch.sym = r.Sym()
- if sc.check(&ch, depth+1) < 0 {
- return -1
- }
-
- // Indirect call. Assume it is a call to a splitting function,
- // so we have to make sure it can call morestack.
- // Arrange the data structures to report both calls, so that
- // if there is an error, stkprint shows all the steps involved.
- case t == objabi.R_CALLIND:
- ch.limit = int(int32(limit) - pcsp.Value - int32(callsize(ctxt)))
- ch.sym = 0
- ch1.limit = ch.limit - callsize(ctxt) // for morestack in called prologue
- ch1.up = &ch
- ch1.sym = sc.morestack
- if sc.check(&ch1, depth+2) < 0 {
- return -1
- }
- }
- }
- }
-
- return 0
-}
-
-func (sc *stkChk) broke(ch *chain, limit int) {
- sc.ctxt.Errorf(ch.sym, "nosplit stack overflow")
- sc.print(ch, limit)
-}
-
-func (sc *stkChk) print(ch *chain, limit int) {
- ldr := sc.ldr
- ctxt := sc.ctxt
- var name string
- if ch.sym != 0 {
- name = fmt.Sprintf("%s<%d>", ldr.SymName(ch.sym), ldr.SymVersion(ch.sym))
- if ldr.IsNoSplit(ch.sym) {
- name += " (nosplit)"
- }
- } else {
- name = "function pointer"
- }
-
- if ch.up == nil {
- // top of chain. ch.sym != 0.
- if ldr.IsNoSplit(ch.sym) {
- fmt.Printf("\t%d\tassumed on entry to %s\n", ch.limit, name)
- } else {
- fmt.Printf("\t%d\tguaranteed after split check in %s\n", ch.limit, name)
- }
- } else {
- sc.print(ch.up, ch.limit+callsize(ctxt))
- if !ctxt.Arch.HasLR {
- fmt.Printf("\t%d\ton entry to %s\n", ch.limit, name)
- }
- }
-
- if ch.limit != limit {
- fmt.Printf("\t%d\tafter %s uses %d\n", limit, name, ch.limit-limit)
- }
-}
-
func usage() {
fmt.Fprintf(os.Stderr, "usage: link [options] main.o\n")
objabi.Flagprint(os.Stderr)
--- /dev/null
+// 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.StackLimit - 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 overflow")
+ 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)
+ }
+ }
+}