github.com/hbdrawn/golang@v0.0.0-20141214014649-6b835209aba2/src/syscall/mksyscall_windows.go (about) 1 // Copyright 2013 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 // +build ignore 6 7 /* 8 mksyscall_windows generates windows system call bodies 9 10 It parses all files specified on command line containing function 11 prototypes (like syscall_windows.go) and prints system call bodies 12 to standard output. 13 14 The prototypes are marked by lines beginning with "//sys" and read 15 like func declarations if //sys is replaced by func, but: 16 17 * The parameter lists must give a name for each argument. This 18 includes return parameters. 19 20 * The parameter lists must give a type for each argument: 21 the (x, y, z int) shorthand is not allowed. 22 23 * If the return parameter is an error number, it must be named err. 24 25 * If go func name needs to be different from it's winapi dll name, 26 the winapi name could be specified at the end, after "=" sign, like 27 //sys LoadLibrary(libname string) (handle uint32, err error) = LoadLibraryA 28 29 * Each function that returns err needs to supply a condition, that 30 return value of winapi will be tested against to detect failure. 31 This would set err to windows "last-error", otherwise it will be nil. 32 The value can be provided at end of //sys declaration, like 33 //sys LoadLibrary(libname string) (handle uint32, err error) [failretval==-1] = LoadLibraryA 34 and is [failretval==0] by default. 35 36 Usage: 37 mksyscall_windows [flags] [path ...] 38 39 The flags are: 40 -trace 41 Generate print statement after every syscall. 42 */ 43 package main 44 45 import ( 46 "bufio" 47 "errors" 48 "flag" 49 "fmt" 50 "go/parser" 51 "go/token" 52 "io" 53 "log" 54 "os" 55 "strconv" 56 "strings" 57 "text/template" 58 ) 59 60 var PrintTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall") 61 62 func trim(s string) string { 63 return strings.Trim(s, " \t") 64 } 65 66 var packageName string 67 68 func packagename() string { 69 return packageName 70 } 71 72 func syscalldot() string { 73 if packageName == "syscall" { 74 return "" 75 } 76 return "syscall." 77 } 78 79 // Param is function parameter 80 type Param struct { 81 Name string 82 Type string 83 fn *Fn 84 tmpVarIdx int 85 } 86 87 // tmpVar returns temp variable name that will be used to represent p during syscall. 88 func (p *Param) tmpVar() string { 89 if p.tmpVarIdx < 0 { 90 p.tmpVarIdx = p.fn.curTmpVarIdx 91 p.fn.curTmpVarIdx++ 92 } 93 return fmt.Sprintf("_p%d", p.tmpVarIdx) 94 } 95 96 // BoolTmpVarCode returns source code for bool temp variable. 97 func (p *Param) BoolTmpVarCode() string { 98 const code = `var %s uint32 99 if %s { 100 %s = 1 101 } else { 102 %s = 0 103 }` 104 tmp := p.tmpVar() 105 return fmt.Sprintf(code, tmp, p.Name, tmp, tmp) 106 } 107 108 // SliceTmpVarCode returns source code for slice temp variable. 109 func (p *Param) SliceTmpVarCode() string { 110 const code = `var %s *%s 111 if len(%s) > 0 { 112 %s = &%s[0] 113 }` 114 tmp := p.tmpVar() 115 return fmt.Sprintf(code, tmp, p.Type[2:], p.Name, tmp, p.Name) 116 } 117 118 // StringTmpVarCode returns source code for string temp variable. 119 func (p *Param) StringTmpVarCode() string { 120 errvar := p.fn.Rets.ErrorVarName() 121 if errvar == "" { 122 errvar = "_" 123 } 124 tmp := p.tmpVar() 125 const code = `var %s %s 126 %s, %s = %s(%s)` 127 s := fmt.Sprintf(code, tmp, p.fn.StrconvType(), tmp, errvar, p.fn.StrconvFunc(), p.Name) 128 if errvar == "-" { 129 return s 130 } 131 const morecode = ` 132 if %s != nil { 133 return 134 }` 135 return s + fmt.Sprintf(morecode, errvar) 136 } 137 138 // TmpVarCode returns source code for temp variable. 139 func (p *Param) TmpVarCode() string { 140 switch { 141 case p.Type == "bool": 142 return p.BoolTmpVarCode() 143 case strings.HasPrefix(p.Type, "[]"): 144 return p.SliceTmpVarCode() 145 default: 146 return "" 147 } 148 } 149 150 // TmpVarHelperCode returns source code for helper's temp variable. 151 func (p *Param) TmpVarHelperCode() string { 152 if p.Type != "string" { 153 return "" 154 } 155 return p.StringTmpVarCode() 156 } 157 158 // SyscallArgList returns source code fragments representing p parameter 159 // in syscall. Slices are translated into 2 syscall parameters: pointer to 160 // the first element and length. 161 func (p *Param) SyscallArgList() []string { 162 t := p.HelperType() 163 var s string 164 switch { 165 case t[0] == '*': 166 s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name) 167 case t == "bool": 168 s = p.tmpVar() 169 case strings.HasPrefix(t, "[]"): 170 return []string{ 171 fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.tmpVar()), 172 fmt.Sprintf("uintptr(len(%s))", p.Name), 173 } 174 default: 175 s = p.Name 176 } 177 return []string{fmt.Sprintf("uintptr(%s)", s)} 178 } 179 180 // IsError determines if p parameter is used to return error. 181 func (p *Param) IsError() bool { 182 return p.Name == "err" && p.Type == "error" 183 } 184 185 // HelperType returns type of parameter p used in helper function. 186 func (p *Param) HelperType() string { 187 if p.Type == "string" { 188 return p.fn.StrconvType() 189 } 190 return p.Type 191 } 192 193 // join concatenates parameters ps into a string with sep separator. 194 // Each parameter is converted into string by applying fn to it 195 // before conversion. 196 func join(ps []*Param, fn func(*Param) string, sep string) string { 197 if len(ps) == 0 { 198 return "" 199 } 200 a := make([]string, 0) 201 for _, p := range ps { 202 a = append(a, fn(p)) 203 } 204 return strings.Join(a, sep) 205 } 206 207 // Rets describes function return parameters. 208 type Rets struct { 209 Name string 210 Type string 211 ReturnsError bool 212 FailCond string 213 } 214 215 // ErrorVarName returns error variable name for r. 216 func (r *Rets) ErrorVarName() string { 217 if r.ReturnsError { 218 return "err" 219 } 220 if r.Type == "error" { 221 return r.Name 222 } 223 return "" 224 } 225 226 // ToParams converts r into slice of *Param. 227 func (r *Rets) ToParams() []*Param { 228 ps := make([]*Param, 0) 229 if len(r.Name) > 0 { 230 ps = append(ps, &Param{Name: r.Name, Type: r.Type}) 231 } 232 if r.ReturnsError { 233 ps = append(ps, &Param{Name: "err", Type: "error"}) 234 } 235 return ps 236 } 237 238 // List returns source code of syscall return parameters. 239 func (r *Rets) List() string { 240 s := join(r.ToParams(), func(p *Param) string { return p.Name + " " + p.Type }, ", ") 241 if len(s) > 0 { 242 s = "(" + s + ")" 243 } 244 return s 245 } 246 247 // PrintList returns source code of trace printing part correspondent 248 // to syscall return values. 249 func (r *Rets) PrintList() string { 250 return join(r.ToParams(), func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) 251 } 252 253 // SetReturnValuesCode returns source code that accepts syscall return values. 254 func (r *Rets) SetReturnValuesCode() string { 255 if r.Name == "" && !r.ReturnsError { 256 return "" 257 } 258 retvar := "r0" 259 if r.Name == "" { 260 retvar = "r1" 261 } 262 errvar := "_" 263 if r.ReturnsError { 264 errvar = "e1" 265 } 266 return fmt.Sprintf("%s, _, %s := ", retvar, errvar) 267 } 268 269 func (r *Rets) useLongHandleErrorCode(retvar string) string { 270 const code = `if %s { 271 if e1 != 0 { 272 err = error(e1) 273 } else { 274 err = %sEINVAL 275 } 276 }` 277 cond := retvar + " == 0" 278 if r.FailCond != "" { 279 cond = strings.Replace(r.FailCond, "failretval", retvar, 1) 280 } 281 return fmt.Sprintf(code, cond, syscalldot()) 282 } 283 284 // SetErrorCode returns source code that sets return parameters. 285 func (r *Rets) SetErrorCode() string { 286 const code = `if r0 != 0 { 287 %s = %sErrno(r0) 288 }` 289 if r.Name == "" && !r.ReturnsError { 290 return "" 291 } 292 if r.Name == "" { 293 return r.useLongHandleErrorCode("r1") 294 } 295 if r.Type == "error" { 296 return fmt.Sprintf(code, r.Name, syscalldot()) 297 } 298 s := "" 299 switch { 300 case r.Type[0] == '*': 301 s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type) 302 case r.Type == "bool": 303 s = fmt.Sprintf("%s = r0 != 0", r.Name) 304 default: 305 s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type) 306 } 307 if !r.ReturnsError { 308 return s 309 } 310 return s + "\n\t" + r.useLongHandleErrorCode(r.Name) 311 } 312 313 // Fn describes syscall function. 314 type Fn struct { 315 Name string 316 Params []*Param 317 Rets *Rets 318 PrintTrace bool 319 dllname string 320 dllfuncname string 321 src string 322 // TODO: get rid of this field and just use parameter index instead 323 curTmpVarIdx int // insure tmp variables have uniq names 324 } 325 326 // extractParams parses s to extract function parameters. 327 func extractParams(s string, f *Fn) ([]*Param, error) { 328 s = trim(s) 329 if s == "" { 330 return nil, nil 331 } 332 a := strings.Split(s, ",") 333 ps := make([]*Param, len(a)) 334 for i := range ps { 335 s2 := trim(a[i]) 336 b := strings.Split(s2, " ") 337 if len(b) != 2 { 338 b = strings.Split(s2, "\t") 339 if len(b) != 2 { 340 return nil, errors.New("Could not extract function parameter from \"" + s2 + "\"") 341 } 342 } 343 ps[i] = &Param{ 344 Name: trim(b[0]), 345 Type: trim(b[1]), 346 fn: f, 347 tmpVarIdx: -1, 348 } 349 } 350 return ps, nil 351 } 352 353 // extractSection extracts text out of string s starting after start 354 // and ending just before end. found return value will indicate success, 355 // and prefix, body and suffix will contain correspondent parts of string s. 356 func extractSection(s string, start, end rune) (prefix, body, suffix string, found bool) { 357 s = trim(s) 358 if strings.HasPrefix(s, string(start)) { 359 // no prefix 360 body = s[1:] 361 } else { 362 a := strings.SplitN(s, string(start), 2) 363 if len(a) != 2 { 364 return "", "", s, false 365 } 366 prefix = a[0] 367 body = a[1] 368 } 369 a := strings.SplitN(body, string(end), 2) 370 if len(a) != 2 { 371 return "", "", "", false 372 } 373 return prefix, a[0], a[1], true 374 } 375 376 // newFn parses string s and return created function Fn. 377 func newFn(s string) (*Fn, error) { 378 s = trim(s) 379 f := &Fn{ 380 Rets: &Rets{}, 381 src: s, 382 PrintTrace: *PrintTraceFlag, 383 } 384 // function name and args 385 prefix, body, s, found := extractSection(s, '(', ')') 386 if !found || prefix == "" { 387 return nil, errors.New("Could not extract function name and parameters from \"" + f.src + "\"") 388 } 389 f.Name = prefix 390 var err error 391 f.Params, err = extractParams(body, f) 392 if err != nil { 393 return nil, err 394 } 395 // return values 396 _, body, s, found = extractSection(s, '(', ')') 397 if found { 398 r, err := extractParams(body, f) 399 if err != nil { 400 return nil, err 401 } 402 switch len(r) { 403 case 0: 404 case 1: 405 if r[0].IsError() { 406 f.Rets.ReturnsError = true 407 } else { 408 f.Rets.Name = r[0].Name 409 f.Rets.Type = r[0].Type 410 } 411 case 2: 412 if !r[1].IsError() { 413 return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"") 414 } 415 f.Rets.ReturnsError = true 416 f.Rets.Name = r[0].Name 417 f.Rets.Type = r[0].Type 418 default: 419 return nil, errors.New("Too many return values in \"" + f.src + "\"") 420 } 421 } 422 // fail condition 423 _, body, s, found = extractSection(s, '[', ']') 424 if found { 425 f.Rets.FailCond = body 426 } 427 // dll and dll function names 428 s = trim(s) 429 if s == "" { 430 return f, nil 431 } 432 if !strings.HasPrefix(s, "=") { 433 return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") 434 } 435 s = trim(s[1:]) 436 a := strings.Split(s, ".") 437 switch len(a) { 438 case 1: 439 f.dllfuncname = a[0] 440 case 2: 441 f.dllname = a[0] 442 f.dllfuncname = a[1] 443 default: 444 return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") 445 } 446 return f, nil 447 } 448 449 // DLLName returns DLL name for function f. 450 func (f *Fn) DLLName() string { 451 if f.dllname == "" { 452 return "kernel32" 453 } 454 return f.dllname 455 } 456 457 // DLLName returns DLL function name for function f. 458 func (f *Fn) DLLFuncName() string { 459 if f.dllfuncname == "" { 460 return f.Name 461 } 462 return f.dllfuncname 463 } 464 465 // ParamList returns source code for function f parameters. 466 func (f *Fn) ParamList() string { 467 return join(f.Params, func(p *Param) string { return p.Name + " " + p.Type }, ", ") 468 } 469 470 // HelperParamList returns source code for helper function f parameters. 471 func (f *Fn) HelperParamList() string { 472 return join(f.Params, func(p *Param) string { return p.Name + " " + p.HelperType() }, ", ") 473 } 474 475 // ParamPrintList returns source code of trace printing part correspondent 476 // to syscall input parameters. 477 func (f *Fn) ParamPrintList() string { 478 return join(f.Params, func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) 479 } 480 481 // ParamCount return number of syscall parameters for function f. 482 func (f *Fn) ParamCount() int { 483 n := 0 484 for _, p := range f.Params { 485 n += len(p.SyscallArgList()) 486 } 487 return n 488 } 489 490 // SyscallParamCount determines which version of Syscall/Syscall6/Syscall9/... 491 // to use. It returns parameter count for correspondent SyscallX function. 492 func (f *Fn) SyscallParamCount() int { 493 n := f.ParamCount() 494 switch { 495 case n <= 3: 496 return 3 497 case n <= 6: 498 return 6 499 case n <= 9: 500 return 9 501 case n <= 12: 502 return 12 503 case n <= 15: 504 return 15 505 default: 506 panic("too many arguments to system call") 507 } 508 } 509 510 // Syscall determines which SyscallX function to use for function f. 511 func (f *Fn) Syscall() string { 512 c := f.SyscallParamCount() 513 if c == 3 { 514 return syscalldot() + "Syscall" 515 } 516 return syscalldot() + "Syscall" + strconv.Itoa(c) 517 } 518 519 // SyscallParamList returns source code for SyscallX parameters for function f. 520 func (f *Fn) SyscallParamList() string { 521 a := make([]string, 0) 522 for _, p := range f.Params { 523 a = append(a, p.SyscallArgList()...) 524 } 525 for len(a) < f.SyscallParamCount() { 526 a = append(a, "0") 527 } 528 return strings.Join(a, ", ") 529 } 530 531 // HelperCallParamList returns source code of call into function f helper. 532 func (f *Fn) HelperCallParamList() string { 533 a := make([]string, 0, len(f.Params)) 534 for _, p := range f.Params { 535 s := p.Name 536 if p.Type == "string" { 537 s = p.tmpVar() 538 } 539 a = append(a, s) 540 } 541 return strings.Join(a, ", ") 542 } 543 544 // IsUTF16 is true, if f is W (utf16) function. It is false 545 // for all A (ascii) functions. 546 func (f *Fn) IsUTF16() bool { 547 s := f.DLLFuncName() 548 return s[len(s)-1] == 'W' 549 } 550 551 // StrconvFunc returns name of Go string to OS string function for f. 552 func (f *Fn) StrconvFunc() string { 553 if f.IsUTF16() { 554 return syscalldot() + "UTF16PtrFromString" 555 } 556 return syscalldot() + "BytePtrFromString" 557 } 558 559 // StrconvType returns Go type name used for OS string for f. 560 func (f *Fn) StrconvType() string { 561 if f.IsUTF16() { 562 return "*uint16" 563 } 564 return "*byte" 565 } 566 567 // HasStringParam is true, if f has at least one string parameter. 568 // Otherwise it is false. 569 func (f *Fn) HasStringParam() bool { 570 for _, p := range f.Params { 571 if p.Type == "string" { 572 return true 573 } 574 } 575 return false 576 } 577 578 // HelperName returns name of function f helper. 579 func (f *Fn) HelperName() string { 580 if !f.HasStringParam() { 581 return f.Name 582 } 583 return "_" + f.Name 584 } 585 586 // Source files and functions. 587 type Source struct { 588 Funcs []*Fn 589 Files []string 590 } 591 592 // ParseFiles parses files listed in fs and extracts all syscall 593 // functions listed in sys comments. It returns source files 594 // and functions collection *Source if successful. 595 func ParseFiles(fs []string) (*Source, error) { 596 src := &Source{ 597 Funcs: make([]*Fn, 0), 598 Files: make([]string, 0), 599 } 600 for _, file := range fs { 601 if err := src.ParseFile(file); err != nil { 602 return nil, err 603 } 604 } 605 return src, nil 606 } 607 608 // DLLs return dll names for a source set src. 609 func (src *Source) DLLs() []string { 610 uniq := make(map[string]bool) 611 r := make([]string, 0) 612 for _, f := range src.Funcs { 613 name := f.DLLName() 614 if _, found := uniq[name]; !found { 615 uniq[name] = true 616 r = append(r, name) 617 } 618 } 619 return r 620 } 621 622 // ParseFile adds adition file path to a source set src. 623 func (src *Source) ParseFile(path string) error { 624 file, err := os.Open(path) 625 if err != nil { 626 return err 627 } 628 defer file.Close() 629 630 s := bufio.NewScanner(file) 631 for s.Scan() { 632 t := trim(s.Text()) 633 if len(t) < 7 { 634 continue 635 } 636 if !strings.HasPrefix(t, "//sys") { 637 continue 638 } 639 t = t[5:] 640 if !(t[0] == ' ' || t[0] == '\t') { 641 continue 642 } 643 f, err := newFn(t[1:]) 644 if err != nil { 645 return err 646 } 647 src.Funcs = append(src.Funcs, f) 648 } 649 if err := s.Err(); err != nil { 650 return err 651 } 652 src.Files = append(src.Files, path) 653 654 // get package name 655 fset := token.NewFileSet() 656 _, err = file.Seek(0, 0) 657 if err != nil { 658 return err 659 } 660 pkg, err := parser.ParseFile(fset, "", file, parser.PackageClauseOnly) 661 if err != nil { 662 return err 663 } 664 packageName = pkg.Name.Name 665 666 return nil 667 } 668 669 // Generate output source file from a source set src. 670 func (src *Source) Generate(w io.Writer) error { 671 funcMap := template.FuncMap{ 672 "syscalldot": syscalldot, 673 "packagename": packagename, 674 } 675 t := template.Must(template.New("main").Funcs(funcMap).Parse(srcTemplate)) 676 err := t.Execute(w, src) 677 if err != nil { 678 return errors.New("Failed to execute template: " + err.Error()) 679 } 680 return nil 681 } 682 683 func usage() { 684 fmt.Fprintf(os.Stderr, "usage: mksyscall_windows [flags] [path ...]\n") 685 flag.PrintDefaults() 686 os.Exit(1) 687 } 688 689 func main() { 690 flag.Usage = usage 691 flag.Parse() 692 if len(os.Args) <= 1 { 693 fmt.Fprintf(os.Stderr, "no files to parse provided\n") 694 usage() 695 } 696 src, err := ParseFiles(os.Args[1:]) 697 if err != nil { 698 log.Fatal(err) 699 } 700 if err := src.Generate(os.Stdout); err != nil { 701 log.Fatal(err) 702 } 703 } 704 705 // TODO: use println instead to print in the following template 706 const srcTemplate = ` 707 708 {{define "main"}}// go build mksyscall_windows.go && ./mksyscall_windows{{range .Files}} {{.}}{{end}} 709 // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT 710 711 package {{packagename}} 712 713 import "unsafe"{{if syscalldot}} 714 import "syscall"{{end}} 715 716 var ( 717 {{template "dlls" .}} 718 {{template "funcnames" .}}) 719 {{range .Funcs}}{{if .HasStringParam}}{{template "helperbody" .}}{{end}}{{template "funcbody" .}}{{end}} 720 {{end}} 721 722 {{/* help functions */}} 723 724 {{define "dlls"}}{{range .DLLs}} mod{{.}} = {{syscalldot}}NewLazyDLL("{{.}}.dll") 725 {{end}}{{end}} 726 727 {{define "funcnames"}}{{range .Funcs}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}") 728 {{end}}{{end}} 729 730 {{define "helperbody"}} 731 func {{.Name}}({{.ParamList}}) {{template "results" .}}{ 732 {{template "helpertmpvars" .}} return {{.HelperName}}({{.HelperCallParamList}}) 733 } 734 {{end}} 735 736 {{define "funcbody"}} 737 func {{.HelperName}}({{.HelperParamList}}) {{template "results" .}}{ 738 {{template "tmpvars" .}} {{template "syscall" .}} 739 {{template "seterror" .}}{{template "printtrace" .}} return 740 } 741 {{end}} 742 743 {{define "helpertmpvars"}}{{range .Params}}{{if .TmpVarHelperCode}} {{.TmpVarHelperCode}} 744 {{end}}{{end}}{{end}} 745 746 {{define "tmpvars"}}{{range .Params}}{{if .TmpVarCode}} {{.TmpVarCode}} 747 {{end}}{{end}}{{end}} 748 749 {{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}} 750 751 {{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.ParamCount}}, {{.SyscallParamList}}){{end}} 752 753 {{define "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}} 754 {{end}}{{end}} 755 756 {{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n") 757 {{end}}{{end}} 758 759 `