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