blob: bed93d4890749b165fa01f89e833732bef1cfeea [file] [log] [blame]
Scott Baker2d897982019-09-24 11:50:08 -07001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build ignore
6
7/*
8This program reads a file containing function prototypes
9(like syscall_darwin.go) and generates system call bodies.
10The prototypes are marked by lines beginning with "//sys"
11and read like func declarations if //sys is replaced by func, but:
12 * The parameter lists must give a name for each argument.
13 This includes return parameters.
14 * The parameter lists must give a type for each argument:
15 the (x, y, z int) shorthand is not allowed.
16 * If the return parameter is an error number, it must be named errno.
17
18A line beginning with //sysnb is like //sys, except that the
19goroutine will not be suspended during the execution of the system
20call. This must only be used for system calls which can never
21block, as otherwise the system call could cause all goroutines to
22hang.
23*/
24package main
25
26import (
27 "bufio"
28 "flag"
29 "fmt"
30 "os"
31 "regexp"
32 "strings"
33)
34
35var (
36 b32 = flag.Bool("b32", false, "32bit big-endian")
37 l32 = flag.Bool("l32", false, "32bit little-endian")
38 plan9 = flag.Bool("plan9", false, "plan9")
39 openbsd = flag.Bool("openbsd", false, "openbsd")
40 netbsd = flag.Bool("netbsd", false, "netbsd")
41 dragonfly = flag.Bool("dragonfly", false, "dragonfly")
42 arm = flag.Bool("arm", false, "arm") // 64-bit value should use (even, odd)-pair
43 tags = flag.String("tags", "", "build tags")
44 filename = flag.String("output", "", "output file name (standard output if omitted)")
45)
46
47// cmdLine returns this programs's commandline arguments
48func cmdLine() string {
49 return "go run mksyscall.go " + strings.Join(os.Args[1:], " ")
50}
51
52// buildTags returns build tags
53func buildTags() string {
54 return *tags
55}
56
57// Param is function parameter
58type Param struct {
59 Name string
60 Type string
61}
62
63// usage prints the program usage
64func usage() {
65 fmt.Fprintf(os.Stderr, "usage: go run mksyscall.go [-b32 | -l32] [-tags x,y] [file ...]\n")
66 os.Exit(1)
67}
68
69// parseParamList parses parameter list and returns a slice of parameters
70func parseParamList(list string) []string {
71 list = strings.TrimSpace(list)
72 if list == "" {
73 return []string{}
74 }
75 return regexp.MustCompile(`\s*,\s*`).Split(list, -1)
76}
77
78// parseParam splits a parameter into name and type
79func parseParam(p string) Param {
80 ps := regexp.MustCompile(`^(\S*) (\S*)$`).FindStringSubmatch(p)
81 if ps == nil {
82 fmt.Fprintf(os.Stderr, "malformed parameter: %s\n", p)
83 os.Exit(1)
84 }
85 return Param{ps[1], ps[2]}
86}
87
88func main() {
89 // Get the OS and architecture (using GOARCH_TARGET if it exists)
90 goos := os.Getenv("GOOS")
91 if goos == "" {
92 fmt.Fprintln(os.Stderr, "GOOS not defined in environment")
93 os.Exit(1)
94 }
95 goarch := os.Getenv("GOARCH_TARGET")
96 if goarch == "" {
97 goarch = os.Getenv("GOARCH")
98 }
99
100 // Check that we are using the Docker-based build system if we should
101 if goos == "linux" {
102 if os.Getenv("GOLANG_SYS_BUILD") != "docker" {
103 fmt.Fprintf(os.Stderr, "In the Docker-based build system, mksyscall should not be called directly.\n")
104 fmt.Fprintf(os.Stderr, "See README.md\n")
105 os.Exit(1)
106 }
107 }
108
109 flag.Usage = usage
110 flag.Parse()
111 if len(flag.Args()) <= 0 {
112 fmt.Fprintf(os.Stderr, "no files to parse provided\n")
113 usage()
114 }
115
116 endianness := ""
117 if *b32 {
118 endianness = "big-endian"
119 } else if *l32 {
120 endianness = "little-endian"
121 }
122
123 libc := false
124 if goos == "darwin" && strings.Contains(buildTags(), ",go1.12") {
125 libc = true
126 }
127 trampolines := map[string]bool{}
128
129 text := ""
130 for _, path := range flag.Args() {
131 file, err := os.Open(path)
132 if err != nil {
133 fmt.Fprintf(os.Stderr, err.Error())
134 os.Exit(1)
135 }
136 s := bufio.NewScanner(file)
137 for s.Scan() {
138 t := s.Text()
139 t = strings.TrimSpace(t)
140 t = regexp.MustCompile(`\s+`).ReplaceAllString(t, ` `)
141 nonblock := regexp.MustCompile(`^\/\/sysnb `).FindStringSubmatch(t)
142 if regexp.MustCompile(`^\/\/sys `).FindStringSubmatch(t) == nil && nonblock == nil {
143 continue
144 }
145
146 // Line must be of the form
147 // func Open(path string, mode int, perm int) (fd int, errno error)
148 // Split into name, in params, out params.
149 f := regexp.MustCompile(`^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*((?i)SYS_[A-Z0-9_]+))?$`).FindStringSubmatch(t)
150 if f == nil {
151 fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
152 os.Exit(1)
153 }
154 funct, inps, outps, sysname := f[2], f[3], f[4], f[5]
155
156 // Split argument lists on comma.
157 in := parseParamList(inps)
158 out := parseParamList(outps)
159
160 // Try in vain to keep people from editing this file.
161 // The theory is that they jump into the middle of the file
162 // without reading the header.
163 text += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
164
165 // Go function header.
166 outDecl := ""
167 if len(out) > 0 {
168 outDecl = fmt.Sprintf(" (%s)", strings.Join(out, ", "))
169 }
170 text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
171
172 // Check if err return available
173 errvar := ""
174 for _, param := range out {
175 p := parseParam(param)
176 if p.Type == "error" {
177 errvar = p.Name
178 break
179 }
180 }
181
182 // Prepare arguments to Syscall.
183 var args []string
184 n := 0
185 for _, param := range in {
186 p := parseParam(param)
187 if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
188 args = append(args, "uintptr(unsafe.Pointer("+p.Name+"))")
189 } else if p.Type == "string" && errvar != "" {
190 text += fmt.Sprintf("\tvar _p%d *byte\n", n)
191 text += fmt.Sprintf("\t_p%d, %s = BytePtrFromString(%s)\n", n, errvar, p.Name)
192 text += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
193 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
194 n++
195 } else if p.Type == "string" {
196 fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
197 text += fmt.Sprintf("\tvar _p%d *byte\n", n)
198 text += fmt.Sprintf("\t_p%d, _ = BytePtrFromString(%s)\n", n, p.Name)
199 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
200 n++
201 } else if regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type) != nil {
202 // Convert slice into pointer, length.
203 // Have to be careful not to take address of &a[0] if len == 0:
204 // pass dummy pointer in that case.
205 // Used to pass nil, but some OSes or simulators reject write(fd, nil, 0).
206 text += fmt.Sprintf("\tvar _p%d unsafe.Pointer\n", n)
207 text += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = unsafe.Pointer(&%s[0])\n\t}", p.Name, n, p.Name)
208 text += fmt.Sprintf(" else {\n\t\t_p%d = unsafe.Pointer(&_zero)\n\t}\n", n)
209 args = append(args, fmt.Sprintf("uintptr(_p%d)", n), fmt.Sprintf("uintptr(len(%s))", p.Name))
210 n++
211 } else if p.Type == "int64" && (*openbsd || *netbsd) {
212 args = append(args, "0")
213 if endianness == "big-endian" {
214 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
215 } else if endianness == "little-endian" {
216 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
217 } else {
218 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
219 }
220 } else if p.Type == "int64" && *dragonfly {
221 if regexp.MustCompile(`^(?i)extp(read|write)`).FindStringSubmatch(funct) == nil {
222 args = append(args, "0")
223 }
224 if endianness == "big-endian" {
225 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
226 } else if endianness == "little-endian" {
227 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
228 } else {
229 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
230 }
231 } else if (p.Type == "int64" || p.Type == "uint64") && endianness != "" {
232 if len(args)%2 == 1 && *arm {
233 // arm abi specifies 64-bit argument uses
234 // (even, odd) pair
235 args = append(args, "0")
236 }
237 if endianness == "big-endian" {
238 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
239 } else {
240 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
241 }
242 } else {
243 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
244 }
245 }
246
247 // Determine which form to use; pad args with zeros.
248 asm := "Syscall"
249 if nonblock != nil {
250 if errvar == "" && goos == "linux" {
251 asm = "RawSyscallNoError"
252 } else {
253 asm = "RawSyscall"
254 }
255 } else {
256 if errvar == "" && goos == "linux" {
257 asm = "SyscallNoError"
258 }
259 }
260 if len(args) <= 3 {
261 for len(args) < 3 {
262 args = append(args, "0")
263 }
264 } else if len(args) <= 6 {
265 asm += "6"
266 for len(args) < 6 {
267 args = append(args, "0")
268 }
269 } else if len(args) <= 9 {
270 asm += "9"
271 for len(args) < 9 {
272 args = append(args, "0")
273 }
274 } else {
275 fmt.Fprintf(os.Stderr, "%s:%s too many arguments to system call\n", path, funct)
276 }
277
278 // System call number.
279 if sysname == "" {
280 sysname = "SYS_" + funct
281 sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
282 sysname = strings.ToUpper(sysname)
283 }
284
285 var libcFn string
286 if libc {
287 asm = "syscall_" + strings.ToLower(asm[:1]) + asm[1:] // internal syscall call
288 sysname = strings.TrimPrefix(sysname, "SYS_") // remove SYS_
289 sysname = strings.ToLower(sysname) // lowercase
290 if sysname == "getdirentries64" {
291 // Special case - libSystem name and
292 // raw syscall name don't match.
293 sysname = "__getdirentries64"
294 }
295 libcFn = sysname
296 sysname = "funcPC(libc_" + sysname + "_trampoline)"
297 }
298
299 // Actual call.
300 arglist := strings.Join(args, ", ")
301 call := fmt.Sprintf("%s(%s, %s)", asm, sysname, arglist)
302
303 // Assign return values.
304 body := ""
305 ret := []string{"_", "_", "_"}
306 doErrno := false
307 for i := 0; i < len(out); i++ {
308 p := parseParam(out[i])
309 reg := ""
310 if p.Name == "err" && !*plan9 {
311 reg = "e1"
312 ret[2] = reg
313 doErrno = true
314 } else if p.Name == "err" && *plan9 {
315 ret[0] = "r0"
316 ret[2] = "e1"
317 break
318 } else {
319 reg = fmt.Sprintf("r%d", i)
320 ret[i] = reg
321 }
322 if p.Type == "bool" {
323 reg = fmt.Sprintf("%s != 0", reg)
324 }
325 if p.Type == "int64" && endianness != "" {
326 // 64-bit number in r1:r0 or r0:r1.
327 if i+2 > len(out) {
328 fmt.Fprintf(os.Stderr, "%s:%s not enough registers for int64 return\n", path, funct)
329 }
330 if endianness == "big-endian" {
331 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
332 } else {
333 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
334 }
335 ret[i] = fmt.Sprintf("r%d", i)
336 ret[i+1] = fmt.Sprintf("r%d", i+1)
337 }
338 if reg != "e1" || *plan9 {
339 body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
340 }
341 }
342 if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
343 text += fmt.Sprintf("\t%s\n", call)
344 } else {
345 if errvar == "" && goos == "linux" {
346 // raw syscall without error on Linux, see golang.org/issue/22924
347 text += fmt.Sprintf("\t%s, %s := %s\n", ret[0], ret[1], call)
348 } else {
349 text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
350 }
351 }
352 text += body
353
354 if *plan9 && ret[2] == "e1" {
355 text += "\tif int32(r0) == -1 {\n"
356 text += "\t\terr = e1\n"
357 text += "\t}\n"
358 } else if doErrno {
359 text += "\tif e1 != 0 {\n"
360 text += "\t\terr = errnoErr(e1)\n"
361 text += "\t}\n"
362 }
363 text += "\treturn\n"
364 text += "}\n\n"
365
366 if libc && !trampolines[libcFn] {
367 // some system calls share a trampoline, like read and readlen.
368 trampolines[libcFn] = true
369 // Declare assembly trampoline.
370 text += fmt.Sprintf("func libc_%s_trampoline()\n", libcFn)
371 // Assembly trampoline calls the libc_* function, which this magic
372 // redirects to use the function from libSystem.
373 text += fmt.Sprintf("//go:linkname libc_%s libc_%s\n", libcFn, libcFn)
374 text += fmt.Sprintf("//go:cgo_import_dynamic libc_%s %s \"/usr/lib/libSystem.B.dylib\"\n", libcFn, libcFn)
375 text += "\n"
376 }
377 }
378 if err := s.Err(); err != nil {
379 fmt.Fprintf(os.Stderr, err.Error())
380 os.Exit(1)
381 }
382 file.Close()
383 }
384 fmt.Printf(srcTemplate, cmdLine(), buildTags(), text)
385}
386
387const srcTemplate = `// %s
388// Code generated by the command above; see README.md. DO NOT EDIT.
389
390// +build %s
391
392package unix
393
394import (
395 "syscall"
396 "unsafe"
397)
398
399var _ syscall.Errno
400
401%s
402`