github.com/Andyfoo/golang/x/sys@v0.0.0-20190901054642-57c1bf301704/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  // +build ignore
     6  
     7  /*
     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.
    17  
    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
    22  hang.
    23  */
    24  package main
    25  
    26  import (
    27  	"bufio"
    28  	"flag"
    29  	"fmt"
    30  	"os"
    31  	"regexp"
    32  	"strings"
    33  )
    34  
    35  var (
    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
    48  func cmdLine() string {
    49  	return "go run mksyscall.go " + strings.Join(os.Args[1:], " ")
    50  }
    51  
    52  // buildTags returns build tags
    53  func buildTags() string {
    54  	return *tags
    55  }
    56  
    57  // Param is function parameter
    58  type Param struct {
    59  	Name string
    60  	Type string
    61  }
    62  
    63  // usage prints the program usage
    64  func 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
    70  func 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
    79  func 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  
    88  func 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  			// ClockGettime doesn't have a syscall number on Darwin, only generate libc wrappers.
   157  			if goos == "darwin" && !libc && funct == "ClockGettime" {
   158  				continue
   159  			}
   160  
   161  			// Split argument lists on comma.
   162  			in := parseParamList(inps)
   163  			out := parseParamList(outps)
   164  
   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"
   169  
   170  			// Go function header.
   171  			outDecl := ""
   172  			if len(out) > 0 {
   173  				outDecl = fmt.Sprintf(" (%s)", strings.Join(out, ", "))
   174  			}
   175  			text += fmt.Sprintf("func %s(%s)%s {\n", funct, strings.Join(in, ", "), outDecl)
   176  
   177  			// Check if err return available
   178  			errvar := ""
   179  			for _, param := range out {
   180  				p := parseParam(param)
   181  				if p.Type == "error" {
   182  					errvar = p.Name
   183  					break
   184  				}
   185  			}
   186  
   187  			// Prepare arguments to Syscall.
   188  			var args []string
   189  			n := 0
   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))
   199  					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))
   205  					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))
   215  					n++
   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))
   222  					} else {
   223  						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
   224  					}
   225  				} else if p.Type == "int64" && *dragonfly {
   226  					if regexp.MustCompile(`^(?i)extp(read|write)`).FindStringSubmatch(funct) == nil {
   227  						args = append(args, "0")
   228  					}
   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))
   233  					} else {
   234  						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
   235  					}
   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
   239  						// (even, odd) pair
   240  						args = append(args, "0")
   241  					}
   242  					if endianness == "big-endian" {
   243  						args = append(args, fmt.Sprintf("uintptr(%s>>32)", p.Name), fmt.Sprintf("uintptr(%s)", p.Name))
   244  					} else {
   245  						args = append(args, fmt.Sprintf("uintptr(%s)", p.Name), fmt.Sprintf("uintptr(%s>>32)", p.Name))
   246  					}
   247  				} else {
   248  					args = append(args, fmt.Sprintf("uintptr(%s)", p.Name))
   249  				}
   250  			}
   251  
   252  			// Determine which form to use; pad args with zeros.
   253  			asm := "Syscall"
   254  			if nonblock != nil {
   255  				if errvar == "" && goos == "linux" {
   256  					asm = "RawSyscallNoError"
   257  				} else {
   258  					asm = "RawSyscall"
   259  				}
   260  			} else {
   261  				if errvar == "" && goos == "linux" {
   262  					asm = "SyscallNoError"
   263  				}
   264  			}
   265  			if len(args) <= 3 {
   266  				for len(args) < 3 {
   267  					args = append(args, "0")
   268  				}
   269  			} else if len(args) <= 6 {
   270  				asm += "6"
   271  				for len(args) < 6 {
   272  					args = append(args, "0")
   273  				}
   274  			} else if len(args) <= 9 {
   275  				asm += "9"
   276  				for len(args) < 9 {
   277  					args = append(args, "0")
   278  				}
   279  			} else {
   280  				fmt.Fprintf(os.Stderr, "%s:%s too many arguments to system call\n", path, funct)
   281  			}
   282  
   283  			// System call number.
   284  			if sysname == "" {
   285  				sysname = "SYS_" + funct
   286  				sysname = regexp.MustCompile(`([a-z])([A-Z])`).ReplaceAllString(sysname, `${1}_$2`)
   287  				sysname = strings.ToUpper(sysname)
   288  			}
   289  
   290  			var libcFn string
   291  			if libc {
   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"
   299  				}
   300  				libcFn = sysname
   301  				sysname = "funcPC(libc_" + sysname + "_trampoline)"
   302  			}
   303  
   304  			// Actual call.
   305  			arglist := strings.Join(args, ", ")
   306  			call := fmt.Sprintf("%s(%s, %s)", asm, sysname, arglist)
   307  
   308  			// Assign return values.
   309  			body := ""
   310  			ret := []string{"_", "_", "_"}
   311  			doErrno := false
   312  			for i := 0; i < len(out); i++ {
   313  				p := parseParam(out[i])
   314  				reg := ""
   315  				if p.Name == "err" && !*plan9 {
   316  					reg = "e1"
   317  					ret[2] = reg
   318  					doErrno = true
   319  				} else if p.Name == "err" && *plan9 {
   320  					ret[0] = "r0"
   321  					ret[2] = "e1"
   322  					break
   323  				} else {
   324  					reg = fmt.Sprintf("r%d", i)
   325  					ret[i] = reg
   326  				}
   327  				if p.Type == "bool" {
   328  					reg = fmt.Sprintf("%s != 0", reg)
   329  				}
   330  				if p.Type == "int64" && endianness != "" {
   331  					// 64-bit number in r1:r0 or r0:r1.
   332  					if i+2 > len(out) {
   333  						fmt.Fprintf(os.Stderr, "%s:%s not enough registers for int64 return\n", path, funct)
   334  					}
   335  					if endianness == "big-endian" {
   336  						reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
   337  					} else {
   338  						reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
   339  					}
   340  					ret[i] = fmt.Sprintf("r%d", i)
   341  					ret[i+1] = fmt.Sprintf("r%d", i+1)
   342  				}
   343  				if reg != "e1" || *plan9 {
   344  					body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
   345  				}
   346  			}
   347  			if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
   348  				text += fmt.Sprintf("\t%s\n", call)
   349  			} else {
   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)
   353  				} else {
   354  					text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
   355  				}
   356  			}
   357  			text += body
   358  
   359  			if *plan9 && ret[2] == "e1" {
   360  				text += "\tif int32(r0) == -1 {\n"
   361  				text += "\t\terr = e1\n"
   362  				text += "\t}\n"
   363  			} else if doErrno {
   364  				text += "\tif e1 != 0 {\n"
   365  				text += "\t\terr = errnoErr(e1)\n"
   366  				text += "\t}\n"
   367  			}
   368  			text += "\treturn\n"
   369  			text += "}\n\n"
   370  
   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)
   380  				text += "\n"
   381  			}
   382  		}
   383  		if err := s.Err(); err != nil {
   384  			fmt.Fprintf(os.Stderr, err.Error())
   385  			os.Exit(1)
   386  		}
   387  		file.Close()
   388  	}
   389  	fmt.Printf(srcTemplate, cmdLine(), buildTags(), text)
   390  }
   391  
   392  const srcTemplate = `// %s
   393  // Code generated by the command above; see README.md. DO NOT EDIT.
   394  
   395  // +build %s
   396  
   397  package unix
   398  
   399  import (
   400  	"syscall"
   401  	"unsafe"
   402  )
   403  
   404  var _ syscall.Errno
   405  
   406  %s
   407  `