1 // 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.
8 This program reads a file containing function prototypes
9 (like syscall_darwin.go) and generates system call bodies.
10 The prototypes are marked by lines beginning with "//sys"
11 and 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.
18 A line beginning with //sysnb is like //sys, except that the
19 goroutine will not be suspended during the execution of the system
20 call. This must only be used for system calls which can never
21 block, as otherwise the system call could cause all goroutines to
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)")
47 // cmdLine returns this programs's commandline arguments
48 func cmdLine() string {
49 return "go run mksyscall.go " + strings.Join(os.Args[1:], " ")
52 // buildTags returns build tags
53 func buildTags() string {
57 // Param is function parameter
63 // usage prints the program usage
65 fmt.Fprintf(os.Stderr, "usage: go run mksyscall.go [-b32 | -l32] [-tags x,y] [file ...]\n")
69 // parseParamList parses parameter list and returns a slice of parameters
70 func parseParamList(list string) []string {
71 list = strings.TrimSpace(list)
75 return regexp.MustCompile(`\s*,\s*`).Split(list, -1)
78 // parseParam splits a parameter into name and type
79 func parseParam(p string) Param {
80 ps := regexp.MustCompile(`^(\S*) (\S*)$`).FindStringSubmatch(p)
82 fmt.Fprintf(os.Stderr, "malformed parameter: %s\n", p)
85 return Param{ps[1], ps[2]}
89 // Get the OS and architecture (using GOARCH_TARGET if it exists)
90 goos := os.Getenv("GOOS")
92 fmt.Fprintln(os.Stderr, "GOOS not defined in environment")
95 goarch := os.Getenv("GOARCH_TARGET")
97 goarch = os.Getenv("GOARCH")
100 // Check that we are using the Docker-based build system if we should
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")
111 if len(flag.Args()) <= 0 {
112 fmt.Fprintf(os.Stderr, "no files to parse provided\n")
118 endianness = "big-endian"
120 endianness = "little-endian"
124 if goos == "darwin" && strings.Contains(buildTags(), ",go1.12") {
127 trampolines := map[string]bool{}
130 for _, path := range flag.Args() {
131 file, err := os.Open(path)
133 fmt.Fprintf(os.Stderr, err.Error())
136 s := bufio.NewScanner(file)
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 {
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)
151 fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
154 funct, inps, outps, sysname := f[2], f[3], f[4], f[5]
156 // ClockGettime doesn't have a syscall number on Darwin, only generate libc wrappers.
157 if goos == "darwin" && !libc && funct == "ClockGettime" {
161 // Split argument lists on comma.
162 in := parseParamList(inps)
163 out := parseParamList(outps)
165 // Try in vain to keep people from editing this file.
166 // The theory is that they jump into the middle of the file
167 // without reading the header.
168 text += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
170 // Go function header.
173 outDecl = fmt.Sprintf(" (%s)", strings.Join(out, ", "))
175 text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
177 // Check if err return available
179 for _, param := range out {
180 p := parseParam(param)
181 if p.Type == "error" {
187 // Prepare arguments to Syscall.
190 for _, param := range in {
191 p := parseParam(param)
192 if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
193 args = append(args, "uintptr(unsafe.Pointer("+p.Name+"))")
194 } else if p.Type == "string" && errvar != "" {
195 text += fmt.Sprintf("\tvar _p%d *byte\n", n)
196 text += fmt.Sprintf("\t_p%d, %s = BytePtrFromString(%s)\n", n, errvar, p.Name)
197 text += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
198 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
200 } else if p.Type == "string" {
201 fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
202 text += fmt.Sprintf("\tvar _p%d *byte\n", n)
203 text += fmt.Sprintf("\t_p%d, _ = BytePtrFromString(%s)\n", n, p.Name)
204 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
206 } else if regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type) != nil {
207 // Convert slice into pointer, length.
208 // Have to be careful not to take address of &a[0] if len == 0:
209 // pass dummy pointer in that case.
210 // Used to pass nil, but some OSes or simulators reject write(fd, nil, 0).
211 text += fmt.Sprintf("\tvar _p%d unsafe.Pointer\n", n)
212 text += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = unsafe.Pointer(&%s[0])\n\t}", p.Name, n, p.Name)
213 text += fmt.Sprintf(" else {\n\t\t_p%d = unsafe.Pointer(&_zero)\n\t}\n", n)
214 args = append(args, fmt.Sprintf("uintptr(_p%d)", n), fmt.Sprintf("uintptr(len(%s))", p.Name))
216 } else if p.Type == "int64" && (*openbsd || *netbsd) {
217 args = append(args, "0")
218 if endianness == "big-endian" {
219 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
220 } else if endianness == "little-endian" {
221 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
223 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
225 } else if p.Type == "int64" && *dragonfly {
226 if regexp.MustCompile(`^(?i)extp(read|write)`).FindStringSubmatch(funct) == nil {
227 args = append(args, "0")
229 if endianness == "big-endian" {
230 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
231 } else if endianness == "little-endian" {
232 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
234 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
236 } else if (p.Type == "int64" || p.Type == "uint64") && endianness != "" {
237 if len(args)%2 == 1 && *arm {
238 // arm abi specifies 64-bit argument uses
240 args = append(args, "0")
242 if endianness == "big-endian" {
243 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
245 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
248 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
252 // Determine which form to use; pad args with zeros.
255 if errvar == "" && goos == "linux" {
256 asm = "RawSyscallNoError"
261 if errvar == "" && goos == "linux" {
262 asm = "SyscallNoError"
267 args = append(args, "0")
269 } else if len(args) <= 6 {
272 args = append(args, "0")
274 } else if len(args) <= 9 {
277 args = append(args, "0")
280 fmt.Fprintf(os.Stderr, "%s:%s too many arguments to system call\n", path, funct)
283 // System call number.
285 sysname = "SYS_" + funct
286 sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
287 sysname = strings.ToUpper(sysname)
292 asm = "syscall_" + strings.ToLower(asm[:1]) + asm[1:] // internal syscall call
293 sysname = strings.TrimPrefix(sysname, "SYS_") // remove SYS_
294 sysname = strings.ToLower(sysname) // lowercase
295 if sysname == "getdirentries64" {
296 // Special case - libSystem name and
297 // raw syscall name don't match.
298 sysname = "__getdirentries64"
301 sysname = "funcPC(libc_" + sysname + "_trampoline)"
305 arglist := strings.Join(args, ", ")
306 call := fmt.Sprintf("%s(%s, %s)", asm, sysname, arglist)
308 // Assign return values.
310 ret := []string{"_", "_", "_"}
312 for i := 0; i < len(out); i++ {
313 p := parseParam(out[i])
315 if p.Name == "err" && !*plan9 {
319 } else if p.Name == "err" && *plan9 {
324 reg = fmt.Sprintf("r%d", i)
327 if p.Type == "bool" {
328 reg = fmt.Sprintf("%s != 0", reg)
330 if p.Type == "int64" && endianness != "" {
331 // 64-bit number in r1:r0 or r0:r1.
333 fmt.Fprintf(os.Stderr, "%s:%s not enough registers for int64 return\n", path, funct)
335 if endianness == "big-endian" {
336 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
338 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
340 ret[i] = fmt.Sprintf("r%d", i)
341 ret[i+1] = fmt.Sprintf("r%d", i+1)
343 if reg != "e1" || *plan9 {
344 body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
347 if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
348 text += fmt.Sprintf("\t%s\n", call)
350 if errvar == "" && goos == "linux" {
351 // raw syscall without error on Linux, see golang.org/issue/22924
352 text += fmt.Sprintf("\t%s, %s := %s\n", ret[0], ret[1], call)
354 text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
359 if *plan9 && ret[2] == "e1" {
360 text += "\tif int32(r0) == -1 {\n"
361 text += "\t\terr = e1\n"
364 text += "\tif e1 != 0 {\n"
365 text += "\t\terr = errnoErr(e1)\n"
371 if libc && !trampolines[libcFn] {
372 // some system calls share a trampoline, like read and readlen.
373 trampolines[libcFn] = true
374 // Declare assembly trampoline.
375 text += fmt.Sprintf("func libc_%s_trampoline()\n", libcFn)
376 // Assembly trampoline calls the libc_* function, which this magic
377 // redirects to use the function from libSystem.
378 text += fmt.Sprintf("//go:linkname libc_%s libc_%s\n", libcFn, libcFn)
379 text += fmt.Sprintf("//go:cgo_import_dynamic libc_%s %s \"/usr/lib/libSystem.B.dylib\"\n", libcFn, libcFn)
383 if err := s.Err(); err != nil {
384 fmt.Fprintf(os.Stderr, err.Error())
389 fmt.Printf(srcTemplate, cmdLine(), buildTags(), text)
392 const srcTemplate = `// %s
393 // Code generated by the command above; see README.md. DO NOT EDIT.