1// run 2 3//go:build !nacl && !js && !aix && !wasip1 && !gcflags_noopt && gc 4 5// Copyright 2014 The Go Authors. All rights reserved. 6// Use of this source code is governed by a BSD-style 7// license that can be found in the LICENSE file. 8 9package main 10 11import ( 12 "bytes" 13 "fmt" 14 "io/ioutil" 15 "log" 16 "os" 17 "os/exec" 18 "path/filepath" 19 "regexp" 20 "runtime" 21 "strconv" 22 "strings" 23) 24 25const debug = false 26 27var tests = ` 28# These are test cases for the linker analysis that detects chains of 29# nosplit functions that would cause a stack overflow. 30# 31# Lines beginning with # are comments. 32# 33# Each test case describes a sequence of functions, one per line. 34# Each function definition is the function name, then the frame size, 35# then optionally the keyword 'nosplit', then the body of the function. 36# The body is assembly code, with some shorthands. 37# The shorthand 'call x' stands for CALL x(SB). 38# The shorthand 'callind' stands for 'CALL R0', where R0 is a register. 39# Each test case must define a function named start, and it must be first. 40# That is, a line beginning "start " indicates the start of a new test case. 41# Within a stanza, ; can be used instead of \n to separate lines. 42# 43# After the function definition, the test case ends with an optional 44# REJECT line, specifying the architectures on which the case should 45# be rejected. "REJECT" without any architectures means reject on all architectures. 46# The linker should accept the test case on systems not explicitly rejected. 47# 48# 64-bit systems do not attempt to execute test cases with frame sizes 49# that are only 32-bit aligned. 50 51# Ordinary function should work 52start 0 53 54# Large frame marked nosplit is always wrong. 55# Frame is so large it overflows cmd/link's int16. 56start 100000 nosplit 57REJECT 58 59# Calling a large frame is okay. 60start 0 call big 61big 10000 62 63# But not if the frame is nosplit. 64start 0 call big 65big 10000 nosplit 66REJECT 67 68# Recursion is okay. 69start 0 call start 70 71# Recursive nosplit runs out of space. 72start 0 nosplit call start 73REJECT 74 75# Non-trivial recursion runs out of space. 76start 0 call f1 77f1 0 nosplit call f2 78f2 0 nosplit call f1 79REJECT 80# Same but cycle starts below nosplit entry. 81start 0 call f1 82f1 0 nosplit call f2 83f2 0 nosplit call f3 84f3 0 nosplit call f2 85REJECT 86 87# Chains of ordinary functions okay. 88start 0 call f1 89f1 80 call f2 90f2 80 91 92# Chains of nosplit must fit in the stack limit, 128 bytes. 93start 0 call f1 94f1 80 nosplit call f2 95f2 80 nosplit 96REJECT 97 98# Larger chains. 99start 0 call f1 100f1 16 call f2 101f2 16 call f3 102f3 16 call f4 103f4 16 call f5 104f5 16 call f6 105f6 16 call f7 106f7 16 call f8 107f8 16 call end 108end 1000 109 110start 0 call f1 111f1 16 nosplit call f2 112f2 16 nosplit call f3 113f3 16 nosplit call f4 114f4 16 nosplit call f5 115f5 16 nosplit call f6 116f6 16 nosplit call f7 117f7 16 nosplit call f8 118f8 16 nosplit call end 119end 1000 120REJECT 121 122# Two paths both go over the stack limit. 123start 0 call f1 124f1 80 nosplit call f2 call f3 125f2 40 nosplit call f4 126f3 96 nosplit 127f4 40 nosplit 128REJECT 129 130# Test cases near the 128-byte limit. 131 132# Ordinary stack split frame is always okay. 133start 112 134start 116 135start 120 136start 124 137start 128 138start 132 139start 136 140 141# A nosplit leaf can use the whole 128-CallSize bytes available on entry. 142# (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.) 143start 96 nosplit 144start 100 nosplit; REJECT ppc64 ppc64le 145start 104 nosplit; REJECT ppc64 ppc64le arm64 146start 108 nosplit; REJECT ppc64 ppc64le 147start 112 nosplit; REJECT ppc64 ppc64le arm64 148start 116 nosplit; REJECT ppc64 ppc64le 149start 120 nosplit; REJECT ppc64 ppc64le amd64 arm64 150start 124 nosplit; REJECT ppc64 ppc64le amd64 151start 128 nosplit; REJECT 152start 132 nosplit; REJECT 153start 136 nosplit; REJECT 154 155# Calling a nosplit function from a nosplit function requires 156# having room for the saved caller PC and the called frame. 157# Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes. 158# Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes. 159# ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 bytes. 160# Because AMD64 uses frame pointer, it has 8 fewer bytes. 161start 96 nosplit call f; f 0 nosplit 162start 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le 163start 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le arm64 164start 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le 165start 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64 166start 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 167start 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64 168start 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386 169start 128 nosplit call f; f 0 nosplit; REJECT 170start 132 nosplit call f; f 0 nosplit; REJECT 171start 136 nosplit call f; f 0 nosplit; REJECT 172 173# Calling a splitting function from a nosplit function requires 174# having room for the saved caller PC of the call but also the 175# saved caller PC for the call to morestack. 176# Architectures differ in the same way as before. 177start 96 nosplit call f; f 0 call f 178start 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le 179start 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64 180start 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 181start 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64 182start 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 183start 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386 arm64 184start 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386 185start 128 nosplit call f; f 0 call f; REJECT 186start 132 nosplit call f; f 0 call f; REJECT 187start 136 nosplit call f; f 0 call f; REJECT 188 189# Indirect calls are assumed to be splitting functions. 190start 96 nosplit callind 191start 100 nosplit callind; REJECT ppc64 ppc64le 192start 104 nosplit callind; REJECT ppc64 ppc64le amd64 arm64 193start 108 nosplit callind; REJECT ppc64 ppc64le amd64 194start 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64 195start 116 nosplit callind; REJECT ppc64 ppc64le amd64 196start 120 nosplit callind; REJECT ppc64 ppc64le amd64 386 arm64 197start 124 nosplit callind; REJECT ppc64 ppc64le amd64 386 198start 128 nosplit callind; REJECT 199start 132 nosplit callind; REJECT 200start 136 nosplit callind; REJECT 201 202# Issue 7623 203start 0 call f; f 112 204start 0 call f; f 116 205start 0 call f; f 120 206start 0 call f; f 124 207start 0 call f; f 128 208start 0 call f; f 132 209start 0 call f; f 136 210` 211 212var ( 213 commentRE = regexp.MustCompile(`(?m)^#.*`) 214 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`) 215 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`) 216 callRE = regexp.MustCompile(`\bcall (\w+)\b`) 217 callindRE = regexp.MustCompile(`\bcallind\b`) 218) 219 220func main() { 221 goarch := os.Getenv("GOARCH") 222 if goarch == "" { 223 goarch = runtime.GOARCH 224 } 225 226 dir, err := ioutil.TempDir("", "go-test-nosplit") 227 if err != nil { 228 bug() 229 fmt.Printf("creating temp dir: %v\n", err) 230 return 231 } 232 defer os.RemoveAll(dir) 233 os.Setenv("GOPATH", filepath.Join(dir, "_gopath")) 234 235 if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module go-test-nosplit\n"), 0666); err != nil { 236 log.Panic(err) 237 } 238 239 tests = strings.Replace(tests, "\t", " ", -1) 240 tests = commentRE.ReplaceAllString(tests, "") 241 242 nok := 0 243 nfail := 0 244TestCases: 245 for len(tests) > 0 { 246 var stanza string 247 i := strings.Index(tests, "\nstart ") 248 if i < 0 { 249 stanza, tests = tests, "" 250 } else { 251 stanza, tests = tests[:i], tests[i+1:] 252 } 253 254 m := rejectRE.FindStringSubmatch(stanza) 255 if m == nil { 256 bug() 257 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza)) 258 continue 259 } 260 lines := strings.TrimSpace(m[1]) 261 reject := false 262 if m[2] != "" { 263 if strings.TrimSpace(m[4]) == "" { 264 reject = true 265 } else { 266 for _, rej := range strings.Fields(m[4]) { 267 if rej == goarch { 268 reject = true 269 } 270 } 271 } 272 } 273 if lines == "" && !reject { 274 continue 275 } 276 277 var gobuf bytes.Buffer 278 fmt.Fprintf(&gobuf, "package main\n") 279 280 var buf bytes.Buffer 281 ptrSize := 4 282 switch goarch { 283 case "mips", "mipsle": 284 fmt.Fprintf(&buf, "#define REGISTER (R0)\n") 285 case "mips64", "mips64le": 286 ptrSize = 8 287 fmt.Fprintf(&buf, "#define REGISTER (R0)\n") 288 case "loong64": 289 ptrSize = 8 290 fmt.Fprintf(&buf, "#define REGISTER (R0)\n") 291 case "ppc64", "ppc64le": 292 ptrSize = 8 293 fmt.Fprintf(&buf, "#define REGISTER (CTR)\n") 294 case "arm": 295 fmt.Fprintf(&buf, "#define REGISTER (R0)\n") 296 case "arm64": 297 ptrSize = 8 298 fmt.Fprintf(&buf, "#define REGISTER (R0)\n") 299 case "amd64": 300 ptrSize = 8 301 fmt.Fprintf(&buf, "#define REGISTER AX\n") 302 case "riscv64": 303 ptrSize = 8 304 fmt.Fprintf(&buf, "#define REGISTER A0\n") 305 case "s390x": 306 ptrSize = 8 307 fmt.Fprintf(&buf, "#define REGISTER R10\n") 308 default: 309 fmt.Fprintf(&buf, "#define REGISTER AX\n") 310 } 311 312 // Since all of the functions we're generating are 313 // ABI0, first enter ABI0 via a splittable function 314 // and then go to the chain we're testing. This way we 315 // don't have to account for ABI wrappers in the chain. 316 fmt.Fprintf(&gobuf, "func main0()\n") 317 fmt.Fprintf(&gobuf, "func main() { main0() }\n") 318 fmt.Fprintf(&buf, "TEXT ·main0(SB),0,$0-0\n\tCALL ·start(SB)\n") 319 320 adjusted := false 321 for _, line := range strings.Split(lines, "\n") { 322 line = strings.TrimSpace(line) 323 if line == "" { 324 continue 325 } 326 for _, subline := range strings.Split(line, ";") { 327 subline = strings.TrimSpace(subline) 328 if subline == "" { 329 continue 330 } 331 m := lineRE.FindStringSubmatch(subline) 332 if m == nil { 333 bug() 334 fmt.Printf("invalid function line: %s\n", subline) 335 continue TestCases 336 } 337 name := m[1] 338 size, _ := strconv.Atoi(m[2]) 339 340 if size%ptrSize == 4 { 341 continue TestCases 342 } 343 nosplit := m[3] 344 body := m[4] 345 346 // The limit was originally 128 but is now 800. 347 // Instead of rewriting the test cases above, adjust 348 // the first nosplit frame to use up the extra bytes. 349 // This isn't exactly right because we could have 350 // nosplit -> split -> nosplit, but it's good enough. 351 if !adjusted && nosplit != "" { 352 const stackNosplitBase = 800 // internal/abi.StackNosplitBase 353 adjusted = true 354 size += stackNosplitBase - 128 355 } 356 357 if nosplit != "" { 358 nosplit = ",7" 359 } else { 360 nosplit = ",0" 361 } 362 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);") 363 body = callindRE.ReplaceAllString(body, "CALL REGISTER;") 364 365 fmt.Fprintf(&gobuf, "func %s()\n", name) 366 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body) 367 } 368 } 369 370 if debug { 371 fmt.Printf("===\n%s\n", strings.TrimSpace(stanza)) 372 fmt.Printf("-- main.go --\n%s", gobuf.String()) 373 fmt.Printf("-- asm.s --\n%s", buf.String()) 374 } 375 376 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil { 377 log.Fatal(err) 378 } 379 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil { 380 log.Fatal(err) 381 } 382 383 cmd := exec.Command("go", "build") 384 cmd.Dir = dir 385 output, err := cmd.CombinedOutput() 386 if err == nil { 387 nok++ 388 if reject { 389 bug() 390 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza))) 391 } 392 } else { 393 nfail++ 394 if !reject { 395 bug() 396 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza))) 397 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output))) 398 } 399 } 400 } 401 402 if !bugged && (nok == 0 || nfail == 0) { 403 bug() 404 fmt.Printf("not enough test cases run\n") 405 } 406} 407 408func indent(s string) string { 409 return strings.Replace(s, "\n", "\n\t", -1) 410} 411 412var bugged = false 413 414func bug() { 415 if !bugged { 416 bugged = true 417 fmt.Printf("BUG\n") 418 } 419} 420