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