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_plan9.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")
91 goarch := os.Getenv("GOARCH_TARGET")
93 goarch = os.Getenv("GOARCH")
96 // Check that we are using the Docker-based build system if we should
98 if os.Getenv("GOLANG_SYS_BUILD") != "docker" {
99 fmt.Fprintf(os.Stderr, "In the Docker-based build system, mksyscall should not be called directly.\n")
100 fmt.Fprintf(os.Stderr, "See README.md\n")
107 if len(flag.Args()) <= 0 {
108 fmt.Fprintf(os.Stderr, "no files to parse provided\n")
114 endianness = "big-endian"
116 endianness = "little-endian"
120 if goos == "darwin" && strings.Contains(buildTags(), ",go1.12") {
123 trampolines := map[string]bool{}
126 for _, path := range flag.Args() {
127 file, err := os.Open(path)
129 fmt.Fprintf(os.Stderr, err.Error())
132 s := bufio.NewScanner(file)
135 t = strings.TrimSpace(t)
136 t = regexp.MustCompile(`\s+`).ReplaceAllString(t, ` `)
137 nonblock := regexp.MustCompile(`^\/\/sysnb `).FindStringSubmatch(t)
138 if regexp.MustCompile(`^\/\/sys `).FindStringSubmatch(t) == nil && nonblock == nil {
142 // Line must be of the form
143 // func Open(path string, mode int, perm int) (fd int, errno error)
144 // Split into name, in params, out params.
145 f := regexp.MustCompile(`^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*((?i)SYS_[A-Z0-9_]+))?$`).FindStringSubmatch(t)
147 fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
150 funct, inps, outps, sysname := f[2], f[3], f[4], f[5]
152 // Split argument lists on comma.
153 in := parseParamList(inps)
154 out := parseParamList(outps)
156 // Try in vain to keep people from editing this file.
157 // The theory is that they jump into the middle of the file
158 // without reading the header.
159 text += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
161 // Go function header.
164 outDecl = fmt.Sprintf(" (%s)", strings.Join(out, ", "))
166 text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
168 // Check if err return available
170 for _, param := range out {
171 p := parseParam(param)
172 if p.Type == "error" {
178 // Prepare arguments to Syscall.
181 for _, param := range in {
182 p := parseParam(param)
183 if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
184 args = append(args, "uintptr(unsafe.Pointer("+p.Name+"))")
185 } else if p.Type == "string" && errvar != "" {
186 text += fmt.Sprintf("\tvar _p%d *byte\n", n)
187 text += fmt.Sprintf("\t_p%d, %s = BytePtrFromString(%s)\n", n, errvar, p.Name)
188 text += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
189 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
191 } else if p.Type == "string" {
192 fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
193 text += fmt.Sprintf("\tvar _p%d *byte\n", n)
194 text += fmt.Sprintf("\t_p%d, _ = BytePtrFromString(%s)\n", n, p.Name)
195 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
197 } else if regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type) != nil {
198 // Convert slice into pointer, length.
199 // Have to be careful not to take address of &a[0] if len == 0:
200 // pass dummy pointer in that case.
201 // Used to pass nil, but some OSes or simulators reject write(fd, nil, 0).
202 text += fmt.Sprintf("\tvar _p%d unsafe.Pointer\n", n)
203 text += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = unsafe.Pointer(&%s[0])\n\t}", p.Name, n, p.Name)
204 text += fmt.Sprintf(" else {\n\t\t_p%d = unsafe.Pointer(&_zero)\n\t}\n", n)
205 args = append(args, fmt.Sprintf("uintptr(_p%d)", n), fmt.Sprintf("uintptr(len(%s))", p.Name))
207 } else if p.Type == "int64" && (*openbsd || *netbsd) {
208 args = append(args, "0")
209 if endianness == "big-endian" {
210 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
211 } else if endianness == "little-endian" {
212 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
214 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
216 } else if p.Type == "int64" && *dragonfly {
217 if regexp.MustCompile(`^(?i)extp(read|write)`).FindStringSubmatch(funct) == nil {
218 args = append(args, "0")
220 if endianness == "big-endian" {
221 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
222 } else if endianness == "little-endian" {
223 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
225 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
227 } else if p.Type == "int64" && endianness != "" {
228 if len(args)%2 == 1 && *arm {
229 // arm abi specifies 64-bit argument uses
231 args = append(args, "0")
233 if endianness == "big-endian" {
234 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
236 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
239 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
243 // Determine which form to use; pad args with zeros.
246 if errvar == "" && goos == "linux" {
247 asm = "RawSyscallNoError"
252 if errvar == "" && goos == "linux" {
253 asm = "SyscallNoError"
258 args = append(args, "0")
260 } else if len(args) <= 6 {
263 args = append(args, "0")
265 } else if len(args) <= 9 {
268 args = append(args, "0")
271 fmt.Fprintf(os.Stderr, "%s:%s too many arguments to system call\n", path, funct)
274 // System call number.
276 sysname = "SYS_" + funct
277 sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
278 sysname = strings.ToUpper(sysname)
283 asm = "syscall_" + strings.ToLower(asm[:1]) + asm[1:] // internal syscall call
284 sysname = strings.TrimPrefix(sysname, "SYS_") // remove SYS_
285 sysname = strings.ToLower(sysname) // lowercase
286 if sysname == "getdirentries64" {
287 // Special case - libSystem name and
288 // raw syscall name don't match.
289 sysname = "__getdirentries64"
292 sysname = "funcPC(libc_" + sysname + "_trampoline)"
296 arglist := strings.Join(args, ", ")
297 call := fmt.Sprintf("%s(%s, %s)", asm, sysname, arglist)
299 // Assign return values.
301 ret := []string{"_", "_", "_"}
303 for i := 0; i < len(out); i++ {
304 p := parseParam(out[i])
306 if p.Name == "err" && !*plan9 {
310 } else if p.Name == "err" && *plan9 {
315 reg = fmt.Sprintf("r%d", i)
318 if p.Type == "bool" {
319 reg = fmt.Sprintf("%s != 0", reg)
321 if p.Type == "int64" && endianness != "" {
322 // 64-bit number in r1:r0 or r0:r1.
324 fmt.Fprintf(os.Stderr, "%s:%s not enough registers for int64 return\n", path, funct)
326 if endianness == "big-endian" {
327 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
329 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
331 ret[i] = fmt.Sprintf("r%d", i)
332 ret[i+1] = fmt.Sprintf("r%d", i+1)
334 if reg != "e1" || *plan9 {
335 body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
338 if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
339 text += fmt.Sprintf("\t%s\n", call)
341 if errvar == "" && goos == "linux" {
342 // raw syscall without error on Linux, see golang.org/issue/22924
343 text += fmt.Sprintf("\t%s, %s := %s\n", ret[0], ret[1], call)
345 text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
350 if *plan9 && ret[2] == "e1" {
351 text += "\tif int32(r0) == -1 {\n"
352 text += "\t\terr = e1\n"
355 text += "\tif e1 != 0 {\n"
356 text += "\t\terr = errnoErr(e1)\n"
362 if libc && !trampolines[libcFn] {
363 // some system calls share a trampoline, like read and readlen.
364 trampolines[libcFn] = true
365 // Declare assembly trampoline.
366 text += fmt.Sprintf("func libc_%s_trampoline()\n", libcFn)
367 // Assembly trampoline calls the libc_* function, which this magic
368 // redirects to use the function from libSystem.
369 text += fmt.Sprintf("//go:linkname libc_%s libc_%s\n", libcFn, libcFn)
370 text += fmt.Sprintf("//go:cgo_import_dynamic libc_%s %s \"/usr/lib/libSystem.B.dylib\"\n", libcFn, libcFn)
374 if err := s.Err(); err != nil {
375 fmt.Fprintf(os.Stderr, err.Error())
380 fmt.Printf(srcTemplate, cmdLine(), buildTags(), text)
383 const srcTemplate = `// %s
384 // Code generated by the command above; see README.md. DO NOT EDIT.