github.com/mdempsky/go@v0.0.0-20151201204031-5dd372bd1e70/src/cmd/link/internal/ld/go.go (about) 1 // Copyright 2009 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-specific code shared across loaders (5l, 6l, 8l). 6 7 package ld 8 9 import ( 10 "bytes" 11 "cmd/internal/obj" 12 "fmt" 13 "os" 14 "strconv" 15 "strings" 16 ) 17 18 // go-specific code shared across loaders (5l, 6l, 8l). 19 20 // replace all "". with pkg. 21 func expandpkg(t0 string, pkg string) string { 22 return strings.Replace(t0, `"".`, pkg+".", -1) 23 } 24 25 // accumulate all type information from .6 files. 26 // check for inconsistencies. 27 28 // TODO: 29 // generate debugging section in binary. 30 // once the dust settles, try to move some code to 31 // libmach, so that other linkers and ar can share. 32 33 /* 34 * package import data 35 */ 36 type Import struct { 37 prefix string // "type", "var", "func", "const" 38 name string 39 def string 40 file string 41 } 42 43 // importmap records type information about imported symbols to detect inconsistencies. 44 // Entries are keyed by qualified symbol name (e.g., "runtime.Callers" or "net/url.Error"). 45 var importmap = map[string]*Import{} 46 47 func lookupImport(name string) *Import { 48 if x, ok := importmap[name]; ok { 49 return x 50 } 51 x := &Import{name: name} 52 importmap[name] = x 53 return x 54 } 55 56 func ldpkg(f *obj.Biobuf, pkg string, length int64, filename string, whence int) { 57 var p0, p1 int 58 59 if Debug['g'] != 0 { 60 return 61 } 62 63 if int64(int(length)) != length { 64 fmt.Fprintf(os.Stderr, "%s: too much pkg data in %s\n", os.Args[0], filename) 65 if Debug['u'] != 0 { 66 errorexit() 67 } 68 return 69 } 70 71 bdata := make([]byte, length) 72 if int64(obj.Bread(f, bdata)) != length { 73 fmt.Fprintf(os.Stderr, "%s: short pkg read %s\n", os.Args[0], filename) 74 if Debug['u'] != 0 { 75 errorexit() 76 } 77 return 78 } 79 data := string(bdata) 80 81 // first \n$$ marks beginning of exports - skip rest of line 82 p0 = strings.Index(data, "\n$$") 83 if p0 < 0 { 84 if Debug['u'] != 0 && whence != ArchiveObj { 85 Exitf("cannot find export data in %s", filename) 86 } 87 return 88 } 89 90 // \n$$B marks the beginning of binary export data - don't skip over the B 91 p0 += 3 92 for p0 < len(data) && data[p0] != '\n' && data[p0] != 'B' { 93 p0++ 94 } 95 96 // second marks end of exports / beginning of local data 97 p1 = strings.Index(data[p0:], "\n$$\n") 98 if p1 < 0 { 99 fmt.Fprintf(os.Stderr, "%s: cannot find end of exports in %s\n", os.Args[0], filename) 100 if Debug['u'] != 0 { 101 errorexit() 102 } 103 return 104 } 105 p1 += p0 106 107 for p0 < p1 && data[p0] != 'B' && (data[p0] == ' ' || data[p0] == '\t' || data[p0] == '\n') { 108 p0++ 109 } 110 // don't check this section if we have binary (B) export data 111 // TODO fix this eventually 112 if p0 < p1 && data[p0] != 'B' { 113 if !strings.HasPrefix(data[p0:], "package ") { 114 fmt.Fprintf(os.Stderr, "%s: bad package section in %s - %.20s\n", os.Args[0], filename, data[p0:]) 115 if Debug['u'] != 0 { 116 errorexit() 117 } 118 return 119 } 120 121 p0 += 8 122 for p0 < p1 && (data[p0] == ' ' || data[p0] == '\t' || data[p0] == '\n') { 123 p0++ 124 } 125 pname := p0 126 for p0 < p1 && data[p0] != ' ' && data[p0] != '\t' && data[p0] != '\n' { 127 p0++ 128 } 129 if Debug['u'] != 0 && whence != ArchiveObj && (p0+6 > p1 || !strings.HasPrefix(data[p0:], " safe\n")) { 130 Exitf("load of unsafe package %s", filename) 131 } 132 133 name := data[pname:p0] 134 for p0 < p1 && data[p0] != '\n' { 135 p0++ 136 } 137 if p0 < p1 { 138 p0++ 139 } 140 141 if pkg == "main" && name != "main" { 142 Exitf("%s: not package main (package %s)", filename, name) 143 } 144 145 loadpkgdata(filename, pkg, data[p0:p1]) 146 } 147 148 // __.PKGDEF has no cgo section - those are in the C compiler-generated object files. 149 if whence == Pkgdef { 150 return 151 } 152 153 // look for cgo section 154 p0 = strings.Index(data[p1:], "\n$$ // cgo") 155 if p0 >= 0 { 156 p0 += p1 157 i := strings.IndexByte(data[p0+1:], '\n') 158 if i < 0 { 159 fmt.Fprintf(os.Stderr, "%s: found $$ // cgo but no newline in %s\n", os.Args[0], filename) 160 if Debug['u'] != 0 { 161 errorexit() 162 } 163 return 164 } 165 p0 += 1 + i 166 167 p1 = strings.Index(data[p0:], "\n$$") 168 if p1 < 0 { 169 p1 = strings.Index(data[p0:], "\n!\n") 170 } 171 if p1 < 0 { 172 fmt.Fprintf(os.Stderr, "%s: cannot find end of // cgo section in %s\n", os.Args[0], filename) 173 if Debug['u'] != 0 { 174 errorexit() 175 } 176 return 177 } 178 p1 += p0 179 180 loadcgo(filename, pkg, data[p0:p1]) 181 } 182 } 183 184 func loadpkgdata(file string, pkg string, data string) { 185 var prefix string 186 var name string 187 var def string 188 189 p := data 190 for parsepkgdata(file, pkg, &p, &prefix, &name, &def) > 0 { 191 x := lookupImport(name) 192 if x.prefix == "" { 193 x.prefix = prefix 194 x.def = def 195 x.file = file 196 } else if x.prefix != prefix { 197 fmt.Fprintf(os.Stderr, "%s: conflicting definitions for %s\n", os.Args[0], name) 198 fmt.Fprintf(os.Stderr, "%s:\t%s %s ...\n", x.file, x.prefix, name) 199 fmt.Fprintf(os.Stderr, "%s:\t%s %s ...\n", file, prefix, name) 200 nerrors++ 201 } else if x.def != def { 202 fmt.Fprintf(os.Stderr, "%s: conflicting definitions for %s\n", os.Args[0], name) 203 fmt.Fprintf(os.Stderr, "%s:\t%s %s %s\n", x.file, x.prefix, name, x.def) 204 fmt.Fprintf(os.Stderr, "%s:\t%s %s %s\n", file, prefix, name, def) 205 nerrors++ 206 } 207 } 208 } 209 210 func parsepkgdata(file string, pkg string, pp *string, prefixp *string, namep *string, defp *string) int { 211 // skip white space 212 p := *pp 213 214 loop: 215 for len(p) > 0 && (p[0] == ' ' || p[0] == '\t' || p[0] == '\n') { 216 p = p[1:] 217 } 218 if len(p) == 0 || strings.HasPrefix(p, "$$\n") { 219 return 0 220 } 221 222 // prefix: (var|type|func|const) 223 prefix := p 224 225 if len(p) < 7 { 226 return -1 227 } 228 if strings.HasPrefix(p, "var ") { 229 p = p[4:] 230 } else if strings.HasPrefix(p, "type ") { 231 p = p[5:] 232 } else if strings.HasPrefix(p, "func ") { 233 p = p[5:] 234 } else if strings.HasPrefix(p, "const ") { 235 p = p[6:] 236 } else if strings.HasPrefix(p, "import ") { 237 p = p[7:] 238 for len(p) > 0 && p[0] != ' ' { 239 p = p[1:] 240 } 241 p = p[1:] 242 line := p 243 for len(p) > 0 && p[0] != '\n' { 244 p = p[1:] 245 } 246 if len(p) == 0 { 247 fmt.Fprintf(os.Stderr, "%s: %s: confused in import line\n", os.Args[0], file) 248 nerrors++ 249 return -1 250 } 251 line = line[:len(line)-len(p)] 252 line = strings.TrimSuffix(line, " // indirect") 253 path, err := strconv.Unquote(line) 254 if err != nil { 255 fmt.Fprintf(os.Stderr, "%s: %s: confused in import path: %q\n", os.Args[0], file, line) 256 nerrors++ 257 return -1 258 } 259 p = p[1:] 260 imported(pkg, path) 261 goto loop 262 } else { 263 fmt.Fprintf(os.Stderr, "%s: %s: confused in pkg data near <<%.40s>>\n", os.Args[0], file, prefix) 264 nerrors++ 265 return -1 266 } 267 268 prefix = prefix[:len(prefix)-len(p)-1] 269 270 // name: a.b followed by space 271 name := p 272 273 inquote := false 274 for len(p) > 0 { 275 if p[0] == ' ' && !inquote { 276 break 277 } 278 279 if p[0] == '\\' { 280 p = p[1:] 281 } else if p[0] == '"' { 282 inquote = !inquote 283 } 284 285 p = p[1:] 286 } 287 288 if len(p) == 0 { 289 return -1 290 } 291 name = name[:len(name)-len(p)] 292 p = p[1:] 293 294 // def: free form to new line 295 def := p 296 297 for len(p) > 0 && p[0] != '\n' { 298 p = p[1:] 299 } 300 if len(p) == 0 { 301 return -1 302 } 303 def = def[:len(def)-len(p)] 304 var defbuf *bytes.Buffer 305 p = p[1:] 306 307 // include methods on successive lines in def of named type 308 var meth string 309 for parsemethod(&p, &meth) > 0 { 310 if defbuf == nil { 311 defbuf = new(bytes.Buffer) 312 defbuf.WriteString(def) 313 } 314 defbuf.WriteString("\n\t") 315 defbuf.WriteString(meth) 316 } 317 if defbuf != nil { 318 def = defbuf.String() 319 } 320 321 name = expandpkg(name, pkg) 322 def = expandpkg(def, pkg) 323 324 // done 325 *pp = p 326 327 *prefixp = prefix 328 *namep = name 329 *defp = def 330 return 1 331 } 332 333 func parsemethod(pp *string, methp *string) int { 334 // skip white space 335 p := *pp 336 337 for len(p) > 0 && (p[0] == ' ' || p[0] == '\t') { 338 p = p[1:] 339 } 340 if len(p) == 0 { 341 return 0 342 } 343 344 // might be a comment about the method 345 if strings.HasPrefix(p, "//") { 346 goto useline 347 } 348 349 // if it says "func (", it's a method 350 if strings.HasPrefix(p, "func (") { 351 goto useline 352 } 353 return 0 354 355 // definition to end of line 356 useline: 357 *methp = p 358 359 for len(p) > 0 && p[0] != '\n' { 360 p = p[1:] 361 } 362 if len(p) == 0 { 363 fmt.Fprintf(os.Stderr, "%s: lost end of line in method definition\n", os.Args[0]) 364 *pp = "" 365 return -1 366 } 367 368 *methp = (*methp)[:len(*methp)-len(p)] 369 *pp = p[1:] 370 return 1 371 } 372 373 func loadcgo(file string, pkg string, p string) { 374 var next string 375 var q string 376 var f []string 377 var local string 378 var remote string 379 var lib string 380 var s *LSym 381 382 p0 := "" 383 for ; p != ""; p = next { 384 if i := strings.Index(p, "\n"); i >= 0 { 385 p, next = p[:i], p[i+1:] 386 } else { 387 next = "" 388 } 389 390 p0 = p // save for error message 391 f = tokenize(p) 392 if len(f) == 0 { 393 continue 394 } 395 396 if f[0] == "cgo_import_dynamic" { 397 if len(f) < 2 || len(f) > 4 { 398 goto err 399 } 400 401 local = f[1] 402 remote = local 403 if len(f) > 2 { 404 remote = f[2] 405 } 406 lib = "" 407 if len(f) > 3 { 408 lib = f[3] 409 } 410 411 if Debug['d'] != 0 { 412 fmt.Fprintf(os.Stderr, "%s: %s: cannot use dynamic imports with -d flag\n", os.Args[0], file) 413 nerrors++ 414 return 415 } 416 417 if local == "_" && remote == "_" { 418 // allow #pragma dynimport _ _ "foo.so" 419 // to force a link of foo.so. 420 havedynamic = 1 421 422 if HEADTYPE == obj.Hdarwin { 423 Machoadddynlib(lib) 424 } else { 425 dynlib = append(dynlib, lib) 426 } 427 continue 428 } 429 430 local = expandpkg(local, pkg) 431 q = "" 432 if i := strings.Index(remote, "#"); i >= 0 { 433 remote, q = remote[:i], remote[i+1:] 434 } 435 s = Linklookup(Ctxt, local, 0) 436 if local != f[1] { 437 } 438 if s.Type == 0 || s.Type == obj.SXREF || s.Type == obj.SHOSTOBJ { 439 s.Dynimplib = lib 440 s.Extname = remote 441 s.Dynimpvers = q 442 if s.Type != obj.SHOSTOBJ { 443 s.Type = obj.SDYNIMPORT 444 } 445 havedynamic = 1 446 } 447 448 continue 449 } 450 451 if f[0] == "cgo_import_static" { 452 if len(f) != 2 { 453 goto err 454 } 455 local = f[1] 456 s = Linklookup(Ctxt, local, 0) 457 s.Type = obj.SHOSTOBJ 458 s.Size = 0 459 continue 460 } 461 462 if f[0] == "cgo_export_static" || f[0] == "cgo_export_dynamic" { 463 if len(f) < 2 || len(f) > 3 { 464 goto err 465 } 466 local = f[1] 467 if len(f) > 2 { 468 remote = f[2] 469 } else { 470 remote = local 471 } 472 local = expandpkg(local, pkg) 473 s = Linklookup(Ctxt, local, 0) 474 475 switch Buildmode { 476 case BuildmodeCShared, BuildmodeCArchive: 477 if s == Linklookup(Ctxt, "main", 0) { 478 continue 479 } 480 } 481 482 // export overrides import, for openbsd/cgo. 483 // see issue 4878. 484 if s.Dynimplib != "" { 485 s.Dynimplib = "" 486 s.Extname = "" 487 s.Dynimpvers = "" 488 s.Type = 0 489 } 490 491 if s.Cgoexport == 0 { 492 s.Extname = remote 493 dynexp = append(dynexp, s) 494 } else if s.Extname != remote { 495 fmt.Fprintf(os.Stderr, "%s: conflicting cgo_export directives: %s as %s and %s\n", os.Args[0], s.Name, s.Extname, remote) 496 nerrors++ 497 return 498 } 499 500 if f[0] == "cgo_export_static" { 501 s.Cgoexport |= CgoExportStatic 502 } else { 503 s.Cgoexport |= CgoExportDynamic 504 } 505 if local != f[1] { 506 } 507 continue 508 } 509 510 if f[0] == "cgo_dynamic_linker" { 511 if len(f) != 2 { 512 goto err 513 } 514 515 if Debug['I'] == 0 { 516 if interpreter != "" && interpreter != f[1] { 517 fmt.Fprintf(os.Stderr, "%s: conflict dynlinker: %s and %s\n", os.Args[0], interpreter, f[1]) 518 nerrors++ 519 return 520 } 521 522 interpreter = f[1] 523 } 524 525 continue 526 } 527 528 if f[0] == "cgo_ldflag" { 529 if len(f) != 2 { 530 goto err 531 } 532 ldflag = append(ldflag, f[1]) 533 continue 534 } 535 } 536 537 return 538 539 err: 540 fmt.Fprintf(os.Stderr, "%s: %s: invalid dynimport line: %s\n", os.Args[0], file, p0) 541 nerrors++ 542 } 543 544 var seenlib = make(map[string]bool) 545 546 func adddynlib(lib string) { 547 if seenlib[lib] || Linkmode == LinkExternal { 548 return 549 } 550 seenlib[lib] = true 551 552 if Iself { 553 s := Linklookup(Ctxt, ".dynstr", 0) 554 if s.Size == 0 { 555 Addstring(s, "") 556 } 557 Elfwritedynent(Linklookup(Ctxt, ".dynamic", 0), DT_NEEDED, uint64(Addstring(s, lib))) 558 } else { 559 Diag("adddynlib: unsupported binary format") 560 } 561 } 562 563 func Adddynsym(ctxt *Link, s *LSym) { 564 if s.Dynid >= 0 || Linkmode == LinkExternal { 565 return 566 } 567 568 if Iself { 569 Elfadddynsym(ctxt, s) 570 } else if HEADTYPE == obj.Hdarwin { 571 Diag("adddynsym: missed symbol %s (%s)", s.Name, s.Extname) 572 } else if HEADTYPE == obj.Hwindows { 573 // already taken care of 574 } else { 575 Diag("adddynsym: unsupported binary format") 576 } 577 } 578 579 var markq *LSym 580 581 var emarkq *LSym 582 583 func mark1(s *LSym, parent *LSym) { 584 if s == nil || s.Reachable { 585 return 586 } 587 if strings.HasPrefix(s.Name, "go.weak.") { 588 return 589 } 590 s.Reachable = true 591 s.Reachparent = parent 592 if markq == nil { 593 markq = s 594 } else { 595 emarkq.Queue = s 596 } 597 emarkq = s 598 } 599 600 func mark(s *LSym) { 601 mark1(s, nil) 602 } 603 604 func markflood() { 605 var a *Auto 606 var i int 607 608 for s := markq; s != nil; s = s.Queue { 609 if s.Type == obj.STEXT { 610 if Debug['v'] > 1 { 611 fmt.Fprintf(&Bso, "marktext %s\n", s.Name) 612 } 613 for a = s.Autom; a != nil; a = a.Link { 614 mark1(a.Gotype, s) 615 } 616 } 617 618 for i = 0; i < len(s.R); i++ { 619 mark1(s.R[i].Sym, s) 620 } 621 if s.Pcln != nil { 622 for i = 0; i < s.Pcln.Nfuncdata; i++ { 623 mark1(s.Pcln.Funcdata[i], s) 624 } 625 } 626 627 mark1(s.Gotype, s) 628 mark1(s.Sub, s) 629 mark1(s.Outer, s) 630 } 631 } 632 633 var markextra = []string{ 634 "runtime.morestack", 635 "runtime.morestackx", 636 "runtime.morestack00", 637 "runtime.morestack10", 638 "runtime.morestack01", 639 "runtime.morestack11", 640 "runtime.morestack8", 641 "runtime.morestack16", 642 "runtime.morestack24", 643 "runtime.morestack32", 644 "runtime.morestack40", 645 "runtime.morestack48", 646 // on arm, lock in the div/mod helpers too 647 "_div", 648 "_divu", 649 "_mod", 650 "_modu", 651 } 652 653 func deadcode() { 654 if Debug['v'] != 0 { 655 fmt.Fprintf(&Bso, "%5.2f deadcode\n", obj.Cputime()) 656 } 657 658 if Buildmode == BuildmodeShared { 659 // Mark all symbols defined in this library as reachable when 660 // building a shared library. 661 for s := Ctxt.Allsym; s != nil; s = s.Allsym { 662 if s.Type != 0 && s.Type != obj.SDYNIMPORT { 663 mark(s) 664 } 665 } 666 markflood() 667 } else { 668 mark(Linklookup(Ctxt, INITENTRY, 0)) 669 if Linkshared && Buildmode == BuildmodeExe { 670 mark(Linkrlookup(Ctxt, "main.main", 0)) 671 mark(Linkrlookup(Ctxt, "main.init", 0)) 672 } 673 for i := 0; i < len(markextra); i++ { 674 mark(Linklookup(Ctxt, markextra[i], 0)) 675 } 676 677 for i := 0; i < len(dynexp); i++ { 678 mark(dynexp[i]) 679 } 680 markflood() 681 682 // keep each beginning with 'typelink.' if the symbol it points at is being kept. 683 for s := Ctxt.Allsym; s != nil; s = s.Allsym { 684 if strings.HasPrefix(s.Name, "go.typelink.") { 685 s.Reachable = len(s.R) == 1 && s.R[0].Sym.Reachable 686 } 687 } 688 689 // remove dead text but keep file information (z symbols). 690 var last *LSym 691 692 for s := Ctxt.Textp; s != nil; s = s.Next { 693 if !s.Reachable { 694 continue 695 } 696 697 // NOTE: Removing s from old textp and adding to new, shorter textp. 698 if last == nil { 699 Ctxt.Textp = s 700 } else { 701 last.Next = s 702 } 703 last = s 704 } 705 706 if last == nil { 707 Ctxt.Textp = nil 708 Ctxt.Etextp = nil 709 } else { 710 last.Next = nil 711 Ctxt.Etextp = last 712 } 713 } 714 715 for s := Ctxt.Allsym; s != nil; s = s.Allsym { 716 if strings.HasPrefix(s.Name, "go.weak.") { 717 s.Special = 1 // do not lay out in data segment 718 s.Reachable = true 719 s.Hide = 1 720 } 721 } 722 723 // record field tracking references 724 var buf bytes.Buffer 725 var p *LSym 726 for s := Ctxt.Allsym; s != nil; s = s.Allsym { 727 if strings.HasPrefix(s.Name, "go.track.") { 728 s.Special = 1 // do not lay out in data segment 729 s.Hide = 1 730 if s.Reachable { 731 buf.WriteString(s.Name[9:]) 732 for p = s.Reachparent; p != nil; p = p.Reachparent { 733 buf.WriteString("\t") 734 buf.WriteString(p.Name) 735 } 736 buf.WriteString("\n") 737 } 738 739 s.Type = obj.SCONST 740 s.Value = 0 741 } 742 } 743 744 if tracksym == "" { 745 return 746 } 747 s := Linklookup(Ctxt, tracksym, 0) 748 if !s.Reachable { 749 return 750 } 751 addstrdata(tracksym, buf.String()) 752 } 753 754 func doweak() { 755 var t *LSym 756 757 // resolve weak references only if 758 // target symbol will be in binary anyway. 759 for s := Ctxt.Allsym; s != nil; s = s.Allsym { 760 if strings.HasPrefix(s.Name, "go.weak.") { 761 t = Linkrlookup(Ctxt, s.Name[8:], int(s.Version)) 762 if t != nil && t.Type != 0 && t.Reachable { 763 s.Value = t.Value 764 s.Type = t.Type 765 s.Outer = t 766 } else { 767 s.Type = obj.SCONST 768 s.Value = 0 769 } 770 771 continue 772 } 773 } 774 } 775 776 func addexport() { 777 if HEADTYPE == obj.Hdarwin { 778 return 779 } 780 781 for _, exp := range dynexp { 782 Adddynsym(Ctxt, exp) 783 } 784 for _, lib := range dynlib { 785 adddynlib(lib) 786 } 787 } 788 789 type Pkg struct { 790 mark bool 791 checked bool 792 path string 793 impby []*Pkg 794 } 795 796 var ( 797 // pkgmap records the imported-by relationship between packages. 798 // Entries are keyed by package path (e.g., "runtime" or "net/url"). 799 pkgmap = map[string]*Pkg{} 800 801 pkgall []*Pkg 802 ) 803 804 func lookupPkg(path string) *Pkg { 805 if p, ok := pkgmap[path]; ok { 806 return p 807 } 808 p := &Pkg{path: path} 809 pkgmap[path] = p 810 pkgall = append(pkgall, p) 811 return p 812 } 813 814 // imported records that package pkg imports package imp. 815 func imported(pkg, imp string) { 816 // everyone imports runtime, even runtime. 817 if imp == "runtime" { 818 return 819 } 820 821 p := lookupPkg(pkg) 822 i := lookupPkg(imp) 823 i.impby = append(i.impby, p) 824 } 825 826 func (p *Pkg) cycle() *Pkg { 827 if p.checked { 828 return nil 829 } 830 831 if p.mark { 832 nerrors++ 833 fmt.Printf("import cycle:\n") 834 fmt.Printf("\t%s\n", p.path) 835 return p 836 } 837 838 p.mark = true 839 for _, q := range p.impby { 840 if bad := q.cycle(); bad != nil { 841 p.mark = false 842 p.checked = true 843 fmt.Printf("\timports %s\n", p.path) 844 if bad == p { 845 return nil 846 } 847 return bad 848 } 849 } 850 851 p.checked = true 852 p.mark = false 853 return nil 854 } 855 856 func importcycles() { 857 for _, p := range pkgall { 858 p.cycle() 859 } 860 } 861 862 func setlinkmode(arg string) { 863 if arg == "internal" { 864 Linkmode = LinkInternal 865 } else if arg == "external" { 866 Linkmode = LinkExternal 867 } else if arg == "auto" { 868 Linkmode = LinkAuto 869 } else { 870 Exitf("unknown link mode -linkmode %s", arg) 871 } 872 }