From: qmuntal Date: Tue, 16 Sep 2025 16:00:10 +0000 (+0200) Subject: cmd/link: internal linking support for windows/arm64 X-Git-Tag: go1.26rc1~451 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=9e25c2f6de;p=gostls13.git 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 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek --- diff --git a/doc/next/5-toolchain.md b/doc/next/5-toolchain.md index cc32f30a52..b5893288e5 100644 --- a/doc/next/5-toolchain.md +++ b/doc/next/5-toolchain.md @@ -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} diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index 2fcdb2d391..2b382a1c02 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -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" { diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 1e74438f48..9c9e1b85f0 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -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 diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go index 1f4282adec..7c4546fb17 100644 --- a/src/cmd/link/internal/arm64/asm.go +++ b/src/cmd/link/internal/arm64/asm.go @@ -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) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index dfb1d7bafb..b70d050c99 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -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, ] + 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 { diff --git a/src/cmd/link/internal/loadpe/ldpe.go b/src/cmd/link/internal/loadpe/ldpe.go index d3a050135c..2073045c47 100644 --- a/src/cmd/link/internal/loadpe/ldpe.go +++ b/src/cmd/link/internal/loadpe/ldpe.go @@ -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 diff --git a/src/internal/platform/supported.go b/src/internal/platform/supported.go index f9706a6988..778d727086 100644 --- a/src/internal/platform/supported.go +++ b/src/internal/platform/supported.go @@ -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