blob: a3f2a9fb7eba4ecf5154b00278c2542d03c279e8 [file] [log] [blame]
Clément Chigot85525c52018-11-02 10:09:30 +01001// +build !nacl,!js,!aix,!gcflags_noopt
Rahul Chaudhry8581d482015-02-03 10:52:18 -08002// run
Russ Cox0c2a7272014-05-20 12:10:19 -04003
Emmanuel Odeke53fd5222016-04-10 14:32:26 -07004// Copyright 2014 The Go Authors. All rights reserved.
Russ Cox5e8c9222014-04-16 22:08:00 -04005// Use of this source code is governed by a BSD-style
6// license that can be found in the LICENSE file.
7
8package main
9
10import (
11 "bytes"
12 "fmt"
13 "io/ioutil"
Russ Cox653fb6d2014-09-16 17:39:55 -040014 "log"
Russ Cox5e8c9222014-04-16 22:08:00 -040015 "os"
16 "os/exec"
17 "path/filepath"
18 "regexp"
19 "runtime"
20 "strconv"
21 "strings"
22)
23
24var 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
49main 0
50
51# Large frame marked nosplit is always wrong.
52main 10000 nosplit
53REJECT
54
55# Calling a large frame is okay.
56main 0 call big
57big 10000
58
59# But not if the frame is nosplit.
60main 0 call big
61big 10000 nosplit
62REJECT
63
64# Recursion is okay.
65main 0 call main
66
67# Recursive nosplit runs out of space.
68main 0 nosplit call main
69REJECT
70
71# Chains of ordinary functions okay.
72main 0 call f1
73f1 80 call f2
74f2 80
75
76# Chains of nosplit must fit in the stack limit, 128 bytes.
77main 0 call f1
78f1 80 nosplit call f2
79f2 80 nosplit
80REJECT
81
82# Larger chains.
83main 0 call f1
84f1 16 call f2
85f2 16 call f3
86f3 16 call f4
87f4 16 call f5
88f5 16 call f6
89f6 16 call f7
90f7 16 call f8
91f8 16 call end
92end 1000
93
94main 0 call f1
95f1 16 nosplit call f2
96f2 16 nosplit call f3
97f3 16 nosplit call f4
98f4 16 nosplit call f5
99f5 16 nosplit call f6
100f6 16 nosplit call f7
101f7 16 nosplit call f8
102f8 16 nosplit call end
103end 1000
104REJECT
105
106# Test cases near the 128-byte limit.
107
108# Ordinary stack split frame is always okay.
109main 112
110main 116
111main 120
112main 124
113main 128
114main 132
115main 136
116
117# A nosplit leaf can use the whole 128-CallSize bytes available on entry.
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400118# (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.)
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300119main 96 nosplit
120main 100 nosplit; REJECT ppc64 ppc64le
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800121main 104 nosplit; REJECT ppc64 ppc64le arm64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300122main 108 nosplit; REJECT ppc64 ppc64le
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800123main 112 nosplit; REJECT ppc64 ppc64le arm64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300124main 116 nosplit; REJECT ppc64 ppc64le
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800125main 120 nosplit; REJECT ppc64 ppc64le amd64 arm64
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400126main 124 nosplit; REJECT ppc64 ppc64le amd64
Russ Cox5e8c9222014-04-16 22:08:00 -0400127main 128 nosplit; REJECT
128main 132 nosplit; REJECT
129main 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.
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300134# Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes.
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400135# ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 bytes.
136# Because AMD64 uses frame pointer, it has 8 fewer bytes.
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300137main 96 nosplit call f; f 0 nosplit
138main 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800139main 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le arm64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300140main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800141main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400142main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
Cherry Zhang289dce22018-08-29 14:49:42 -0400143main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 arm64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300144main 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
Russ Cox5e8c9222014-04-16 22:08:00 -0400145main 128 nosplit call f; f 0 nosplit; REJECT
146main 132 nosplit call f; f 0 nosplit; REJECT
147main 136 nosplit call f; f 0 nosplit; REJECT
148
149# Calling a splitting function from a nosplit function requires
150# having room for the saved caller PC of the call but also the
Russ Coxed68c7d2014-08-14 15:29:37 -0400151# saved caller PC for the call to morestack.
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400152# Architectures differ in the same way as before.
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300153main 96 nosplit call f; f 0 call f
154main 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800155main 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400156main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800157main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 arm64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300158main 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800159main 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386 arm64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300160main 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
Russ Cox5e8c9222014-04-16 22:08:00 -0400161main 128 nosplit call f; f 0 call f; REJECT
162main 132 nosplit call f; f 0 call f; REJECT
163main 136 nosplit call f; f 0 call f; REJECT
164
165# Indirect calls are assumed to be splitting functions.
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300166main 96 nosplit callind
167main 100 nosplit callind; REJECT ppc64 ppc64le
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800168main 104 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400169main 108 nosplit callind; REJECT ppc64 ppc64le amd64
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800170main 112 nosplit callind; REJECT ppc64 ppc64le amd64 arm64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300171main 116 nosplit callind; REJECT ppc64 ppc64le amd64
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800172main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386 arm64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300173main 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
Russ Cox5e8c9222014-04-16 22:08:00 -0400174main 128 nosplit callind; REJECT
175main 132 nosplit callind; REJECT
176main 136 nosplit callind; REJECT
177
178# Issue 7623
179main 0 call f; f 112
180main 0 call f; f 116
181main 0 call f; f 120
182main 0 call f; f 124
183main 0 call f; f 128
184main 0 call f; f 132
185main 0 call f; f 136
186`
187
188var (
189 commentRE = regexp.MustCompile(`(?m)^#.*`)
190 rejectRE = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
191 lineRE = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
192 callRE = regexp.MustCompile(`\bcall (\w+)\b`)
193 callindRE = regexp.MustCompile(`\bcallind\b`)
194)
195
196func main() {
197 goarch := os.Getenv("GOARCH")
198 if goarch == "" {
199 goarch = runtime.GOARCH
200 }
201
Russ Cox0f4132c2015-05-21 13:28:13 -0400202 version, err := exec.Command("go", "tool", "compile", "-V").Output()
Austin Clements3c0fee12015-01-14 11:09:50 -0500203 if err != nil {
204 bug()
Josh Bleecher Snyder8b186df2015-05-27 12:33:43 -0700205 fmt.Printf("running go tool compile -V: %v\n", err)
Austin Clements3c0fee12015-01-14 11:09:50 -0500206 return
207 }
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400208 if s := string(version); goarch == "amd64" && strings.Contains(s, "X:") && !strings.Contains(s, "framepointer") {
209 // Skip this test if framepointer is NOT enabled on AMD64
Austin Clements3c0fee12015-01-14 11:09:50 -0500210 return
211 }
212
Russ Cox5e8c9222014-04-16 22:08:00 -0400213 dir, err := ioutil.TempDir("", "go-test-nosplit")
214 if err != nil {
215 bug()
216 fmt.Printf("creating temp dir: %v\n", err)
217 return
218 }
219 defer os.RemoveAll(dir)
Bryan C. Millsaf13cfc2019-03-12 16:28:03 -0400220 os.Setenv("GOPATH", filepath.Join(dir, "_gopath"))
Russ Cox5e8c9222014-04-16 22:08:00 -0400221
Bryan C. Mills1670da92019-02-22 15:58:15 -0500222 if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module go-test-nosplit\n"), 0666); err != nil {
223 log.Panic(err)
224 }
225
Russ Cox5e8c9222014-04-16 22:08:00 -0400226 tests = strings.Replace(tests, "\t", " ", -1)
227 tests = commentRE.ReplaceAllString(tests, "")
228
229 nok := 0
230 nfail := 0
231TestCases:
232 for len(tests) > 0 {
233 var stanza string
234 i := strings.Index(tests, "\nmain ")
235 if i < 0 {
236 stanza, tests = tests, ""
237 } else {
238 stanza, tests = tests[:i], tests[i+1:]
239 }
240
241 m := rejectRE.FindStringSubmatch(stanza)
242 if m == nil {
243 bug()
244 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
245 continue
246 }
247 lines := strings.TrimSpace(m[1])
248 reject := false
249 if m[2] != "" {
250 if strings.TrimSpace(m[4]) == "" {
251 reject = true
252 } else {
253 for _, rej := range strings.Fields(m[4]) {
254 if rej == goarch {
255 reject = true
256 }
257 }
258 }
259 }
260 if lines == "" && !reject {
261 continue
262 }
263
Russ Cox653fb6d2014-09-16 17:39:55 -0400264 var gobuf bytes.Buffer
265 fmt.Fprintf(&gobuf, "package main\n")
266
Russ Cox5e8c9222014-04-16 22:08:00 -0400267 var buf bytes.Buffer
Shenghou Ma80e76e22014-08-14 13:59:58 -0400268 ptrSize := 4
269 switch goarch {
Vladimir Stefanovic0cd2bf42016-12-15 18:05:26 +0100270 case "mips", "mipsle":
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400271 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
Yao Zhang15676b52015-09-28 17:30:32 -0400272 case "mips64", "mips64le":
273 ptrSize = 8
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400274 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
Russ Cox09d92b62014-12-05 19:13:20 -0500275 case "ppc64", "ppc64le":
Shenghou Ma80e76e22014-08-14 13:59:58 -0400276 ptrSize = 8
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400277 fmt.Fprintf(&buf, "#define REGISTER (CTR)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400278 case "arm":
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400279 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
Aram Hăvărneanuddf6d802015-03-08 14:27:51 +0100280 case "arm64":
281 ptrSize = 8
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400282 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400283 case "amd64":
284 ptrSize = 8
285 fmt.Fprintf(&buf, "#define REGISTER AX\n")
Joel Sing7f331e02020-01-25 03:10:04 +1100286 case "riscv64":
287 ptrSize = 8
288 fmt.Fprintf(&buf, "#define REGISTER A0\n")
Michael Munday4402ee92016-03-20 19:41:34 -0400289 case "s390x":
290 ptrSize = 8
291 fmt.Fprintf(&buf, "#define REGISTER R10\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400292 default:
Russ Cox5e8c9222014-04-16 22:08:00 -0400293 fmt.Fprintf(&buf, "#define REGISTER AX\n")
294 }
295
296 for _, line := range strings.Split(lines, "\n") {
297 line = strings.TrimSpace(line)
298 if line == "" {
299 continue
300 }
Russ Coxfe910062014-08-27 14:08:26 -0400301 for i, subline := range strings.Split(line, ";") {
Russ Cox5e8c9222014-04-16 22:08:00 -0400302 subline = strings.TrimSpace(subline)
303 if subline == "" {
304 continue
305 }
306 m := lineRE.FindStringSubmatch(subline)
307 if m == nil {
308 bug()
309 fmt.Printf("invalid function line: %s\n", subline)
310 continue TestCases
311 }
312 name := m[1]
313 size, _ := strconv.Atoi(m[2])
Russ Coxfe910062014-08-27 14:08:26 -0400314
Dan Scales0a820002019-11-13 17:34:47 -0800315 // The limit was originally 128 but is now 800 (928-128).
Russ Coxfe910062014-08-27 14:08:26 -0400316 // Instead of rewriting the test cases above, adjust
Keith Randall6f42b612015-05-01 09:36:18 -0700317 // the first stack frame to use up the extra bytes.
Russ Coxfe910062014-08-27 14:08:26 -0400318 if i == 0 {
Dan Scales0a820002019-11-13 17:34:47 -0800319 size += (928 - 128) - 128
Keith Randall6f42b612015-05-01 09:36:18 -0700320 // Noopt builds have a larger stackguard.
David Chasee99dd522015-10-19 11:36:07 -0400321 // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
Matthew Dempsky1e3570a2017-04-18 12:53:25 -0700322 // This increase is included in objabi.StackGuard
Keith Randall6f42b612015-05-01 09:36:18 -0700323 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
324 if s == "-N" {
Dan Scales0a820002019-11-13 17:34:47 -0800325 size += 928
Keith Randall6f42b612015-05-01 09:36:18 -0700326 }
327 }
Russ Coxfe910062014-08-27 14:08:26 -0400328 }
329
Zheng Xu8f4fd3f2018-08-29 14:55:03 +0800330 if size%ptrSize == 4 {
Russ Cox5e8c9222014-04-16 22:08:00 -0400331 continue TestCases
332 }
333 nosplit := m[3]
334 body := m[4]
335
336 if nosplit != "" {
337 nosplit = ",7"
338 } else {
339 nosplit = ",0"
340 }
341 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
342 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
343
Russ Cox653fb6d2014-09-16 17:39:55 -0400344 fmt.Fprintf(&gobuf, "func %s()\n", name)
Russ Cox5e8c9222014-04-16 22:08:00 -0400345 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
346 }
347 }
348
Russ Cox653fb6d2014-09-16 17:39:55 -0400349 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
350 log.Fatal(err)
351 }
352 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
353 log.Fatal(err)
354 }
Russ Cox5e8c9222014-04-16 22:08:00 -0400355
Russ Cox5e8c9222014-04-16 22:08:00 -0400356 cmd := exec.Command("go", "build")
357 cmd.Dir = dir
358 output, err := cmd.CombinedOutput()
359 if err == nil {
360 nok++
361 if reject {
362 bug()
363 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
364 }
365 } else {
366 nfail++
367 if !reject {
368 bug()
369 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
370 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
371 }
372 }
373 }
374
375 if !bugged && (nok == 0 || nfail == 0) {
376 bug()
377 fmt.Printf("not enough test cases run\n")
378 }
379}
380
381func indent(s string) string {
382 return strings.Replace(s, "\n", "\n\t", -1)
383}
384
385var bugged = false
386
387func bug() {
388 if !bugged {
389 bugged = true
390 fmt.Printf("BUG\n")
391 }
392}