Commit b4efd09d authored by Lynn Boger's avatar Lynn Boger Committed by Ian Lance Taylor

cmd/link: split large elf text sections on ppc64x

Some applications built with Go on ppc64x with external linking
can fail to link with relocation truncation errors if the elf
text section that is generated is larger than 2^26 bytes and that
section contains a call instruction (bl) which calls a function
beyond the limit addressable by the 24 bit field in the
instruction.

This solution consists of generating multiple text sections where
each is small enough to allow the GNU linker to resolve the calls
by generating long branch code where needed.  Other changes were added
to handle differences in processing when multiple text sections exist.

Some adjustments were required to the computation of a method's address
when using the method offset table when there are multiple text sections.

The number of possible section headers was increased to allow for up
to 128 text sections.  A test case was also added.

Fixes #15823.

Change-Id: If8117b0e0afb058cbc072258425a35aef2363c92
Reviewed-on: https://go-review.googlesource.com/27790
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent 445f51fb
......@@ -544,7 +544,14 @@ func relocsym(ctxt *Link, s *Symbol) {
o = Symaddr(r.Sym) + r.Add - int64(r.Sym.Sect.Vaddr)
case obj.R_ADDROFF:
o = Symaddr(r.Sym) - int64(r.Sym.Sect.Vaddr) + r.Add
// The method offset tables using this relocation expect the offset to be relative
// to the start of the first text section, even if there are multiple.
if r.Sym.Sect.Name == ".text" {
o = Symaddr(r.Sym) - int64(Segtext.Vaddr) + r.Add
} else {
o = Symaddr(r.Sym) - int64(r.Sym.Sect.Vaddr) + r.Add
}
// r->sym can be null when CALL $(constant) is transformed from absolute PC to relative PC call.
case obj.R_CALL, obj.R_GOTPCREL, obj.R_PCREL:
......@@ -1881,11 +1888,11 @@ func (ctxt *Link) textaddress() {
sect.Align = int32(Funcalign)
ctxt.Syms.Lookup("runtime.text", 0).Sect = sect
ctxt.Syms.Lookup("runtime.etext", 0).Sect = sect
if Headtype == obj.Hwindows || Headtype == obj.Hwindowsgui {
ctxt.Syms.Lookup(".text", 0).Sect = sect
}
va := uint64(*FlagTextAddr)
n := 1
sect.Vaddr = va
for _, sym := range ctxt.Textp {
sym.Sect = sect
......@@ -1901,14 +1908,38 @@ func (ctxt *Link) textaddress() {
for sub := sym; sub != nil; sub = sub.Sub {
sub.Value += int64(va)
}
if sym.Size < MINFUNC {
va += MINFUNC // spacing required for findfunctab
} else {
va += uint64(sym.Size)
funcsize := uint64(MINFUNC) // spacing required for findfunctab
if sym.Size > MINFUNC {
funcsize = uint64(sym.Size)
}
// On ppc64x a text section should not be larger than 2^26 bytes due to the size of
// call target offset field in the bl instruction. Splitting into smaller text
// sections smaller than this limit allows the GNU linker to modify the long calls
// appropriately. The limit allows for the space needed for tables inserted by the linker.
// If this function doesn't fit in the current text section, then create a new one.
// Only break at outermost syms.
if SysArch.InFamily(sys.PPC64) && sym.Outer == nil && Iself && Linkmode == LinkExternal && va-sect.Vaddr+funcsize > 0x1c00000 {
// Set the length for the previous text section
sect.Length = va - sect.Vaddr
// Create new section, set the starting Vaddr
sect = addsection(&Segtext, ".text", 05)
sect.Vaddr = va
// Create a symbol for the start of the secondary text sections
ctxt.Syms.Lookup(fmt.Sprintf("runtime.text.%d", n), 0).Sect = sect
n++
}
va += funcsize
}
sect.Length = va - sect.Vaddr
ctxt.Syms.Lookup("runtime.etext", 0).Sect = sect
}
// assign addresses
......@@ -2052,6 +2083,11 @@ func (ctxt *Link) address() {
pclntab = ctxt.Syms.Lookup("runtime.pclntab", 0).Sect
types = ctxt.Syms.Lookup("runtime.types", 0).Sect
)
lasttext := text
// Could be multiple .text sections
for sect := text.Next; sect != nil && sect.Name == ".text"; sect = sect.Next {
lasttext = sect
}
for _, s := range datap {
if s.Sect != nil {
......@@ -2079,10 +2115,20 @@ func (ctxt *Link) address() {
}
ctxt.xdefine("runtime.text", obj.STEXT, int64(text.Vaddr))
ctxt.xdefine("runtime.etext", obj.STEXT, int64(text.Vaddr+text.Length))
ctxt.xdefine("runtime.etext", obj.STEXT, int64(lasttext.Vaddr+lasttext.Length))
if Headtype == obj.Hwindows || Headtype == obj.Hwindowsgui {
ctxt.xdefine(".text", obj.STEXT, int64(text.Vaddr))
}
// If there are multiple text sections, create runtime.text.n for
// their section Vaddr, using n for index
n := 1
for sect := Segtext.Sect.Next; sect != nil && sect.Name == ".text"; sect = sect.Next {
symname := fmt.Sprintf("runtime.text.%d", n)
ctxt.xdefine(symname, obj.STEXT, int64(sect.Vaddr))
n++
}
ctxt.xdefine("runtime.rodata", obj.SRODATA, int64(rodata.Vaddr))
ctxt.xdefine("runtime.erodata", obj.SRODATA, int64(rodata.Vaddr+rodata.Length))
ctxt.xdefine("runtime.types", obj.SRODATA, int64(types.Vaddr))
......
......@@ -879,7 +879,7 @@ const (
* written in the 32-bit format on the 32-bit machines.
*/
const (
NSECT = 48
NSECT = 400
)
var (
......@@ -1634,6 +1634,25 @@ func elfshname(name string) *ElfShdr {
return nil
}
// Create an ElfShdr for the section with name.
// Create a duplicate if one already exists with that name
func elfshnamedup(name string) *ElfShdr {
var off int
var sh *ElfShdr
for i := 0; i < nelfstr; i++ {
if name == elfstr[i].s {
off = elfstr[i].off
sh = newElfShdr(int64(off))
return sh
}
}
Errorf(nil, "cannot find elf name %s", name)
errorexit()
return nil
}
func elfshalloc(sect *Section) *ElfShdr {
sh := elfshname(sect.Name)
sect.Elfsect = sh
......@@ -1641,7 +1660,17 @@ func elfshalloc(sect *Section) *ElfShdr {
}
func elfshbits(sect *Section) *ElfShdr {
sh := elfshalloc(sect)
var sh *ElfShdr
if sect.Name == ".text" {
if sect.Elfsect == nil {
sect.Elfsect = elfshnamedup(sect.Name)
}
sh = sect.Elfsect
} else {
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 {
......@@ -1717,6 +1746,15 @@ func elfshreloc(sect *Section) *ElfShdr {
}
sh := elfshname(elfRelType + sect.Name)
// There could be multiple text sections but each needs
// its own .rela.text.
if sect.Name == ".text" {
if sh.info != 0 && sh.info != uint32(sect.Elfsect.shnum) {
sh = elfshnamedup(elfRelType + sect.Name)
}
}
sh.type_ = uint32(typ)
sh.entsize = uint64(SysArch.RegSize) * 2
if typ == SHT_RELA {
......@@ -1788,10 +1826,14 @@ func Elfemitreloc(ctxt *Link) {
Cput(0)
}
elfrelocsect(ctxt, Segtext.Sect, ctxt.Textp)
for sect := Segtext.Sect.Next; sect != nil; sect = sect.Next {
elfrelocsect(ctxt, sect, datap)
for sect := Segtext.Sect; sect != nil; sect = sect.Next {
if sect.Name == ".text" {
elfrelocsect(ctxt, sect, ctxt.Textp)
} else {
elfrelocsect(ctxt, sect, datap)
}
}
for sect := Segrodata.Sect; sect != nil; sect = sect.Next {
elfrelocsect(ctxt, sect, datap)
}
......@@ -2124,7 +2166,15 @@ func Asmbelfsetup() {
elfshname("")
for sect := Segtext.Sect; sect != nil; sect = sect.Next {
elfshalloc(sect)
// There could be multiple .text sections. Instead check the Elfsect
// field to determine if already has an ElfShdr and if not, create one.
if sect.Name == ".text" {
if sect.Elfsect == nil {
sect.Elfsect = elfshnamedup(sect.Name)
}
} else {
elfshalloc(sect)
}
}
for sect := Segrodata.Sect; sect != nil; sect = sect.Next {
elfshalloc(sect)
......@@ -2162,6 +2212,23 @@ func Asmbelf(ctxt *Link, symo int64) {
}
elfreserve := int64(ELFRESERVE)
numtext := int64(0)
for sect := Segtext.Sect; sect != nil; sect = sect.Next {
if sect.Name == ".text" {
numtext++
}
}
// If there are multiple text sections, extra space is needed
// in the elfreserve for the additional .text and .rela.text
// section headers. It can handle 4 extra now. Headers are
// 64 bytes.
if numtext > 4 {
elfreserve += elfreserve + numtext*64*2
}
startva := *FlagTextAddr - int64(HEADR)
resoff := elfreserve
......@@ -2630,7 +2697,7 @@ elfobj:
}
if a > elfreserve {
Errorf(nil, "ELFRESERVE too small: %d > %d", a, elfreserve)
Errorf(nil, "ELFRESERVE too small: %d > %d with %d text sections", a, elfreserve, numtext)
}
}
......
......@@ -1812,6 +1812,28 @@ func genasmsym(ctxt *Link, put func(*Link, *Symbol, string, SymbolType, int64, *
if s.Type == obj.STEXT {
put(ctxt, s, s.Name, TextSym, s.Value, nil)
}
n := 0
// Generate base addresses for all text sections if there are multiple
for sect := Segtext.Sect; sect != nil; sect = sect.Next {
if n == 0 {
n++
continue
}
if sect.Name != ".text" {
break
}
s = ctxt.Syms.ROLookup(fmt.Sprintf("runtime.text.%d", n), 0)
if s == nil {
break
}
if s.Type == obj.STEXT {
put(ctxt, s, s.Name, TextSym, s.Value, nil)
}
n++
}
s = ctxt.Syms.Lookup("runtime.etext", 0)
if s.Type == obj.STEXT {
put(ctxt, s, s.Name, TextSym, s.Value, nil)
......
......@@ -302,6 +302,62 @@ func (libs byPkg) Swap(a, b int) {
libs[a], libs[b] = libs[b], libs[a]
}
// Create a table with information on the text sections.
func textsectionmap(ctxt *Link) uint32 {
t := ctxt.Syms.Lookup("runtime.textsectionmap", 0)
t.Type = obj.SRODATA
t.Attr |= AttrReachable
nsections := int64(0)
for sect := Segtext.Sect; sect != nil; sect = sect.Next {
if sect.Name == ".text" {
nsections++
} else {
break
}
}
Symgrow(t, nsections*(2*int64(SysArch.IntSize)+int64(SysArch.PtrSize)))
off := int64(0)
n := 0
// The vaddr for each text section is the difference between the section's
// Vaddr and the Vaddr for the first text section as determined at compile
// time.
// The symbol for the first text section is named runtime.text as before.
// Additional text sections are named runtime.text.n where n is the
// order of creation starting with 1. These symbols provide the section's
// address after relocation by the linker.
textbase := Segtext.Sect.Vaddr
for sect := Segtext.Sect; sect != nil; sect = sect.Next {
if sect.Name != ".text" {
break
}
off = setuintxx(ctxt, t, off, sect.Vaddr-textbase, int64(SysArch.IntSize))
off = setuintxx(ctxt, t, off, sect.Length, int64(SysArch.IntSize))
if n == 0 {
s := ctxt.Syms.ROLookup("runtime.text", 0)
if s == nil {
Errorf(nil, "Unable to find symbol runtime.text\n")
}
off = setaddr(ctxt, t, off, s)
} else {
s := ctxt.Syms.Lookup(fmt.Sprintf("runtime.text.%d", n), 0)
if s == nil {
Errorf(nil, "Unable to find symbol runtime.text.%d\n", n)
}
off = setaddr(ctxt, t, off, s)
}
n++
}
return uint32(n)
}
func (ctxt *Link) symtab() {
dosymtype(ctxt)
......@@ -492,6 +548,8 @@ func (ctxt *Link) symtab() {
adduint(ctxt, abihashgostr, uint64(hashsym.Size))
}
nsections := textsectionmap(ctxt)
// Information about the layout of the executable image for the
// runtime to use. Any changes here must be matched by changes to
// the definition of moduledata in runtime/symtab.go.
......@@ -530,6 +588,12 @@ func (ctxt *Link) symtab() {
Addaddr(ctxt, moduledata, ctxt.Syms.Lookup("runtime.gcbss", 0))
Addaddr(ctxt, moduledata, ctxt.Syms.Lookup("runtime.types", 0))
Addaddr(ctxt, moduledata, ctxt.Syms.Lookup("runtime.etypes", 0))
// text section information
Addaddr(ctxt, moduledata, ctxt.Syms.Lookup("runtime.textsectionmap", 0))
adduint(ctxt, moduledata, uint64(nsections))
adduint(ctxt, moduledata, uint64(nsections))
// The typelinks slice
Addaddr(ctxt, moduledata, ctxt.Syms.Lookup("runtime.typelink", 0))
adduint(ctxt, moduledata, uint64(ntypelinks))
......
......@@ -811,12 +811,14 @@ func asmb(ctxt *ld.Link) {
ld.Asmbelfsetup()
}
sect := ld.Segtext.Sect
ld.Cseek(int64(sect.Vaddr - ld.Segtext.Vaddr + ld.Segtext.Fileoff))
ld.Codeblk(ctxt, int64(sect.Vaddr), int64(sect.Length))
for sect = sect.Next; sect != nil; sect = sect.Next {
for sect := ld.Segtext.Sect; sect != nil; sect = sect.Next {
ld.Cseek(int64(sect.Vaddr - ld.Segtext.Vaddr + ld.Segtext.Fileoff))
ld.Datblk(ctxt, int64(sect.Vaddr), int64(sect.Length))
// Handle additional text sections with Codeblk
if sect.Name == ".text" {
ld.Codeblk(ctxt, int64(sect.Vaddr), int64(sect.Length))
} else {
ld.Datblk(ctxt, int64(sect.Vaddr), int64(sect.Length))
}
}
if ld.Segrodata.Filelen > 0 {
......
// Copyright 2016 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.
// This program generates a test to verify that a program can be
// successfully linked even when there are very large text
// sections present.
package main
import (
"bytes"
"cmd/internal/obj"
"fmt"
"io/ioutil"
"os"
"os/exec"
"testing"
)
func TestLargeText(t *testing.T) {
var w bytes.Buffer
if testing.Short() || (obj.GOARCH != "ppc64le" && obj.GOARCH != "ppc64") {
t.Skip("Skipping large text section test in short mode or if not ppc64x")
}
const FN = 4
tmpdir, err := ioutil.TempDir("", "bigtext")
defer os.RemoveAll(tmpdir)
// Generate the scenario where the total amount of text exceeds the
// limit for the bl instruction, on RISC architectures like ppc64le,
// which is 2^26. When that happens the call requires special trampolines or
// long branches inserted by the linker where supported.
// Multiple .s files are generated instead of one.
for j := 0; j < FN; j++ {
testname := fmt.Sprintf("bigfn%d", j)
fmt.Fprintf(&w, "TEXT ·%s(SB),$0\n", testname)
for i := 0; i < 2200000; i++ {
fmt.Fprintf(&w, "\tMOVD\tR0,R3\n")
}
fmt.Fprintf(&w, "\tRET\n")
err := ioutil.WriteFile(tmpdir+"/"+testname+".s", w.Bytes(), 0666)
if err != nil {
t.Fatalf("can't write output: %v\n", err)
}
w.Reset()
}
fmt.Fprintf(&w, "package main\n")
fmt.Fprintf(&w, "\nimport (\n")
fmt.Fprintf(&w, "\t\"os\"\n")
fmt.Fprintf(&w, "\t\"fmt\"\n")
fmt.Fprintf(&w, ")\n\n")
for i := 0; i < FN; i++ {
fmt.Fprintf(&w, "func bigfn%d()\n", i)
}
fmt.Fprintf(&w, "\nfunc main() {\n")
// There are lots of dummy code generated in the .s files just to generate a lot
// of text. Link them in but guard their call so their code is not executed but
// the main part of the program can be run.
fmt.Fprintf(&w, "\tif os.Getenv(\"LINKTESTARG\") != \"\" {\n")
for i := 0; i < FN; i++ {
fmt.Fprintf(&w, "\t\tbigfn%d()\n", i)
}
fmt.Fprintf(&w, "\t}\n")
fmt.Fprintf(&w, "\tfmt.Printf(\"PASS\\n\")\n")
fmt.Fprintf(&w, "}")
err = ioutil.WriteFile(tmpdir+"/bigfn.go", w.Bytes(), 0666)
if err != nil {
t.Fatalf("can't write output: %v\n", err)
}
os.Chdir(tmpdir)
cmd := exec.Command("go", "build", "-o", "bigtext", "-ldflags", "'-linkmode=external'")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("Build of big text program failed: %v, output: %s", err, out)
}
cmd = exec.Command(tmpdir + "/bigtext")
out, err = cmd.CombinedOutput()
if err != nil {
t.Fatalf("Program failed with err %v, output: %s", err, out)
}
}
......@@ -195,8 +195,9 @@ type moduledata struct {
end, gcdata, gcbss uintptr
types, etypes uintptr
typelinks []int32 // offsets from types
itablinks []*itab
textsectmap []textsect
typelinks []int32 // offsets from types
itablinks []*itab
ptab []ptabEntry
......@@ -228,6 +229,14 @@ type functab struct {
funcoff uintptr
}
// Mapping information for secondary text sections
type textsect struct {
vaddr uintptr // prelinked section vaddr
length uintptr // section length
baseaddr uintptr // relocated section address
}
const minfunc = 16 // minimum function size
const pcbucketsize = 256 * minfunc // size of bucket in the pc->func lookup table
......@@ -370,12 +379,23 @@ func findfunc(pc uintptr) *_func {
ffb := (*findfuncbucket)(add(unsafe.Pointer(datap.findfunctab), b*unsafe.Sizeof(findfuncbucket{})))
idx := ffb.idx + uint32(ffb.subbuckets[i])
if pc < datap.ftab[idx].entry {
throw("findfunc: bad findfunctab entry")
}
// linear search to find func with pc >= entry.
for datap.ftab[idx+1].entry <= pc {
idx++
// If there are multiple text sections then the buckets for the secondary
// text sections will be off because the addresses in those text sections
// were relocated to higher addresses. Search back to find it.
for datap.ftab[idx].entry > pc && idx > 0 {
idx--
}
if idx == 0 {
throw("findfunc: bad findfunctab entry idx")
}
} else {
// linear search to find func with pc >= entry.
for datap.ftab[idx+1].entry <= pc {
idx++
}
}
return (*_func)(unsafe.Pointer(&datap.pclntable[datap.ftab[idx].funcoff]))
}
......
......@@ -257,7 +257,30 @@ func (t *_type) textOff(off textOff) unsafe.Pointer {
}
return res
}
res := md.text + uintptr(off)
res := uintptr(0)
// The text, or instruction stream is generated as one large buffer. The off (offset) for a method is
// its offset within this buffer. If the total text size gets too large, there can be issues on platforms like ppc64 if
// the target of calls are too far for the call instruction. To resolve the large text issue, the text is split
// into multiple text sections to allow the linker to generate long calls when necessary. When this happens, the vaddr
// for each text section is set to its offset within the text. Each method's offset is compared against the section
// vaddrs and sizes to determine the containing section. Then the section relative offset is added to the section's
// relocated baseaddr to compute the method addess.
if len(md.textsectmap) > 1 {
for i := range md.textsectmap {
sectaddr := md.textsectmap[i].vaddr
sectlen := md.textsectmap[i].length
if uintptr(off) >= sectaddr && uintptr(off) <= sectaddr+sectlen {
res = md.textsectmap[i].baseaddr + uintptr(off) - uintptr(md.textsectmap[i].vaddr)
break
}
}
} else {
// single text section
res = md.text + uintptr(off)
}
if res > md.etext {
println("runtime: textOff", hex(off), "out of range", hex(md.text), "-", hex(md.etext))
throw("runtime: text offset out of range")
......
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