golang.org/x/sys@v0.9.0/unix/mksyscall.go (about)

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