github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/link/ld/stackcheck.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ld 6 7 import ( 8 "fmt" 9 "sort" 10 "strings" 11 12 "github.com/go-asm/go/buildcfg" 13 "github.com/go-asm/go/cmd/link/loader" 14 "github.com/go-asm/go/cmd/obj" 15 "github.com/go-asm/go/cmd/objabi" 16 ) 17 18 type stackCheck struct { 19 ctxt *Link 20 ldr *loader.Loader 21 morestack loader.Sym 22 callSize int // The number of bytes added by a CALL 23 24 // height records the maximum number of bytes a function and 25 // its callees can add to the stack without a split check. 26 height map[loader.Sym]int16 27 28 // graph records the out-edges from each symbol. This is only 29 // populated on a second pass if the first pass reveals an 30 // over-limit function. 31 graph map[loader.Sym][]stackCheckEdge 32 } 33 34 type stackCheckEdge struct { 35 growth int // Stack growth in bytes at call to target 36 target loader.Sym // 0 for stack growth without a call 37 } 38 39 // stackCheckCycle is a sentinel stored in the height map to detect if 40 // we've found a cycle. This is effectively an "infinite" stack 41 // height, so we use the closest value to infinity that we can. 42 const stackCheckCycle int16 = 1<<15 - 1 43 44 // stackCheckIndirect is a sentinel Sym value used to represent the 45 // target of an indirect/closure call. 46 const stackCheckIndirect loader.Sym = ^loader.Sym(0) 47 48 // doStackCheck walks the call tree to check that there is always 49 // enough stack space for call frames, especially for a chain of 50 // nosplit functions. 51 // 52 // It walks all functions to accumulate the number of bytes they can 53 // grow the stack by without a split check and checks this against the 54 // limit. 55 func (ctxt *Link) doStackCheck() { 56 sc := newStackCheck(ctxt, false) 57 58 // limit is number of bytes a splittable function ensures are 59 // available on the stack. If any call chain exceeds this 60 // depth, the stack check test fails. 61 // 62 // The call to morestack in every splittable function ensures 63 // that there are at least StackLimit bytes available below SP 64 // when morestack returns. 65 limit := objabi.StackNosplit(*flagRace) - sc.callSize 66 if buildcfg.GOARCH == "arm64" { 67 // Need an extra 8 bytes below SP to save FP. 68 limit -= 8 69 } 70 71 // Compute stack heights without any back-tracking information. 72 // This will almost certainly succeed and we can simply 73 // return. If it fails, we do a second pass with back-tracking 74 // to produce a good error message. 75 // 76 // This accumulates stack heights bottom-up so it only has to 77 // visit every function once. 78 var failed []loader.Sym 79 for _, s := range ctxt.Textp { 80 if sc.check(s) > limit { 81 failed = append(failed, s) 82 } 83 } 84 85 if len(failed) > 0 { 86 // Something was over-limit, so now we do the more 87 // expensive work to report a good error. First, for 88 // the over-limit functions, redo the stack check but 89 // record the graph this time. 90 sc = newStackCheck(ctxt, true) 91 for _, s := range failed { 92 sc.check(s) 93 } 94 95 // Find the roots of the graph (functions that are not 96 // called by any other function). 97 roots := sc.findRoots() 98 99 // Find and report all paths that go over the limit. 100 // This accumulates stack depths top-down. This is 101 // much less efficient because we may have to visit 102 // the same function multiple times at different 103 // depths, but lets us find all paths. 104 for _, root := range roots { 105 ctxt.Errorf(root, "nosplit stack over %d byte limit", limit) 106 chain := []stackCheckChain{{stackCheckEdge{0, root}, false}} 107 sc.report(root, limit, &chain) 108 } 109 } 110 } 111 112 func newStackCheck(ctxt *Link, graph bool) *stackCheck { 113 sc := &stackCheck{ 114 ctxt: ctxt, 115 ldr: ctxt.loader, 116 morestack: ctxt.loader.Lookup("runtime.morestack", 0), 117 height: make(map[loader.Sym]int16, len(ctxt.Textp)), 118 } 119 // Compute stack effect of a CALL operation. 0 on LR machines. 120 // 1 register pushed on non-LR machines. 121 if !ctxt.Arch.HasLR { 122 sc.callSize = ctxt.Arch.RegSize 123 } 124 125 if graph { 126 // We're going to record the call graph. 127 sc.graph = make(map[loader.Sym][]stackCheckEdge) 128 } 129 130 return sc 131 } 132 133 func (sc *stackCheck) symName(sym loader.Sym) string { 134 switch sym { 135 case stackCheckIndirect: 136 return "indirect" 137 case 0: 138 return "leaf" 139 } 140 return fmt.Sprintf("%s<%d>", sc.ldr.SymName(sym), sc.ldr.SymVersion(sym)) 141 } 142 143 // check returns the stack height of sym. It populates sc.height and 144 // sc.graph for sym and every function in its call tree. 145 func (sc *stackCheck) check(sym loader.Sym) int { 146 if h, ok := sc.height[sym]; ok { 147 // We've already visited this symbol or we're in a cycle. 148 return int(h) 149 } 150 // Store the sentinel so we can detect cycles. 151 sc.height[sym] = stackCheckCycle 152 // Compute and record the height and optionally edges. 153 h, edges := sc.computeHeight(sym, *flagDebugNosplit || sc.graph != nil) 154 if h > int(stackCheckCycle) { // Prevent integer overflow 155 h = int(stackCheckCycle) 156 } 157 sc.height[sym] = int16(h) 158 if sc.graph != nil { 159 sc.graph[sym] = edges 160 } 161 162 if *flagDebugNosplit { 163 for _, edge := range edges { 164 fmt.Printf("nosplit: %s +%d", sc.symName(sym), edge.growth) 165 if edge.target == 0 { 166 // Local stack growth or leaf function. 167 fmt.Printf("\n") 168 } else { 169 fmt.Printf(" -> %s\n", sc.symName(edge.target)) 170 } 171 } 172 } 173 174 return h 175 } 176 177 // computeHeight returns the stack height of sym. If graph is true, it 178 // also returns the out-edges of sym. 179 // 180 // Caching is applied to this in check. Call check instead of calling 181 // this directly. 182 func (sc *stackCheck) computeHeight(sym loader.Sym, graph bool) (int, []stackCheckEdge) { 183 ldr := sc.ldr 184 185 // Check special cases. 186 if sym == sc.morestack { 187 // morestack looks like it calls functions, but they 188 // either happen only when already on the system stack 189 // (where there is ~infinite space), or after 190 // switching to the system stack. Hence, its stack 191 // height on the user stack is 0. 192 return 0, nil 193 } 194 if sym == stackCheckIndirect { 195 // Assume that indirect/closure calls are always to 196 // splittable functions, so they just need enough room 197 // to call morestack. 198 return sc.callSize, []stackCheckEdge{{sc.callSize, sc.morestack}} 199 } 200 201 // Ignore calls to external functions. Assume that these calls 202 // are only ever happening on the system stack, where there's 203 // plenty of room. 204 if ldr.AttrExternal(sym) { 205 return 0, nil 206 } 207 if info := ldr.FuncInfo(sym); !info.Valid() { // also external 208 return 0, nil 209 } 210 211 // Track the maximum height of this function and, if we're 212 // recording the graph, its out-edges. 213 var edges []stackCheckEdge 214 maxHeight := 0 215 ctxt := sc.ctxt 216 // addEdge adds a stack growth out of this function to 217 // function "target" or, if target == 0, a local stack growth 218 // within the function. 219 addEdge := func(growth int, target loader.Sym) { 220 if graph { 221 edges = append(edges, stackCheckEdge{growth, target}) 222 } 223 height := growth 224 if target != 0 { // Don't walk into the leaf "edge" 225 height += sc.check(target) 226 } 227 if height > maxHeight { 228 maxHeight = height 229 } 230 } 231 232 if !ldr.IsNoSplit(sym) { 233 // Splittable functions start with a call to 234 // morestack, after which their height is 0. Account 235 // for the height of the call to morestack. 236 addEdge(sc.callSize, sc.morestack) 237 return maxHeight, edges 238 } 239 240 // This function is nosplit, so it adjusts SP without a split 241 // check. 242 // 243 // Walk through SP adjustments in function, consuming relocs 244 // and following calls. 245 maxLocalHeight := 0 246 relocs, ri := ldr.Relocs(sym), 0 247 pcsp := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) 248 for pcsp.Init(ldr.Data(ldr.Pcsp(sym))); !pcsp.Done; pcsp.Next() { 249 // pcsp.value is in effect for [pcsp.pc, pcsp.nextpc). 250 height := int(pcsp.Value) 251 if height > maxLocalHeight { 252 maxLocalHeight = height 253 } 254 255 // Process calls in this span. 256 for ; ri < relocs.Count(); ri++ { 257 r := relocs.At(ri) 258 if uint32(r.Off()) >= pcsp.NextPC { 259 break 260 } 261 t := r.Type() 262 if t.IsDirectCall() || t == objabi.R_CALLIND { 263 growth := height + sc.callSize 264 var target loader.Sym 265 if t == objabi.R_CALLIND { 266 target = stackCheckIndirect 267 } else { 268 target = r.Sym() 269 } 270 addEdge(growth, target) 271 } 272 } 273 } 274 if maxLocalHeight > maxHeight { 275 // This is either a leaf function, or the function 276 // grew its stack to larger than the maximum call 277 // height between calls. Either way, record that local 278 // stack growth. 279 addEdge(maxLocalHeight, 0) 280 } 281 282 return maxHeight, edges 283 } 284 285 func (sc *stackCheck) findRoots() []loader.Sym { 286 // Collect all nodes. 287 nodes := make(map[loader.Sym]struct{}) 288 for k := range sc.graph { 289 nodes[k] = struct{}{} 290 } 291 292 // Start a DFS from each node and delete all reachable 293 // children. If we encounter an unrooted cycle, this will 294 // delete everything in that cycle, so we detect this case and 295 // track the lowest-numbered node encountered in the cycle and 296 // put that node back as a root. 297 var walk func(origin, sym loader.Sym) (cycle bool, lowest loader.Sym) 298 walk = func(origin, sym loader.Sym) (cycle bool, lowest loader.Sym) { 299 if _, ok := nodes[sym]; !ok { 300 // We already deleted this node. 301 return false, 0 302 } 303 delete(nodes, sym) 304 305 if origin == sym { 306 // We found an unrooted cycle. We already 307 // deleted all children of this node. Walk 308 // back up, tracking the lowest numbered 309 // symbol in this cycle. 310 return true, sym 311 } 312 313 // Delete children of this node. 314 for _, out := range sc.graph[sym] { 315 if c, l := walk(origin, out.target); c { 316 cycle = true 317 if lowest == 0 { 318 // On first cycle detection, 319 // add sym to the set of 320 // lowest-numbered candidates. 321 lowest = sym 322 } 323 if l < lowest { 324 lowest = l 325 } 326 } 327 } 328 return 329 } 330 for k := range nodes { 331 // Delete all children of k. 332 for _, out := range sc.graph[k] { 333 if cycle, lowest := walk(k, out.target); cycle { 334 // This is an unrooted cycle so we 335 // just deleted everything. Put back 336 // the lowest-numbered symbol. 337 nodes[lowest] = struct{}{} 338 } 339 } 340 } 341 342 // Sort roots by height. This makes the result deterministic 343 // and also improves the error reporting. 344 var roots []loader.Sym 345 for k := range nodes { 346 roots = append(roots, k) 347 } 348 sort.Slice(roots, func(i, j int) bool { 349 h1, h2 := sc.height[roots[i]], sc.height[roots[j]] 350 if h1 != h2 { 351 return h1 > h2 352 } 353 // Secondary sort by Sym. 354 return roots[i] < roots[j] 355 }) 356 return roots 357 } 358 359 type stackCheckChain struct { 360 stackCheckEdge 361 printed bool 362 } 363 364 func (sc *stackCheck) report(sym loader.Sym, depth int, chain *[]stackCheckChain) { 365 // Walk the out-edges of sym. We temporarily pull the edges 366 // out of the graph to detect cycles and prevent infinite 367 // recursion. 368 edges, ok := sc.graph[sym] 369 isCycle := !(ok || sym == 0) 370 delete(sc.graph, sym) 371 for _, out := range edges { 372 *chain = append(*chain, stackCheckChain{out, false}) 373 sc.report(out.target, depth-out.growth, chain) 374 *chain = (*chain)[:len(*chain)-1] 375 } 376 sc.graph[sym] = edges 377 378 // If we've reached the end of a chain and it went over the 379 // stack limit or was a cycle that would eventually go over, 380 // print the whole chain. 381 // 382 // We should either be in morestack (which has no out-edges) 383 // or the sentinel 0 Sym "called" from a leaf function (which 384 // has no out-edges), or we came back around a cycle (possibly 385 // to ourselves) and edges was temporarily nil'd. 386 if len(edges) == 0 && (depth < 0 || isCycle) { 387 var indent string 388 for i := range *chain { 389 ent := &(*chain)[i] 390 if ent.printed { 391 // Already printed on an earlier part 392 // of this call tree. 393 continue 394 } 395 ent.printed = true 396 397 if i == 0 { 398 // chain[0] is just the root function, 399 // not a stack growth. 400 fmt.Printf("%s\n", sc.symName(ent.target)) 401 continue 402 } 403 404 indent = strings.Repeat(" ", i) 405 fmt.Print(indent) 406 // Grows the stack X bytes and (maybe) calls Y. 407 fmt.Printf("grows %d bytes", ent.growth) 408 if ent.target == 0 { 409 // Not a call, just a leaf. Print nothing. 410 } else { 411 fmt.Printf(", calls %s", sc.symName(ent.target)) 412 } 413 fmt.Printf("\n") 414 } 415 // Print how far over this chain went. 416 if isCycle { 417 fmt.Printf("%sinfinite cycle\n", indent) 418 } else { 419 fmt.Printf("%s%d bytes over limit\n", indent, -depth) 420 } 421 } 422 }