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