github.com/Kalvelign/golang-windows-sys-lib@v0.0.0-20221121121202-63da651435e1/plan9/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_plan9.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 buildTags() 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  	// Get the OS and architecture (using GOARCH_TARGET if it exists)
    96  	goos := os.Getenv("GOOS")
    97  	goarch := os.Getenv("GOARCH_TARGET")
    98  	if goarch == "" {
    99  		goarch = os.Getenv("GOARCH")
   100  	}
   101  
   102  	// Check that we are using the Docker-based build system if we should
   103  	if goos == "linux" {
   104  		if os.Getenv("GOLANG_SYS_BUILD") != "docker" {
   105  			fmt.Fprintf(os.Stderr, "In the Docker-based build system, mksyscall should not be called directly.\n")
   106  			fmt.Fprintf(os.Stderr, "See README.md\n")
   107  			os.Exit(1)
   108  		}
   109  	}
   110  
   111  	flag.Usage = usage
   112  	flag.Parse()
   113  	if len(flag.Args()) <= 0 {
   114  		fmt.Fprintf(os.Stderr, "no files to parse provided\n")
   115  		usage()
   116  	}
   117  
   118  	endianness := ""
   119  	if *b32 {
   120  		endianness = "big-endian"
   121  	} else if *l32 {
   122  		endianness = "little-endian"
   123  	}
   124  
   125  	libc := false
   126  	if goos == "darwin" && strings.Contains(buildTags(), ",go1.12") {
   127  		libc = true
   128  	}
   129  	trampolines := map[string]bool{}
   130  
   131  	text := ""
   132  	for _, path := range flag.Args() {
   133  		file, err := os.Open(path)
   134  		if err != nil {
   135  			fmt.Fprintf(os.Stderr, err.Error())
   136  			os.Exit(1)
   137  		}
   138  		s := bufio.NewScanner(file)
   139  		for s.Scan() {
   140  			t := s.Text()
   141  			t = strings.TrimSpace(t)
   142  			t = regexp.MustCompile(`\s+`).ReplaceAllString(t, ` `)
   143  			nonblock := regexp.MustCompile(`^\/\/sysnb `).FindStringSubmatch(t)
   144  			if regexp.MustCompile(`^\/\/sys `).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)? (\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" && 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  				if sysname == "getdirentries64" {
   293  					// Special case - libSystem name and
   294  					// raw syscall name don't match.
   295  					sysname = "__getdirentries64"
   296  				}
   297  				libcFn = sysname
   298  				sysname = "funcPC(libc_" + sysname + "_trampoline)"
   299  			}
   300  
   301  			// Actual call.
   302  			arglist := strings.Join(args, ", ")
   303  			call := fmt.Sprintf("%s(%s, %s)", asm, sysname, arglist)
   304  
   305  			// Assign return values.
   306  			body := ""
   307  			ret := []string{"_", "_", "_"}
   308  			doErrno := false
   309  			for i := 0; i < len(out); i++ {
   310  				p := parseParam(out[i])
   311  				reg := ""
   312  				if p.Name == "err" && !*plan9 {
   313  					reg = "e1"
   314  					ret[2] = reg
   315  					doErrno = true
   316  				} else if p.Name == "err" && *plan9 {
   317  					ret[0] = "r0"
   318  					ret[2] = "e1"
   319  					break
   320  				} else {
   321  					reg = fmt.Sprintf("r%d", i)
   322  					ret[i] = reg
   323  				}
   324  				if p.Type == "bool" {
   325  					reg = fmt.Sprintf("%s != 0", reg)
   326  				}
   327  				if p.Type == "int64" && endianness != "" {
   328  					// 64-bit number in r1:r0 or r0:r1.
   329  					if i+2 > len(out) {
   330  						fmt.Fprintf(os.Stderr, "%s:%s not enough registers for int64 return\n", path, funct)
   331  					}
   332  					if endianness == "big-endian" {
   333  						reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i, i+1)
   334  					} else {
   335  						reg = fmt.Sprintf("int64(r%d)<<32 | int64(r%d)", i+1, i)
   336  					}
   337  					ret[i] = fmt.Sprintf("r%d", i)
   338  					ret[i+1] = fmt.Sprintf("r%d", i+1)
   339  				}
   340  				if reg != "e1" || *plan9 {
   341  					body += fmt.Sprintf("\t%s = %s(%s)\n", p.Name, p.Type, reg)
   342  				}
   343  			}
   344  			if ret[0] == "_" && ret[1] == "_" && ret[2] == "_" {
   345  				text += fmt.Sprintf("\t%s\n", call)
   346  			} else {
   347  				if errvar == "" && goos == "linux" {
   348  					// raw syscall without error on Linux, see golang.org/issue/22924
   349  					text += fmt.Sprintf("\t%s, %s := %s\n", ret[0], ret[1], call)
   350  				} else {
   351  					text += fmt.Sprintf("\t%s, %s, %s := %s\n", ret[0], ret[1], ret[2], call)
   352  				}
   353  			}
   354  			text += body
   355  
   356  			if *plan9 && ret[2] == "e1" {
   357  				text += "\tif int32(r0) == -1 {\n"
   358  				text += "\t\terr = e1\n"
   359  				text += "\t}\n"
   360  			} else if doErrno {
   361  				text += "\tif e1 != 0 {\n"
   362  				text += "\t\terr = errnoErr(e1)\n"
   363  				text += "\t}\n"
   364  			}
   365  			text += "\treturn\n"
   366  			text += "}\n\n"
   367  
   368  			if libc && !trampolines[libcFn] {
   369  				// some system calls share a trampoline, like read and readlen.
   370  				trampolines[libcFn] = true
   371  				// Declare assembly trampoline.
   372  				text += fmt.Sprintf("func libc_%s_trampoline()\n", libcFn)
   373  				// Assembly trampoline calls the libc_* function, which this magic
   374  				// redirects to use the function from libSystem.
   375  				text += fmt.Sprintf("//go:linkname libc_%s libc_%s\n", libcFn, libcFn)
   376  				text += fmt.Sprintf("//go:cgo_import_dynamic libc_%s %s \"/usr/lib/libSystem.B.dylib\"\n", libcFn, libcFn)
   377  				text += "\n"
   378  			}
   379  		}
   380  		if err := s.Err(); err != nil {
   381  			fmt.Fprintf(os.Stderr, err.Error())
   382  			os.Exit(1)
   383  		}
   384  		file.Close()
   385  	}
   386  	fmt.Printf(srcTemplate, cmdLine(), goBuildTags(), plusBuildTags(), text)
   387  }
   388  
   389  const srcTemplate = `// %s
   390  // Code generated by the command above; see README.md. DO NOT EDIT.
   391  
   392  //go:build %s
   393  // +build %s
   394  
   395  package plan9
   396  
   397  import "unsafe"
   398  
   399  %s
   400  `