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