1
0
mirror of https://github.com/golang/go synced 2024-11-18 05:04:47 -07:00

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/10707
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Russ Cox 2015-06-04 14:41:02 -04:00
parent ac1f48e2f6
commit 16ebe9f72e
4 changed files with 124 additions and 7 deletions

View File

@ -2372,6 +2372,9 @@ func (gcToolchain) ld(b *builder, p *Package, out string, allactions []*action,
}
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)
}

View File

@ -5,10 +5,12 @@
package main
import (
"bytes"
"debug/elf"
"encoding/binary"
"fmt"
"io"
"os"
)
func readAligned4(r io.Reader, sz int32) ([]byte, error) {
@ -64,3 +66,51 @@ func readELFNote(filename, name string, typ 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
}

45
src/cmd/go/note_test.go Normal file
View File

@ -0,0 +1,45 @@
// 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)
}
}

View File

@ -1091,7 +1091,7 @@ func readBuildID(p *Package) (id string, err error) {
// 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,
@ -1166,9 +1166,15 @@ var (
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),
@ -1182,17 +1188,30 @@ var (
// 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
@ -1204,7 +1223,7 @@ func readBuildIDFromBinary(p *Package) (id string, err error) {
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
}
@ -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
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
}