]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: read new non-ELF build ID in binaries
authorRuss Cox <rsc@golang.org>
Thu, 4 Jun 2015 19:20:41 +0000 (15:20 -0400)
committerRuss Cox <rsc@golang.org>
Fri, 5 Jun 2015 04:08:51 +0000 (04:08 +0000)
Fixes #11048.
Fixes #11075.

Change-Id: I81f5ef1e1944056ce5494c91aa4a4a63c758f566
Reviewed-on: https://go-review.googlesource.com/10709
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/cmd/go/build.go
src/cmd/go/note_test.go
src/cmd/go/pkg.go

index 8f9a7b87ed2e2c07816e6e953343ad607c8ac9aa..c83738237c11b344b6c94deea99fd7a9d557e1de 100644 (file)
@@ -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
index c2a7e678c4bb2b50e04a30fa71c6335b77a1d670..efe8198c73cd369701b41f11311e07dc5c4d9fa9 100644 (file)
@@ -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)
index 7befaa43bcb891bc7b74c4b1e291113ce247ae69..d2d4da1af9a51f9d8bc6d32c94c8939c5e526f96 100644 (file)
@@ -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:
-//
-//     <NUL>
-//
-//     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
 }