Skip to content

Commit 9c9090e

Browse files
committed
cmd/link: generate PPC64 ABI register save/restore functions if needed
They are usually needed when internally linking gcc code compiled with -Os. These are typically generated by ld or gold, but are missing when linking internally. The PPC64 ELF ABI describes a set of functions to save/restore non-volatile, callee-save registers using R1/R0/R12: _savegpr0_n: Save Rn-R31 relative to R1, save LR (in R0), return _restgpr0_n: Restore Rn-R31 from R1, and return to saved LR _savefpr_n: Save Fn-F31 based on R1, and save LR (in R0), return _restfpr_n: Restore Fn-F31 from R1, and return to 16(R1) _savegpr1_n: Save Rn-R31 based on R12, return _restgpr1_n: Restore Rn-R31 based on R12, return _savevr_m: Save VRm-VR31 based on R0, R12 is scratch, return _restvr_m: Restore VRm-VR31 based on R0, R12 is scratch, return m is a value 20<=m<=31 n is a value 14<=n<=31 Add several new functions similar to those suggested by the PPC64 ELFv2 ABI. And update the linker to scan external relocs for these calls, and redirect them to runtime.elf_<func>+offset in runtime/asm_ppc64x.go. Similarly, code which generates plt stubs is moved into a dedicated function. This avoids an extra scan of relocs. fixes #52336 Change-Id: I2f0f8b5b081a7b294dff5c92b4b1db8eba9a9400 Reviewed-on: https://go-review.googlesource.com/c/go/+/400796 Reviewed-by: Cherry Mui <[email protected]> Reviewed-by: David Chase <[email protected]> Run-TryBot: Paul Murphy <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 4a5711c commit 9c9090e

File tree

3 files changed

+392
-42
lines changed

3 files changed

+392
-42
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Tests that the linker implements the PPC64 ELFv2 ABI
2+
# register save and restore functions as defined in
3+
# section 2.3.3.1 of the PPC64 ELFv2 ABI when linking
4+
# external objects most likely compiled with gcc's
5+
# -Os option.
6+
#
7+
# Verifies golang.org/issue/52366 for linux/ppc64le
8+
[!linux] skip
9+
[!gc] skip
10+
[!cgo] skip
11+
[!ppc64le] skip
12+
13+
go build -ldflags='-linkmode=internal'
14+
exec ./abitest
15+
stdout success
16+
17+
-- go.mod --
18+
module abitest
19+
20+
-- abitest.go --
21+
package main
22+
23+
/*
24+
#cgo CFLAGS: -Os
25+
26+
int foo_fpr() {
27+
asm volatile("":::"fr31","fr30","fr29","fr28");
28+
}
29+
int foo_gpr0() {
30+
asm volatile("":::"r30","r29","r28");
31+
}
32+
int foo_gpr1() {
33+
asm volatile("":::"fr31", "fr30","fr29","fr28","r30","r29","r28");
34+
}
35+
int foo_vr() {
36+
asm volatile("":::"v31","v30","v29","v28");
37+
}
38+
*/
39+
import "C"
40+
41+
import "fmt"
42+
43+
func main() {
44+
C.foo_fpr()
45+
C.foo_gpr0()
46+
C.foo_gpr1()
47+
C.foo_vr()
48+
fmt.Println("success")
49+
}

src/cmd/link/internal/ppc64/asm.go

Lines changed: 140 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ import (
4040
"encoding/binary"
4141
"fmt"
4242
"log"
43+
"strconv"
4344
"strings"
4445
)
4546

