blob: 09b159d7734deb08ee06a47cd94a44c8776c809c [file] [log] [blame]
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +12001// 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"
Michael Hudson-Doyle65518032015-05-25 13:59:08 +120011 "encoding/binary"
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +120012 "errors"
13 "flag"
14 "fmt"
15 "go/build"
Michael Hudson-Doyle65518032015-05-25 13:59:08 +120016 "io"
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +120017 "io/ioutil"
18 "log"
19 "math/rand"
20 "os"
21 "os/exec"
22 "path/filepath"
23 "strings"
24 "testing"
25 "time"
26)
27
28var gopathInstallDir, gorootInstallDir, suffix string
29
30// This is the smallest set of packages we can link into a shared
31// library (runtime/cgo is built implicitly).
32var minpkgs = []string{"runtime", "sync/atomic"}
33var soname = "libruntime,sync-atomic.so"
34
35// run runs a command and calls t.Errorf if it fails.
36func run(t *testing.T, msg string, args ...string) {
37 c := exec.Command(args[0], args[1:]...)
38 if output, err := c.CombinedOutput(); err != nil {
39 t.Errorf("executing %s (%s) failed %s:\n%s", strings.Join(args, " "), msg, err, output)
40 }
41}
42
43// goCmd invokes the go tool with the installsuffix set up by TestMain. It calls
44// t.Errorf if the command fails.
45func goCmd(t *testing.T, args ...string) {
46 newargs := []string{args[0], "-installsuffix=" + suffix}
47 if testing.Verbose() {
48 newargs = append(newargs, "-v")
49 }
50 newargs = append(newargs, args[1:]...)
51 c := exec.Command("go", newargs...)
Michael Hudson-Doyle9262e212015-05-27 10:30:55 +120052 var output []byte
53 var err error
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +120054 if testing.Verbose() {
55 fmt.Printf("+ go %s\n", strings.Join(newargs, " "))
Michael Hudson-Doyle9262e212015-05-27 10:30:55 +120056 c.Stdout = os.Stdout
57 c.Stderr = os.Stderr
58 err = c.Run()
59 } else {
60 output, err = c.CombinedOutput()
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +120061 }
Michael Hudson-Doyle9262e212015-05-27 10:30:55 +120062 if err != nil {
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +120063 if t != nil {
64 t.Errorf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
65 } else {
66 log.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
67 }
68 }
69}
70
71// TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit).
72func testMain(m *testing.M) (int, error) {
73 // Because go install -buildmode=shared $standard_library_package always
74 // installs into $GOROOT, here are some gymnastics to come up with a unique
75 // installsuffix to use in this test that we can clean up afterwards.
76 myContext := build.Default
77 runtimeP, err := myContext.Import("runtime", ".", build.ImportComment)
78 if err != nil {
79 return 0, fmt.Errorf("import failed: %v", err)
80 }
81 for i := 0; i < 10000; i++ {
82 try := fmt.Sprintf("%s_%d_dynlink", runtimeP.PkgTargetRoot, rand.Int63())
83 err = os.Mkdir(try, 0700)
84 if os.IsExist(err) {
85 continue
86 }
87 if err == nil {
88 gorootInstallDir = try
89 }
90 break
91 }
92 if err != nil {
93 return 0, fmt.Errorf("can't create temporary directory: %v", err)
94 }
95 if gorootInstallDir == "" {
96 return 0, errors.New("could not create temporary directory after 10000 tries")
97 }
98 defer os.RemoveAll(gorootInstallDir)
99
100 // Some tests need to edit the source in GOPATH, so copy this directory to a
101 // temporary directory and chdir to that.
102 scratchDir, err := ioutil.TempDir("", "testshared")
103 if err != nil {
104 return 0, fmt.Errorf("TempDir failed: %v", err)
105 }
106 defer os.RemoveAll(scratchDir)
107 err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
108 scratchPath := filepath.Join(scratchDir, path)
109 if info.IsDir() {
110 if path == "." {
111 return nil
112 }
113 return os.Mkdir(scratchPath, info.Mode())
114 } else {
115 fromBytes, err := ioutil.ReadFile(path)
116 if err != nil {
117 return err
118 }
119 return ioutil.WriteFile(scratchPath, fromBytes, info.Mode())
120 }
121 })
122 if err != nil {
123 return 0, fmt.Errorf("walk failed: %v", err)
124 }
125 os.Setenv("GOPATH", scratchDir)
126 myContext.GOPATH = scratchDir
127 os.Chdir(scratchDir)
128
129 // All tests depend on runtime being built into a shared library. Because
130 // that takes a few seconds, do it here and have all tests use the version
131 // built here.
132 suffix = strings.Split(filepath.Base(gorootInstallDir), "_")[2]
133 goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...)
134
135 myContext.InstallSuffix = suffix + "_dynlink"
136 depP, err := myContext.Import("dep", ".", build.ImportComment)
137 if err != nil {
138 return 0, fmt.Errorf("import failed: %v", err)
139 }
140 gopathInstallDir = depP.PkgTargetRoot
141 return m.Run(), nil
142}
143
144func TestMain(m *testing.M) {
145 flag.Parse()
146 exitCode, err := testMain(m)
147 if err != nil {
148 log.Fatal(err)
149 }
150 os.Exit(exitCode)
151}
152
153// The shared library was built at the expected location.
154func TestSOBuilt(t *testing.T) {
155 _, err := os.Stat(filepath.Join(gorootInstallDir, soname))
156 if err != nil {
157 t.Error(err)
158 }
159}
160
161// The install command should have created a "shlibname" file for the
162// listed packages (and runtime/cgo) indicating the name of the shared
163// library containing it.
164func TestShlibnameFiles(t *testing.T) {
165 pkgs := append([]string{}, minpkgs...)
166 pkgs = append(pkgs, "runtime/cgo")
167 for _, pkg := range pkgs {
168 shlibnamefile := filepath.Join(gorootInstallDir, pkg+".shlibname")
169 contentsb, err := ioutil.ReadFile(shlibnamefile)
170 if err != nil {
171 t.Errorf("error reading shlibnamefile for %s: %v", pkg, err)
172 continue
173 }
174 contents := strings.TrimSpace(string(contentsb))
175 if contents != soname {
176 t.Errorf("shlibnamefile for %s has wrong contents: %q", pkg, contents)
177 }
178 }
179}
180
Michael Hudson-Doyle65518032015-05-25 13:59:08 +1200181// Is a given offset into the file contained in a loaded segment?
182func isOffsetLoaded(f *elf.File, offset uint64) bool {
183 for _, prog := range f.Progs {
184 if prog.Type == elf.PT_LOAD {
185 if prog.Off <= offset && offset < prog.Off+prog.Filesz {
186 return true
187 }
188 }
189 }
190 return false
191}
192
193func rnd(v int32, r int32) int32 {
194 if r <= 0 {
195 return v
196 }
197 v += r - 1
198 c := v % r
199 if c < 0 {
200 c += r
201 }
202 v -= c
203 return v
204}
205
206func readwithpad(r io.Reader, sz int32) ([]byte, error) {
207 data := make([]byte, rnd(sz, 4))
208 _, err := io.ReadFull(r, data)
209 if err != nil {
210 return nil, err
211 }
212 data = data[:sz]
213 return data, nil
214}
215
216type note struct {
217 name string
218 tag int32
219 desc string
220 section *elf.Section
221}
222
223// Read all notes from f. As ELF section names are not supposed to be special, one
224// looks for a particular note by scanning all SHT_NOTE sections looking for a note
225// with a particular "name" and "tag".
226func readNotes(f *elf.File) ([]*note, error) {
227 var notes []*note
228 for _, sect := range f.Sections {
229 if sect.Type != elf.SHT_NOTE {
230 continue
231 }
232 r := sect.Open()
233 for {
234 var namesize, descsize, tag int32
235 err := binary.Read(r, f.ByteOrder, &namesize)
236 if err != nil {
237 if err == io.EOF {
238 break
239 }
240 return nil, fmt.Errorf("read namesize failed:", err)
241 }
242 err = binary.Read(r, f.ByteOrder, &descsize)
243 if err != nil {
244 return nil, fmt.Errorf("read descsize failed:", err)
245 }
246 err = binary.Read(r, f.ByteOrder, &tag)
247 if err != nil {
248 return nil, fmt.Errorf("read type failed:", err)
249 }
250 name, err := readwithpad(r, namesize)
251 if err != nil {
252 return nil, fmt.Errorf("read name failed:", err)
253 }
254 desc, err := readwithpad(r, descsize)
255 if err != nil {
256 return nil, fmt.Errorf("read desc failed:", err)
257 }
258 notes = append(notes, &note{name: string(name), tag: tag, desc: string(desc), section: sect})
259 }
260 }
261 return notes, nil
262}
263
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +1200264func dynStrings(path string, flag elf.DynTag) []string {
265 f, err := elf.Open(path)
266 defer f.Close()
267 if err != nil {
268 log.Fatal("elf.Open failed: ", err)
269 }
270 dynstrings, err := f.DynString(flag)
271 if err != nil {
272 log.Fatal("dynstring failed: ", err)
273 }
274 return dynstrings
275}
276
277func AssertIsLinkedTo(t *testing.T, path, lib string) {
278 for _, dynstring := range dynStrings(path, elf.DT_NEEDED) {
279 if dynstring == lib {
280 return
281 }
282 }
283 t.Errorf("%s is not linked to %s", path, lib)
284}
285
286func AssertHasRPath(t *testing.T, path, dir string) {
Shenghou Mac0134172015-05-19 02:48:15 -0400287 for _, tag := range []elf.DynTag{elf.DT_RPATH, elf.DT_RUNPATH} {
288 for _, dynstring := range dynStrings(path, tag) {
289 for _, rpath := range strings.Split(dynstring, ":") {
290 if filepath.Clean(rpath) == filepath.Clean(dir) {
291 return
292 }
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +1200293 }
294 }
295 }
296 t.Errorf("%s does not have rpath %s", path, dir)
297}
298
299// Build a trivial program that links against the shared runtime and check it runs.
300func TestTrivialExecutable(t *testing.T) {
301 goCmd(t, "install", "-linkshared", "trivial")
302 run(t, "trivial executable", "./bin/trivial")
303 AssertIsLinkedTo(t, "./bin/trivial", soname)
304 AssertHasRPath(t, "./bin/trivial", gorootInstallDir)
305}
306
307// Build a GOPATH package into a shared library that links against the goroot runtime
308// and an executable that links against both.
309func TestGOPathShlib(t *testing.T) {
310 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
311 AssertIsLinkedTo(t, filepath.Join(gopathInstallDir, "libdep.so"), soname)
312 goCmd(t, "install", "-linkshared", "exe")
313 AssertIsLinkedTo(t, "./bin/exe", soname)
314 AssertIsLinkedTo(t, "./bin/exe", "libdep.so")
315 AssertHasRPath(t, "./bin/exe", gorootInstallDir)
316 AssertHasRPath(t, "./bin/exe", gopathInstallDir)
317 // And check it runs.
318 run(t, "executable linked to GOPATH library", "./bin/exe")
319}
320
Michael Hudson-Doyle65518032015-05-25 13:59:08 +1200321// The shared library contains a note listing the packages it contains in a section
322// that is not mapped into memory.
323func testPkgListNote(t *testing.T, f *elf.File, note *note) {
324 if note.section.Flags != 0 {
325 t.Errorf("package list section has flags %v", note.section.Flags)
326 }
327 if isOffsetLoaded(f, note.section.Offset) {
328 t.Errorf("package list section contained in PT_LOAD segment")
329 }
330 if note.desc != "dep\n" {
331 t.Errorf("incorrect package list %q", note.desc)
332 }
333}
334
335// The shared library contains a note containing the ABI hash that is mapped into
336// memory and there is a local symbol called go.link.abihashbytes that points 16
337// bytes into it.
338func testABIHashNote(t *testing.T, f *elf.File, note *note) {
339 if note.section.Flags != elf.SHF_ALLOC {
340 t.Errorf("abi hash section has flags %v", note.section.Flags)
341 }
342 if !isOffsetLoaded(f, note.section.Offset) {
343 t.Errorf("abihash section not contained in PT_LOAD segment")
344 }
345 var hashbytes elf.Symbol
346 symbols, err := f.Symbols()
347 if err != nil {
348 t.Errorf("error reading symbols %v", err)
349 return
350 }
351 for _, sym := range symbols {
352 if sym.Name == "go.link.abihashbytes" {
353 hashbytes = sym
354 }
355 }
356 if hashbytes.Name == "" {
357 t.Errorf("no symbol called go.link.abihashbytes")
358 return
359 }
360 if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL {
361 t.Errorf("%s has incorrect binding %v", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
362 }
363 if f.Sections[hashbytes.Section] != note.section {
364 t.Errorf("%s has incorrect section %v", hashbytes.Name, f.Sections[hashbytes.Section].Name)
365 }
366 if hashbytes.Value-note.section.Addr != 16 {
367 t.Errorf("%s has incorrect offset into section %d", hashbytes.Name, hashbytes.Value-note.section.Addr)
368 }
369}
370
Michael Hudson-Doylebcc18702015-05-25 14:51:02 +1200371// A Go shared library contains a note indicating which other Go shared libraries it
372// was linked against in an unmapped section.
373func testDepsNote(t *testing.T, f *elf.File, note *note) {
374 if note.section.Flags != 0 {
375 t.Errorf("package list section has flags %v", note.section.Flags)
376 }
377 if isOffsetLoaded(f, note.section.Offset) {
378 t.Errorf("package list section contained in PT_LOAD segment")
379 }
380 // libdep.so just links against the lib containing the runtime.
381 if note.desc != soname {
382 t.Errorf("incorrect dependency list %q", note.desc)
383 }
384}
385
Michael Hudson-Doyle65518032015-05-25 13:59:08 +1200386// The shared library contains notes with defined contents; see above.
387func TestNotes(t *testing.T) {
388 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
389 f, err := elf.Open(filepath.Join(gopathInstallDir, "libdep.so"))
390 if err != nil {
391 t.Fatal(err)
392 }
393 defer f.Close()
394 notes, err := readNotes(f)
395 if err != nil {
396 t.Fatal(err)
397 }
398 pkgListNoteFound := false
399 abiHashNoteFound := false
Michael Hudson-Doylebcc18702015-05-25 14:51:02 +1200400 depsNoteFound := false
Michael Hudson-Doyle65518032015-05-25 13:59:08 +1200401 for _, note := range notes {
Russ Cox7e276252015-06-04 14:27:39 -0400402 if note.name != "Go\x00\x00" {
Michael Hudson-Doyle65518032015-05-25 13:59:08 +1200403 continue
404 }
405 switch note.tag {
406 case 1: // ELF_NOTE_GOPKGLIST_TAG
407 if pkgListNoteFound {
408 t.Error("multiple package list notes")
409 }
410 testPkgListNote(t, f, note)
411 pkgListNoteFound = true
412 case 2: // ELF_NOTE_GOABIHASH_TAG
413 if abiHashNoteFound {
414 t.Error("multiple abi hash notes")
415 }
416 testABIHashNote(t, f, note)
417 abiHashNoteFound = true
Michael Hudson-Doylebcc18702015-05-25 14:51:02 +1200418 case 3: // ELF_NOTE_GODEPS_TAG
419 if depsNoteFound {
420 t.Error("multiple abi hash notes")
421 }
422 testDepsNote(t, f, note)
423 depsNoteFound = true
Michael Hudson-Doyle65518032015-05-25 13:59:08 +1200424 }
425 }
426 if !pkgListNoteFound {
427 t.Error("package list note not found")
428 }
429 if !abiHashNoteFound {
430 t.Error("abi hash note not found")
431 }
Michael Hudson-Doylebcc18702015-05-25 14:51:02 +1200432 if !depsNoteFound {
433 t.Error("deps note not found")
434 }
435}
436
437// Build a GOPATH package (dep) into a shared library that links against the goroot
438// runtime, another package (dep2) that links against the first, and and an
439// executable that links against dep2.
440func TestTwoGOPathShlibs(t *testing.T) {
441 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
442 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2")
443 goCmd(t, "install", "-linkshared", "exe2")
444 run(t, "executable linked to GOPATH library", "./bin/exe2")
Michael Hudson-Doyle65518032015-05-25 13:59:08 +1200445}
446
Michael Hudson-Doyle362a40e2015-05-07 21:29:47 +1200447// Testing rebuilding of shared libraries when they are stale is a bit more
448// complicated that it seems like it should be. First, we make everything "old": but
449// only a few seconds old, or it might be older than 6g (or the runtime source) and
450// everything will get rebuilt. Then define a timestamp slightly newer than this
451// time, which is what we set the mtime to of a file to cause it to be seen as new,
452// and finally another slightly even newer one that we can compare files against to
453// see if they have been rebuilt.
454var oldTime = time.Now().Add(-9 * time.Second)
455var nearlyNew = time.Now().Add(-6 * time.Second)
456var stampTime = time.Now().Add(-3 * time.Second)
457
458// resetFileStamps makes "everything" (bin, src, pkg from GOPATH and the
459// test-specific parts of GOROOT) appear old.
460func resetFileStamps() {
461 chtime := func(path string, info os.FileInfo, err error) error {
462 return os.Chtimes(path, oldTime, oldTime)
463 }
464 reset := func(path string) {
465 if err := filepath.Walk(path, chtime); err != nil {
466 log.Fatalf("resetFileStamps failed: %v", err)
467 }
468
469 }
470 reset("bin")
471 reset("pkg")
472 reset("src")
473 reset(gorootInstallDir)
474}
475
476// touch makes path newer than the "old" time stamp used by resetFileStamps.
477func touch(path string) {
478 if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil {
479 log.Fatalf("os.Chtimes failed: %v", err)
480 }
481}
482
483// isNew returns if the path is newer than the time stamp used by touch.
484func isNew(path string) bool {
485 fi, err := os.Stat(path)
486 if err != nil {
487 log.Fatalf("os.Stat failed: %v", err)
488 }
489 return fi.ModTime().After(stampTime)
490}
491
492// Fail unless path has been rebuilt (i.e. is newer than the time stamp used by
493// isNew)
494func AssertRebuilt(t *testing.T, msg, path string) {
495 if !isNew(path) {
496 t.Errorf("%s was not rebuilt (%s)", msg, path)
497 }
498}
499
500// Fail if path has been rebuilt (i.e. is newer than the time stamp used by isNew)
501func AssertNotRebuilt(t *testing.T, msg, path string) {
502 if isNew(path) {
503 t.Errorf("%s was rebuilt (%s)", msg, path)
504 }
505}
506
507func TestRebuilding(t *testing.T) {
508 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
509 goCmd(t, "install", "-linkshared", "exe")
510
511 // If the source is newer than both the .a file and the .so, both are rebuilt.
512 resetFileStamps()
513 touch("src/dep/dep.go")
514 goCmd(t, "install", "-linkshared", "exe")
515 AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "dep.a"))
516 AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "libdep.so"))
517
518 // If the .a file is newer than the .so, the .so is rebuilt (but not the .a)
519 resetFileStamps()
520 touch(filepath.Join(gopathInstallDir, "dep.a"))
521 goCmd(t, "install", "-linkshared", "exe")
522 AssertNotRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "dep.a"))
523 AssertRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "libdep.so"))
524}
525
526func appendFile(path, content string) {
527 f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0660)
528 if err != nil {
529 log.Fatalf("os.OpenFile failed: %v", err)
530 }
531 defer func() {
532 err := f.Close()
533 if err != nil {
534 log.Fatalf("f.Close failed: %v", err)
535 }
536 }()
537 _, err = f.WriteString(content)
538 if err != nil {
539 log.Fatalf("f.WriteString failed: %v", err)
540 }
541}
542
543func TestABIChecking(t *testing.T) {
544 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
545 goCmd(t, "install", "-linkshared", "exe")
546
547 // If we make an ABI-breaking change to dep and rebuild libp.so but not exe,
548 // exe will abort with a complaint on startup.
549 // This assumes adding an exported function breaks ABI, which is not true in
550 // some senses but suffices for the narrow definition of ABI compatiblity the
551 // toolchain uses today.
552 appendFile("src/dep/dep.go", "func ABIBreak() {}\n")
553 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
554 c := exec.Command("./bin/exe")
555 output, err := c.CombinedOutput()
556 if err == nil {
557 t.Fatal("executing exe did not fail after ABI break")
558 }
559 scanner := bufio.NewScanner(bytes.NewReader(output))
560 foundMsg := false
561 const wantLine = "abi mismatch detected between the executable and libdep.so"
562 for scanner.Scan() {
563 if scanner.Text() == wantLine {
564 foundMsg = true
565 break
566 }
567 }
568 if err = scanner.Err(); err != nil {
569 t.Errorf("scanner encountered error: %v", err)
570 }
571 if !foundMsg {
572 t.Fatalf("exe failed, but without line %q; got output:\n%s", wantLine, output)
573 }
574
575 // Rebuilding exe makes it work again.
576 goCmd(t, "install", "-linkshared", "exe")
577 run(t, "rebuilt exe", "./bin/exe")
578
579 // If we make a change which does not break ABI (such as adding an unexported
580 // function) and rebuild libdep.so, exe still works.
581 appendFile("src/dep/dep.go", "func noABIBreak() {}\n")
582 goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
583 run(t, "after non-ABI breaking change", "./bin/exe")
584}