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