cmd/link: internal linking support for windows/arm64

The internal linker was missing some pieces to support windows/arm64.

Closes #75485

Cq-Include-Trybots: luci.golang.try:gotip-windows-arm64
Change-Id: I5c18a47e63e09b8ae22c9b24832249b54f544b7e
Reviewed-on: https://go-review.googlesource.com/c/go/+/704295
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
qmuntal 2025-09-16 18:00:10 +02:00 committed by Quim Muntal
parent ff2ebf69c4
commit 9e25c2f6de
7 changed files with 80 additions and 21 deletions

View file

@ -4,6 +4,10 @@
## Linker {#linker}
On 64-bit ARM-based Windows (the `windows/arm64` port), the linker now supports internal
linking mode of cgo programs, which can be requested with the
`-ldflags=-linkmode=internal` flag.
## Bootstrap {#bootstrap}
<!-- go.dev/issue/69315 -->

View file

@ -624,11 +624,6 @@ func mustLinkExternal(goos, goarch string, cgoEnabled bool) bool {
// Internally linking cgo is incomplete on some architectures.
// https://golang.org/issue/14449
return true
case "arm64":
if goos == "windows" {
// windows/arm64 internal linking is not implemented.
return true
}
case "ppc64":
// Big Endian PPC64 cgo internal linking is not implemented for aix or linux.
if goos == "aix" || goos == "linux" {

View file

@ -1183,9 +1183,6 @@ func (t *tester) internalLink() bool {
if goos == "ios" {
return false
}
if goos == "windows" && goarch == "arm64" {
return false
}
// Internally linking cgo is incomplete on some architectures.
// https://golang.org/issue/10373
// https://golang.org/issue/14449
@ -1212,7 +1209,7 @@ func (t *tester) internalLinkPIE() bool {
case "darwin-amd64", "darwin-arm64",
"linux-amd64", "linux-arm64", "linux-loong64", "linux-ppc64le",
"android-arm64",
"windows-amd64", "windows-386":
"windows-amd64", "windows-386", "windows-arm64":
return true
}
return false

View file

@ -1027,25 +1027,35 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade
}
case objabi.R_ARM64_PCREL:
// When targetting Windows, the instruction immediate field is cleared
// before applying relocations, as it contains the offset as bytes
// instead of pages. It has already been accounted for in loadpe.Load
// by adjusting r.Add().
if (val>>24)&0x9f == 0x90 {
// R_AARCH64_ADR_PREL_PG_HI21
// ELF R_AARCH64_ADR_PREL_PG_HI21, or Mach-O ARM64_RELOC_PAGE21, or PE IMAGE_REL_ARM64_PAGEBASE_REL21
// patch instruction: adrp
t := ldr.SymAddr(rs) + r.Add() - ((ldr.SymValue(s) + int64(r.Off())) &^ 0xfff)
if t >= 1<<32 || t < -1<<32 {
ldr.Errorf(s, "program too large, address relocation distance = %d", t)
}
o0 := (uint32((t>>12)&3) << 29) | (uint32((t>>12>>2)&0x7ffff) << 5)
if target.IsWindows() {
val &^= 3<<29 | 0x7ffff<<5
}
return val | int64(o0), noExtReloc, isOk
} else if (val>>24)&0x9f == 0x91 {
// ELF R_AARCH64_ADD_ABS_LO12_NC or Mach-O ARM64_RELOC_PAGEOFF12
// ELF R_AARCH64_ADD_ABS_LO12_NC, or Mach-O ARM64_RELOC_PAGEOFF12, or PE IMAGE_REL_ARM64_PAGEOFFSET_12A
// patch instruction: add
t := ldr.SymAddr(rs) + r.Add() - ((ldr.SymValue(s) + int64(r.Off())) &^ 0xfff)
o1 := uint32(t&0xfff) << 10
if target.IsWindows() {
val &^= 0xfff << 10
}
return val | int64(o1), noExtReloc, isOk
} else if (val>>24)&0x3b == 0x39 {
// Mach-O ARM64_RELOC_PAGEOFF12
// Mach-O ARM64_RELOC_PAGEOFF12 or PE IMAGE_REL_ARM64_PAGEOFFSET_12L
// patch ldr/str(b/h/w/d/q) (integer or vector) instructions, which have different scaling factors.
// Mach-O uses same relocation type for them.
// Mach-O and PE use same relocation type for them.
shift := uint32(val) >> 30
if shift == 0 && (val>>20)&0x048 == 0x048 { // 128-bit vector load
shift = 4
@ -1055,6 +1065,9 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade
ldr.Errorf(s, "invalid address: %x for relocation type: ARM64_RELOC_PAGEOFF12", t)
}
o1 := (uint32(t&0xfff) >> shift) << 10
if target.IsWindows() {
val &^= 0xfff << 10
}
return val | int64(o1), noExtReloc, isOk
} else {
ldr.Errorf(s, "unsupported instruction for %x R_ARM64_PCREL", val)

View file

@ -913,6 +913,23 @@ func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) error {
rel.AddPCRelPlus(ctxt.Arch, targ, 0)
rel.AddUint8(0x90)
rel.AddUint8(0x90)
case sys.ARM64:
// adrp x16, addr
rel.AddUint32(ctxt.Arch, 0x90000010)
r, _ := rel.AddRel(objabi.R_ARM64_PCREL)
r.SetOff(int32(rel.Size() - 4))
r.SetSiz(4)
r.SetSym(targ)
// ldr x17, [x16, <offset>]
rel.AddUint32(ctxt.Arch, 0xf9400211)
r, _ = rel.AddRel(objabi.R_ARM64_PCREL)
r.SetOff(int32(rel.Size() - 4))
r.SetSiz(4)
r.SetSym(targ)
// br x17
rel.AddUint32(ctxt.Arch, 0xd61f0220)
}
} else if tplt >= 0 {
if su == nil {

View file

@ -392,21 +392,59 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
switch r.Type {
case IMAGE_REL_ARM64_ADDR32:
rType = objabi.R_ADDR
case IMAGE_REL_ARM64_ADDR64:
rType = objabi.R_ADDR
rSize = 8
case IMAGE_REL_ARM64_ADDR32NB:
rType = objabi.R_PEIMAGEOFF
case IMAGE_REL_ARM64_BRANCH26:
rType = objabi.R_CALLARM64
case IMAGE_REL_ARM64_PAGEBASE_REL21,
IMAGE_REL_ARM64_PAGEOFFSET_12A,
IMAGE_REL_ARM64_PAGEOFFSET_12L:
rType = objabi.R_ARM64_PCREL
}
}
if rType == 0 {
return nil, fmt.Errorf("%s: %v: unknown relocation type %v", pn, state.sectsyms[rsect], r.Type)
}
var rAdd int64
var val int64
switch rSize {
default:
panic("unexpected relocation size " + strconv.Itoa(int(rSize)))
case 4:
rAdd = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:])))
val = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:])))
case 8:
rAdd = int64(binary.LittleEndian.Uint64(state.sectdata[rsect][rOff:]))
val = int64(binary.LittleEndian.Uint64(state.sectdata[rsect][rOff:]))
}
var rAdd int64
if arch.Family == sys.ARM64 {
switch r.Type {
case IMAGE_REL_ARM64_BRANCH26:
// This instruction doesn't support an addend.
case IMAGE_REL_ARM64_PAGEOFFSET_12A:
// The addend is stored in the immediate field of the instruction.
// Get the addend from the instruction.
rAdd = (val >> 10) & 0xfff
case IMAGE_REL_ARM64_PAGEOFFSET_12L:
// Same as IMAGE_REL_ARM64_PAGEOFFSET_12A, but taking into account the shift.
shift := uint32(val) >> 30
if shift == 0 && (val>>20)&0x048 == 0x048 { // 128-bit vector load
shift = 4
}
rAdd = ((val >> 10) & 0xfff) << shift
case IMAGE_REL_ARM64_PAGEBASE_REL21:
// The addend is stored in the immediate field of the instruction
// as a byte offset. Get the addend from the instruction and clear
// the immediate bits.
immlo := (val >> 29) & 3
immhi := (val >> 5) & 0x7ffff
rAdd = (immhi << 2) | immlo
default:
rAdd = val
}
} else {
rAdd = val
}
// ld -r could generate multiple section symbols for the
// same section but with different values, we have to take

View file

@ -89,11 +89,6 @@ func MustLinkExternal(goos, goarch string, withCgo bool) bool {
// Internally linking cgo is incomplete on some architectures.
// https://go.dev/issue/14449
return true
case "arm64":
if goos == "windows" {
// windows/arm64 internal linking is not implemented.
return true
}
case "ppc64":
// Big Endian PPC64 cgo internal linking is not implemented for aix or linux.
// https://go.dev/issue/8912