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