mirror of
https://github.com/golang/go
synced 2024-11-17 18:14:46 -07:00
cmd/link: split large text sections on Darwin/ARM64 when external linking
The Darwin linker does not like text sections that are larger than the jump limit (even if we already inserted trampolines). Split the text section to multiple smaller sections. Now external linking very large binaries works on Darwin/ARM64. Updates #40492. Change-Id: I584f1ec673170c5e4d2dc1e00c701964d6f14333 Reviewed-on: https://go-review.googlesource.com/c/go/+/316050 Trust: Cherry Mui <cherryyz@google.com> Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
parent
90d6bbbe42
commit
f39997b2be
@ -67,7 +67,7 @@ func isRuntimeDepPkg(pkg string) bool {
|
|||||||
// Estimate the max size needed to hold any new trampolines created for this function. This
|
// Estimate the max size needed to hold any new trampolines created for this function. This
|
||||||
// is used to determine when the section can be split if it becomes too large, to ensure that
|
// is used to determine when the section can be split if it becomes too large, to ensure that
|
||||||
// the trampolines are in the same section as the function that uses them.
|
// the trampolines are in the same section as the function that uses them.
|
||||||
func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uint64 {
|
func maxSizeTrampolines(ctxt *Link, ldr *loader.Loader, s loader.Sym, isTramp bool) uint64 {
|
||||||
// If thearch.Trampoline is nil, then trampoline support is not available on this arch.
|
// If thearch.Trampoline is nil, then trampoline support is not available on this arch.
|
||||||
// A trampoline does not need any dependent trampolines.
|
// A trampoline does not need any dependent trampolines.
|
||||||
if thearch.Trampoline == nil || isTramp {
|
if thearch.Trampoline == nil || isTramp {
|
||||||
@ -82,8 +82,14 @@ func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uin
|
|||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Trampolines in ppc64 are 4 instructions.
|
|
||||||
return n * 16
|
if ctxt.IsPPC64() {
|
||||||
|
return n * 16 // Trampolines in PPC64 are 4 instructions.
|
||||||
|
}
|
||||||
|
if ctxt.IsARM64() {
|
||||||
|
return n * 12 // Trampolines in ARM64 are 3 instructions.
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect too-far jumps in function s, and add trampolines if necessary
|
// detect too-far jumps in function s, and add trampolines if necessary
|
||||||
@ -2348,15 +2354,11 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
|
|||||||
funcsize = uint64(ldr.SymSize(s))
|
funcsize = uint64(ldr.SymSize(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
// On ppc64x a text section should not be larger than 2^26 bytes due to the size of
|
// If we need to split text sections, and this function doesn't fit in the current
|
||||||
// call target offset field in the bl instruction. Splitting into smaller text
|
// section, then create a new one.
|
||||||
// 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.
|
// Only break at outermost syms.
|
||||||
if ctxt.Arch.InFamily(sys.PPC64) && ldr.OuterSym(s) == 0 && ctxt.IsExternal() && big {
|
if big && splitTextSections(ctxt) && ldr.OuterSym(s) == 0 {
|
||||||
// For debugging purposes, allow text size limit to be cranked down,
|
// For debugging purposes, allow text size limit to be cranked down,
|
||||||
// so as to stress test the code that handles multiple text sections.
|
// so as to stress test the code that handles multiple text sections.
|
||||||
var textSizelimit uint64 = thearch.TrampLimit
|
var textSizelimit uint64 = thearch.TrampLimit
|
||||||
@ -2367,10 +2369,12 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
|
|||||||
// Sanity check: make sure the limit is larger than any
|
// Sanity check: make sure the limit is larger than any
|
||||||
// individual text symbol.
|
// individual text symbol.
|
||||||
if funcsize > textSizelimit {
|
if funcsize > textSizelimit {
|
||||||
panic(fmt.Sprintf("error: ppc64 text size limit %d less than text symbol %s size of %d", textSizelimit, ldr.SymName(s), funcsize))
|
panic(fmt.Sprintf("error: text size limit %d less than text symbol %s size of %d", textSizelimit, ldr.SymName(s), funcsize))
|
||||||
}
|
}
|
||||||
|
|
||||||
if va-sect.Vaddr+funcsize+maxSizeTrampolinesPPC64(ldr, s, isTramp) > textSizelimit {
|
if va-sect.Vaddr+funcsize+maxSizeTrampolines(ctxt, ldr, s, isTramp) > textSizelimit {
|
||||||
|
sectAlign := int32(thearch.Funcalign)
|
||||||
|
if ctxt.IsPPC64() {
|
||||||
// Align the next text section to the worst case function alignment likely
|
// Align the next text section to the worst case function alignment likely
|
||||||
// to be encountered when processing function symbols. The start address
|
// to be encountered when processing function symbols. The start address
|
||||||
// is rounded against the final alignment of the text section later on in
|
// is rounded against the final alignment of the text section later on in
|
||||||
@ -2378,7 +2382,9 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
|
|||||||
// larger than Funcalign, or usage of ISA 3.1 prefixed instructions
|
// larger than Funcalign, or usage of ISA 3.1 prefixed instructions
|
||||||
// (see ISA 3.1 Book I 1.9).
|
// (see ISA 3.1 Book I 1.9).
|
||||||
const ppc64maxFuncalign = 64
|
const ppc64maxFuncalign = 64
|
||||||
|
sectAlign = ppc64maxFuncalign
|
||||||
va = uint64(Rnd(int64(va), ppc64maxFuncalign))
|
va = uint64(Rnd(int64(va), ppc64maxFuncalign))
|
||||||
|
}
|
||||||
|
|
||||||
// Set the length for the previous text section
|
// Set the length for the previous text section
|
||||||
sect.Length = va - sect.Vaddr
|
sect.Length = va - sect.Vaddr
|
||||||
@ -2387,7 +2393,7 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
|
|||||||
sect = addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05)
|
sect = addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05)
|
||||||
|
|
||||||
sect.Vaddr = va
|
sect.Vaddr = va
|
||||||
sect.Align = ppc64maxFuncalign
|
sect.Align = sectAlign
|
||||||
ldr.SetSymSect(s, sect)
|
ldr.SetSymSect(s, sect)
|
||||||
|
|
||||||
// Create a symbol for the start of the secondary text sections
|
// Create a symbol for the start of the secondary text sections
|
||||||
@ -2400,7 +2406,7 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
|
|||||||
ntext.SetType(sym.STEXT)
|
ntext.SetType(sym.STEXT)
|
||||||
ntext.SetSize(int64(MINFUNC))
|
ntext.SetSize(int64(MINFUNC))
|
||||||
ntext.SetOnList(true)
|
ntext.SetOnList(true)
|
||||||
ntext.SetAlign(ppc64maxFuncalign)
|
ntext.SetAlign(sectAlign)
|
||||||
ctxt.tramps = append(ctxt.tramps, ntext.Sym())
|
ctxt.tramps = append(ctxt.tramps, ntext.Sym())
|
||||||
|
|
||||||
ntext.SetValue(int64(va))
|
ntext.SetValue(int64(va))
|
||||||
@ -2429,6 +2435,19 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
|
|||||||
return sect, n, va
|
return sect, n, va
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return whether we may need to split text sections.
|
||||||
|
//
|
||||||
|
// On PPC64x whem external linking a text section should not be larger than 2^25 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 system linker to modify the long
|
||||||
|
// calls appropriately. The limit allows for the space needed for tables inserted by the
|
||||||
|
// linker.
|
||||||
|
//
|
||||||
|
// The same applies to Darwin/ARM64, with 2^27 byte threshold.
|
||||||
|
func splitTextSections(ctxt *Link) bool {
|
||||||
|
return (ctxt.IsPPC64() || (ctxt.IsARM64() && ctxt.IsDarwin())) && ctxt.IsExternal()
|
||||||
|
}
|
||||||
|
|
||||||
// address assigns virtual addresses to all segments and sections and
|
// address assigns virtual addresses to all segments and sections and
|
||||||
// returns all segments in file order.
|
// returns all segments in file order.
|
||||||
func (ctxt *Link) address() []*sym.Segment {
|
func (ctxt *Link) address() []*sym.Segment {
|
||||||
|
@ -131,10 +131,16 @@ func TestArchiveBuildInvokeWithExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPPC64LargeTextSectionSplitting(t *testing.T) {
|
func TestLargeTextSectionSplitting(t *testing.T) {
|
||||||
// The behavior we're checking for is of interest only on ppc64.
|
switch runtime.GOARCH {
|
||||||
if !strings.HasPrefix(runtime.GOARCH, "ppc64") {
|
case "ppc64", "ppc64le":
|
||||||
t.Skip("test useful only for ppc64")
|
case "arm64":
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
testenv.MustHaveGoBuild(t)
|
testenv.MustHaveGoBuild(t)
|
||||||
@ -142,13 +148,13 @@ func TestPPC64LargeTextSectionSplitting(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
// NB: the use of -ldflags=-debugppc64textsize=1048576 tells the linker to
|
// NB: the use of -ldflags=-debugtextsize=1048576 tells the linker to
|
||||||
// split text sections at a size threshold of 1M instead of the
|
// split text sections at a size threshold of 1M instead of the
|
||||||
// architected limit of 67M. The choice of building cmd/go is
|
// architected limit of 67M or larger. The choice of building cmd/go
|
||||||
// arbitrary; we just need something sufficiently large that uses
|
// is arbitrary; we just need something sufficiently large that uses
|
||||||
// external linking.
|
// external linking.
|
||||||
exe := filepath.Join(dir, "go.exe")
|
exe := filepath.Join(dir, "go.exe")
|
||||||
out, eerr := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugppc64textsize=1048576", "cmd/go").CombinedOutput()
|
out, eerr := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugtextsize=1048576", "cmd/go").CombinedOutput()
|
||||||
if eerr != nil {
|
if eerr != nil {
|
||||||
t.Fatalf("build failure: %s\n%s\n", eerr, string(out))
|
t.Fatalf("build failure: %s\n%s\n", eerr, string(out))
|
||||||
}
|
}
|
||||||
|
@ -1241,8 +1241,12 @@ func machoEmitReloc(ctxt *Link) {
|
|||||||
|
|
||||||
relocSect(ctxt, Segtext.Sections[0], ctxt.Textp)
|
relocSect(ctxt, Segtext.Sections[0], ctxt.Textp)
|
||||||
for _, sect := range Segtext.Sections[1:] {
|
for _, sect := range Segtext.Sections[1:] {
|
||||||
|
if sect.Name == ".text" {
|
||||||
|
relocSect(ctxt, sect, ctxt.Textp)
|
||||||
|
} else {
|
||||||
relocSect(ctxt, sect, ctxt.datap)
|
relocSect(ctxt, sect, ctxt.datap)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for _, sect := range Segrelrodata.Sections {
|
for _, sect := range Segrelrodata.Sections {
|
||||||
relocSect(ctxt, sect, ctxt.datap)
|
relocSect(ctxt, sect, ctxt.datap)
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ var (
|
|||||||
flag8 bool // use 64-bit addresses in symbol table
|
flag8 bool // use 64-bit addresses in symbol table
|
||||||
flagInterpreter = flag.String("I", "", "use `linker` as ELF dynamic linker")
|
flagInterpreter = flag.String("I", "", "use `linker` as ELF dynamic linker")
|
||||||
FlagDebugTramp = flag.Int("debugtramp", 0, "debug trampolines")
|
FlagDebugTramp = flag.Int("debugtramp", 0, "debug trampolines")
|
||||||
FlagDebugTextSize = flag.Int("debugppc64textsize", 0, "debug PPC64 text section max")
|
FlagDebugTextSize = flag.Int("debugtextsize", 0, "debug text section max size")
|
||||||
FlagStrictDups = flag.Int("strictdups", 0, "sanity check duplicate symbol contents during object file reading (1=warn 2=err).")
|
FlagStrictDups = flag.Int("strictdups", 0, "sanity check duplicate symbol contents during object file reading (1=warn 2=err).")
|
||||||
FlagRound = flag.Int("R", -1, "set address rounding `quantum`")
|
FlagRound = flag.Int("R", -1, "set address rounding `quantum`")
|
||||||
FlagTextAddr = flag.Int64("T", -1, "set text segment `address`")
|
FlagTextAddr = flag.Int64("T", -1, "set text segment `address`")
|
||||||
|
Loading…
Reference in New Issue
Block a user