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} ## 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} ## Bootstrap {#bootstrap}
<!-- go.dev/issue/69315 --> <!-- 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. // Internally linking cgo is incomplete on some architectures.
// https://golang.org/issue/14449 // https://golang.org/issue/14449
return true return true
case "arm64":
if goos == "windows" {
// windows/arm64 internal linking is not implemented.
return true
}
case "ppc64": case "ppc64":
// Big Endian PPC64 cgo internal linking is not implemented for aix or linux. // Big Endian PPC64 cgo internal linking is not implemented for aix or linux.
if goos == "aix" || goos == "linux" { if goos == "aix" || goos == "linux" {

View file

@ -1183,9 +1183,6 @@ func (t *tester) internalLink() bool {
if goos == "ios" { if goos == "ios" {
return false return false
} }
if goos == "windows" && goarch == "arm64" {
return false
}
// Internally linking cgo is incomplete on some architectures. // Internally linking cgo is incomplete on some architectures.
// https://golang.org/issue/10373 // https://golang.org/issue/10373
// https://golang.org/issue/14449 // https://golang.org/issue/14449
@ -1212,7 +1209,7 @@ func (t *tester) internalLinkPIE() bool {
case "darwin-amd64", "darwin-arm64", case "darwin-amd64", "darwin-arm64",
"linux-amd64", "linux-arm64", "linux-loong64", "linux-ppc64le", "linux-amd64", "linux-arm64", "linux-loong64", "linux-ppc64le",
"android-arm64", "android-arm64",
"windows-amd64", "windows-386": "windows-amd64", "windows-386", "windows-arm64":
return true return true
} }
return false 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: 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 { 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 // patch instruction: adrp
t := ldr.SymAddr(rs) + r.Add() - ((ldr.SymValue(s) + int64(r.Off())) &^ 0xfff) t := ldr.SymAddr(rs) + r.Add() - ((ldr.SymValue(s) + int64(r.Off())) &^ 0xfff)
if t >= 1<<32 || t < -1<<32 { if t >= 1<<32 || t < -1<<32 {
ldr.Errorf(s, "program too large, address relocation distance = %d", t) ldr.Errorf(s, "program too large, address relocation distance = %d", t)
} }
o0 := (uint32((t>>12)&3) << 29) | (uint32((t>>12>>2)&0x7ffff) << 5) 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 return val | int64(o0), noExtReloc, isOk
} else if (val>>24)&0x9f == 0x91 { } 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 // patch instruction: add
t := ldr.SymAddr(rs) + r.Add() - ((ldr.SymValue(s) + int64(r.Off())) &^ 0xfff) t := ldr.SymAddr(rs) + r.Add() - ((ldr.SymValue(s) + int64(r.Off())) &^ 0xfff)
o1 := uint32(t&0xfff) << 10 o1 := uint32(t&0xfff) << 10
if target.IsWindows() {
val &^= 0xfff << 10
}
return val | int64(o1), noExtReloc, isOk return val | int64(o1), noExtReloc, isOk
} else if (val>>24)&0x3b == 0x39 { } 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. // 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 shift := uint32(val) >> 30
if shift == 0 && (val>>20)&0x048 == 0x048 { // 128-bit vector load if shift == 0 && (val>>20)&0x048 == 0x048 { // 128-bit vector load
shift = 4 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) ldr.Errorf(s, "invalid address: %x for relocation type: ARM64_RELOC_PAGEOFF12", t)
} }
o1 := (uint32(t&0xfff) >> shift) << 10 o1 := (uint32(t&0xfff) >> shift) << 10
if target.IsWindows() {
val &^= 0xfff << 10
}
return val | int64(o1), noExtReloc, isOk return val | int64(o1), noExtReloc, isOk
} else { } else {
ldr.Errorf(s, "unsupported instruction for %x R_ARM64_PCREL", val) 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.AddPCRelPlus(ctxt.Arch, targ, 0)
rel.AddUint8(0x90) rel.AddUint8(0x90)
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 { } else if tplt >= 0 {
if su == nil { 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 { switch r.Type {
case IMAGE_REL_ARM64_ADDR32: case IMAGE_REL_ARM64_ADDR32:
rType = objabi.R_ADDR rType = objabi.R_ADDR
case IMAGE_REL_ARM64_ADDR64:
rType = objabi.R_ADDR
rSize = 8
case IMAGE_REL_ARM64_ADDR32NB: case IMAGE_REL_ARM64_ADDR32NB:
rType = objabi.R_PEIMAGEOFF 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 { if rType == 0 {
return nil, fmt.Errorf("%s: %v: unknown relocation type %v", pn, state.sectsyms[rsect], r.Type) return nil, fmt.Errorf("%s: %v: unknown relocation type %v", pn, state.sectsyms[rsect], r.Type)
} }
var rAdd int64 var val int64
switch rSize { switch rSize {
default: default:
panic("unexpected relocation size " + strconv.Itoa(int(rSize))) panic("unexpected relocation size " + strconv.Itoa(int(rSize)))
case 4: case 4:
rAdd = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:]))) val = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:])))
case 8: 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 // ld -r could generate multiple section symbols for the
// same section but with different values, we have to take // 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. // Internally linking cgo is incomplete on some architectures.
// https://go.dev/issue/14449 // https://go.dev/issue/14449
return true return true
case "arm64":
if goos == "windows" {
// windows/arm64 internal linking is not implemented.
return true
}
case "ppc64": case "ppc64":
// Big Endian PPC64 cgo internal linking is not implemented for aix or linux. // Big Endian PPC64 cgo internal linking is not implemented for aix or linux.
// https://go.dev/issue/8912 // https://go.dev/issue/8912