]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: fix loading of buildid on OS X executables
authorRuss Cox <rsc@golang.org>
Wed, 18 Nov 2015 20:38:26 +0000 (15:38 -0500)
committerRuss Cox <rsc@golang.org>
Thu, 19 Nov 2015 20:57:16 +0000 (20:57 +0000)
This is a bit of a belt-and-suspenders fix.
On OS X, we now parse the Mach-O file to find the __text section,
which is arguably the more proper fix. But it's a bit worrisome to
depend on a name like __text not changing, so we also read more
of the initial file (now 32 kB, up from 8 kB) and scan that too.

Fixes #12327.

Change-Id: I3a201a3dc278d24707109bb3961c3bdd8b8a0b7b
Reviewed-on: https://go-review.googlesource.com/17038
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/cmd/dist/deps.go
src/cmd/go/note.go
src/cmd/go/note_test.go
src/cmd/go/pkg.go

index ba360e5b3b062dca8863faea62029717d5f9bee8..15d2743be84d1b299484c9392561c54023af3179 100644 (file)
@@ -10,6 +10,7 @@ var builddeps = map[string][]string{
        "crypto/sha1":                       {"crypto", "errors", "hash", "io", "math", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "strconv", "sync", "sync/atomic", "unicode/utf8"},
        "debug/dwarf":                       {"encoding/binary", "errors", "fmt", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "path", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
        "debug/elf":                         {"bytes", "debug/dwarf", "encoding/binary", "errors", "fmt", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "path", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
+       "debug/macho":                       {"bytes", "debug/dwarf", "encoding/binary", "errors", "fmt", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "math", "os", "path", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "time", "unicode", "unicode/utf16", "unicode/utf8"},
        "encoding":                          {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
        "encoding/base64":                   {"errors", "io", "math", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "strconv", "sync", "sync/atomic", "unicode/utf8"},
        "encoding/binary":                   {"errors", "io", "math", "reflect", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "strconv", "sync", "sync/atomic", "unicode/utf8"},
@@ -55,5 +56,5 @@ var builddeps = map[string][]string{
        "unicode":                 {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
        "unicode/utf16":           {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
        "unicode/utf8":            {"runtime", "runtime/internal/atomic", "runtime/internal/sys"},
-       "cmd/go":                  {"bufio", "bytes", "container/heap", "crypto", "crypto/sha1", "debug/dwarf", "debug/elf", "encoding", "encoding/base64", "encoding/binary", "encoding/json", "errors", "flag", "fmt", "go/ast", "go/build", "go/doc", "go/parser", "go/scanner", "go/token", "hash", "internal/singleflight", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "io/ioutil", "log", "math", "net/url", "os", "os/exec", "os/signal", "path", "path/filepath", "reflect", "regexp", "regexp/syntax", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "text/template", "text/template/parse", "time", "unicode", "unicode/utf16", "unicode/utf8"},
+       "cmd/go":                  {"bufio", "bytes", "container/heap", "crypto", "crypto/sha1", "debug/dwarf", "debug/elf", "debug/macho", "encoding", "encoding/base64", "encoding/binary", "encoding/json", "errors", "flag", "fmt", "go/ast", "go/build", "go/doc", "go/parser", "go/scanner", "go/token", "hash", "internal/singleflight", "internal/syscall/windows", "internal/syscall/windows/registry", "io", "io/ioutil", "log", "math", "net/url", "os", "os/exec", "os/signal", "path", "path/filepath", "reflect", "regexp", "regexp/syntax", "runtime", "runtime/internal/atomic", "runtime/internal/sys", "sort", "strconv", "strings", "sync", "sync/atomic", "syscall", "text/template", "text/template/parse", "time", "unicode", "unicode/utf16", "unicode/utf8"},
 }
index 5c953c4d004e37b5821211ac30b83636c5aa2c69..c7346a5731e61202957d2e3db8c503a2fb67aecb 100644 (file)
@@ -7,6 +7,7 @@ package main
 import (
        "bytes"
        "debug/elf"
+       "debug/macho"
        "encoding/binary"
        "fmt"
        "io"
@@ -134,3 +135,42 @@ func readELFGoBuildID(filename string, f *os.File, data []byte) (buildid string,
        // No note. Treat as successful but build ID empty.
        return "", nil
 }
+
+// The Go build ID is stored at the beginning of the Mach-O __text segment.
+// The caller has already opened filename, to get f, and read a few kB out, in data.
+// Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount
+// of other junk placed in the file ahead of the main text.
+func readMachoGoBuildID(filename string, f *os.File, data []byte) (buildid string, err error) {
+       // If the data we want has already been read, don't worry about Mach-O parsing.
+       // This is both an optimization and a hedge against the Mach-O parsing failing
+       // in the future due to, for example, the name of the __text section changing.
+       if b, err := readRawGoBuildID(filename, data); b != "" && err == nil {
+               return b, err
+       }
+
+       mf, err := macho.NewFile(f)
+       if err != nil {
+               return "", &os.PathError{Path: filename, Op: "parse", Err: err}
+       }
+
+       sect := mf.Section("__text")
+       if sect == nil {
+               // Every binary has a __text section. Something is wrong.
+               return "", &os.PathError{Path: filename, Op: "parse", Err: fmt.Errorf("cannot find __text section")}
+       }
+
+       // It should be in the first few bytes, but read a lot just in case,
+       // especially given our past problems on OS X with the build ID moving.
+       // There shouldn't be much difference between reading 4kB and 32kB:
+       // the hard part is getting to the data, not transferring it.
+       n := sect.Size
+       if n > uint64(BuildIDReadSize) {
+               n = uint64(BuildIDReadSize)
+       }
+       buf := make([]byte, n)
+       if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil {
+               return "", err
+       }
+
+       return readRawGoBuildID(filename, buf)
+}
index 1809f94cac298ff150f7b177a7b0d9e140a53686..2771de3ed0a295f20410894f6d48939ffb5e5a0c 100644 (file)
@@ -11,6 +11,20 @@ import (
 )
 
 func TestNoteReading(t *testing.T) {
+       testNoteReading(t)
+}
+
+func TestNoteReading2K(t *testing.T) {
+       // Set BuildIDReadSize to 2kB to exercise Mach-O parsing more strictly.
+       defer func(old int) {
+               main.BuildIDReadSize = old
+       }(main.BuildIDReadSize)
+       main.BuildIDReadSize = 2 * 1024
+
+       testNoteReading(t)
+}
+
+func testNoteReading(t *testing.T) {
        tg := testgo(t)
        defer tg.cleanup()
        tg.tempFile("hello.go", `package main; func main() { print("hello, world\n") }`)
index b1b27e4662da9e5f30c55ffd33f33ea56f360dc3..6c3a09a2d23513e4ad0b1597caa6ac9e736caf30 100644 (file)
@@ -1799,8 +1799,17 @@ var (
        goBuildEnd    = []byte("\"\n \xff")
 
        elfPrefix = []byte("\x7fELF")
+
+       machoPrefixes = [][]byte{
+               {0xfe, 0xed, 0xfa, 0xce},
+               {0xfe, 0xed, 0xfa, 0xcf},
+               {0xce, 0xfa, 0xed, 0xfe},
+               {0xcf, 0xfa, 0xed, 0xfe},
+       }
 )
 
+var BuildIDReadSize = 32 * 1024 // changed for testing
+
 // ReadBuildIDFromBinary reads the build ID from a binary.
 //
 // ELF binaries store the build ID in a proper PT_NOTE section.
@@ -1815,10 +1824,11 @@ func ReadBuildIDFromBinary(filename string) (id string, err error) {
                return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDUnknown}
        }
 
-       // Read the first 16 kB of the binary file.
+       // Read the first 32 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 16 kB.
+       // which are typically less than 4 kB, not to mention 32 kB.
+       // In Mach-O files, there's no limit, so we have to parse the file.
        // 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
@@ -1826,7 +1836,6 @@ func ReadBuildIDFromBinary(filename string) (id string, err error) {
        //
        //      Plan 9: 0x20
        //      Windows: 0x600
-       //      Mach-O: 0x2000
        //
        f, err := os.Open(filename)
        if err != nil {
@@ -1834,7 +1843,7 @@ func ReadBuildIDFromBinary(filename string) (id string, err error) {
        }
        defer f.Close()
 
-       data := make([]byte, 16*1024)
+       data := make([]byte, BuildIDReadSize)
        _, err = io.ReadFull(f, data)
        if err == io.ErrUnexpectedEOF {
                err = nil
@@ -1846,7 +1855,17 @@ func ReadBuildIDFromBinary(filename string) (id string, err error) {
        if bytes.HasPrefix(data, elfPrefix) {
                return readELFGoBuildID(filename, f, data)
        }
+       for _, m := range machoPrefixes {
+               if bytes.HasPrefix(data, m) {
+                       return readMachoGoBuildID(filename, f, data)
+               }
+       }
+
+       return readRawGoBuildID(filename, data)
+}
 
+// readRawGoBuildID finds the raw build ID stored in text segment data.
+func readRawGoBuildID(filename string, data []byte) (id string, err error) {
        i := bytes.Index(data, goBuildPrefix)
        if i < 0 {
                // Missing. Treat as successful but build ID empty.