46-
func genplt(ctxt *ld.Link, ldr *loader.Loader) {
47+
func genpltstub(ctxt *ld.Link, ldr *loader.Loader, r loader.Reloc, s loader.Sym) (sym loader.Sym, firstUse bool) {
4748
// The ppc64 ABI PLT has similar concepts to other
4849
// architectures, but is laid out quite differently. When we
4950
// see an R_PPC64_REL24 relocation to a dynamic symbol
@@ -92,53 +93,82 @@ func genplt(ctxt *ld.Link, ldr *loader.Loader) {
9293
//
9394
// This assumes "case 1" from the ABI, where the caller needs
9495
// us to save and restore the TOC pointer.
95-
var stubs []loader.Sym
96-
for _, s := range ctxt.Textp {
97-
relocs := ldr.Relocs(s)
98-
for i := 0; i < relocs.Count(); i++ {
99-
r := relocs.At(i)
100-
if r.Type() != objabi.ElfRelocOffset+objabi.RelocType(elf.R_PPC64_REL24) || ldr.SymType(r.Sym()) != sym.SDYNIMPORT {
101-
continue
102-
}
10396

104-
// Reserve PLT entry and generate symbol
105-
// resolver
106-
addpltsym(ctxt, ldr, r.Sym())
107-
108-
// Generate call stub. Important to note that we're looking
109-
// up the stub using the same version as the parent symbol (s),
110-
// needed so that symtoc() will select the right .TOC. symbol
111-
// when processing the stub. In older versions of the linker
112-
// this was done by setting stub.Outer to the parent, but
113-
// if the stub has the right version initially this is not needed.
114-
n := fmt.Sprintf("%s.%s", ldr.SymName(s), ldr.SymName(r.Sym()))
115-
stub := ldr.CreateSymForUpdate(n, ldr.SymVersion(s))
116-
if stub.Size() == 0 {
117-
stubs = append(stubs, stub.Sym())
118-
gencallstub(ctxt, ldr, 1, stub, r.Sym())
119-
}
97+
// Reserve PLT entry and generate symbol
98+
// resolver
99+
addpltsym(ctxt, ldr, r.Sym())
100+
101+
// Generate call stub. Important to note that we're looking
102+
// up the stub using the same version as the parent symbol (s),
103+
// needed so that symtoc() will select the right .TOC. symbol
104+
// when processing the stub. In older versions of the linker
105+
// this was done by setting stub.Outer to the parent, but
106+
// if the stub has the right version initially this is not needed.
107+
n := fmt.Sprintf("%s.%s", ldr.SymName(s), ldr.SymName(r.Sym()))
108+
stub := ldr.CreateSymForUpdate(n, ldr.SymVersion(s))
109+
firstUse = stub.Size() == 0
110+
if firstUse {
111+
gencallstub(ctxt, ldr, 1, stub, r.Sym())
112+
}
120113

121-
// Update the relocation to use the call stub
122-
r.SetSym(stub.Sym())
114+
// Update the relocation to use the call stub
115+
r.SetSym(stub.Sym())
123116

124-
// Make the symbol writeable so we can fixup toc.
125-
su := ldr.MakeSymbolUpdater(s)
126-
su.MakeWritable()
127-
p := su.Data()
117+
// Make the symbol writeable so we can fixup toc.
118+
su := ldr.MakeSymbolUpdater(s)
119+
su.MakeWritable()
120+
p := su.Data()
128121

129-
// Check for toc restore slot (a nop), and replace with toc restore.
130-
var nop uint32
131-
if len(p) >= int(r.Off()+8) {
132-
nop = ctxt.Arch.ByteOrder.Uint32(p[r.Off()+4:])
133-
}
134-
if nop != 0x60000000 {
135-
ldr.Errorf(s, "Symbol %s is missing toc restoration slot at offset %d", ldr.SymName(s), r.Off()+4)
122+
// Check for toc restore slot (a nop), and replace with toc restore.
123+
var nop uint32
124+
if len(p) >= int(r.Off()+8) {
125+
nop = ctxt.Arch.ByteOrder.Uint32(p[r.Off()+4:])
126+
}
127+
if nop != 0x60000000 {
128+
ldr.Errorf(s, "Symbol %s is missing toc restoration slot at offset %d", ldr.SymName(s), r.Off()+4)
129+
}
130+
const o1 = 0xe8410018 // ld r2,24(r1)
131+
ctxt.Arch.ByteOrder.PutUint32(p[r.Off()+4:], o1)
132+
133+
return stub.Sym(), firstUse
134+
}
135+
136+
// Scan relocs and generate PLT stubs and generate/fixup ABI defined functions created by the linker
137+
func genstubs(ctxt *ld.Link, ldr *loader.Loader) {
138+
var stubs []loader.Sym
139+
var abifuncs []loader.Sym
140+
for _, s := range ctxt.Textp {
141+
relocs := ldr.Relocs(s)
142+
for i := 0; i < relocs.Count(); i++ {
143+
if r := relocs.At(i); r.Type() == objabi.ElfRelocOffset+objabi.RelocType(elf.R_PPC64_REL24) {
144+
switch ldr.SymType(r.Sym()) {
145+
case sym.SDYNIMPORT:
146+
// This call goes throught the PLT, generate and call through a PLT stub.
147+
if sym, firstUse := genpltstub(ctxt, ldr, r, s); firstUse {
148+
stubs = append(stubs, sym)
149+
}
150+
151+
case sym.SXREF:
152+
// Is this an ELF ABI defined function which is (in practice)
153+
// generated by the linker to save/restore callee save registers?
154+
// These are defined similarly for both PPC64 ELF and ELFv2.
155+
targName := ldr.SymName(r.Sym())
156+
if strings.HasPrefix(targName, "_save") || strings.HasPrefix(targName, "_rest") {
157+
if sym, firstUse := rewriteABIFuncReloc(ctxt, ldr, targName, r); firstUse {
158+
abifuncs = append(abifuncs, sym)
159+
}
160+
}
161+
}
136162
}
137-
const o1 = 0xe8410018 // ld r2,24(r1)
138-
ctxt.Arch.ByteOrder.PutUint32(p[r.Off()+4:], o1)
139163
}
140164
}
141-
// Put call stubs at the beginning (instead of the end).
165+
166+
// Append any usage of the go versions of ELF save/restore
167+
// functions to the end of the callstub list to minimize
168+
// chances a trampoline might be needed.
169+
stubs = append(stubs, abifuncs...)
170+
171+
// Put stubs at the beginning (instead of the end).
142172
// So when resolving the relocations to calls to the stubs,
143173
// the addresses are known and trampolines can be inserted
144174
// when necessary.
@@ -202,13 +232,74 @@ func genaddmoduledata(ctxt *ld.Link, ldr *loader.Loader) {
202232
o(0x4e800020)
203233
}
204234

235+
// Rewrite ELF (v1 or v2) calls to _savegpr0_n, _savegpr1_n, _savefpr_n, _restfpr_n, _savevr_m, or
236+
// _restvr_m (14<=n<=31, 20<=m<=31). Redirect them to runtime.elf_restgpr0+(n-14)*4,
237+
// runtime.elf_restvr+(m-20)*8, and similar.
238+
//
239+
// These functions are defined in the ELFv2 ABI (generated when using gcc -Os option) to save and
240+
// restore callee-saved registers (as defined in the PPC64 ELF ABIs) from registers n or m to 31 of
241+
// the named type. R12 and R0 are sometimes used in exceptional ways described in the ABI.
242+
//
243+
// Final note, this is only needed when linking internally. The external linker will generate these
244+
// functions if they are used.
245+
func rewriteABIFuncReloc(ctxt *ld.Link, ldr *loader.Loader, tname string, r loader.Reloc) (sym loader.Sym, firstUse bool) {
246+
s := strings.Split(tname, "_")
247+
// A valid call will split like {"", "savegpr0", "20"}
248+
if len(s) != 3 {
249+
return 0, false // Not an abi func.
250+
}
251+
minReg := 14 // _savegpr0_{n}, _savegpr1_{n}, _savefpr_{n}, 14 <= n <= 31
252+
offMul := 4 // 1 instruction per register op.
253+
switch s[1] {
254+
case "savegpr0", "savegpr1", "savefpr":
255+
case "restgpr0", "restgpr1", "restfpr":
256+
case "savevr", "restvr":
257+
minReg = 20 // _savevr_{n} or _restvr_{n}, 20 <= n <= 31
258+
offMul = 8 // 2 instructions per register op.
259+
default:
260+
return 0, false // Not an abi func
261+
}
262+
n, e := strconv.Atoi(s[2])
263+
if e != nil || n < minReg || n > 31 || r.Add() != 0 {
264+
return 0, false // Invalid register number, or non-zero addend. Not an abi func.
265+
}
266+
267+
// tname is a valid relocation to an ABI defined register save/restore function. Re-relocate
268+
// them to a go version of these functions in runtime/asm_ppc64x.s
269+
ts := ldr.LookupOrCreateSym("runtime.elf_"+s[1], 0)
270+
r.SetSym(ts)
271+
r.SetAdd(int64((n - minReg) * offMul))
272+
firstUse = !ldr.AttrReachable(ts)
273+
if firstUse {
274+
ldr.SetAttrReachable(ts, true)
275+
// This function only becomes reachable now. It has been dropped from
276+
// the text section (it was unreachable until now), it needs included.
277+
//
278+
// Similarly, TOC regeneration should not happen for these functions,
279+
// remove it from this save/restore function.
280+
if ldr.AttrShared(ts) {
281+
sb := ldr.MakeSymbolUpdater(ts)
282+
sb.SetData(sb.Data()[8:])
283+
sb.SetSize(sb.Size() - 8)
284+
relocs := sb.Relocs()
285+
// Only one PCREL reloc to .TOC. should be present.
286+
if relocs.Count() != 1 {
287+
log.Fatalf("Unexpected number of relocs in %s\n", ldr.SymName(ts))
288+
}
289+
sb.ResetRelocs()
290+
291+
}
292+
}
293+
return ts, firstUse
294+
}
295+
205296
func gentext(ctxt *ld.Link, ldr *loader.Loader) {
206297
if ctxt.DynlinkingGo() {
207298
genaddmoduledata(ctxt, ldr)
208299
}
209300

210301
if ctxt.LinkMode == ld.LinkInternal {
211-
genplt(ctxt, ldr)
302+
genstubs(ctxt, ldr)
212303
}
213304
}
214305

@@ -863,6 +954,13 @@ func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, ta
863954
o1 = uint32(0x3c000000) | 12<<21 // lis r12,targetaddr hi
864955
o2 = uint32(0x38000000) | 12<<21 | 12<<16 // addi r12,r12,targetaddr lo
865956

957+
// ELFv2 save/restore functions use R0/R12 in special ways, therefore trampolines
958+
// as generated here will not always work correctly.
959+
if strings.HasPrefix(ldr.SymName(target), "runtime.elf_") {
960+
log.Fatalf("Internal linker does not support trampolines to ELFv2 ABI"+
961+
" register save/restore function %s", ldr.SymName(target))
962+
}
963+
866964
t := ldr.SymValue(target)
867965
if t == 0 || r2Valid(ctxt) || ctxt.IsExternal() {
868966
// Target address is unknown, generate relocations

0 commit comments

Comments
 (0)