From: Russ Cox Date: Thu, 4 Jun 2015 19:20:41 +0000 (-0400) Subject: cmd/go: read new non-ELF build ID in binaries X-Git-Tag: go1.5beta1~351 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=de305a197f5c9ca3bb09da024e06ba7be0c7435d;p=gostls13.git cmd/go: read new non-ELF build ID in binaries Fixes #11048. Fixes #11075. Change-Id: I81f5ef1e1944056ce5494c91aa4a4a63c758f566 Reviewed-on: https://go-review.googlesource.com/10709 Reviewed-by: Ian Lance Taylor --- diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go index 8f9a7b87ed..c83738237c 100644 --- a/src/cmd/go/build.go +++ b/src/cmd/go/build.go @@ -1430,26 +1430,6 @@ func (b *builder) build(a *action) (err error) { if err := buildToolchain.ld(b, a.p, a.target, all, a.objpkg, objects); err != nil { return err } - - // Write build ID to end of binary. - // We could try to put it in a custom section or some such, - // but then we'd need different code for ELF, Mach-O, PE, and Plan 9. - // Instead, just append to the binary. No one should care. - // Issue #11048 is to fix this for ELF and Mach-O at least. - if buildToolchain == (gcToolchain{}) && a.p.buildID != "" { - f, err := os.OpenFile(a.target, os.O_WRONLY|os.O_APPEND, 0) - if err != nil { - return err - } - defer f.Close() - // Note: This string must match readBuildIDFromBinary in pkg.go. - if _, err := fmt.Fprintf(f, "\x00\n\ngo binary\nbuild id %q\nend go binary\n", a.p.buildID); err != nil { - return err - } - if err := f.Close(); err != nil { - return err - } - } } return nil diff --git a/src/cmd/go/note_test.go b/src/cmd/go/note_test.go index c2a7e678c4..efe8198c73 100644 --- a/src/cmd/go/note_test.go +++ b/src/cmd/go/note_test.go @@ -6,16 +6,17 @@ package main import ( "io/ioutil" + "os" "os/exec" "runtime" "testing" ) func TestNoteReading(t *testing.T) { - // TODO: Enable on non-ELF systems. - switch runtime.GOOS { - case "darwin", "windows", "plan9", "nacl": - t.Skipf("skipping on %q", runtime.GOOS) + // No file system access on these systems. + switch sys := runtime.GOOS + "/" + runtime.GOARCH; sys { + case "darwin/arm", "darwin/arm64", "nacl/386", "nacl/amd64p32", "nacl/arm": + t.Skipf("skipping on %s/%s - no file system", runtime.GOOS, runtime.GOARCH) } // TODO: Replace with new test scaffolding by iant. @@ -23,6 +24,8 @@ func TestNoteReading(t *testing.T) { if err != nil { t.Fatal(err) } + defer os.RemoveAll(d) + out, err := exec.Command("go", "build", "-o", d+"/go.exe", "cmd/go").CombinedOutput() if err != nil { t.Fatalf("go build cmd/go: %v\n%s", err, out) diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go index 7befaa43bc..d2d4da1af9 100644 --- a/src/cmd/go/pkg.go +++ b/src/cmd/go/pkg.go @@ -1163,43 +1163,46 @@ func readBuildID(p *Package) (id string, err error) { } var ( - goBinary = []byte("\x00\n\ngo binary\n") - endGoBinary = []byte("\nend go binary\n") - newlineAndBuildid = []byte("\nbuild id ") + goBuildPrefix = []byte("\xff Go build ID: \"") + goBuildEnd = []byte("\"\n \xff") elfPrefix = []byte("ELF\x7F") ) // readBuildIDFromBinary reads the build ID from a binary. // -// The location of the build ID differs by object file type. -// ELF uses a proper PT_NOTE section. -// -// Instead of trying to be good citizens and store the build ID in a -// custom section of the binary, which would be different for each -// of the four binary types we support (ELF, Mach-O, Plan 9, PE), -// we write a few lines to the end of the binary. -// -// At the very end of the binary we expect to find: -// -// -// -// go binary -// build id "XXX" -// end go binary +// ELF binaries store the build ID in a proper PT_NOTE section. // +// Other binary formats are not so flexible. For those, the linker +// stores the build ID as non-instruction bytes at the very beginning +// of the text segment, which should appear near the beginning +// of the file. This is clumsy but fairly portable. Custom locations +// can be added for other binary types as needed, like we did for ELF. func readBuildIDFromBinary(filename string) (id string, err error) { if filename == "" { return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDUnknown} } + // Read the first 8 kB of the binary file. + // That should be enough to find the build ID. + // In ELF files, the build ID is in the leading headers, + // which are typically less than 4 kB, not to mention 8 kB. + // On other systems, we're trying to read enough that + // we get the beginning of the text segment in the read. + // The offset where the text segment begins in a hello + // world compiled for each different object format today: + // + // Plan 9: 0x20 + // Windows: 0x600 + // Mach-O: 0x1000 + // f, err := os.Open(filename) if err != nil { return "", err } defer f.Close() - data := make([]byte, 4096) + data := make([]byte, 8192) _, err = io.ReadFull(f, data) if err == io.ErrUnexpectedEOF { err = nil @@ -1212,43 +1215,22 @@ func readBuildIDFromBinary(filename string) (id string, err error) { return readELFGoBuildID(filename, f, data) } - off, err := f.Seek(0, 2) - if err != nil { - return "", err - } - n := 1024 - if off < int64(n) { - n = int(off) - } - if _, err := f.Seek(off-int64(n), 0); err != nil { - return "", err - } - data = make([]byte, n) - if _, err := io.ReadFull(f, data); err != nil { - return "", err - } - if !bytes.HasSuffix(data, endGoBinary) { - // Trailer missing. Treat as successful but build ID empty. - return "", nil - } - i := bytes.LastIndex(data, goBinary) + i := bytes.Index(data, goBuildPrefix) if i < 0 { - // Trailer missing. Treat as successful but build ID empty. + // Missing. Treat as successful but build ID empty. return "", nil } - // Have trailer. Find build id line. - data = data[i:] - i = bytes.Index(data, newlineAndBuildid) - if i < 0 { - // Trailer present; build ID missing. Treat as successful but empty. - return "", nil + j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd) + if j < 0 { + return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDMalformed} } - line := data[i+len(newlineAndBuildid):] - j := bytes.IndexByte(line, '\n') // must succeed - endGoBinary is at end and has newlines - id, err = strconv.Unquote(string(line[:j])) + + quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1] + id, err = strconv.Unquote(string(quoted)) if err != nil { return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDMalformed} } + return id, nil }