github.com/epfl-dcsl/gotee@v0.0.0-20200909122901-014b35f5e5e9/misc/cgo/testshared/shared_test.go (about) 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 5 package shared_test 6 7 import ( 8 "bufio" 9 "bytes" 10 "debug/elf" 11 "encoding/binary" 12 "errors" 13 "flag" 14 "fmt" 15 "go/build" 16 "io" 17 "io/ioutil" 18 "log" 19 "math/rand" 20 "os" 21 "os/exec" 22 "path/filepath" 23 "regexp" 24 "runtime" 25 "strings" 26 "testing" 27 "time" 28 ) 29 30 var gopathInstallDir, gorootInstallDir, suffix string 31 32 // This is the smallest set of packages we can link into a shared 33 // library (runtime/cgo is built implicitly). 34 var minpkgs = []string{"runtime", "sync/atomic"} 35 var soname = "libruntime,sync-atomic.so" 36 37 // run runs a command and calls t.Errorf if it fails. 38 func run(t *testing.T, msg string, args ...string) { 39 c := exec.Command(args[0], args[1:]...) 40 if output, err := c.CombinedOutput(); err != nil { 41 t.Errorf("executing %s (%s) failed %s:\n%s", strings.Join(args, " "), msg, err, output) 42 } 43 } 44 45 // goCmd invokes the go tool with the installsuffix set up by TestMain. It calls 46 // t.Fatalf if the command fails. 47 func goCmd(t *testing.T, args ...string) { 48 newargs := []string{args[0], "-installsuffix=" + suffix} 49 if testing.Verbose() { 50 newargs = append(newargs, "-x") 51 } 52 newargs = append(newargs, args[1:]...) 53 c := exec.Command("go", newargs...) 54 var output []byte 55 var err error 56 if testing.Verbose() { 57 fmt.Printf("+ go %s\n", strings.Join(newargs, " ")) 58 c.Stdout = os.Stdout 59 c.Stderr = os.Stderr 60 err = c.Run() 61 output = []byte("(output above)") 62 } else { 63 output, err = c.CombinedOutput() 64 } 65 if err != nil { 66 if t != nil { 67 t.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output) 68 } else { 69 log.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output) 70 } 71 } 72 } 73 74 // TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit). 75 func testMain(m *testing.M) (int, error) { 76 // Because go install -buildmode=shared $standard_library_package always 77 // installs into $GOROOT, here are some gymnastics to come up with a unique 78 // installsuffix to use in this test that we can clean up afterwards. 79 myContext := build.Default 80 runtimeP, err := myContext.Import("runtime", ".", build.ImportComment) 81 if err != nil { 82 return 0, fmt.Errorf("import failed: %v", err) 83 } 84 for i := 0; i < 10000; i++ { 85 try := fmt.Sprintf("%s_%d_dynlink", runtimeP.PkgTargetRoot, rand.Int63()) 86 err = os.Mkdir(try, 0700) 87 if os.IsExist(err) { 88 continue 89 } 90 if err == nil { 91 gorootInstallDir = try 92 } 93 break 94 } 95 if err != nil { 96 return 0, fmt.Errorf("can't create temporary directory: %v", err) 97 } 98 if gorootInstallDir == "" { 99 return 0, errors.New("could not create temporary directory after 10000 tries") 100 } 101 if testing.Verbose() { 102 fmt.Printf("+ mkdir -p %s\n", gorootInstallDir) 103 } 104 defer os.RemoveAll(gorootInstallDir) 105 106 // Some tests need to edit the source in GOPATH, so copy this directory to a 107 // temporary directory and chdir to that. 108 scratchDir, err := ioutil.TempDir("", "testshared") 109 if err != nil { 110 return 0, fmt.Errorf("TempDir failed: %v", err) 111 } 112 if testing.Verbose() { 113 fmt.Printf("+ mkdir -p %s\n", scratchDir) 114 } 115 defer os.RemoveAll(scratchDir) 116 err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 117 scratchPath := filepath.Join(scratchDir, path) 118 if info.IsDir() { 119 if path == "." { 120 return nil 121 } 122 if testing.Verbose() { 123 fmt.Printf("+ mkdir -p %s\n", scratchPath) 124 } 125 return os.Mkdir(scratchPath, info.Mode()) 126 } else { 127 fromBytes, err := ioutil.ReadFile(path) 128 if err != nil { 129 return err 130 } 131 if testing.Verbose() { 132 fmt.Printf("+ cp %s %s\n", path, scratchPath) 133 } 134 return ioutil.WriteFile(scratchPath, fromBytes, info.Mode()) 135 } 136 }) 137 if err != nil { 138 return 0, fmt.Errorf("walk failed: %v", err) 139 } 140 os.Setenv("GOPATH", scratchDir) 141 if testing.Verbose() { 142 fmt.Printf("+ export GOPATH=%s\n", scratchDir) 143 } 144 myContext.GOPATH = scratchDir 145 if testing.Verbose() { 146 fmt.Printf("+ cd %s\n", scratchDir) 147 } 148 os.Chdir(scratchDir) 149 150 // All tests depend on runtime being built into a shared library. Because 151 // that takes a few seconds, do it here and have all tests use the version 152 // built here. 153 suffix = strings.Split(filepath.Base(gorootInstallDir), "_")[2] 154 goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...) 155 156 myContext.InstallSuffix = suffix + "_dynlink" 157 depP, err := myContext.Import("depBase", ".", build.ImportComment) 158 if err != nil { 159 return 0, fmt.Errorf("import failed: %v", err) 160 } 161 gopathInstallDir = depP.PkgTargetRoot 162 return m.Run(), nil 163 } 164 165 func TestMain(m *testing.M) { 166 flag.Parse() 167 168 // Some of the tests install binaries into a custom GOPATH. 169 // That won't work if GOBIN is set. 170 os.Unsetenv("GOBIN") 171 172 exitCode, err := testMain(m) 173 if err != nil { 174 log.Fatal(err) 175 } 176 os.Exit(exitCode) 177 } 178 179 // The shared library was built at the expected location. 180 func TestSOBuilt(t *testing.T) { 181 _, err := os.Stat(filepath.Join(gorootInstallDir, soname)) 182 if err != nil { 183 t.Error(err) 184 } 185 } 186 187 func hasDynTag(f *elf.File, tag elf.DynTag) bool { 188 ds := f.SectionByType(elf.SHT_DYNAMIC) 189 if ds == nil { 190 return false 191 } 192 d, err := ds.Data() 193 if err != nil { 194 return false 195 } 196 for len(d) > 0 { 197 var t elf.DynTag 198 switch f.Class { 199 case elf.ELFCLASS32: 200 t = elf.DynTag(f.ByteOrder.Uint32(d[0:4])) 201 d = d[8:] 202 case elf.ELFCLASS64: 203 t = elf.DynTag(f.ByteOrder.Uint64(d[0:8])) 204 d = d[16:] 205 } 206 if t == tag { 207 return true 208 } 209 } 210 return false 211 } 212 213 // The shared library does not have relocations against the text segment. 214 func TestNoTextrel(t *testing.T) { 215 sopath := filepath.Join(gorootInstallDir, soname) 216 f, err := elf.Open(sopath) 217 if err != nil { 218 t.Fatal("elf.Open failed: ", err) 219 } 220 defer f.Close() 221 if hasDynTag(f, elf.DT_TEXTREL) { 222 t.Errorf("%s has DT_TEXTREL set", soname) 223 } 224 } 225 226 // The shared library does not contain symbols called ".dup" 227 func TestNoDupSymbols(t *testing.T) { 228 sopath := filepath.Join(gorootInstallDir, soname) 229 f, err := elf.Open(sopath) 230 if err != nil { 231 t.Fatal("elf.Open failed: ", err) 232 } 233 defer f.Close() 234 syms, err := f.Symbols() 235 if err != nil { 236 t.Errorf("error reading symbols %v", err) 237 return 238 } 239 for _, s := range syms { 240 if s.Name == ".dup" { 241 t.Fatalf("%s contains symbol called .dup", sopath) 242 } 243 } 244 } 245 246 // The install command should have created a "shlibname" file for the 247 // listed packages (and runtime/cgo, and math on arm) indicating the 248 // name of the shared library containing it. 249 func TestShlibnameFiles(t *testing.T) { 250 pkgs := append([]string{}, minpkgs...) 251 pkgs = append(pkgs, "runtime/cgo") 252 if runtime.GOARCH == "arm" { 253 pkgs = append(pkgs, "math") 254 } 255 for _, pkg := range pkgs { 256 shlibnamefile := filepath.Join(gorootInstallDir, pkg+".shlibname") 257 contentsb, err := ioutil.ReadFile(shlibnamefile) 258 if err != nil { 259 t.Errorf("error reading shlibnamefile for %s: %v", pkg, err) 260 continue 261 } 262 contents := strings.TrimSpace(string(contentsb)) 263 if contents != soname { 264 t.Errorf("shlibnamefile for %s has wrong contents: %q", pkg, contents) 265 } 266 } 267 } 268 269 // Is a given offset into the file contained in a loaded segment? 270 func isOffsetLoaded(f *elf.File, offset uint64) bool { 271 for _, prog := range f.Progs { 272 if prog.Type == elf.PT_LOAD { 273 if prog.Off <= offset && offset < prog.Off+prog.Filesz { 274 return true 275 } 276 } 277 } 278 return false 279 } 280 281 func rnd(v int32, r int32) int32 { 282 if r <= 0 { 283 return v 284 } 285 v += r - 1 286 c := v % r 287 if c < 0 { 288 c += r 289 } 290 v -= c 291 return v 292 } 293 294 func readwithpad(r io.Reader, sz int32) ([]byte, error) { 295 data := make([]byte, rnd(sz, 4)) 296 _, err := io.ReadFull(r, data) 297 if err != nil { 298 return nil, err 299 } 300 data = data[:sz] 301 return data, nil 302 } 303 304 type note struct { 305 name string 306 tag int32 307 desc string 308 section *elf.Section 309 } 310 311 // Read all notes from f. As ELF section names are not supposed to be special, one 312 // looks for a particular note by scanning all SHT_NOTE sections looking for a note 313 // with a particular "name" and "tag". 314 func readNotes(f *elf.File) ([]*note, error) { 315 var notes []*note 316 for _, sect := range f.Sections { 317 if sect.Type != elf.SHT_NOTE { 318 continue 319 } 320 r := sect.Open() 321 for { 322 var namesize, descsize, tag int32 323 err := binary.Read(r, f.ByteOrder, &namesize) 324 if err != nil { 325 if err == io.EOF { 326 break 327 } 328 return nil, fmt.Errorf("read namesize failed: %v", err) 329 } 330 err = binary.Read(r, f.ByteOrder, &descsize) 331 if err != nil { 332 return nil, fmt.Errorf("read descsize failed: %v", err) 333 } 334 err = binary.Read(r, f.ByteOrder, &tag) 335 if err != nil { 336 return nil, fmt.Errorf("read type failed: %v", err) 337 } 338 name, err := readwithpad(r, namesize) 339 if err != nil { 340 return nil, fmt.Errorf("read name failed: %v", err) 341 } 342 desc, err := readwithpad(r, descsize) 343 if err != nil { 344 return nil, fmt.Errorf("read desc failed: %v", err) 345 } 346 notes = append(notes, ¬e{name: string(name), tag: tag, desc: string(desc), section: sect}) 347 } 348 } 349 return notes, nil 350 } 351 352 func dynStrings(t *testing.T, path string, flag elf.DynTag) []string { 353 f, err := elf.Open(path) 354 if err != nil { 355 t.Fatalf("elf.Open(%q) failed: %v", path, err) 356 } 357 defer f.Close() 358 dynstrings, err := f.DynString(flag) 359 if err != nil { 360 t.Fatalf("DynString(%s) failed on %s: %v", flag, path, err) 361 } 362 return dynstrings 363 } 364 365 func AssertIsLinkedToRegexp(t *testing.T, path string, re *regexp.Regexp) { 366 for _, dynstring := range dynStrings(t, path, elf.DT_NEEDED) { 367 if re.MatchString(dynstring) { 368 return 369 } 370 } 371 t.Errorf("%s is not linked to anything matching %v", path, re) 372 } 373 374 func AssertIsLinkedTo(t *testing.T, path, lib string) { 375 AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib))) 376 } 377 378 func AssertHasRPath(t *testing.T, path, dir string) { 379 for _, tag := range []elf.DynTag{elf.DT_RPATH, elf.DT_RUNPATH} { 380 for _, dynstring := range dynStrings(t, path, tag) { 381 for _, rpath := range strings.Split(dynstring, ":") { 382 if filepath.Clean(rpath) == filepath.Clean(dir) { 383 return 384 } 385 } 386 } 387 } 388 t.Errorf("%s does not have rpath %s", path, dir) 389 } 390 391 // Build a trivial program that links against the shared runtime and check it runs. 392 func TestTrivialExecutable(t *testing.T) { 393 goCmd(t, "install", "-linkshared", "trivial") 394 run(t, "trivial executable", "./bin/trivial") 395 AssertIsLinkedTo(t, "./bin/trivial", soname) 396 AssertHasRPath(t, "./bin/trivial", gorootInstallDir) 397 } 398 399 // Build a trivial program in PIE mode that links against the shared runtime and check it runs. 400 func TestTrivialExecutablePIE(t *testing.T) { 401 goCmd(t, "build", "-buildmode=pie", "-o", "trivial.pie", "-linkshared", "trivial") 402 run(t, "trivial executable", "./trivial.pie") 403 AssertIsLinkedTo(t, "./trivial.pie", soname) 404 AssertHasRPath(t, "./trivial.pie", gorootInstallDir) 405 } 406 407 // Build a division test program and check it runs. 408 func TestDivisionExecutable(t *testing.T) { 409 goCmd(t, "install", "-linkshared", "division") 410 run(t, "division executable", "./bin/division") 411 } 412 413 // Build an executable that uses cgo linked against the shared runtime and check it 414 // runs. 415 func TestCgoExecutable(t *testing.T) { 416 goCmd(t, "install", "-linkshared", "execgo") 417 run(t, "cgo executable", "./bin/execgo") 418 } 419 420 func checkPIE(t *testing.T, name string) { 421 f, err := elf.Open(name) 422 if err != nil { 423 t.Fatal("elf.Open failed: ", err) 424 } 425 defer f.Close() 426 if f.Type != elf.ET_DYN { 427 t.Errorf("%s has type %v, want ET_DYN", name, f.Type) 428 } 429 if hasDynTag(f, elf.DT_TEXTREL) { 430 t.Errorf("%s has DT_TEXTREL set", name) 431 } 432 } 433 434 func TestTrivialPIE(t *testing.T) { 435 name := "trivial_pie" 436 goCmd(t, "build", "-buildmode=pie", "-o="+name, "trivial") 437 defer os.Remove(name) 438 run(t, name, "./"+name) 439 checkPIE(t, name) 440 } 441 442 func TestCgoPIE(t *testing.T) { 443 name := "cgo_pie" 444 goCmd(t, "build", "-buildmode=pie", "-o="+name, "execgo") 445 defer os.Remove(name) 446 run(t, name, "./"+name) 447 checkPIE(t, name) 448 } 449 450 // Build a GOPATH package into a shared library that links against the goroot runtime 451 // and an executable that links against both. 452 func TestGopathShlib(t *testing.T) { 453 goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase") 454 AssertIsLinkedTo(t, filepath.Join(gopathInstallDir, "libdepBase.so"), soname) 455 goCmd(t, "install", "-linkshared", "exe") 456 AssertIsLinkedTo(t, "./bin/exe", soname) 457 AssertIsLinkedTo(t, "./bin/exe", "libdepBase.so") 458 AssertHasRPath(t, "./bin/exe", gorootInstallDir) 459 AssertHasRPath(t, "./bin/exe", gopathInstallDir) 460 // And check it runs. 461 run(t, "executable linked to GOPATH library", "./bin/exe") 462 } 463 464 // The shared library contains a note listing the packages it contains in a section 465 // that is not mapped into memory. 466 func testPkgListNote(t *testing.T, f *elf.File, note *note) { 467 if note.section.Flags != 0 { 468 t.Errorf("package list section has flags %v, want 0", note.section.Flags) 469 } 470 if isOffsetLoaded(f, note.section.Offset) { 471 t.Errorf("package list section contained in PT_LOAD segment") 472 } 473 if note.desc != "depBase\n" { 474 t.Errorf("incorrect package list %q, want %q", note.desc, "depBase\n") 475 } 476 } 477 478 // The shared library contains a note containing the ABI hash that is mapped into 479 // memory and there is a local symbol called go.link.abihashbytes that points 16 480 // bytes into it. 481 func testABIHashNote(t *testing.T, f *elf.File, note *note) { 482 if note.section.Flags != elf.SHF_ALLOC { 483 t.Errorf("abi hash section has flags %v, want SHF_ALLOC", note.section.Flags) 484 } 485 if !isOffsetLoaded(f, note.section.Offset) { 486 t.Errorf("abihash section not contained in PT_LOAD segment") 487 } 488 var hashbytes elf.Symbol 489 symbols, err := f.Symbols() 490 if err != nil { 491 t.Errorf("error reading symbols %v", err) 492 return 493 } 494 for _, sym := range symbols { 495 if sym.Name == "go.link.abihashbytes" { 496 hashbytes = sym 497 } 498 } 499 if hashbytes.Name == "" { 500 t.Errorf("no symbol called go.link.abihashbytes") 501 return 502 } 503 if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL { 504 t.Errorf("%s has incorrect binding %v, want STB_LOCAL", hashbytes.Name, elf.ST_BIND(hashbytes.Info)) 505 } 506 if f.Sections[hashbytes.Section] != note.section { 507 t.Errorf("%s has incorrect section %v, want %s", hashbytes.Name, f.Sections[hashbytes.Section].Name, note.section.Name) 508 } 509 if hashbytes.Value-note.section.Addr != 16 { 510 t.Errorf("%s has incorrect offset into section %d, want 16", hashbytes.Name, hashbytes.Value-note.section.Addr) 511 } 512 } 513 514 // A Go shared library contains a note indicating which other Go shared libraries it 515 // was linked against in an unmapped section. 516 func testDepsNote(t *testing.T, f *elf.File, note *note) { 517 if note.section.Flags != 0 { 518 t.Errorf("package list section has flags %v, want 0", note.section.Flags) 519 } 520 if isOffsetLoaded(f, note.section.Offset) { 521 t.Errorf("package list section contained in PT_LOAD segment") 522 } 523 // libdepBase.so just links against the lib containing the runtime. 524 if note.desc != soname { 525 t.Errorf("incorrect dependency list %q, want %q", note.desc, soname) 526 } 527 } 528 529 // The shared library contains notes with defined contents; see above. 530 func TestNotes(t *testing.T) { 531 goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase") 532 f, err := elf.Open(filepath.Join(gopathInstallDir, "libdepBase.so")) 533 if err != nil { 534 t.Fatal(err) 535 } 536 defer f.Close() 537 notes, err := readNotes(f) 538 if err != nil { 539 t.Fatal(err) 540 } 541 pkgListNoteFound := false 542 abiHashNoteFound := false 543 depsNoteFound := false 544 for _, note := range notes { 545 if note.name != "Go\x00\x00" { 546 continue 547 } 548 switch note.tag { 549 case 1: // ELF_NOTE_GOPKGLIST_TAG 550 if pkgListNoteFound { 551 t.Error("multiple package list notes") 552 } 553 testPkgListNote(t, f, note) 554 pkgListNoteFound = true 555 case 2: // ELF_NOTE_GOABIHASH_TAG 556 if abiHashNoteFound { 557 t.Error("multiple abi hash notes") 558 } 559 testABIHashNote(t, f, note) 560 abiHashNoteFound = true 561 case 3: // ELF_NOTE_GODEPS_TAG 562 if depsNoteFound { 563 t.Error("multiple depedency list notes") 564 } 565 testDepsNote(t, f, note) 566 depsNoteFound = true 567 } 568 } 569 if !pkgListNoteFound { 570 t.Error("package list note not found") 571 } 572 if !abiHashNoteFound { 573 t.Error("abi hash note not found") 574 } 575 if !depsNoteFound { 576 t.Error("deps note not found") 577 } 578 } 579 580 // Build a GOPATH package (depBase) into a shared library that links against the goroot 581 // runtime, another package (dep2) that links against the first, and and an 582 // executable that links against dep2. 583 func TestTwoGopathShlibs(t *testing.T) { 584 goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase") 585 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2") 586 goCmd(t, "install", "-linkshared", "exe2") 587 run(t, "executable linked to GOPATH library", "./bin/exe2") 588 } 589 590 func TestThreeGopathShlibs(t *testing.T) { 591 goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase") 592 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2") 593 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep3") 594 goCmd(t, "install", "-linkshared", "exe3") 595 run(t, "executable linked to GOPATH library", "./bin/exe3") 596 } 597 598 // If gccgo is not available or not new enough call t.Skip. Otherwise, 599 // return a build.Context that is set up for gccgo. 600 func prepGccgo(t *testing.T) build.Context { 601 gccgoName := os.Getenv("GCCGO") 602 if gccgoName == "" { 603 gccgoName = "gccgo" 604 } 605 gccgoPath, err := exec.LookPath(gccgoName) 606 if err != nil { 607 t.Skip("gccgo not found") 608 } 609 cmd := exec.Command(gccgoPath, "-dumpversion") 610 output, err := cmd.CombinedOutput() 611 if err != nil { 612 t.Fatalf("%s -dumpversion failed: %v\n%s", gccgoPath, err, output) 613 } 614 if string(output) < "5" { 615 t.Skipf("gccgo too old (%s)", strings.TrimSpace(string(output))) 616 } 617 gccgoContext := build.Default 618 gccgoContext.InstallSuffix = suffix + "_fPIC" 619 gccgoContext.Compiler = "gccgo" 620 gccgoContext.GOPATH = os.Getenv("GOPATH") 621 return gccgoContext 622 } 623 624 // Build a GOPATH package into a shared library with gccgo and an executable that 625 // links against it. 626 func TestGoPathShlibGccgo(t *testing.T) { 627 gccgoContext := prepGccgo(t) 628 629 libgoRE := regexp.MustCompile("libgo.so.[0-9]+") 630 631 depP, err := gccgoContext.Import("depBase", ".", build.ImportComment) 632 if err != nil { 633 t.Fatalf("import failed: %v", err) 634 } 635 gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs") 636 goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "depBase") 637 AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdepBase.so"), libgoRE) 638 goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe") 639 AssertIsLinkedToRegexp(t, "./bin/exe", libgoRE) 640 AssertIsLinkedTo(t, "./bin/exe", "libdepBase.so") 641 AssertHasRPath(t, "./bin/exe", gccgoInstallDir) 642 // And check it runs. 643 run(t, "gccgo-built", "./bin/exe") 644 } 645 646 // The gccgo version of TestTwoGopathShlibs: build a GOPATH package into a shared 647 // library with gccgo, another GOPATH package that depends on the first and an 648 // executable that links the second library. 649 func TestTwoGopathShlibsGccgo(t *testing.T) { 650 gccgoContext := prepGccgo(t) 651 652 libgoRE := regexp.MustCompile("libgo.so.[0-9]+") 653 654 depP, err := gccgoContext.Import("depBase", ".", build.ImportComment) 655 if err != nil { 656 t.Fatalf("import failed: %v", err) 657 } 658 gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs") 659 goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "depBase") 660 goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep2") 661 goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe2") 662 663 AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdepBase.so"), libgoRE) 664 AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep2.so"), libgoRE) 665 AssertIsLinkedTo(t, filepath.Join(gccgoInstallDir, "libdep2.so"), "libdepBase.so") 666 AssertIsLinkedToRegexp(t, "./bin/exe2", libgoRE) 667 AssertIsLinkedTo(t, "./bin/exe2", "libdep2") 668 AssertIsLinkedTo(t, "./bin/exe2", "libdepBase.so") 669 670 // And check it runs. 671 run(t, "gccgo-built", "./bin/exe2") 672 } 673 674 // Testing rebuilding of shared libraries when they are stale is a bit more 675 // complicated that it seems like it should be. First, we make everything "old": but 676 // only a few seconds old, or it might be older than gc (or the runtime source) and 677 // everything will get rebuilt. Then define a timestamp slightly newer than this 678 // time, which is what we set the mtime to of a file to cause it to be seen as new, 679 // and finally another slightly even newer one that we can compare files against to 680 // see if they have been rebuilt. 681 var oldTime = time.Now().Add(-9 * time.Second) 682 var nearlyNew = time.Now().Add(-6 * time.Second) 683 var stampTime = time.Now().Add(-3 * time.Second) 684 685 // resetFileStamps makes "everything" (bin, src, pkg from GOPATH and the 686 // test-specific parts of GOROOT) appear old. 687 func resetFileStamps() { 688 chtime := func(path string, info os.FileInfo, err error) error { 689 return os.Chtimes(path, oldTime, oldTime) 690 } 691 reset := func(path string) { 692 if err := filepath.Walk(path, chtime); err != nil { 693 log.Fatalf("resetFileStamps failed: %v", err) 694 } 695 696 } 697 reset("bin") 698 reset("pkg") 699 reset("src") 700 reset(gorootInstallDir) 701 } 702 703 // touch changes path and returns a function that changes it back. 704 // It also sets the time of the file, so that we can see if it is rewritten. 705 func touch(t *testing.T, path string) (cleanup func()) { 706 data, err := ioutil.ReadFile(path) 707 if err != nil { 708 t.Fatal(err) 709 } 710 old := make([]byte, len(data)) 711 copy(old, data) 712 if bytes.HasPrefix(data, []byte("!<arch>\n")) { 713 // Change last digit of build ID. 714 // (Content ID in the new content-based build IDs.) 715 const marker = `build id "` 716 i := bytes.Index(data, []byte(marker)) 717 if i < 0 { 718 t.Fatal("cannot find build id in archive") 719 } 720 j := bytes.IndexByte(data[i+len(marker):], '"') 721 if j < 0 { 722 t.Fatal("cannot find build id in archive") 723 } 724 i += len(marker) + j - 1 725 if data[i] == 'a' { 726 data[i] = 'b' 727 } else { 728 data[i] = 'a' 729 } 730 } else { 731 // assume it's a text file 732 data = append(data, '\n') 733 } 734 if err := ioutil.WriteFile(path, data, 0666); err != nil { 735 t.Fatal(err) 736 } 737 if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil { 738 t.Fatal(err) 739 } 740 return func() { 741 if err := ioutil.WriteFile(path, old, 0666); err != nil { 742 t.Fatal(err) 743 } 744 } 745 } 746 747 // isNew returns if the path is newer than the time stamp used by touch. 748 func isNew(t *testing.T, path string) bool { 749 fi, err := os.Stat(path) 750 if err != nil { 751 t.Fatal(err) 752 } 753 return fi.ModTime().After(stampTime) 754 } 755 756 // Fail unless path has been rebuilt (i.e. is newer than the time stamp used by 757 // isNew) 758 func AssertRebuilt(t *testing.T, msg, path string) { 759 t.Helper() 760 if !isNew(t, path) { 761 t.Errorf("%s was not rebuilt (%s)", msg, path) 762 } 763 } 764 765 // Fail if path has been rebuilt (i.e. is newer than the time stamp used by isNew) 766 func AssertNotRebuilt(t *testing.T, msg, path string) { 767 t.Helper() 768 if isNew(t, path) { 769 t.Errorf("%s was rebuilt (%s)", msg, path) 770 } 771 } 772 773 func TestRebuilding(t *testing.T) { 774 goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase") 775 goCmd(t, "install", "-linkshared", "exe") 776 777 // If the source is newer than both the .a file and the .so, both are rebuilt. 778 t.Run("newsource", func(t *testing.T) { 779 resetFileStamps() 780 cleanup := touch(t, "src/depBase/dep.go") 781 defer func() { 782 cleanup() 783 goCmd(t, "install", "-linkshared", "exe") 784 }() 785 goCmd(t, "install", "-linkshared", "exe") 786 AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "depBase.a")) 787 AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "libdepBase.so")) 788 }) 789 790 // If the .a file is newer than the .so, the .so is rebuilt (but not the .a) 791 t.Run("newarchive", func(t *testing.T) { 792 resetFileStamps() 793 AssertNotRebuilt(t, "new .a file before build", filepath.Join(gopathInstallDir, "depBase.a")) 794 goCmd(t, "list", "-linkshared", "-f={{.ImportPath}} {{.Stale}} {{.StaleReason}} {{.Target}}", "depBase") 795 AssertNotRebuilt(t, "new .a file before build", filepath.Join(gopathInstallDir, "depBase.a")) 796 cleanup := touch(t, filepath.Join(gopathInstallDir, "depBase.a")) 797 defer func() { 798 cleanup() 799 goCmd(t, "install", "-v", "-linkshared", "exe") 800 }() 801 goCmd(t, "install", "-v", "-linkshared", "exe") 802 AssertNotRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "depBase.a")) 803 AssertRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "libdepBase.so")) 804 }) 805 } 806 807 func appendFile(t *testing.T, path, content string) { 808 f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0660) 809 if err != nil { 810 t.Fatalf("os.OpenFile failed: %v", err) 811 } 812 defer func() { 813 err := f.Close() 814 if err != nil { 815 t.Fatalf("f.Close failed: %v", err) 816 } 817 }() 818 _, err = f.WriteString(content) 819 if err != nil { 820 t.Fatalf("f.WriteString failed: %v", err) 821 } 822 } 823 824 func writeFile(t *testing.T, path, content string) { 825 err := ioutil.WriteFile(path, []byte(content), 0644) 826 if err != nil { 827 t.Fatalf("ioutil.WriteFile failed: %v", err) 828 } 829 } 830 831 func TestABIChecking(t *testing.T) { 832 goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase") 833 goCmd(t, "install", "-linkshared", "exe") 834 835 // If we make an ABI-breaking change to depBase and rebuild libp.so but not exe, 836 // exe will abort with a complaint on startup. 837 // This assumes adding an exported function breaks ABI, which is not true in 838 // some senses but suffices for the narrow definition of ABI compatibility the 839 // toolchain uses today. 840 resetFileStamps() 841 appendFile(t, "src/depBase/dep.go", "func ABIBreak() {}\n") 842 goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase") 843 c := exec.Command("./bin/exe") 844 output, err := c.CombinedOutput() 845 if err == nil { 846 t.Fatal("executing exe did not fail after ABI break") 847 } 848 scanner := bufio.NewScanner(bytes.NewReader(output)) 849 foundMsg := false 850 const wantLine = "abi mismatch detected between the executable and libdepBase.so" 851 for scanner.Scan() { 852 if scanner.Text() == wantLine { 853 foundMsg = true 854 break 855 } 856 } 857 if err = scanner.Err(); err != nil { 858 t.Errorf("scanner encountered error: %v", err) 859 } 860 if !foundMsg { 861 t.Fatalf("exe failed, but without line %q; got output:\n%s", wantLine, output) 862 } 863 864 // Rebuilding exe makes it work again. 865 goCmd(t, "install", "-linkshared", "exe") 866 run(t, "rebuilt exe", "./bin/exe") 867 868 // If we make a change which does not break ABI (such as adding an unexported 869 // function) and rebuild libdepBase.so, exe still works, even if new function 870 // is in a file by itself. 871 resetFileStamps() 872 writeFile(t, "src/depBase/dep2.go", "package depBase\nfunc noABIBreak() {}\n") 873 goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase") 874 run(t, "after non-ABI breaking change", "./bin/exe") 875 } 876 877 // If a package 'explicit' imports a package 'implicit', building 878 // 'explicit' into a shared library implicitly includes implicit in 879 // the shared library. Building an executable that imports both 880 // explicit and implicit builds the code from implicit into the 881 // executable rather than fetching it from the shared library. The 882 // link still succeeds and the executable still runs though. 883 func TestImplicitInclusion(t *testing.T) { 884 goCmd(t, "install", "-buildmode=shared", "-linkshared", "explicit") 885 goCmd(t, "install", "-linkshared", "implicitcmd") 886 run(t, "running executable linked against library that contains same package as it", "./bin/implicitcmd") 887 } 888 889 // Tests to make sure that the type fields of empty interfaces and itab 890 // fields of nonempty interfaces are unique even across modules, 891 // so that interface equality works correctly. 892 func TestInterface(t *testing.T) { 893 goCmd(t, "install", "-buildmode=shared", "-linkshared", "iface_a") 894 // Note: iface_i gets installed implicitly as a dependency of iface_a. 895 goCmd(t, "install", "-buildmode=shared", "-linkshared", "iface_b") 896 goCmd(t, "install", "-linkshared", "iface") 897 run(t, "running type/itab uniqueness tester", "./bin/iface") 898 } 899 900 // Access a global variable from a library. 901 func TestGlobal(t *testing.T) { 902 goCmd(t, "install", "-buildmode=shared", "-linkshared", "globallib") 903 goCmd(t, "install", "-linkshared", "global") 904 run(t, "global executable", "./bin/global") 905 AssertIsLinkedTo(t, "./bin/global", soname) 906 AssertHasRPath(t, "./bin/global", gorootInstallDir) 907 }