"bufio"
"bytes"
"debug/elf"
+ "encoding/binary"
"errors"
"flag"
"fmt"
"go/build"
+ "io"
"io/ioutil"
"log"
"math/rand"
}
}
+// Is a given offset into the file contained in a loaded segment?
+func isOffsetLoaded(f *elf.File, offset uint64) bool {
+ for _, prog := range f.Progs {
+ if prog.Type == elf.PT_LOAD {
+ if prog.Off <= offset && offset < prog.Off+prog.Filesz {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func rnd(v int32, r int32) int32 {
+ if r <= 0 {
+ return v
+ }
+ v += r - 1
+ c := v % r
+ if c < 0 {
+ c += r
+ }
+ v -= c
+ return v
+}
+
+func readwithpad(r io.Reader, sz int32) ([]byte, error) {
+ data := make([]byte, rnd(sz, 4))
+ _, err := io.ReadFull(r, data)
+ if err != nil {
+ return nil, err
+ }
+ data = data[:sz]
+ return data, nil
+}
+
+type note struct {
+ name string
+ tag int32
+ desc string
+ section *elf.Section
+}
+
+// Read all notes from f. As ELF section names are not supposed to be special, one
+// looks for a particular note by scanning all SHT_NOTE sections looking for a note
+// with a particular "name" and "tag".
+func readNotes(f *elf.File) ([]*note, error) {
+ var notes []*note
+ for _, sect := range f.Sections {
+ if sect.Type != elf.SHT_NOTE {
+ continue
+ }
+ r := sect.Open()
+ for {
+ var namesize, descsize, tag int32
+ err := binary.Read(r, f.ByteOrder, &namesize)
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return nil, fmt.Errorf("read namesize failed:", err)
+ }
+ err = binary.Read(r, f.ByteOrder, &descsize)
+ if err != nil {
+ return nil, fmt.Errorf("read descsize failed:", err)
+ }
+ err = binary.Read(r, f.ByteOrder, &tag)
+ if err != nil {
+ return nil, fmt.Errorf("read type failed:", err)
+ }
+ name, err := readwithpad(r, namesize)
+ if err != nil {
+ return nil, fmt.Errorf("read name failed:", err)
+ }
+ desc, err := readwithpad(r, descsize)
+ if err != nil {
+ return nil, fmt.Errorf("read desc failed:", err)
+ }
+ notes = append(notes, ¬e{name: string(name), tag: tag, desc: string(desc), section: sect})
+ }
+ }
+ return notes, nil
+}
+
func dynStrings(path string, flag elf.DynTag) []string {
f, err := elf.Open(path)
defer f.Close()
run(t, "executable linked to GOPATH library", "./bin/exe")
}
+// The shared library contains a note listing the packages it contains in a section
+// that is not mapped into memory.
+func testPkgListNote(t *testing.T, f *elf.File, note *note) {
+ if note.section.Flags != 0 {
+ t.Errorf("package list section has flags %v", note.section.Flags)
+ }
+ if isOffsetLoaded(f, note.section.Offset) {
+ t.Errorf("package list section contained in PT_LOAD segment")
+ }
+ if note.desc != "dep\n" {
+ t.Errorf("incorrect package list %q", note.desc)
+ }
+}
+
+// The shared library contains a note containing the ABI hash that is mapped into
+// memory and there is a local symbol called go.link.abihashbytes that points 16
+// bytes into it.
+func testABIHashNote(t *testing.T, f *elf.File, note *note) {
+ if note.section.Flags != elf.SHF_ALLOC {
+ t.Errorf("abi hash section has flags %v", note.section.Flags)
+ }
+ if !isOffsetLoaded(f, note.section.Offset) {
+ t.Errorf("abihash section not contained in PT_LOAD segment")
+ }
+ var hashbytes elf.Symbol
+ symbols, err := f.Symbols()
+ if err != nil {
+ t.Errorf("error reading symbols %v", err)
+ return
+ }
+ for _, sym := range symbols {
+ if sym.Name == "go.link.abihashbytes" {
+ hashbytes = sym
+ }
+ }
+ if hashbytes.Name == "" {
+ t.Errorf("no symbol called go.link.abihashbytes")
+ return
+ }
+ if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL {
+ t.Errorf("%s has incorrect binding %v", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
+ }
+ if f.Sections[hashbytes.Section] != note.section {
+ t.Errorf("%s has incorrect section %v", hashbytes.Name, f.Sections[hashbytes.Section].Name)
+ }
+ if hashbytes.Value-note.section.Addr != 16 {
+ t.Errorf("%s has incorrect offset into section %d", hashbytes.Name, hashbytes.Value-note.section.Addr)
+ }
+}
+
+// The shared library contains notes with defined contents; see above.
+func TestNotes(t *testing.T) {
+ goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
+ f, err := elf.Open(filepath.Join(gopathInstallDir, "libdep.so"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ notes, err := readNotes(f)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pkgListNoteFound := false
+ abiHashNoteFound := false
+ for _, note := range notes {
+ if note.name != "GO\x00\x00" {
+ continue
+ }
+ switch note.tag {
+ case 1: // ELF_NOTE_GOPKGLIST_TAG
+ if pkgListNoteFound {
+ t.Error("multiple package list notes")
+ }
+ testPkgListNote(t, f, note)
+ pkgListNoteFound = true
+ case 2: // ELF_NOTE_GOABIHASH_TAG
+ if abiHashNoteFound {
+ t.Error("multiple abi hash notes")
+ }
+ testABIHashNote(t, f, note)
+ abiHashNoteFound = true
+ }
+ }
+ if !pkgListNoteFound {
+ t.Error("package list note not found")
+ }
+ if !abiHashNoteFound {
+ t.Error("abi hash note not found")
+ }
+}
+
// Testing rebuilding of shared libraries when they are stale is a bit more
// complicated that it seems like it should be. First, we make everything "old": but
// only a few seconds old, or it might be older than 6g (or the runtime source) and
ld.Elfinit()
ld.HEADR = ld.ELFRESERVE
- if ld.Buildmode == ld.BuildmodeShared {
- // When building a shared library we write a package list
- // note that can get quite large. The external linker will
- // re-layout all the sections anyway, so making this larger
- // just wastes a little space in the intermediate object
- // file, not the final shared library.
- ld.HEADR *= 3
- }
if ld.INITTEXT == -1 {
ld.INITTEXT = (1 << 22) + int64(ld.HEADR)
}
}
}
+ if Buildmode == BuildmodeShared {
+ s := Linklookup(Ctxt, "go.link.abihashbytes", 0)
+ sectSym := Linklookup(Ctxt, ".note.go.abihash", 0)
+ s.Sect = sectSym.Sect
+ s.Value = int64(sectSym.Sect.Vaddr + 16)
+ }
+
xdefine("runtime.text", obj.STEXT, int64(text.Vaddr))
xdefine("runtime.etext", obj.STEXT, int64(text.Vaddr+text.Length))
xdefine("runtime.rodata", obj.SRODATA, int64(rodata.Vaddr))
import (
"cmd/internal/obj"
+ "crypto/sha1"
"encoding/binary"
"fmt"
+ "sort"
)
/*
return int(sh.size)
}
-// Go package list note
+// Go specific notes
const (
ELF_NOTE_GOPKGLIST_TAG = 1
+ ELF_NOTE_GOABIHASH_TAG = 2
)
var ELF_NOTE_GO_NAME = []byte("GO\x00\x00")
-func elfgopkgnote(sh *ElfShdr, startva uint64, resoff uint64) int {
- n := len(ELF_NOTE_GO_NAME) + int(Rnd(int64(len(pkglistfornote)), 4))
- return elfnote(sh, startva, resoff, n, false)
-}
-
-func elfwritegopkgnote() int {
- sh := elfwritenotehdr(".note.go.pkg-list", uint32(len(ELF_NOTE_GO_NAME)), uint32(len(pkglistfornote)), ELF_NOTE_GOPKGLIST_TAG)
- if sh == nil {
- return 0
- }
-
- Cwrite(ELF_NOTE_GO_NAME)
- Cwrite(pkglistfornote)
- var zero = make([]byte, 4)
- Cwrite(zero[:int(Rnd(int64(len(pkglistfornote)), 4)-int64(len(pkglistfornote)))])
-
- return int(sh.size)
-}
-
var elfverneed int
type Elfaux struct {
func elfshbits(sect *Section) *ElfShdr {
sh := elfshalloc(sect)
+ // If this section has already been set up as a note, we assume type_ and
+ // flags are already correct, but the other fields still need filling in.
+ if sh.type_ == SHT_NOTE {
+ if Linkmode != LinkExternal {
+ // TODO(mwhudson): the approach here will work OK when
+ // linking internally for notes that we want to be included
+ // in a loadable segment (e.g. the abihash note) but not for
+ // notes that we do not want to be mapped (e.g. the package
+ // list note). The real fix is probably to define new values
+ // for LSym.Type corresponding to mapped and unmapped notes
+ // and handle them in dodata().
+ Diag("sh.type_ == SHT_NOTE in elfshbits when linking internally")
+ }
+ sh.addralign = uint64(sect.Align)
+ sh.size = sect.Length
+ sh.off = sect.Seg.Fileoff + sect.Vaddr - sect.Seg.Vaddr
+ return sh
+ }
if sh.type_ > 0 {
return sh
}
func elfshreloc(sect *Section) *ElfShdr {
// If main section is SHT_NOBITS, nothing to relocate.
- // Also nothing to relocate in .shstrtab.
+ // Also nothing to relocate in .shstrtab or notes.
if sect.Vaddr >= sect.Seg.Vaddr+sect.Seg.Filelen {
return nil
}
if sect.Name == ".shstrtab" || sect.Name == ".tbss" {
return nil
}
+ if sect.Elfsect.type_ == SHT_NOTE {
+ return nil
+ }
var prefix string
var typ int
}
}
+func addgonote(sectionName string, tag uint32, desc []byte) {
+ s := Linklookup(Ctxt, sectionName, 0)
+ s.Reachable = true
+ s.Type = obj.SELFROSECT
+ // namesz
+ Adduint32(Ctxt, s, uint32(len(ELF_NOTE_GO_NAME)))
+ // descsz
+ Adduint32(Ctxt, s, uint32(len(desc)))
+ // tag
+ Adduint32(Ctxt, s, tag)
+ // name + padding
+ s.P = append(s.P, ELF_NOTE_GO_NAME...)
+ for len(s.P)%4 != 0 {
+ s.P = append(s.P, 0)
+ }
+ // desc + padding
+ s.P = append(s.P, desc...)
+ for len(s.P)%4 != 0 {
+ s.P = append(s.P, 0)
+ }
+ s.Size = int64(len(s.P))
+}
+
func doelf() {
if !Iself {
return
if len(buildinfo) > 0 {
Addstring(shstrtab, ".note.gnu.build-id")
}
- if Buildmode == BuildmodeShared {
- Addstring(shstrtab, ".note.go.pkg-list")
- }
Addstring(shstrtab, ".elfdata")
Addstring(shstrtab, ".rodata")
Addstring(shstrtab, ".typelink")
// add a .note.GNU-stack section to mark the stack as non-executable
Addstring(shstrtab, ".note.GNU-stack")
+
+ if Buildmode == BuildmodeShared {
+ Addstring(shstrtab, ".note.go.abihash")
+ Addstring(shstrtab, ".note.go.pkg-list")
+ }
}
hasinitarr := Linkshared
// size of .rel(a).plt section.
Elfwritedynent(s, DT_DEBUG, 0)
}
+
+ if Buildmode == BuildmodeShared {
+ // The go.link.abihashbytes symbol will be pointed at the appropriate
+ // part of the .note.go.abihash section in data.go:func address().
+ s := Linklookup(Ctxt, "go.link.abihashbytes", 0)
+ s.Local = true
+ s.Type = obj.SRODATA
+ s.Special = 1
+ s.Reachable = true
+ s.Size = int64(sha1.Size)
+
+ sort.Sort(byPkg(Ctxt.Library))
+ h := sha1.New()
+ for _, l := range Ctxt.Library {
+ h.Write(l.hash)
+ }
+ addgonote(".note.go.abihash", ELF_NOTE_GOABIHASH_TAG, h.Sum([]byte{}))
+ addgonote(".note.go.pkg-list", ELF_NOTE_GOPKGLIST_TAG, []byte(pkglistfornote))
+ }
}
// Do not write DT_NULL. elfdynhash will finish it.
eh.phentsize = 0
if Buildmode == BuildmodeShared {
- // The package list note we make space for here can get quite
- // large. The external linker will re-layout all the sections
- // anyway, so making this larger just wastes a little space
- // in the intermediate object file, not the final shared
- // library.
- elfreserve *= 3
- resoff = elfreserve
sh := elfshname(".note.go.pkg-list")
- resoff -= int64(elfgopkgnote(sh, uint64(startva), uint64(resoff)))
+ sh.type_ = SHT_NOTE
+ sh = elfshname(".note.go.abihash")
+ sh.type_ = SHT_NOTE
+ sh.flags = SHF_ALLOC
}
goto elfobj
}
a += int64(elfwritebuildinfo())
}
}
- if Buildmode == BuildmodeShared {
- a += int64(elfwritegopkgnote())
- }
if a > elfreserve {
Diag("ELFRESERVE too small: %d > %d", a, elfreserve)
"cmd/internal/obj"
"crypto/sha1"
"debug/elf"
+ "encoding/binary"
"fmt"
"io"
"io/ioutil"
func readelfsymboldata(f *elf.File, sym *elf.Symbol) []byte {
data := make([]byte, sym.Size)
sect := f.Sections[sym.Section]
- if sect.Type != elf.SHT_PROGBITS {
- Diag("reading %s from non-PROGBITS section", sym.Name)
+ if sect.Type != elf.SHT_PROGBITS && sect.Type != elf.SHT_NOTE {
+ Diag("reading %s from non-data section", sym.Name)
}
n, err := sect.ReadAt(data, int64(sym.Value-sect.Offset))
if uint64(n) != sym.Size {
return data
}
+func readwithpad(r io.Reader, sz int32) ([]byte, error) {
+ data := make([]byte, Rnd(int64(sz), 4))
+ _, err := io.ReadFull(r, data)
+ if err != nil {
+ return nil, err
+ }
+ data = data[:sz]
+ return data, nil
+}
+
+func readnote(f *elf.File, name []byte, typ int32) ([]byte, error) {
+ for _, sect := range f.Sections {
+ if sect.Type != elf.SHT_NOTE {
+ continue
+ }
+ r := sect.Open()
+ for {
+ var namesize, descsize, noteType int32
+ err := binary.Read(r, f.ByteOrder, &namesize)
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return nil, fmt.Errorf("read namesize failed:", err)
+ }
+ err = binary.Read(r, f.ByteOrder, &descsize)
+ if err != nil {
+ return nil, fmt.Errorf("read descsize failed:", err)
+ }
+ err = binary.Read(r, f.ByteOrder, ¬eType)
+ if err != nil {
+ return nil, fmt.Errorf("read type failed:", err)
+ }
+ noteName, err := readwithpad(r, namesize)
+ if err != nil {
+ return nil, fmt.Errorf("read name failed:", err)
+ }
+ desc, err := readwithpad(r, descsize)
+ if err != nil {
+ return nil, fmt.Errorf("read desc failed:", err)
+ }
+ if string(name) == string(noteName) && typ == noteType {
+ return desc, nil
+ }
+ }
+ }
+ return nil, nil
+}
+
func ldshlibsyms(shlib string) {
found := false
libpath := ""
return
}
defer f.Close()
+
+ hash, err := readnote(f, ELF_NOTE_GO_NAME, ELF_NOTE_GOABIHASH_TAG)
+ if err != nil {
+ Diag("cannot read ABI hash from shared library %s: %v", libpath, err)
+ return
+ }
+
syms, err := f.Symbols()
if err != nil {
Diag("cannot read symbols from shared library: %s", libpath)
// table removed.
gcmasks := make(map[uint64][]byte)
types := []*LSym{}
- var hash []byte
for _, s := range syms {
if elf.ST_TYPE(s.Info) == elf.STT_NOTYPE || elf.ST_TYPE(s.Info) == elf.STT_SECTION {
continue
if strings.HasPrefix(s.Name, "runtime.gcbits.") {
gcmasks[s.Value] = readelfsymboldata(f, &s)
}
- if s.Name == "go.link.abihashbytes" {
- hash = readelfsymboldata(f, &s)
- }
if elf.ST_BIND(s.Info) != elf.STB_GLOBAL {
continue
}
import (
"cmd/internal/obj"
- "crypto/sha1"
"fmt"
"path/filepath"
- "sort"
"strings"
)
}
if Buildmode == BuildmodeShared {
- sort.Sort(byPkg(Ctxt.Library))
- h := sha1.New()
- for _, l := range Ctxt.Library {
- h.Write(l.hash)
- }
abihashgostr := Linklookup(Ctxt, "go.link.abihash."+filepath.Base(outfile), 0)
abihashgostr.Reachable = true
abihashgostr.Type = obj.SRODATA
- var hashbytes []byte
- addgostring(abihashgostr, "go.link.abihashbytes", string(h.Sum(hashbytes)))
+ hashsym := Linklookup(Ctxt, "go.link.abihashbytes", 0)
+ Addaddr(Ctxt, abihashgostr, hashsym)
+ adduint(Ctxt, abihashgostr, uint64(hashsym.Size))
}
// Information about the layout of the executable image for the