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 goos := os.Getenv("GOOS_TARGET")
91 goos = os.Getenv("GOOS")
94 fmt.Fprintln(os.Stderr, "GOOS not defined in environment")
98 // Check that we are using the Docker-based build system if we should
100 if os.Getenv("GOLANG_SYS_BUILD") != "docker" {
101 fmt.Fprintf(os.Stderr, "In the Docker-based build system, mksyscall should not be called directly.\n")
102 fmt.Fprintf(os.Stderr, "See README.md\n")
109 if len(flag.Args()) <= 0 {
110 fmt.Fprintf(os.Stderr, "no files to parse provided\n")
116 endianness = "big-endian"
118 endianness = "little-endian"
122 if goos == "darwin" {
125 trampolines := map[string]bool{}
128 for _, path := range flag.Args() {
129 file, err := os.Open(path)
131 fmt.Fprintf(os.Stderr, err.Error())
134 s := bufio.NewScanner(file)
137 t = strings.TrimSpace(t)
138 t = regexp.MustCompile(`\s+`).ReplaceAllString(t, ` `)
139 nonblock := regexp.MustCompile(`^\/\/sysnb `).FindStringSubmatch(t)
140 if regexp.MustCompile(`^\/\/sys `).FindStringSubmatch(t) == nil && nonblock == nil {
144 // Line must be of the form
145 // func Open(path string, mode int, perm int) (fd int, errno error)
146 // Split into name, in params, out params.
147 f := regexp.MustCompile(`^\/\/sys(nb)? (\w+)\(([^()]*)\)\s*(?:\(([^()]+)\))?\s*(?:=\s*((?i)SYS_[A-Z0-9_]+))?$`).FindStringSubmatch(t)
149 fmt.Fprintf(os.Stderr, "%s:%s\nmalformed //sys declaration\n", path, t)
152 funct, inps, outps, sysname := f[2], f[3], f[4], f[5]
154 // Split argument lists on comma.
155 in := parseParamList(inps)
156 out := parseParamList(outps)
158 // Try in vain to keep people from editing this file.
159 // The theory is that they jump into the middle of the file
160 // without reading the header.
161 text += "// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n\n"
163 // Go function header.
166 outDecl = fmt.Sprintf(" (%s)", strings.Join(out, ", "))
168 text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
170 // Check if err return available
172 for _, param := range out {
173 p := parseParam(param)
174 if p.Type == "error" {
180 // Prepare arguments to Syscall.
183 for _, param := range in {
184 p := parseParam(param)
185 if regexp.MustCompile(`^\*`).FindStringSubmatch(p.Type) != nil {
186 args = append(args, "uintptr(unsafe.Pointer("+p.Name+"))")
187 } else if p.Type == "string" && errvar != "" {
188 text += fmt.Sprintf("\tvar _p%d *byte\n", n)
189 text += fmt.Sprintf("\t_p%d, %s = BytePtrFromString(%s)\n", n, errvar, p.Name)
190 text += fmt.Sprintf("\tif %s != nil {\n\t\treturn\n\t}\n", errvar)
191 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
193 } else if p.Type == "string" {
194 fmt.Fprintf(os.Stderr, path+":"+funct+" uses string arguments, but has no error return\n")
195 text += fmt.Sprintf("\tvar _p%d *byte\n", n)
196 text += fmt.Sprintf("\t_p%d, _ = BytePtrFromString(%s)\n", n, p.Name)
197 args = append(args, fmt.Sprintf("uintptr(unsafe.Pointer(_p%d))", n))
199 } else if regexp.MustCompile(`^\[\](.*)`).FindStringSubmatch(p.Type) != nil {
200 // Convert slice into pointer, length.
201 // Have to be careful not to take address of &a[0] if len == 0:
202 // pass dummy pointer in that case.
203 // Used to pass nil, but some OSes or simulators reject write(fd, nil, 0).
204 text += fmt.Sprintf("\tvar _p%d unsafe.Pointer\n", n)
205 text += fmt.Sprintf("\tif len(%s) > 0 {\n\t\t_p%d = unsafe.Pointer(&%s[0])\n\t}", p.Name, n, p.Name)
206 text += fmt.Sprintf(" else {\n\t\t_p%d = unsafe.Pointer(&_zero)\n\t}\n", n)
207 args = append(args, fmt.Sprintf("uintptr(_p%d)", n), fmt.Sprintf("uintptr(len(%s))", p.Name))
209 } else if p.Type == "int64" && (*openbsd || *netbsd) {
210 args = append(args, "0")
211 if endianness == "big-endian" {
212 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
213 } else if endianness == "little-endian" {
214 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
216 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
218 } else if p.Type == "int64" && *dragonfly {
219 if regexp.MustCompile(`^(?i)extp(read|write)`).FindStringSubmatch(funct) == nil {
220 args = append(args, "0")
222 if endianness == "big-endian" {
223 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
224 } else if endianness == "little-endian" {
225 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
227 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
229 } else if (p.Type == "int64" || p.Type == "uint64") && endianness != "" {
230 if len(args)%2 == 1 && *arm {
231 // arm abi specifies 64-bit argument uses
233 args = append(args, "0")
235 if endianness == "big-endian" {
236 args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
238 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
241 args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
245 // Determine which form to use; pad args with zeros.
248 if errvar == "" && goos == "linux" {
249 asm = "RawSyscallNoError"
254 if errvar == "" && goos == "linux" {
255 asm = "SyscallNoError"
260 args = append(args, "0")
262 } else if len(args) <= 6 {
265 args = append(args, "0")
267 } else if len(args) <= 9 {
270 args = append(args, "0")
273 fmt.Fprintf(os.Stderr, "%s:%s too many arguments to system call\n", path, funct)
276 // System call number.
278 sysname = "SYS_" + funct
279 sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
280 sysname = strings.ToUpper(sysname)
285 asm = "syscall_" + strings.ToLower(asm[:1]) + asm[1:] // internal syscall call
286 sysname = strings.TrimPrefix(sysname, "SYS_") // remove SYS_
287 sysname = strings.ToLower(sysname) // lowercase
289 sysname = "funcPC(libc_" + sysname + "_trampoline)"
293 arglist := strings.Join(args, ", ")
294 call := fmt.Sprintf("%s(%s, %s)", asm, sysname, arglist)
296 // Assign return values.
298 ret := []string{"_", "_", "_"}
300 for i := 0; i < len(out); i++ {
301 p := parseParam(out[i])
303 if p.Name == "err" && !*plan9 {
307 } else if p.Name == "err" && *plan9 {
312 reg = fmt.Sprintf("r%d", i)
315 if p.Type == "bool" {
316 reg = fmt.Sprintf("%s != 0", reg)
318 if p.Type == "int64" && endianness != "" {
319 // 64-bit number in r1:r0 or r0:r1.
321 fmt.Fprintf(os.Stderr, "%s:%s not enough registers for int64 return\n", path, funct)
323 if endianness == "big-endian" {
324 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
326 reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
328 ret[i] = fmt.Sprintf("r%d", i)
329 ret[i+1] = fmt.Sprintf("r%d", i+1)
331 if reg != "e1" || *plan9 {
332 body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
335 if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
336 text += fmt.Sprintf("\t%s\n", call)
338 if errvar == "" && goos == "linux" {
339 // raw syscall without error on Linux, see golang.org/issue/22924
340 text += fmt.Sprintf("\t%s, %s := %s\n", ret[0], ret[1], call)
342 text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
347 if *plan9 && ret[2] == "e1" {
348 text += "\tif int32(r0) == -1 {\n"
349 text += "\t\terr = e1\n"
352 text += "\tif e1 != 0 {\n"
353 text += "\t\terr = errnoErr(e1)\n"
359 if libc && !trampolines[libcFn] {
360 // some system calls share a trampoline, like read and readlen.
361 trampolines[libcFn] = true
362 // Declare assembly trampoline.
363 text += fmt.Sprintf("func libc_%s_trampoline()\n", libcFn)
364 // Assembly trampoline calls the libc_* function, which this magic
365 // redirects to use the function from libSystem.
366 text += fmt.Sprintf("//go:cgo_import_dynamic libc_%s %s \"/usr/lib/libSystem.B.dylib\"\n", libcFn, libcFn)
370 if err := s.Err(); err != nil {
371 fmt.Fprintf(os.Stderr, err.Error())
376 fmt.Printf(srcTemplate, cmdLine(), buildTags(), text)
379 const srcTemplate = `// %s
380 // Code generated by the command above; see README.md. DO NOT EDIT.