Commit 16ebe9f7 authored by Russ Cox's avatar Russ Cox

cmd/go: use ELF note instead of binary stamp on ELF systems

Other binary formats to follow.

For #11048.

Change-Id: Ia2d8b47c99c99d171c014b7cfd23c1c7ada5231c
Reviewed-on: https://go-review.googlesource.com/10707Reviewed-by: 's avatarIan Lance Taylor <iant@golang.org>
parent ac1f48e2
...@@ -2372,6 +2372,9 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action, ...@@ -2372,6 +2372,9 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action,
} }
ldflags = setextld(ldflags, compiler) ldflags = setextld(ldflags, compiler)
ldflags = append(ldflags, "-buildmode="+ldBuildmode) ldflags = append(ldflags, "-buildmode="+ldBuildmode)
if p.buildID != "" {
ldflags = append(ldflags, "-buildid="+p.buildID)
}
ldflags = append(ldflags, buildLdflags...) ldflags = append(ldflags, buildLdflags...)
return b.run(".", p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg) return b.run(".", p.ImportPath, nil, buildToolExec, tool("link"), "-o", out, importArgs, ldflags, mainpkg)
} }
......
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
package main package main
import ( import (
"bytes"
"debug/elf" "debug/elf"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"os"
) )
func readAligned4(r io.Reader, sz int32) ([]byte, error) { func readAligned4(r io.Reader, sz int32) ([]byte, error) {
...@@ -64,3 +66,51 @@ func readELFNote(filename, name string, typ int32) ([]byte, error) { ...@@ -64,3 +66,51 @@ func readELFNote(filename, name string, typ int32) ([]byte, error) {
} }
return nil, nil 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
}
// 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)
}
}
...@@ -1091,7 +1091,7 @@ func readBuildID(p *Package) (id string, err error) { ...@@ -1091,7 +1091,7 @@ func readBuildID(p *Package) (id string, err error) {
// For commands, read build ID directly from binary. // For commands, read build ID directly from binary.
if p.Name == "main" { if p.Name == "main" {
return readBuildIDFromBinary(p) return readBuildIDFromBinary(p.Target)
} }
// Otherwise, we expect to have an archive (.a) file, // Otherwise, we expect to have an archive (.a) file,
...@@ -1166,9 +1166,15 @@ var ( ...@@ -1166,9 +1166,15 @@ var (
goBinary = []byte("\x00\n\ngo binary\n") goBinary = []byte("\x00\n\ngo binary\n")
endGoBinary = []byte("\nend go binary\n") endGoBinary = []byte("\nend go binary\n")
newlineAndBuildid = []byte("\nbuild id ") newlineAndBuildid = []byte("\nbuild id ")
elfPrefix = []byte("ELF\x7F")
) )
// readBuildIDFromBinary reads the build ID from a binary. // 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 // 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 // custom section of the binary, which would be different for each
// of the four binary types we support (ELF, Mach-O, Plan 9, PE), // of the four binary types we support (ELF, Mach-O, Plan 9, PE),
...@@ -1182,17 +1188,30 @@ var ( ...@@ -1182,17 +1188,30 @@ var (
// build id "XXX" // build id "XXX"
// end go binary // end go binary
// //
func readBuildIDFromBinary(p *Package) (id string, err error) { func readBuildIDFromBinary(filename string) (id string, err error) {
if p.Target == "" { if filename == "" {
return "", &os.PathError{Op: "parse", Path: p.Target, Err: errBuildIDUnknown} return "", &os.PathError{Op: "parse", Path: filename, Err: errBuildIDUnknown}
} }
f, err := os.Open(p.Target) f, err := os.Open(filename)
if err != nil { if err != nil {
return "", err return "", err
} }
defer f.Close() 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) off, err := f.Seek(0, 2)
if err != nil { if err != nil {
return "", err return "", err
...@@ -1204,7 +1223,7 @@ func readBuildIDFromBinary(p *Package) (id string, err error) { ...@@ -1204,7 +1223,7 @@ func readBuildIDFromBinary(p *Package) (id string, err error) {
if _, err := f.Seek(off-int64(n), 0); err != nil { if _, err := f.Seek(off-int64(n), 0); err != nil {
return "", err return "", err
} }
data := make([]byte, n) data = make([]byte, n)
if _, err := io.ReadFull(f, data); err != nil { if _, err := io.ReadFull(f, data); err != nil {
return "", err return "", err
} }
...@@ -1229,7 +1248,7 @@ func readBuildIDFromBinary(p *Package) (id string, err error) { ...@@ -1229,7 +1248,7 @@ func readBuildIDFromBinary(p *Package) (id string, err error) {
j := bytes.IndexByte(line, '\n') // must succeed - endGoBinary is at end and has newlines j := bytes.IndexByte(line, '\n') // must succeed - endGoBinary is at end and has newlines
id, err = strconv.Unquote(string(line[:j])) id, err = strconv.Unquote(string(line[:j]))
if err != nil { 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 return id, nil
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment