1// Copyright 2015 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 5package shared_test 6 7import ( 8 "bufio" 9 "bytes" 10 "debug/elf" 11 "encoding/binary" 12 "flag" 13 "fmt" 14 "go/build" 15 "io" 16 "io/ioutil" 17 "log" 18 "os" 19 "os/exec" 20 "path/filepath" 21 "regexp" 22 "runtime" 23 "sort" 24 "strings" 25 "testing" 26 "time" 27) 28 29var gopathInstallDir, gorootInstallDir string 30 31// This is the smallest set of packages we can link into a shared 32// library (runtime/cgo is built implicitly). 33var minpkgs = []string{"runtime", "sync/atomic"} 34var soname = "libruntime,sync-atomic.so" 35 36var testX = flag.Bool("testx", false, "if true, pass -x to 'go' subcommands invoked by the test") 37var testWork = flag.Bool("testwork", false, "if true, log and do not delete the temporary working directory") 38 39// run runs a command and calls t.Errorf if it fails. 40func run(t *testing.T, msg string, args ...string) { 41 runWithEnv(t, msg, nil, args...) 42} 43 44// runWithEnv runs a command under the given environment and calls t.Errorf if it fails. 45func runWithEnv(t *testing.T, msg string, env []string, args ...string) { 46 c := exec.Command(args[0], args[1:]...) 47 if len(env) != 0 { 48 c.Env = append(os.Environ(), env...) 49 } 50 if output, err := c.CombinedOutput(); err != nil { 51 t.Errorf("executing %s (%s) failed %s:\n%s", strings.Join(args, " "), msg, err, output) 52 } 53} 54 55// goCmd invokes the go tool with the installsuffix set up by TestMain. It calls 56// t.Fatalf if the command fails. 57func goCmd(t *testing.T, args ...string) string { 58 newargs := []string{args[0]} 59 if *testX { 60 newargs = append(newargs, "-x") 61 } 62 newargs = append(newargs, args[1:]...) 63 c := exec.Command("go", newargs...) 64 stderr := new(strings.Builder) 65 c.Stderr = stderr 66 67 if testing.Verbose() && t == nil { 68 fmt.Fprintf(os.Stderr, "+ go %s\n", strings.Join(args, " ")) 69 c.Stderr = os.Stderr 70 } 71 output, err := c.Output() 72 73 if err != nil { 74 if t != nil { 75 t.Helper() 76 t.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, stderr) 77 } else { 78 // Panic instead of using log.Fatalf so that deferred cleanup may run in testMain. 79 log.Panicf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, stderr) 80 } 81 } 82 if testing.Verbose() && t != nil { 83 t.Logf("go %s", strings.Join(args, " ")) 84 if stderr.Len() > 0 { 85 t.Logf("%s", stderr) 86 } 87 } 88 return string(bytes.TrimSpace(output)) 89} 90 91// TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit). 92func testMain(m *testing.M) (int, error) { 93 workDir, err := ioutil.TempDir("", "shared_test") 94 if err != nil { 95 return 0, err 96 } 97 if *testWork || testing.Verbose() { 98 fmt.Printf("+ mkdir -p %s\n", workDir) 99 } 100 if !*testWork { 101 defer os.RemoveAll(workDir) 102 } 103 104 // Some tests need to edit the source in GOPATH, so copy this directory to a 105 // temporary directory and chdir to that. 106 gopath := filepath.Join(workDir, "gopath") 107 modRoot, err := cloneTestdataModule(gopath) 108 if err != nil { 109 return 0, err 110 } 111 if testing.Verbose() { 112 fmt.Printf("+ export GOPATH=%s\n", gopath) 113 fmt.Printf("+ cd %s\n", modRoot) 114 } 115 os.Setenv("GOPATH", gopath) 116 // Explicitly override GOBIN as well, in case it was set through a GOENV file. 117 os.Setenv("GOBIN", filepath.Join(gopath, "bin")) 118 os.Chdir(modRoot) 119 os.Setenv("PWD", modRoot) 120 121 // The test also needs to install libraries into GOROOT/pkg, so copy the 122 // subset of GOROOT that we need. 123 // 124 // TODO(golang.org/issue/28553): Rework -buildmode=shared so that it does not 125 // need to write to GOROOT. 126 goroot := filepath.Join(workDir, "goroot") 127 if err := cloneGOROOTDeps(goroot); err != nil { 128 return 0, err 129 } 130 if testing.Verbose() { 131 fmt.Fprintf(os.Stderr, "+ export GOROOT=%s\n", goroot) 132 } 133 os.Setenv("GOROOT", goroot) 134 135 myContext := build.Default 136 myContext.GOROOT = goroot 137 myContext.GOPATH = gopath 138 runtimeP, err := myContext.Import("runtime", ".", build.ImportComment) 139 if err != nil { 140 return 0, fmt.Errorf("import failed: %v", err) 141 } 142 gorootInstallDir = runtimeP.PkgTargetRoot + "_dynlink" 143 144 // All tests depend on runtime being built into a shared library. Because 145 // that takes a few seconds, do it here and have all tests use the version 146 // built here. 147 goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...) 148 149 myContext.InstallSuffix = "_dynlink" 150 depP, err := myContext.Import("./depBase", ".", build.ImportComment) 151 if err != nil { 152 return 0, fmt.Errorf("import failed: %v", err) 153 } 154 if depP.PkgTargetRoot == "" { 155 gopathInstallDir = filepath.Dir(goCmd(nil, "list", "-buildmode=shared", "-f", "{{.Target}}", "./depBase")) 156 } else { 157 gopathInstallDir = filepath.Join(depP.PkgTargetRoot, "testshared") 158 } 159 return m.Run(), nil 160} 161 162func TestMain(m *testing.M) { 163 log.SetFlags(log.Lshortfile) 164 flag.Parse() 165 166 exitCode, err := testMain(m) 167 if err != nil { 168 log.Fatal(err) 169 } 170 os.Exit(exitCode) 171} 172 173// cloneTestdataModule clones the packages from src/testshared into gopath. 174// It returns the directory within gopath at which the module root is located. 175func cloneTestdataModule(gopath string) (string, error) { 176 modRoot := filepath.Join(gopath, "src", "testshared") 177 if err := overlayDir(modRoot, "testdata"); err != nil { 178 return "", err 179 } 180 if err := ioutil.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module testshared\n"), 0644); err != nil { 181 return "", err 182 } 183 return modRoot, nil 184} 185 186// cloneGOROOTDeps copies (or symlinks) the portions of GOROOT/src and 187// GOROOT/pkg relevant to this test into the given directory. 188// It must be run from within the testdata module. 189func cloneGOROOTDeps(goroot string) error { 190 oldGOROOT := strings.TrimSpace(goCmd(nil, "env", "GOROOT")) 191 if oldGOROOT == "" { 192 return fmt.Errorf("go env GOROOT returned an empty string") 193 } 194 195 // Before we clone GOROOT, figure out which packages we need to copy over. 196 listArgs := []string{ 197 "list", 198 "-deps", 199 "-f", "{{if and .Standard (not .ForTest)}}{{.ImportPath}}{{end}}", 200 } 201 stdDeps := goCmd(nil, append(listArgs, minpkgs...)...) 202 testdataDeps := goCmd(nil, append(listArgs, "-test", "./...")...) 203 204 pkgs := append(strings.Split(strings.TrimSpace(stdDeps), "\n"), 205 strings.Split(strings.TrimSpace(testdataDeps), "\n")...) 206 sort.Strings(pkgs) 207 var pkgRoots []string 208 for _, pkg := range pkgs { 209 parentFound := false 210 for _, prev := range pkgRoots { 211 if strings.HasPrefix(pkg, prev) { 212 // We will copy in the source for pkg when we copy in prev. 213 parentFound = true 214 break 215 } 216 } 217 if !parentFound { 218 pkgRoots = append(pkgRoots, pkg) 219 } 220 } 221 222 gorootDirs := []string{ 223 "pkg/tool", 224 "pkg/include", 225 } 226 for _, pkg := range pkgRoots { 227 gorootDirs = append(gorootDirs, filepath.Join("src", pkg)) 228 } 229 230 for _, dir := range gorootDirs { 231 if testing.Verbose() { 232 fmt.Fprintf(os.Stderr, "+ cp -r %s %s\n", filepath.Join(oldGOROOT, dir), filepath.Join(goroot, dir)) 233 } 234 if err := overlayDir(filepath.Join(goroot, dir), filepath.Join(oldGOROOT, dir)); err != nil { 235 return err 236 } 237 } 238 239 return nil 240} 241 242// The shared library was built at the expected location. 243func TestSOBuilt(t *testing.T) { 244 _, err := os.Stat(filepath.Join(gorootInstallDir, soname)) 245 if err != nil { 246 t.Error(err) 247 } 248} 249 250func hasDynTag(f *elf.File, tag elf.DynTag) bool { 251 ds := f.SectionByType(elf.SHT_DYNAMIC) 252 if ds == nil { 253 return false 254 } 255 d, err := ds.Data() 256 if err != nil { 257 return false 258 } 259 for len(d) > 0 { 260 var t elf.DynTag 261 switch f.Class { 262 case elf.ELFCLASS32: 263 t = elf.DynTag(f.ByteOrder.Uint32(d[0:4])) 264 d = d[8:] 265 case elf.ELFCLASS64: 266 t = elf.DynTag(f.ByteOrder.Uint64(d[0:8])) 267 d = d[16:] 268 } 269 if t == tag { 270 return true 271 } 272 } 273 return false 274} 275 276// The shared library does not have relocations against the text segment. 277func TestNoTextrel(t *testing.T) { 278 sopath := filepath.Join(gorootInstallDir, soname) 279 f, err := elf.Open(sopath) 280 if err != nil { 281 t.Fatal("elf.Open failed: ", err) 282 } 283 defer f.Close() 284 if hasDynTag(f, elf.DT_TEXTREL) { 285 t.Errorf("%s has DT_TEXTREL set", soname) 286 } 287} 288 289// The shared library does not contain symbols called ".dup" 290// (See golang.org/issue/14841.) 291func TestNoDupSymbols(t *testing.T) { 292 sopath := filepath.Join(gorootInstallDir, soname) 293 f, err := elf.Open(sopath) 294 if err != nil { 295 t.Fatal("elf.Open failed: ", err) 296 } 297 defer f.Close() 298 syms, err := f.Symbols() 299 if err != nil { 300 t.Errorf("error reading symbols %v", err) 301 return 302 } 303 for _, s := range syms { 304 if s.Name == ".dup" { 305 t.Fatalf("%s contains symbol called .dup", sopath) 306 } 307 } 308} 309 310// The install command should have created a "shlibname" file for the 311// listed packages (and runtime/cgo, and math on arm) indicating the 312// name of the shared library containing it. 313func TestShlibnameFiles(t *testing.T) { 314 pkgs := append([]string{}, minpkgs...) 315 pkgs = append(pkgs, "runtime/cgo") 316 if runtime.GOARCH == "arm" { 317 pkgs = append(pkgs, "math") 318 } 319 for _, pkg := range pkgs { 320 shlibnamefile := filepath.Join(gorootInstallDir, pkg+".shlibname") 321 contentsb, err := ioutil.ReadFile(shlibnamefile) 322 if err != nil { 323 t.Errorf("error reading shlibnamefile for %s: %v", pkg, err) 324 continue 325 } 326 contents := strings.TrimSpace(string(contentsb)) 327 if contents != soname { 328 t.Errorf("shlibnamefile for %s has wrong contents: %q", pkg, contents) 329 } 330 } 331} 332 333// Is a given offset into the file contained in a loaded segment? 334func isOffsetLoaded(f *elf.File, offset uint64) bool { 335 for _, prog := range f.Progs { 336 if prog.Type == elf.PT_LOAD { 337 if prog.Off <= offset && offset < prog.Off+prog.Filesz { 338 return true 339 } 340 } 341 } 342 return false 343} 344 345func rnd(v int32, r int32) int32 { 346 if r <= 0 { 347 return v 348 } 349 v += r - 1 350 c := v % r 351 if c < 0 { 352 c += r 353 } 354 v -= c 355 return v 356} 357 358func readwithpad(r io.Reader, sz int32) ([]byte, error) { 359 data := make([]byte, rnd(sz, 4)) 360 _, err := io.ReadFull(r, data) 361 if err != nil { 362 return nil, err 363 } 364 data = data[:sz] 365 return data, nil 366} 367 368type note struct { 369 name string 370 tag int32 371 desc string 372 section *elf.Section 373} 374 375// Read all notes from f. As ELF section names are not supposed to be special, one 376// looks for a particular note by scanning all SHT_NOTE sections looking for a note 377// with a particular "name" and "tag". 378func readNotes(f *elf.File) ([]*note, error) { 379 var notes []*note 380 for _, sect := range f.Sections { 381 if sect.Type != elf.SHT_NOTE { 382 continue 383 } 384 r := sect.Open() 385 for { 386 var namesize, descsize, tag int32 387 err := binary.Read(r, f.ByteOrder, &namesize) 388 if err != nil { 389 if err == io.EOF { 390 break 391 } 392 return nil, fmt.Errorf("read namesize failed: %v", err) 393 } 394 err = binary.Read(r, f.ByteOrder, &descsize) 395 if err != nil { 396 return nil, fmt.Errorf("read descsize failed: %v", err) 397 } 398 err = binary.Read(r, f.ByteOrder, &tag) 399 if err != nil { 400 return nil, fmt.Errorf("read type failed: %v", err) 401 } 402 name, err := readwithpad(r, namesize) 403 if err != nil { 404 return nil, fmt.Errorf("read name failed: %v", err) 405 } 406 desc, err := readwithpad(r, descsize) 407 if err != nil { 408 return nil, fmt.Errorf("read desc failed: %v", err) 409 } 410 notes = append(notes, ¬e{name: string(name), tag: tag, desc: string(desc), section: sect}) 411 } 412 } 413 return notes, nil 414} 415 416func dynStrings(t *testing.T, path string, flag elf.DynTag) []string { 417 t.Helper() 418 f, err := elf.Open(path) 419 if err != nil { 420 t.Fatalf("elf.Open(%q) failed: %v", path, err) 421 } 422 defer f.Close() 423 dynstrings, err := f.DynString(flag) 424 if err != nil { 425 t.Fatalf("DynString(%s) failed on %s: %v", flag, path, err) 426 } 427 return dynstrings 428} 429 430func AssertIsLinkedToRegexp(t *testing.T, path string, re *regexp.Regexp) { 431 t.Helper() 432 for _, dynstring := range dynStrings(t, path, elf.DT_NEEDED) { 433 if re.MatchString(dynstring) { 434 return 435 } 436 } 437 t.Errorf("%s is not linked to anything matching %v", path, re) 438} 439 440func AssertIsLinkedTo(t *testing.T, path, lib string) { 441 t.Helper() 442 AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib))) 443} 444 445func AssertHasRPath(t *testing.T, path, dir string) { 446 t.Helper() 447 for _, tag := range []elf.DynTag{elf.DT_RPATH, elf.DT_RUNPATH} { 448 for _, dynstring := range dynStrings(t, path, tag) { 449 for _, rpath := range strings.Split(dynstring, ":") { 450 if filepath.Clean(rpath) == filepath.Clean(dir) { 451 return 452 } 453 } 454 } 455 } 456 t.Errorf("%s does not have rpath %s", path, dir) 457} 458 459// Build a trivial program that links against the shared runtime and check it runs. 460func TestTrivialExecutable(t *testing.T) { 461 goCmd(t, "install", "-linkshared", "./trivial") 462 run(t, "trivial executable", "../../bin/trivial") 463 AssertIsLinkedTo(t, "../../bin/trivial", soname) 464 AssertHasRPath(t, "../../bin/trivial", gorootInstallDir) 465 checkSize(t, "../../bin/trivial", 100000) // it is 19K on linux/amd64, 100K should be enough 466} 467 468// Build a trivial program in PIE mode that links against the shared runtime and check it runs. 469func TestTrivialExecutablePIE(t *testing.T) { 470 goCmd(t, "build", "-buildmode=pie", "-o", "trivial.pie", "-linkshared", "./trivial") 471 run(t, "trivial executable", "./trivial.pie") 472 AssertIsLinkedTo(t, "./trivial.pie", soname) 473 AssertHasRPath(t, "./trivial.pie", gorootInstallDir) 474 checkSize(t, "./trivial.pie", 100000) // it is 19K on linux/amd64, 100K should be enough 475} 476 477// Check that the file size does not exceed a limit. 478func checkSize(t *testing.T, f string, limit int64) { 479 fi, err := os.Stat(f) 480 if err != nil { 481 t.Fatalf("stat failed: %v", err) 482 } 483 if sz := fi.Size(); sz > limit { 484 t.Errorf("file too large: got %d, want <= %d", sz, limit) 485 } 486} 487 488// Build a division test program and check it runs. 489func TestDivisionExecutable(t *testing.T) { 490 goCmd(t, "install", "-linkshared", "./division") 491 run(t, "division executable", "../../bin/division") 492} 493 494// Build an executable that uses cgo linked against the shared runtime and check it 495// runs. 496func TestCgoExecutable(t *testing.T) { 497 goCmd(t, "install", "-linkshared", "./execgo") 498 run(t, "cgo executable", "../../bin/execgo") 499} 500 501func checkPIE(t *testing.T, name string) { 502 f, err := elf.Open(name) 503 if err != nil { 504 t.Fatal("elf.Open failed: ", err) 505 } 506 defer f.Close() 507 if f.Type != elf.ET_DYN { 508 t.Errorf("%s has type %v, want ET_DYN", name, f.Type) 509 } 510 if hasDynTag(f, elf.DT_TEXTREL) { 511 t.Errorf("%s has DT_TEXTREL set", name) 512 } 513} 514 515func TestTrivialPIE(t *testing.T) { 516 name := "trivial_pie" 517 goCmd(t, "build", "-buildmode=pie", "-o="+name, "./trivial") 518 defer os.Remove(name) 519 run(t, name, "./"+name) 520 checkPIE(t, name) 521} 522 523func TestCgoPIE(t *testing.T) { 524 name := "cgo_pie" 525 goCmd(t, "build", "-buildmode=pie", "-o="+name, "./execgo") 526 defer os.Remove(name) 527 run(t, name, "./"+name) 528 checkPIE(t, name) 529} 530 531// Build a GOPATH package into a shared library that links against the goroot runtime 532// and an executable that links against both. 533func TestGopathShlib(t *testing.T) { 534 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase") 535 shlib := goCmd(t, "list", "-f", "{{.Shlib}}", "-buildmode=shared", "-linkshared", "./depBase") 536 AssertIsLinkedTo(t, shlib, soname) 537 goCmd(t, "install", "-linkshared", "./exe") 538 AssertIsLinkedTo(t, "../../bin/exe", soname) 539 AssertIsLinkedTo(t, "../../bin/exe", filepath.Base(shlib)) 540 AssertHasRPath(t, "../../bin/exe", gorootInstallDir) 541 AssertHasRPath(t, "../../bin/exe", filepath.Dir(gopathInstallDir)) 542 // And check it runs. 543 run(t, "executable linked to GOPATH library", "../../bin/exe") 544} 545 546// The shared library contains a note listing the packages it contains in a section 547// that is not mapped into memory. 548func testPkgListNote(t *testing.T, f *elf.File, note *note) { 549 if note.section.Flags != 0 { 550 t.Errorf("package list section has flags %v, want 0", note.section.Flags) 551 } 552 if isOffsetLoaded(f, note.section.Offset) { 553 t.Errorf("package list section contained in PT_LOAD segment") 554 } 555 if note.desc != "testshared/depBase\n" { 556 t.Errorf("incorrect package list %q, want %q", note.desc, "testshared/depBase\n") 557 } 558} 559 560// The shared library contains a note containing the ABI hash that is mapped into 561// memory and there is a local symbol called go.link.abihashbytes that points 16 562// bytes into it. 563func testABIHashNote(t *testing.T, f *elf.File, note *note) { 564 if note.section.Flags != elf.SHF_ALLOC { 565 t.Errorf("abi hash section has flags %v, want SHF_ALLOC", note.section.Flags) 566 } 567 if !isOffsetLoaded(f, note.section.Offset) { 568 t.Errorf("abihash section not contained in PT_LOAD segment") 569 } 570 var hashbytes elf.Symbol 571 symbols, err := f.Symbols() 572 if err != nil { 573 t.Errorf("error reading symbols %v", err) 574 return 575 } 576 for _, sym := range symbols { 577 if sym.Name == "go.link.abihashbytes" { 578 hashbytes = sym 579 } 580 } 581 if hashbytes.Name == "" { 582 t.Errorf("no symbol called go.link.abihashbytes") 583 return 584 } 585 if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL { 586 t.Errorf("%s has incorrect binding %v, want STB_LOCAL", hashbytes.Name, elf.ST_BIND(hashbytes.Info)) 587 } 588 if f.Sections[hashbytes.Section] != note.section { 589 t.Errorf("%s has incorrect section %v, want %s", hashbytes.Name, f.Sections[hashbytes.Section].Name, note.section.Name) 590 } 591 if hashbytes.Value-note.section.Addr != 16 { 592 t.Errorf("%s has incorrect offset into section %d, want 16", hashbytes.Name, hashbytes.Value-note.section.Addr) 593 } 594} 595 596// A Go shared library contains a note indicating which other Go shared libraries it 597// was linked against in an unmapped section. 598func testDepsNote(t *testing.T, f *elf.File, note *note) { 599 if note.section.Flags != 0 { 600 t.Errorf("package list section has flags %v, want 0", note.section.Flags) 601 } 602 if isOffsetLoaded(f, note.section.Offset) { 603 t.Errorf("package list section contained in PT_LOAD segment") 604 } 605 // libdepBase.so just links against the lib containing the runtime. 606 if note.desc != soname { 607 t.Errorf("incorrect dependency list %q, want %q", note.desc, soname) 608 } 609} 610 611// The shared library contains notes with defined contents; see above. 612func TestNotes(t *testing.T) { 613 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase") 614 shlib := goCmd(t, "list", "-f", "{{.Shlib}}", "-buildmode=shared", "-linkshared", "./depBase") 615 f, err := elf.Open(shlib) 616 if err != nil { 617 t.Fatal(err) 618 } 619 defer f.Close() 620 notes, err := readNotes(f) 621 if err != nil { 622 t.Fatal(err) 623 } 624 pkgListNoteFound := false 625 abiHashNoteFound := false 626 depsNoteFound := false 627 for _, note := range notes { 628 if note.name != "Go\x00\x00" { 629 continue 630 } 631 switch note.tag { 632 case 1: // ELF_NOTE_GOPKGLIST_TAG 633 if pkgListNoteFound { 634 t.Error("multiple package list notes") 635 } 636 testPkgListNote(t, f, note) 637 pkgListNoteFound = true 638 case 2: // ELF_NOTE_GOABIHASH_TAG 639 if abiHashNoteFound { 640 t.Error("multiple abi hash notes") 641 } 642 testABIHashNote(t, f, note) 643 abiHashNoteFound = true 644 case 3: // ELF_NOTE_GODEPS_TAG 645 if depsNoteFound { 646 t.Error("multiple dependency list notes") 647 } 648 testDepsNote(t, f, note) 649 depsNoteFound = true 650 } 651 } 652 if !pkgListNoteFound { 653 t.Error("package list note not found") 654 } 655 if !abiHashNoteFound { 656 t.Error("abi hash note not found") 657 } 658 if !depsNoteFound { 659 t.Error("deps note not found") 660 } 661} 662 663// Build a GOPATH package (depBase) into a shared library that links against the goroot 664// runtime, another package (dep2) that links against the first, and an 665// executable that links against dep2. 666func TestTwoGopathShlibs(t *testing.T) { 667 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase") 668 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./dep2") 669 goCmd(t, "install", "-linkshared", "./exe2") 670 run(t, "executable linked to GOPATH library", "../../bin/exe2") 671} 672 673func TestThreeGopathShlibs(t *testing.T) { 674 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase") 675 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./dep2") 676 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./dep3") 677 goCmd(t, "install", "-linkshared", "./exe3") 678 run(t, "executable linked to GOPATH library", "../../bin/exe3") 679} 680 681// If gccgo is not available or not new enough, call t.Skip. 682func requireGccgo(t *testing.T) { 683 t.Helper() 684 685 gccgoName := os.Getenv("GCCGO") 686 if gccgoName == "" { 687 gccgoName = "gccgo" 688 } 689 gccgoPath, err := exec.LookPath(gccgoName) 690 if err != nil { 691 t.Skip("gccgo not found") 692 } 693 cmd := exec.Command(gccgoPath, "-dumpversion") 694 output, err := cmd.CombinedOutput() 695 if err != nil { 696 t.Fatalf("%s -dumpversion failed: %v\n%s", gccgoPath, err, output) 697 } 698 if string(output) < "5" { 699 t.Skipf("gccgo too old (%s)", strings.TrimSpace(string(output))) 700 } 701 702 gomod, err := exec.Command("go", "env", "GOMOD").Output() 703 if err != nil { 704 t.Fatalf("go env GOMOD: %v", err) 705 } 706 if len(bytes.TrimSpace(gomod)) > 0 { 707 t.Skipf("gccgo not supported in module mode; see golang.org/issue/30344") 708 } 709} 710 711// Build a GOPATH package into a shared library with gccgo and an executable that 712// links against it. 713func TestGoPathShlibGccgo(t *testing.T) { 714 requireGccgo(t) 715 716 libgoRE := regexp.MustCompile("libgo.so.[0-9]+") 717 718 goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "./depBase") 719 720 // Run 'go list' after 'go install': with gccgo, we apparently don't know the 721 // shlib location until after we've installed it. 722 shlib := goCmd(t, "list", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "-f", "{{.Shlib}}", "./depBase") 723 724 AssertIsLinkedToRegexp(t, shlib, libgoRE) 725 goCmd(t, "install", "-compiler=gccgo", "-linkshared", "./exe") 726 AssertIsLinkedToRegexp(t, "../../bin/exe", libgoRE) 727 AssertIsLinkedTo(t, "../../bin/exe", filepath.Base(shlib)) 728 AssertHasRPath(t, "../../bin/exe", filepath.Dir(shlib)) 729 // And check it runs. 730 run(t, "gccgo-built", "../../bin/exe") 731} 732 733// The gccgo version of TestTwoGopathShlibs: build a GOPATH package into a shared 734// library with gccgo, another GOPATH package that depends on the first and an 735// executable that links the second library. 736func TestTwoGopathShlibsGccgo(t *testing.T) { 737 requireGccgo(t) 738 739 libgoRE := regexp.MustCompile("libgo.so.[0-9]+") 740 741 goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "./depBase") 742 goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "./dep2") 743 goCmd(t, "install", "-compiler=gccgo", "-linkshared", "./exe2") 744 745 // Run 'go list' after 'go install': with gccgo, we apparently don't know the 746 // shlib location until after we've installed it. 747 dep2 := goCmd(t, "list", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "-f", "{{.Shlib}}", "./dep2") 748 depBase := goCmd(t, "list", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "-f", "{{.Shlib}}", "./depBase") 749 750 AssertIsLinkedToRegexp(t, depBase, libgoRE) 751 AssertIsLinkedToRegexp(t, dep2, libgoRE) 752 AssertIsLinkedTo(t, dep2, filepath.Base(depBase)) 753 AssertIsLinkedToRegexp(t, "../../bin/exe2", libgoRE) 754 AssertIsLinkedTo(t, "../../bin/exe2", filepath.Base(dep2)) 755 AssertIsLinkedTo(t, "../../bin/exe2", filepath.Base(depBase)) 756 757 // And check it runs. 758 run(t, "gccgo-built", "../../bin/exe2") 759} 760 761// Testing rebuilding of shared libraries when they are stale is a bit more 762// complicated that it seems like it should be. First, we make everything "old": but 763// only a few seconds old, or it might be older than gc (or the runtime source) and 764// everything will get rebuilt. Then define a timestamp slightly newer than this 765// time, which is what we set the mtime to of a file to cause it to be seen as new, 766// and finally another slightly even newer one that we can compare files against to 767// see if they have been rebuilt. 768var oldTime = time.Now().Add(-9 * time.Second) 769var nearlyNew = time.Now().Add(-6 * time.Second) 770var stampTime = time.Now().Add(-3 * time.Second) 771 772// resetFileStamps makes "everything" (bin, src, pkg from GOPATH and the 773// test-specific parts of GOROOT) appear old. 774func resetFileStamps() { 775 chtime := func(path string, info os.FileInfo, err error) error { 776 return os.Chtimes(path, oldTime, oldTime) 777 } 778 reset := func(path string) { 779 if err := filepath.Walk(path, chtime); err != nil { 780 log.Panicf("resetFileStamps failed: %v", err) 781 } 782 783 } 784 reset("../../bin") 785 reset("../../pkg") 786 reset("../../src") 787 reset(gorootInstallDir) 788} 789 790// touch changes path and returns a function that changes it back. 791// It also sets the time of the file, so that we can see if it is rewritten. 792func touch(t *testing.T, path string) (cleanup func()) { 793 t.Helper() 794 data, err := ioutil.ReadFile(path) 795 if err != nil { 796 t.Fatal(err) 797 } 798 old := make([]byte, len(data)) 799 copy(old, data) 800 if bytes.HasPrefix(data, []byte("!<arch>\n")) { 801 // Change last digit of build ID. 802 // (Content ID in the new content-based build IDs.) 803 const marker = `build id "` 804 i := bytes.Index(data, []byte(marker)) 805 if i < 0 { 806 t.Fatal("cannot find build id in archive") 807 } 808 j := bytes.IndexByte(data[i+len(marker):], '"') 809 if j < 0 { 810 t.Fatal("cannot find build id in archive") 811 } 812 i += len(marker) + j - 1 813 if data[i] == 'a' { 814 data[i] = 'b' 815 } else { 816 data[i] = 'a' 817 } 818 } else { 819 // assume it's a text file 820 data = append(data, '\n') 821 } 822 823 // If the file is still a symlink from an overlay, delete it so that we will 824 // replace it with a regular file instead of overwriting the symlinked one. 825 fi, err := os.Lstat(path) 826 if err == nil && !fi.Mode().IsRegular() { 827 fi, err = os.Stat(path) 828 if err := os.Remove(path); err != nil { 829 t.Fatal(err) 830 } 831 } 832 if err != nil { 833 t.Fatal(err) 834 } 835 836 // If we're replacing a symlink to a read-only file, make the new file 837 // user-writable. 838 perm := fi.Mode().Perm() | 0200 839 840 if err := ioutil.WriteFile(path, data, perm); err != nil { 841 t.Fatal(err) 842 } 843 if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil { 844 t.Fatal(err) 845 } 846 return func() { 847 if err := ioutil.WriteFile(path, old, perm); err != nil { 848 t.Fatal(err) 849 } 850 } 851} 852 853// isNew returns if the path is newer than the time stamp used by touch. 854func isNew(t *testing.T, path string) bool { 855 t.Helper() 856 fi, err := os.Stat(path) 857 if err != nil { 858 t.Fatal(err) 859 } 860 return fi.ModTime().After(stampTime) 861} 862 863// Fail unless path has been rebuilt (i.e. is newer than the time stamp used by 864// isNew) 865func AssertRebuilt(t *testing.T, msg, path string) { 866 t.Helper() 867 if !isNew(t, path) { 868 t.Errorf("%s was not rebuilt (%s)", msg, path) 869 } 870} 871 872// Fail if path has been rebuilt (i.e. is newer than the time stamp used by isNew) 873func AssertNotRebuilt(t *testing.T, msg, path string) { 874 t.Helper() 875 if isNew(t, path) { 876 t.Errorf("%s was rebuilt (%s)", msg, path) 877 } 878} 879 880func TestRebuilding(t *testing.T) { 881 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase") 882 goCmd(t, "install", "-linkshared", "./exe") 883 info := strings.Fields(goCmd(t, "list", "-buildmode=shared", "-linkshared", "-f", "{{.Target}} {{.Shlib}}", "./depBase")) 884 if len(info) != 2 { 885 t.Fatalf("go list failed to report Target and/or Shlib") 886 } 887 target := info[0] 888 shlib := info[1] 889 890 // If the source is newer than both the .a file and the .so, both are rebuilt. 891 t.Run("newsource", func(t *testing.T) { 892 resetFileStamps() 893 cleanup := touch(t, "./depBase/dep.go") 894 defer func() { 895 cleanup() 896 goCmd(t, "install", "-linkshared", "./exe") 897 }() 898 goCmd(t, "install", "-linkshared", "./exe") 899 AssertRebuilt(t, "new source", target) 900 AssertRebuilt(t, "new source", shlib) 901 }) 902 903 // If the .a file is newer than the .so, the .so is rebuilt (but not the .a) 904 t.Run("newarchive", func(t *testing.T) { 905 resetFileStamps() 906 AssertNotRebuilt(t, "new .a file before build", target) 907 goCmd(t, "list", "-linkshared", "-f={{.ImportPath}} {{.Stale}} {{.StaleReason}} {{.Target}}", "./depBase") 908 AssertNotRebuilt(t, "new .a file before build", target) 909 cleanup := touch(t, target) 910 defer func() { 911 cleanup() 912 goCmd(t, "install", "-v", "-linkshared", "./exe") 913 }() 914 goCmd(t, "install", "-v", "-linkshared", "./exe") 915 AssertNotRebuilt(t, "new .a file", target) 916 AssertRebuilt(t, "new .a file", shlib) 917 }) 918} 919 920func appendFile(t *testing.T, path, content string) { 921 t.Helper() 922 f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0660) 923 if err != nil { 924 t.Fatalf("os.OpenFile failed: %v", err) 925 } 926 defer func() { 927 err := f.Close() 928 if err != nil { 929 t.Fatalf("f.Close failed: %v", err) 930 } 931 }() 932 _, err = f.WriteString(content) 933 if err != nil { 934 t.Fatalf("f.WriteString failed: %v", err) 935 } 936} 937 938func createFile(t *testing.T, path, content string) { 939 t.Helper() 940 f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) 941 if err != nil { 942 t.Fatalf("os.OpenFile failed: %v", err) 943 } 944 _, err = f.WriteString(content) 945 if closeErr := f.Close(); err == nil { 946 err = closeErr 947 } 948 if err != nil { 949 t.Fatalf("WriteString failed: %v", err) 950 } 951} 952 953func TestABIChecking(t *testing.T) { 954 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase") 955 goCmd(t, "install", "-linkshared", "./exe") 956 957 // If we make an ABI-breaking change to depBase and rebuild libp.so but not exe, 958 // exe will abort with a complaint on startup. 959 // This assumes adding an exported function breaks ABI, which is not true in 960 // some senses but suffices for the narrow definition of ABI compatibility the 961 // toolchain uses today. 962 resetFileStamps() 963 964 createFile(t, "./depBase/break.go", "package depBase\nfunc ABIBreak() {}\n") 965 defer os.Remove("./depBase/break.go") 966 967 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase") 968 c := exec.Command("../../bin/exe") 969 output, err := c.CombinedOutput() 970 if err == nil { 971 t.Fatal("executing exe did not fail after ABI break") 972 } 973 scanner := bufio.NewScanner(bytes.NewReader(output)) 974 foundMsg := false 975 const wantPrefix = "abi mismatch detected between the executable and lib" 976 for scanner.Scan() { 977 if strings.HasPrefix(scanner.Text(), wantPrefix) { 978 foundMsg = true 979 break 980 } 981 } 982 if err = scanner.Err(); err != nil { 983 t.Errorf("scanner encountered error: %v", err) 984 } 985 if !foundMsg { 986 t.Fatalf("exe failed, but without line %q; got output:\n%s", wantPrefix, output) 987 } 988 989 // Rebuilding exe makes it work again. 990 goCmd(t, "install", "-linkshared", "./exe") 991 run(t, "rebuilt exe", "../../bin/exe") 992 993 // If we make a change which does not break ABI (such as adding an unexported 994 // function) and rebuild libdepBase.so, exe still works, even if new function 995 // is in a file by itself. 996 resetFileStamps() 997 createFile(t, "./depBase/dep2.go", "package depBase\nfunc noABIBreak() {}\n") 998 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase") 999 run(t, "after non-ABI breaking change", "../../bin/exe") 1000} 1001 1002// If a package 'explicit' imports a package 'implicit', building 1003// 'explicit' into a shared library implicitly includes implicit in 1004// the shared library. Building an executable that imports both 1005// explicit and implicit builds the code from implicit into the 1006// executable rather than fetching it from the shared library. The 1007// link still succeeds and the executable still runs though. 1008func TestImplicitInclusion(t *testing.T) { 1009 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./explicit") 1010 goCmd(t, "install", "-linkshared", "./implicitcmd") 1011 run(t, "running executable linked against library that contains same package as it", "../../bin/implicitcmd") 1012} 1013 1014// Tests to make sure that the type fields of empty interfaces and itab 1015// fields of nonempty interfaces are unique even across modules, 1016// so that interface equality works correctly. 1017func TestInterface(t *testing.T) { 1018 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./iface_a") 1019 // Note: iface_i gets installed implicitly as a dependency of iface_a. 1020 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./iface_b") 1021 goCmd(t, "install", "-linkshared", "./iface") 1022 run(t, "running type/itab uniqueness tester", "../../bin/iface") 1023} 1024 1025// Access a global variable from a library. 1026func TestGlobal(t *testing.T) { 1027 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./globallib") 1028 goCmd(t, "install", "-linkshared", "./global") 1029 run(t, "global executable", "../../bin/global") 1030 AssertIsLinkedTo(t, "../../bin/global", soname) 1031 AssertHasRPath(t, "../../bin/global", gorootInstallDir) 1032} 1033 1034// Run a test using -linkshared of an installed shared package. 1035// Issue 26400. 1036func TestTestInstalledShared(t *testing.T) { 1037 goCmd(nil, "test", "-linkshared", "-test.short", "sync/atomic") 1038} 1039 1040// Test generated pointer method with -linkshared. 1041// Issue 25065. 1042func TestGeneratedMethod(t *testing.T) { 1043 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue25065") 1044} 1045 1046// Test use of shared library struct with generated hash function. 1047// Issue 30768. 1048func TestGeneratedHash(t *testing.T) { 1049 goCmd(nil, "install", "-buildmode=shared", "-linkshared", "./issue30768/issue30768lib") 1050 goCmd(nil, "test", "-linkshared", "./issue30768") 1051} 1052 1053// Test that packages can be added not in dependency order (here a depends on b, and a adds 1054// before b). This could happen with e.g. go build -buildmode=shared std. See issue 39777. 1055func TestPackageOrder(t *testing.T) { 1056 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue39777/a", "./issue39777/b") 1057} 1058 1059// Test that GC data are generated correctly by the linker when it needs a type defined in 1060// a shared library. See issue 39927. 1061func TestGCData(t *testing.T) { 1062 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./gcdata/p") 1063 goCmd(t, "build", "-linkshared", "./gcdata/main") 1064 runWithEnv(t, "running gcdata/main", []string{"GODEBUG=clobberfree=1"}, "./main") 1065} 1066 1067// Test that we don't decode type symbols from shared libraries (which has no data, 1068// causing panic). See issue 44031. 1069func TestIssue44031(t *testing.T) { 1070 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue44031/a") 1071 goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue44031/b") 1072 goCmd(t, "run", "-linkshared", "./issue44031/main") 1073} 1074