Skip to content

Commit 6551803

Browse files
mwhudsonianlancetaylor
authored andcommitted
cmd/link/internal/ld: put abi hash into a note
This makes for a more stable API for tools (including cmd/link itself) to extract the abi hash from a shared library and makes it possible at all for a library that has had the local symbol table removed. The existing note-writing code only supports writing notes into the very start of the object file so they are easy to find in core dumps. This doesn't apply to the "go" notes and means that all notes have to fit into a fixed size budget. That's annoying now we have more notes (and the next CL will add another one) so this does a little bit of work to make adding notes that do not have to go at the start of the file easier and moves the writing of the package list note over to that mechanism, which lets me revert a hack that increased the size budget mentioned above for -buildmode=shared builds. Change-Id: I6077a68d395c8a2bc43dec8506e73c71ef77d9b9 Reviewed-on: https://go-review.googlesource.com/10375 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 9262e21 commit 6551803

File tree

6 files changed

+322
-58
lines changed

6 files changed

+322
-58
lines changed

misc/cgo/testshared/shared_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import (
88
"bufio"
99
"bytes"
1010
"debug/elf"
11+
"encoding/binary"
1112
"errors"
1213
"flag"
1314
"fmt"
1415
"go/build"
16+
"io"
1517
"io/ioutil"
1618
"log"
1719
"math/rand"
@@ -176,6 +178,89 @@ func TestShlibnameFiles(t *testing.T) {
176178
}
177179
}
178180

