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 main 112 nosplit 119 main 116 nosplit 120 main 120 nosplit 121 main 124 nosplit 122 main 128 nosplit; REJECT 123 main 132 nosplit; REJECT 124 main 136 nosplit; REJECT 125 126 # Calling a nosplit function from a nosplit function requires 127 # having room for the saved caller PC and the called frame. 128 # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes. 129 # Because ppc64 doesn't save LR in the leaf, it gets an extra 8 bytes. 130 main 112 nosplit call f; f 0 nosplit 131 main 116 nosplit call f; f 0 nosplit 132 main 120 nosplit call f; f 0 nosplit; REJECT amd64 133 main 124 nosplit call f; f 0 nosplit; REJECT amd64 386 134 main 128 nosplit call f; f 0 nosplit; REJECT 135 main 132 nosplit call f; f 0 nosplit; REJECT 136 main 136 nosplit call f; f 0 nosplit; REJECT 137 138 # Calling a splitting function from a nosplit function requires 139 # having room for the saved caller PC of the call but also the 140 # saved caller PC for the call to morestack. 141 # Again the ARM and ppc64 work in less space. 142 main 104 nosplit call f; f 0 call f 143 main 108 nosplit call f; f 0 call f 144 main 112 nosplit call f; f 0 call f; REJECT amd64 145 main 116 nosplit call f; f 0 call f; REJECT amd64 146 main 120 nosplit call f; f 0 call f; REJECT amd64 386 147 main 124 nosplit call f; f 0 call f; REJECT amd64 386 148 main 128 nosplit call f; f 0 call f; REJECT 149 main 132 nosplit call f; f 0 call f; REJECT 150 main 136 nosplit call f; f 0 call f; REJECT 151 152 # Indirect calls are assumed to be splitting functions. 153 main 104 nosplit callind 154 main 108 nosplit callind 155 main 112 nosplit callind; REJECT amd64 156 main 116 nosplit callind; REJECT amd64 157 main 120 nosplit callind; REJECT amd64 386 158 main 124 nosplit callind; REJECT amd64 386 159 main 128 nosplit callind; REJECT 160 main 132 nosplit callind; REJECT 161 main 136 nosplit callind; REJECT 162 163 # Issue 7623 164 main 0 call f; f 112 165 main 0 call f; f 116 166 main 0 call f; f 120 167 main 0 call f; f 124 168 main 0 call f; f 128 169 main 0 call f; f 132 170 main 0 call f; f 136 171 ` 172 173 var ( 174 commentRE = regexp.MustCompile(`(?m)^#.*`) 175 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`) 176 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`) 177 callRE = regexp.MustCompile(`\bcall (\w+)\b`) 178 callindRE = regexp.MustCompile(`\bcallind\b`) 179 ) 180 181 func main() { 182 goarch := os.Getenv("GOARCH") 183 if goarch == "" { 184 goarch = runtime.GOARCH 185 } 186 187 version, err := exec.Command("go", "tool", "compile", "-V").Output() 188 if err != nil { 189 bug() 190 fmt.Printf("running go tool compile -V: %v\n", err) 191 return 192 } 193 if strings.Contains(string(version), "framepointer") { 194 // Skip this test if GOEXPERIMENT=framepointer 195 return 196 } 197 198 dir, err := ioutil.TempDir("", "go-test-nosplit") 199 if err != nil { 200 bug() 201 fmt.Printf("creating temp dir: %v\n", err) 202 return 203 } 204 defer os.RemoveAll(dir) 205 206 tests = strings.Replace(tests, "\t", " ", -1) 207 tests = commentRE.ReplaceAllString(tests, "") 208 209 nok := 0 210 nfail := 0 211 TestCases: 212 for len(tests) > 0 { 213 var stanza string 214 i := strings.Index(tests, "\nmain ") 215 if i < 0 { 216 stanza, tests = tests, "" 217 } else { 218 stanza, tests = tests[:i], tests[i+1:] 219 } 220 221 m := rejectRE.FindStringSubmatch(stanza) 222 if m == nil { 223 bug() 224 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza)) 225 continue 226 } 227 lines := strings.TrimSpace(m[1]) 228 reject := false 229 if m[2] != "" { 230 if strings.TrimSpace(m[4]) == "" { 231 reject = true 232 } else { 233 for _, rej := range strings.Fields(m[4]) { 234 if rej == goarch { 235 reject = true 236 } 237 } 238 } 239 } 240 if lines == "" && !reject { 241 continue 242 } 243 244 var gobuf bytes.Buffer 245 fmt.Fprintf(&gobuf, "package main\n") 246 247 var buf bytes.Buffer 248 ptrSize := 4 249 switch goarch { 250 case "ppc64", "ppc64le": 251 ptrSize = 8 252 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n") 253 case "arm": 254 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n") 255 case "arm64": 256 ptrSize = 8 257 fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n") 258 case "amd64": 259 ptrSize = 8 260 fmt.Fprintf(&buf, "#define REGISTER AX\n") 261 default: 262 fmt.Fprintf(&buf, "#define REGISTER AX\n") 263 } 264 265 for _, line := range strings.Split(lines, "\n") { 266 line = strings.TrimSpace(line) 267 if line == "" { 268 continue 269 } 270 for i, subline := range strings.Split(line, ";") { 271 subline = strings.TrimSpace(subline) 272 if subline == "" { 273 continue 274 } 275 m := lineRE.FindStringSubmatch(subline) 276 if m == nil { 277 bug() 278 fmt.Printf("invalid function line: %s\n", subline) 279 continue TestCases 280 } 281 name := m[1] 282 size, _ := strconv.Atoi(m[2]) 283 284 // The limit was originally 128 but is now 512. 285 // Instead of rewriting the test cases above, adjust 286 // the first stack frame to use up the extra bytes. 287 if i == 0 { 288 size += 512 - 128 289 // Noopt builds have a larger stackguard. 290 // See ../cmd/dist/buildruntime.go:stackGuardMultiplier 291 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") { 292 if s == "-N" { 293 size += 640 294 } 295 } 296 } 297 298 if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 { 299 continue TestCases 300 } 301 nosplit := m[3] 302 body := m[4] 303 304 if nosplit != "" { 305 nosplit = ",7" 306 } else { 307 nosplit = ",0" 308 } 309 body = callRE.ReplaceAllString(body, "CALL $1(SB);") 310 body = callindRE.ReplaceAllString(body, "CALL REGISTER;") 311 312 fmt.Fprintf(&gobuf, "func %s()\n", name) 313 fmt.Fprintf(&buf, "TEXT %s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body) 314 } 315 } 316 317 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil { 318 log.Fatal(err) 319 } 320 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil { 321 log.Fatal(err) 322 } 323 324 cmd := exec.Command("go", "build") 325 cmd.Dir = dir 326 output, err := cmd.CombinedOutput() 327 if err == nil { 328 nok++ 329 if reject { 330 bug() 331 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza))) 332 } 333 } else { 334 nfail++ 335 if !reject { 336 bug() 337 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza))) 338 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output))) 339 } 340 } 341 } 342 343 if !bugged && (nok == 0 || nfail == 0) { 344 bug() 345 fmt.Printf("not enough test cases run\n") 346 } 347 } 348 349 func indent(s string) string { 350 return strings.Replace(s, "\n", "\n\t", -1) 351 } 352 353 var bugged = false 354 355 func bug() { 356 if !bugged { 357 bugged = true 358 fmt.Printf("BUG\n") 359 } 360 } 361