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