181+
// Is a given offset into the file contained in a loaded segment?
182+
func isOffsetLoaded(f *elf.File, offset uint64) bool {
183+
for _, prog := range f.Progs {
184+
if prog.Type == elf.PT_LOAD {
185+
if prog.Off <= offset && offset < prog.Off+prog.Filesz {
186+
return true
187+
}
188+
}
189+
}
190+
return false
191+
}
192+
193+
func rnd(v int32, r int32) int32 {
194+
if r <= 0 {
195+
return v
196+
}
197+
v += r - 1
198+
c := v % r
199+
if c < 0 {
200+
c += r
201+
}
202+
v -= c
203+
return v
204+
}
205+
206+
func readwithpad(r io.Reader, sz int32) ([]byte, error) {
207+
data := make([]byte, rnd(sz, 4))
208+
_, err := io.ReadFull(r, data)
209+
if err != nil {
210+
return nil, err
211+
}
212+
data = data[:sz]
213+
return data, nil
214+
}
215+
216+
type note struct {
217+
name string
218+
tag int32
219+
desc string
220+
section *elf.Section
221+
}
222+
223+
// Read all notes from f. As ELF section names are not supposed to be special, one
224+
// looks for a particular note by scanning all SHT_NOTE sections looking for a note
225+
// with a particular "name" and "tag".
226+
func readNotes(f *elf.File) ([]*note, error) {
227+
var notes []*note
228+
for _, sect := range f.Sections {
229+
if sect.Type != elf.SHT_NOTE {
230+
continue
231+
}
232+
r := sect.Open()
233+
for {
234+
var namesize, descsize, tag int32
235+
err := binary.Read(r, f.ByteOrder, &namesize)
236+
if err != nil {
237+
if err == io.EOF {
238+
break
239+
}
240+
return nil, fmt.Errorf("read namesize failed:", err)
241+
}
242+
err = binary.Read(r, f.ByteOrder, &descsize)
243+
if err != nil {
244+
return nil, fmt.Errorf("read descsize failed:", err)
245+
}
246+
err = binary.Read(r, f.ByteOrder, &tag)
247+
if err != nil {
248+
return nil, fmt.Errorf("read type failed:", err)
249+
}
250+
name, err := readwithpad(r, namesize)
251+
if err != nil {
252+
return nil, fmt.Errorf("read name failed:", err)
253+
}
254+
desc, err := readwithpad(r, descsize)
255+
if err != nil {
256+
return nil, fmt.Errorf("read desc failed:", err)
257+
}
258+
notes = append(notes, &note{name: string(name), tag: tag, desc: string(desc), section: sect})
259+
}
260+
}
261+
return notes, nil
262+
}
263+
179264
func dynStrings(path string, flag elf.DynTag) []string {
180265
f, err := elf.Open(path)
181266
defer f.Close()
@@ -233,6 +318,97 @@ func TestGOPathShlib(t *testing.T) {
233318
run(t, "executable linked to GOPATH library", "./bin/exe")
234319
}
235320

321+
// The shared library contains a note listing the packages it contains in a section
322+
// that is not mapped into memory.
323+
func testPkgListNote(t *testing.T, f *elf.File, note *note) {
324+
if note.section.Flags != 0 {
325+
t.Errorf("package list section has flags %v", note.section.Flags)
326+
}
327+
if isOffsetLoaded(f, note.section.Offset) {
328+
t.Errorf("package list section contained in PT_LOAD segment")
329+
}
330+
if note.desc != "dep\n" {
331+
t.Errorf("incorrect package list %q", note.desc)
332+
}
333+
}
334+
335+
// The shared library contains a note containing the ABI hash that is mapped into
336+
// memory and there is a local symbol called go.link.abihashbytes that points 16
337+
// bytes into it.
338+
func testABIHashNote(t *testing.T, f *elf.File, note *note) {
339+
if note.section.Flags != elf.SHF_ALLOC {
340+
t.Errorf("abi hash section has flags %v", note.section.Flags)
341+
}
342+
if !isOffsetLoaded(f, note.section.Offset) {
343+
t.Errorf("abihash section not contained in PT_LOAD segment")
344+
}
345+
var hashbytes elf.Symbol
346+
symbols, err := f.Symbols()
347+
if err != nil {
348+
t.Errorf("error reading symbols %v", err)
349+
return
350+
}
351+
for _, sym := range symbols {
352+
if sym.Name == "go.link.abihashbytes" {
353+
hashbytes = sym
354+
}
355+
}
356+
if hashbytes.Name == "" {
357+
t.Errorf("no symbol called go.link.abihashbytes")
358+
return
359+
}
360+
if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL {
361+
t.Errorf("%s has incorrect binding %v", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
362+
}
363+
if f.Sections[hashbytes.Section] != note.section {
364+
t.Errorf("%s has incorrect section %v", hashbytes.Name, f.Sections[hashbytes.Section].Name)
365+
}
366+
if hashbytes.Value-note.section.Addr != 16 {
367+
t.Errorf("%s has incorrect offset into section %d", hashbytes.Name, hashbytes.Value-note.section.Addr)
368+
}
369+
}
370+
371+
// The shared library contains notes with defined contents; see above.
372+
func TestNotes(t *testing.T) {
373+
goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
374+
f, err := elf.Open(filepath.Join(gopathInstallDir, "libdep.so"))
375+
if err != nil {
376+
t.Fatal(err)
377+
}
378+
defer f.Close()
379+
notes, err := readNotes(f)
380+
if err != nil {
381+
t.Fatal(err)
382+
}
383+
pkgListNoteFound := false
384+
abiHashNoteFound := false
385+
for _, note := range notes {
386+
if note.name != "GO\x00\x00" {
387+
continue
388+
}
389+
switch note.tag {
390+
case 1: // ELF_NOTE_GOPKGLIST_TAG
391+
if pkgListNoteFound {
392+
t.Error("multiple package list notes")
393+
}
394+
testPkgListNote(t, f, note)
395+
pkgListNoteFound = true
396+
case 2: // ELF_NOTE_GOABIHASH_TAG
397+
if abiHashNoteFound {
398+
t.Error("multiple abi hash notes")
399+
}
400+
testABIHashNote(t, f, note)
401+
abiHashNoteFound = true
402+
}
403+
}
404+
if !pkgListNoteFound {
405+
t.Error("package list note not found")
406+
}
407+
if !abiHashNoteFound {
408+
t.Error("abi hash note not found")
409+
}
410+
}
411+
236412
// Testing rebuilding of shared libraries when they are stale is a bit more
237413
// complicated that it seems like it should be. First, we make everything "old": but
238414
// only a few seconds old, or it might be older than 6g (or the runtime source) and

src/cmd/link/internal/amd64/obj.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,6 @@ func archinit() {
168168
ld.Elfinit()
169169

170170
ld.HEADR = ld.ELFRESERVE
171-
if ld.Buildmode == ld.BuildmodeShared {
172-
// When building a shared library we write a package list
173-
// note that can get quite large. The external linker will
174-
// re-layout all the sections anyway, so making this larger
175-
// just wastes a little space in the intermediate object
176-
// file, not the final shared library.
177-
ld.HEADR *= 3
178-
}
179171
if ld.INITTEXT == -1 {
180172
ld.INITTEXT = (1 << 22) + int64(ld.HEADR)
181173
}

src/cmd/link/internal/ld/data.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,6 +1688,13 @@ func address() {
16881688
}
16891689
}
16901690

1691+
if Buildmode == BuildmodeShared {
1692+
s := Linklookup(Ctxt, "go.link.abihashbytes", 0)
1693+
sectSym := Linklookup(Ctxt, ".note.go.abihash", 0)
1694+
s.Sect = sectSym.Sect
1695+
s.Value = int64(sectSym.Sect.Vaddr + 16)
1696+
}
1697+
16911698
xdefine("runtime.text", obj.STEXT, int64(text.Vaddr))
16921699
xdefine("runtime.etext", obj.STEXT, int64(text.Vaddr+text.Length))
16931700
xdefine("runtime.rodata", obj.SRODATA, int64(rodata.Vaddr))

0 commit comments

Comments
 (0)