Skip to content

Commit bf1aab7

Browse files
committed
cmd/internal/ld: put read-only relocated data into .data.rel.ro when making a shared object
Currently Go produces shared libraries that cannot be shared between processes because they have relocations against the text segment (not text section). This fixes this by moving some data from .rodata to .data.rel.ro and by marking some sections rw (so the program linker puts them into a different segment). I think this is all way too much for 1.5 but I wanted to hammer my way to something working. Will fix after 1.6. Fixes golang#10914 Change-Id: I7178daadc0ae87953d5a084aa3d580f4e3b46d47
1 parent 001438b commit bf1aab7

File tree

6 files changed

+145
-10
lines changed

6 files changed

+145
-10
lines changed

misc/cgo/testshared/shared_test.go

+50-4
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,17 @@ func goCmd(t *testing.T, args ...string) {
4747
}
4848
newargs = append(newargs, args[1:]...)
4949
c := exec.Command("go", newargs...)
50+
var output []byte
51+
var err error
5052
if testing.Verbose() {
5153
fmt.Printf("+ go %s\n", strings.Join(newargs, " "))
54+
c.Stdout = os.Stdout
55+
c.Stderr = os.Stderr
56+
err = c.Run()
57+
} else {
58+
output, err = c.CombinedOutput()
5259
}
53-
if output, err := c.CombinedOutput(); err != nil {
60+
if err != nil {
5461
if t != nil {
5562
t.Errorf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
5663
} else {
@@ -86,15 +93,15 @@ func testMain(m *testing.M) (int, error) {
8693
if gorootInstallDir == "" {
8794
return 0, errors.New("could not create temporary directory after 10000 tries")
8895
}
89-
defer os.RemoveAll(gorootInstallDir)
96+
//defer os.RemoveAll(gorootInstallDir)
9097

9198
// Some tests need to edit the source in GOPATH, so copy this directory to a
9299
// temporary directory and chdir to that.
93100
scratchDir, err := ioutil.TempDir("", "testshared")
94101
if err != nil {
95102
return 0, fmt.Errorf("TempDir failed: %v", err)
96103
}
97-
defer os.RemoveAll(scratchDir)
104+
//defer os.RemoveAll(scratchDir)
98105
err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
99106
scratchPath := filepath.Join(scratchDir, path)
100107
if info.IsDir() {
@@ -121,7 +128,7 @@ func testMain(m *testing.M) (int, error) {
121128
// that takes a few seconds, do it here and have all tests use the version
122129
// built here.
123130
suffix = strings.Split(filepath.Base(gorootInstallDir), "_")[2]
124-
goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...)
131+
goCmd(nil, append([]string{"install", "-buildmode=shared", "-ldflags=-tmpdir=/home/mwhudson/tmp/hl/std"}, minpkgs...)...)
125132

126133
myContext.InstallSuffix = suffix + "_dynlink"
127134
depP, err := myContext.Import("dep", ".", build.ImportComment)
@@ -149,6 +156,45 @@ func TestSOBuilt(t *testing.T) {
149156
}
150157
}
151158

159+
func hasDynTag(f *elf.File, tag elf.DynTag) bool {
160+
ds := f.SectionByType(elf.SHT_DYNAMIC)
161+
if ds == nil {
162+
return false
163+
}
164+
d, err := ds.Data()
165+
if err != nil {
166+
return false
167+
}
168+
for len(d) > 0 {
169+
var t elf.DynTag
170+
switch f.Class {
171+
case elf.ELFCLASS32:
172+
t = elf.DynTag(f.ByteOrder.Uint32(d[0:4]))
173+
d = d[8:]
174+
case elf.ELFCLASS64:
175+
t = elf.DynTag(f.ByteOrder.Uint64(d[0:8]))
176+
d = d[16:]
177+
}
178+
if t == tag {
179+
return true
180+
}
181+
}
182+
return false
183+
}
184+
185+
// The shared library does not have relocations against the text segment.
186+
func TestNoTextrel(t *testing.T) {
187+
sopath := filepath.Join(gorootInstallDir, soname)
188+
f, err := elf.Open(sopath)
189+
defer f.Close()
190+
if err != nil {
191+
log.Fatal("elf.Open failed: ", err)
192+
}
193+
if hasDynTag(f, elf.DT_TEXTREL) {
194+
t.Errorf("%s has DT_TEXTREL set", soname)
195+
}
196+
}
197+
152198
// The install command should have created a "shlibname" file for the
153199
// listed packages (and runtime/cgo) indicating the name of the shared
154200
// library containing it.

src/cmd/internal/obj/link.go

+18
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,30 @@ const (
321321
Sxxx = iota
322322
STEXT
323323
SELFRXSECT
324+
324325
STYPE
325326
SSTRING
326327
SGOSTRING
327328
SGOFUNC
328329
SRODATA
329330
SFUNCTAB
331+
332+
// Types STYPE-SFUNCTAB above are written to the .rodata section by default.
333+
// When linking a shared object, some conceptually "read only" types need to
334+
// be written to by relocations and putting them in a section called
335+
// ".rodata" interacts poorly with the system linkers. So in this case the
336+
// linker checks all the objects of the above types and bumps any object that
337+
// has a relocation to it to the corresponding type below, and objects of
338+
// these types are written to another magically named section,
339+
// ".data.rel.ro", that will be mprotected read only by the dynamic linker
340+
// after relocations have been applied.
341+
STYPERELRO
342+
SSTRINGRELRO
343+
SGOSTRINGRELRO
344+
SGOFUNCRELRO
345+
SRODATARELRO
346+
SFUNCTABRELRO
347+
330348
STYPELINK
331349
SSYMTAB
332350
SPCLNTAB

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

+67-5
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,29 @@ func dodata() {
11831183

11841184
*l = nil
11851185

1186+
if Buildmode == BuildmodeShared {
1187+
// "read only" data with relocations needs to go in its own section
1188+
// when building a shared library.
1189+
for s := datap; s != nil; s = s.Next {
1190+
if s.Type >= obj.STYPE && s.Type <= obj.SFUNCTAB && len(s.R) > 0 {
1191+
s.Type += (obj.STYPERELRO - obj.STYPE)
1192+
if s.Outer != nil && s.Outer.Type != s.Type {
1193+
s.Outer.Type = s.Type
1194+
}
1195+
}
1196+
}
1197+
// Check that we haven't made two symbols with the same .Outer into
1198+
// different types.
1199+
for s := datap; s != nil; s = s.Next {
1200+
if s.Outer != nil && s.Outer.Type != s.Type {
1201+
Diag(
1202+
"inconsisent types for %s and its Outer %s (%d != %d)",
1203+
s.Name, s.Outer.Name, s.Type, s.Outer.Type)
1204+
}
1205+
}
1206+
1207+
}
1208+
11861209
datap = listsort(datap, datcmp, listnextp)
11871210

11881211
if Iself {
@@ -1442,12 +1465,12 @@ func dodata() {
14421465
/* read-only data */
14431466
sect = addsection(segro, ".rodata", 04)
14441467

1445-
sect.Align = maxalign(s, obj.STYPELINK-1)
1468+
sect.Align = maxalign(s, obj.STYPERELRO-1)
14461469
datsize = Rnd(datsize, int64(sect.Align))
14471470
sect.Vaddr = 0
14481471
Linklookup(Ctxt, "runtime.rodata", 0).Sect = sect
14491472
Linklookup(Ctxt, "runtime.erodata", 0).Sect = sect
1450-
for ; s != nil && s.Type < obj.STYPELINK; s = s.Next {
1473+
for ; s != nil && s.Type < obj.STYPERELRO; s = s.Next {
14511474
datsize = aligndatsize(datsize, s)
14521475
s.Sect = sect
14531476
s.Type = obj.SRODATA
@@ -1457,8 +1480,43 @@ func dodata() {
14571480

14581481
sect.Length = uint64(datsize) - sect.Vaddr
14591482

1483+
// There are a few sections that are conceptually read-only but are written
1484+
// to by relocations. We can arrange for the dynamic linker to mprotect
1485+
// exactly one section after relocations are applied by calling it
1486+
// ".data.rel.ro". We use that section for things that would ordinarily be go
1487+
// into .rodata and leave the other sections (.typelink, .gosymtab,
1488+
// .gopclntab) writable.
1489+
// TODO(mwhudson): Why can't we just put the typelink table and so on into
1490+
// .data.rel.ro? What exactly depends on them being their own sections?
1491+
relro_perms := 04
1492+
1493+
if Buildmode == BuildmodeShared {
1494+
relro_perms = 06
1495+
/* data only written by relocations */
1496+
sect = addsection(segro, ".data.rel.ro", 06)
1497+
1498+
sect.Align = maxalign(s, obj.STYPELINK-1)
1499+
datsize = Rnd(datsize, int64(sect.Align))
1500+
sect.Vaddr = 0
1501+
Linklookup(Ctxt, "runtime.rodata", 0).Sect = sect
1502+
Linklookup(Ctxt, "runtime.erodata", 0).Sect = sect
1503+
for ; s != nil && s.Type < obj.STYPELINK; s = s.Next {
1504+
datsize = aligndatsize(datsize, s)
1505+
if s.Outer != nil && s.Outer.Sect != nil && s.Outer.Sect != sect {
1506+
Diag("vomit %s", s.Name)
1507+
}
1508+
s.Sect = sect
1509+
s.Type = obj.SRODATA
1510+
s.Value = int64(uint64(datsize) - sect.Vaddr)
1511+
growdatsize(&datsize, s)
1512+
}
1513+
1514+
sect.Length = uint64(datsize) - sect.Vaddr
1515+
1516+
}
1517+
14601518
/* typelink */
1461-
sect = addsection(segro, ".typelink", 04)
1519+
sect = addsection(segro, ".typelink", relro_perms)
14621520

14631521
sect.Align = maxalign(s, obj.STYPELINK)
14641522
datsize = Rnd(datsize, int64(sect.Align))
@@ -1476,7 +1534,7 @@ func dodata() {
14761534
sect.Length = uint64(datsize) - sect.Vaddr
14771535

14781536
/* gosymtab */
1479-
sect = addsection(segro, ".gosymtab", 04)
1537+
sect = addsection(segro, ".gosymtab", relro_perms)
14801538

14811539
sect.Align = maxalign(s, obj.SPCLNTAB-1)
14821540
datsize = Rnd(datsize, int64(sect.Align))
@@ -1494,7 +1552,7 @@ func dodata() {
14941552
sect.Length = uint64(datsize) - sect.Vaddr
14951553

14961554
/* gopclntab */
1497-
sect = addsection(segro, ".gopclntab", 04)
1555+
sect = addsection(segro, ".gopclntab", relro_perms)
14981556

14991557
sect.Align = maxalign(s, obj.SELFROSECT-1)
15001558
datsize = Rnd(datsize, int64(sect.Align))
@@ -1674,6 +1732,10 @@ func address() {
16741732
rodata = text.Next
16751733
}
16761734
typelink := rodata.Next
1735+
if Buildmode == BuildmodeShared {
1736+
// There is another section (.data.rel.ro) when building a shared object...
1737+
typelink = typelink.Next
1738+
}
16771739
symtab := typelink.Next
16781740
pclntab := symtab.Next
16791741

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

+3
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,7 @@ func doelf() {
16111611
Addstring(shstrtab, ".text")
16121612
Addstring(shstrtab, ".noptrdata")
16131613
Addstring(shstrtab, ".data")
1614+
Addstring(shstrtab, ".data.rel.ro")
16141615
Addstring(shstrtab, ".bss")
16151616
Addstring(shstrtab, ".noptrbss")
16161617

@@ -1655,6 +1656,7 @@ func doelf() {
16551656
Addstring(shstrtab, ".rela.gopclntab")
16561657
Addstring(shstrtab, ".rela.noptrdata")
16571658
Addstring(shstrtab, ".rela.data")
1659+
Addstring(shstrtab, ".rela.data.rel.ro")
16581660

16591661
default:
16601662
Addstring(shstrtab, ".rel.text")
@@ -1664,6 +1666,7 @@ func doelf() {
16641666
Addstring(shstrtab, ".rel.gopclntab")
16651667
Addstring(shstrtab, ".rel.noptrdata")
16661668
Addstring(shstrtab, ".rel.data")
1669+
Addstring(shstrtab, ".rel.data.rel.ro")
16671670
}
16681671

16691672
// add a .note.GNU-stack section to mark the stack as non-executable

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ func readelfsymboldata(f *elf.File, sym *elf.Symbol) []byte {
11541154
if sect.Type != elf.SHT_PROGBITS {
11551155
Diag("reading %s from non-PROGBITS section", sym.Name)
11561156
}
1157-
n, err := sect.ReadAt(data, int64(sym.Value-sect.Offset))
1157+
n, err := sect.ReadAt(data, int64(sym.Value-sect.Addr))
11581158
if uint64(n) != sym.Size {
11591159
Diag("reading contents of %s: %v", sym.Name, err)
11601160
}
@@ -1682,6 +1682,11 @@ func genasmsym(put func(*LSym, string, int, int64, int64, int, *LSym)) {
16821682
obj.SSTRING,
16831683
obj.SGOSTRING,
16841684
obj.SGOFUNC,
1685+
obj.STYPERELRO,
1686+
obj.SSTRINGRELRO,
1687+
obj.SGOSTRINGRELRO,
1688+
obj.SGOFUNCRELRO,
1689+
obj.SRODATARELRO,
16851690
obj.SWINDOWS:
16861691
if !s.Reachable {
16871692
continue

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

+1
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ func symtab() {
376376
symgofunc := s
377377

378378
symtypelink := Linklookup(Ctxt, "runtime.typelink", 0)
379+
symtypelink.Type = obj.STYPELINK
379380

380381
symt = Linklookup(Ctxt, "runtime.symtab", 0)
381382
symt.Local = true

0 commit comments

Comments
 (0)