golang.org/x/sys@v0.9.0/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 // buildTags 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 `