Other binary formats to follow.
For #11048.
Change-Id: Ia2d8b47c99c99d171c014b7cfd23c1c7ada5231c
Reviewed-on: https://go-review.googlesource.com/10707
Reviewed-by: Ian Lance Taylor <iant@golang.org>
}
ldflags = setextld(ldflags, compiler)
ldflags = append(ldflags, "-buildmode="+ldBuildmode)
+ if p.buildID != "" {
+ ldflags = append(ldflags, "-buildid="+p.buildID)
+ }
ldflags = append(ldflags, buildLdflags...)
return b.run(".", p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg)
}
package main
import (
+ "bytes"
"debug/elf"
"encoding/binary"
"fmt"
"io"
+ "os"
)
func readAligned4(r io.Reader, sz int32) ([]byte, error) {
}
return nil, nil
}
+
+var elfGoNote = []byte("Go\x00\x00")
+
+// readELFGoBuildID the Go build ID string from an ELF binary.
+// The Go build ID is stored in a note described by an ELF PT_NOTE prog header.
+// The caller has already opened filename, to get f, and read the first 4 kB out, in data.
+func readELFGoBuildID(filename string, f *os.File, data []byte) (buildid string, err error) {
+ // Assume the note content is in the first 4 kB, already read.
+ // Rewrite the ELF header to set shnum to 0, so that we can pass
+ // the data to elf.NewFile and it will decode the Prog list but not
+ // try to read the section headers and the string table from disk.
+ // That's a waste of I/O when all we care about is the Prog list
+ // and the one ELF note.
+ switch elf.Class(data[elf.EI_CLASS]) {
+ case elf.ELFCLASS32:
+ data[48] = 0
+ data[49] = 0
+ case elf.ELFCLASS64:
+ data[60] = 0
+ data[61] = 0
+ }
+
+ const elfGoBuildIDTag = 4
+
+ ef, err := elf.NewFile(bytes.NewReader(data))
+ if err != nil {
+ return "", &os.PathError{Path: filename, Op: "parse", Err: err}
+ }
+ for _, p := range ef.Progs {
+ if p.Type != elf.PT_NOTE || p.Off >= uint64(len(data)) || p.Off+p.Filesz >= uint64(len(data)) || p.Filesz < 16 {
+ continue
+ }
+
+ note := data[p.Off : p.Off+p.Filesz]
+ nameSize := ef.ByteOrder.Uint32(note)
+ valSize := ef.ByteOrder.Uint32(note[4:])
+ tag := ef.ByteOrder.Uint32(note[8:])
+ name := note[12:16]
+ if nameSize != 4 || 16+valSize > uint32(len(note)) || tag != elfGoBuildIDTag || !bytes.Equal(name, elfGoNote) {
+ continue
+ }
+
+ return string(note[16 : 16+valSize]), nil
+ }
+
+ // No note. Treat as successful but build ID empty.
+ return "", nil
+}
--- /dev/null
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "io/ioutil"
+ "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)
+ }
+
+ // TODO: Replace with new test scaffolding by iant.
+ d, err := ioutil.TempDir("", "go-test-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ 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)
+ }
+
+ const buildID = "TestNoteReading-Build-ID"
+ out, err = exec.Command(d+"/go.exe", "build", "-ldflags", "-buildid="+buildID, "-o", d+"/hello.exe", "../../../test/helloworld.go").CombinedOutput()
+ if err != nil {
+ t.Fatalf("go build hello: %v\n%s", err, out)
+ }
+
+ id, err := readBuildIDFromBinary(d + "/hello.exe")
+ if err != nil {
+ t.Fatalf("reading build ID from hello binary: %v", err)
+ }
+
+ if id != buildID {
+ t.Fatalf("buildID in hello binary = %q, want %q", id, buildID)
+ }
+}
// For commands, read build ID directly from binary.
if p.Name == "main" {
- return readBuildIDFromBinary(p)
+ return readBuildIDFromBinary(p.Target)
}
// Otherwise, we expect to have an archive (.a) file,
goBinary = []byte("\x00\n\ngo binary\n")
endGoBinary = []byte("\nend go binary\n")
newlineAndBuildid = []byte("\nbuild id ")
+
+ 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),
// build id "XXX"
// end go binary
//
-func readBuildIDFromBinary(p *Package) (id string, err error) {
- if p.Target == "" {
- return "", &os.PathError{Op: "parse", Path: p.Target, Err: errBuildIDUnknown}
+func readBuildIDFromBinary(filename string) (id string, err error) {
+ if filename == "" {
+ return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDUnknown}
}
- f, err := os.Open(p.Target)
+ f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
+ data := make([]byte, 4096)
+ _, err = io.ReadFull(f, data)
+ if err == io.ErrUnexpectedEOF {
+ err = nil
+ }
+ if err != nil {
+ return "", err
+ }
+
+ if bytes.HasPrefix(data, elfPrefix) {
+ return readELFGoBuildID(filename, f, data)
+ }
+
off, err := f.Seek(0, 2)
if err != nil {
return "", err
if _, err := f.Seek(off-int64(n), 0); err != nil {
return "", err
}
- data := make([]byte, n)
+ data = make([]byte, n)
if _, err := io.ReadFull(f, data); err != nil {
return "", err
}
j := bytes.IndexByte(line, '\n') // must succeed - endGoBinary is at end and has newlines
id, err = strconv.Unquote(string(line[:j]))
if err != nil {
- return "", &os.PathError{Op: "parse", Path: p.Target, Err: errBuildIDMalformed}
+ return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDMalformed}
}
return id, nil
}