blob: e6cecebde36dea2e8e070ea9274f11dc8af20c6f [file] [log] [blame]
Russ Cox0c2a7272014-05-20 12:10:19 -04001// +build !nacl
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
121main 104 nosplit; REJECT ppc64 ppc64le
122main 108 nosplit; REJECT ppc64 ppc64le
123main 112 nosplit; REJECT ppc64 ppc64le
124main 116 nosplit; REJECT ppc64 ppc64le
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400125main 120 nosplit; REJECT ppc64 ppc64le amd64
126main 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
139main 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
140main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400141main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
142main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300143main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
144main 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
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400155main 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
156main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300157main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
158main 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
159main 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
160main 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
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400168main 104 nosplit callind; REJECT ppc64 ppc64le amd64
169main 108 nosplit callind; REJECT ppc64 ppc64le amd64
Michael Hudson-Doylec1b6e392015-10-30 12:47:24 +1300170main 112 nosplit callind; REJECT ppc64 ppc64le amd64
171main 116 nosplit callind; REJECT ppc64 ppc64le amd64
172main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386
173main 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)
Russ Cox5e8c9222014-04-16 22:08:00 -0400220
221 tests = strings.Replace(tests, "\t", " ", -1)
222 tests = commentRE.ReplaceAllString(tests, "")
223
224 nok := 0
225 nfail := 0
226TestCases:
227 for len(tests) > 0 {
228 var stanza string
229 i := strings.Index(tests, "\nmain ")
230 if i < 0 {
231 stanza, tests = tests, ""
232 } else {
233 stanza, tests = tests[:i], tests[i+1:]
234 }
235
236 m := rejectRE.FindStringSubmatch(stanza)
237 if m == nil {
238 bug()
239 fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
240 continue
241 }
242 lines := strings.TrimSpace(m[1])
243 reject := false
244 if m[2] != "" {
245 if strings.TrimSpace(m[4]) == "" {
246 reject = true
247 } else {
248 for _, rej := range strings.Fields(m[4]) {
249 if rej == goarch {
250 reject = true
251 }
252 }
253 }
254 }
255 if lines == "" && !reject {
256 continue
257 }
258
Russ Cox653fb6d2014-09-16 17:39:55 -0400259 var gobuf bytes.Buffer
260 fmt.Fprintf(&gobuf, "package main\n")
261
Russ Cox5e8c9222014-04-16 22:08:00 -0400262 var buf bytes.Buffer
Shenghou Ma80e76e22014-08-14 13:59:58 -0400263 ptrSize := 4
264 switch goarch {
Vladimir Stefanovic0cd2bf42016-12-15 18:05:26 +0100265 case "mips", "mipsle":
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400266 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
Yao Zhang15676b52015-09-28 17:30:32 -0400267 case "mips64", "mips64le":
268 ptrSize = 8
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400269 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
Russ Cox09d92b62014-12-05 19:13:20 -0500270 case "ppc64", "ppc64le":
Shenghou Ma80e76e22014-08-14 13:59:58 -0400271 ptrSize = 8
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400272 fmt.Fprintf(&buf, "#define REGISTER (CTR)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400273 case "arm":
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400274 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
Aram Hăvărneanuddf6d802015-03-08 14:27:51 +0100275 case "arm64":
276 ptrSize = 8
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400277 fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400278 case "amd64":
279 ptrSize = 8
280 fmt.Fprintf(&buf, "#define REGISTER AX\n")
Michael Munday4402ee92016-03-20 19:41:34 -0400281 case "s390x":
282 ptrSize = 8
283 fmt.Fprintf(&buf, "#define REGISTER R10\n")
Shenghou Ma80e76e22014-08-14 13:59:58 -0400284 default:
Russ Cox5e8c9222014-04-16 22:08:00 -0400285 fmt.Fprintf(&buf, "#define REGISTER AX\n")
286 }
287
288 for _, line := range strings.Split(lines, "\n") {
289 line = strings.TrimSpace(line)
290 if line == "" {
291 continue
292 }
Russ Coxfe910062014-08-27 14:08:26 -0400293 for i, subline := range strings.Split(line, ";") {
Russ Cox5e8c9222014-04-16 22:08:00 -0400294 subline = strings.TrimSpace(subline)
295 if subline == "" {
296 continue
297 }
298 m := lineRE.FindStringSubmatch(subline)
299 if m == nil {
300 bug()
301 fmt.Printf("invalid function line: %s\n", subline)
302 continue TestCases
303 }
304 name := m[1]
305 size, _ := strconv.Atoi(m[2])
Russ Coxfe910062014-08-27 14:08:26 -0400306
Cherry Zhangf33f20e2017-10-05 16:14:14 -0400307 // The limit was originally 128 but is now 752 (880-128).
Russ Coxfe910062014-08-27 14:08:26 -0400308 // Instead of rewriting the test cases above, adjust
Keith Randall6f42b612015-05-01 09:36:18 -0700309 // the first stack frame to use up the extra bytes.
Russ Coxfe910062014-08-27 14:08:26 -0400310 if i == 0 {
David Chase5b9ff112016-08-15 13:51:00 -0700311 size += (880 - 128) - 128
Keith Randall6f42b612015-05-01 09:36:18 -0700312 // Noopt builds have a larger stackguard.
David Chasee99dd522015-10-19 11:36:07 -0400313 // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
Matthew Dempsky1e3570a2017-04-18 12:53:25 -0700314 // This increase is included in objabi.StackGuard
Keith Randall6f42b612015-05-01 09:36:18 -0700315 for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
316 if s == "-N" {
David Chase5b9ff112016-08-15 13:51:00 -0700317 size += 880
Keith Randall6f42b612015-05-01 09:36:18 -0700318 }
319 }
Russ Coxfe910062014-08-27 14:08:26 -0400320 }
321
Russ Cox034a10d2015-07-29 15:15:03 -0400322 if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
Russ Cox5e8c9222014-04-16 22:08:00 -0400323 continue TestCases
324 }
325 nosplit := m[3]
326 body := m[4]
327
328 if nosplit != "" {
329 nosplit = ",7"
330 } else {
331 nosplit = ",0"
332 }
333 body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
334 body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
335
Russ Cox653fb6d2014-09-16 17:39:55 -0400336 fmt.Fprintf(&gobuf, "func %s()\n", name)
Russ Cox5e8c9222014-04-16 22:08:00 -0400337 fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
338 }
339 }
340
Russ Cox653fb6d2014-09-16 17:39:55 -0400341 if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
342 log.Fatal(err)
343 }
344 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
345 log.Fatal(err)
346 }
Russ Cox5e8c9222014-04-16 22:08:00 -0400347
Russ Cox5e8c9222014-04-16 22:08:00 -0400348 cmd := exec.Command("go", "build")
349 cmd.Dir = dir
350 output, err := cmd.CombinedOutput()
351 if err == nil {
352 nok++
353 if reject {
354 bug()
355 fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
356 }
357 } else {
358 nfail++
359 if !reject {
360 bug()
361 fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
362 fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
363 }
364 }
365 }
366
367 if !bugged && (nok == 0 || nfail == 0) {
368 bug()
369 fmt.Printf("not enough test cases run\n")
370 }
371}
372
373func indent(s string) string {
374 return strings.Replace(s, "\n", "\n\t", -1)
375}
376
377var bugged = false
378
379func bug() {
380 if !bugged {
381 bugged = true
382 fmt.Printf("BUG\n")
383 }
384}