[go] cmd/link: add Cortex-A53 erratum 843419 workaround for arm64

1 view
Skip to first unread message

Gerrit Bot (Gerrit)

unread,
Apr 8, 2026, 3:59:21 PM (9 hours ago) Apr 8
to goph...@pubsubhelper.golang.org, Luiz, golang-co...@googlegroups.com

Gerrit Bot has uploaded the change for review

Commit message

cmd/link: add Cortex-A53 erratum 843419 workaround for arm64

On Cortex-A53, an ADRP instruction at page offset 0xFF8 or 0xFFC followed by a specific load/store sequence may compute an incorrect address (ARM erratum 843419). The Go internal linker had no workaround for this, while GNU ld and LLVM lld fix it by default.

During the trampoline pass, check ADRP relocations at dangerous page offsets against the erratum pattern (ported from lld's AArch64ErrataFix.cpp). When matched, redirect the ADRP+op pair through a veneer at a safe 16-byte aligned address.

Fixes #78577
Change-Id: Idf00a4d810fc9a2a3bfada3dd7f6618d01da86e9
GitHub-Last-Rev: 5ea7dc83627bb31dcf27a9c7f1648fee494beab5
GitHub-Pull-Request: golang/go#78589

Change diff

diff --git a/src/cmd/link/internal/arm64/asm.go b/src/cmd/link/internal/arm64/asm.go
index 9864788e..7bce547 100644
--- a/src/cmd/link/internal/arm64/asm.go
+++ b/src/cmd/link/internal/arm64/asm.go
@@ -1359,9 +1359,17 @@
}

// Convert the direct jump relocation r to refer to a trampoline if the target is too far.
+// Also handles Cortex-A53 erratum 843419 for ADRP relocations.
func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) {
relocs := ldr.Relocs(s)
r := relocs.At(ri)
+
+ // Cortex-A53 erratum 843419: check ADRP relocations for dangerous page offsets.
+ if isErratum843419Reloc(r.Type()) {
+ erratum843419Check(ctxt, ldr, ri, rs, s)
+ return
+ }
+
const pcrel = 1
switch r.Type() {
case objabi.ElfRelocOffset + objabi.RelocType(elf.R_AARCH64_CALL26),
diff --git a/src/cmd/link/internal/arm64/erratum843419.go b/src/cmd/link/internal/arm64/erratum843419.go
new file mode 100644
index 0000000..98822a2
--- /dev/null
+++ b/src/cmd/link/internal/arm64/erratum843419.go
@@ -0,0 +1,322 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package arm64
+
+import (
+ "cmd/internal/objabi"
+ "cmd/link/internal/ld"
+ "cmd/link/internal/loader"
+ "fmt"
+)
+
+// Cortex-A53 Erratum 843419
+//
+// On Cortex-A53 (and some other cores), an ADRP instruction placed at the last
+// two word-aligned addresses of a 4KB page (page offset 0xFF8 or 0xFFC) may
+// compute an incorrect address when followed by a specific instruction sequence.
+//
+// The triggering sequences (from ARM-EPM-048406 and LLVM lld AArch64ErrataFix.cpp):
+//
+// 3-instruction variant (ADRP at page offset 0xFFC):
+//
+// Pos1 (0xFFC): ADRP Xn
+// Pos2 (0x000): load/store (not exclusive, not writeback to Xn)
+// Pos3 (0x004): unsigned-immediate load/store using Xn as base
+//
+// 4-instruction variant (ADRP at page offset 0xFF8 or 0xFFC):
+//
+// Pos1 (0xFF8): ADRP Xn
+// Pos2 (0xFFC): load/store (not exclusive, not writeback to Xn)
+// Pos3 (0x000): non-branch instruction
+// Pos4 (0x004): unsigned-immediate load/store using Xn as base
+//
+// The Go internal linker emits ADRP instructions via R_ADDRARM64 and
+// R_ARM64_PCREL_LDST* relocations. These relocations cover an ADRP+op pair
+// (siz=8). After address assignment, we know where each ADRP will land. If the
+// sequence matches the erratum pattern, we redirect the ADRP+op pair through a
+// veneer at a safe address.
+//
+// GNU ld implements this as --fix-cortex-a53-843419 (enabled by default for
+// aarch64). LLVM lld does the same.
+//
+// Limitation: the detection checks instruction sequences within a single
+// symbol's data. If an ADRP is at the very end of a symbol and the following
+// instructions are in the next symbol, the pattern will not be detected.
+// In practice this is unlikely because Go functions do not end with ADRP
+// instructions (they end with RET or branch instructions).
+
+// ARM64 instruction encoding helpers, following LLVM lld's AArch64ErrataFix.cpp.
+
+func isADRP(insn uint32) bool {
+ return (insn & 0x9F000000) == 0x90000000
+}
+
+func isBranch(insn uint32) bool {
+ // Unconditional branch (immediate): B, BL
+ if (insn & 0x7C000000) == 0x14000000 {
+ return true
+ }
+ // Compare and branch: CBZ, CBNZ
+ if (insn & 0x7E000000) == 0x34000000 {
+ return true
+ }
+ // Conditional branch: B.cond
+ if (insn & 0xFF000010) == 0x54000000 {
+ return true
+ }
+ // Test and branch: TBZ, TBNZ
+ if (insn & 0x7E000000) == 0x36000000 {
+ return true
+ }
+ // Unconditional branch (register): BR, BLR, RET
+ if (insn & 0xFE1F0000) == 0xD61F0000 {
+ return true
+ }
+ return false
+}
+
+// getRt returns the Rd/Rt field (bits [4:0]) of an instruction.
+func getRt(insn uint32) uint32 {
+ return insn & 0x1F
+}
+
+// getRn returns the Rn field (bits [9:5]) of an instruction.
+func getRn(insn uint32) uint32 {
+ return (insn >> 5) & 0x1F
+}
+
+// isLoadStoreClass reports whether insn is in the load/store instruction class
+// (bit [27] == 1, bit [25] == 0).
+func isLoadStoreClass(insn uint32) bool {
+ return (insn & 0x0A000000) == 0x08000000
+}
+
+// isLoadStoreExclusive reports whether insn is a load/store exclusive
+// (bits [31:24] == 0x08 pattern: bit[29:24] == 001000).
+func isLoadStoreExclusive(insn uint32) bool {
+ return (insn & 0x3F000000) == 0x08000000
+}
+
+// isLoadStoreRegisterUnsigned reports whether insn is a load/store with
+// unsigned immediate offset (bits [29:24] == 111001 for unscaled or
+// [31:24] matches the unsigned imm pattern).
+func isLoadStoreRegisterUnsigned(insn uint32) bool {
+ // LDR/STR (unsigned immediate): bits [29:24] == 111001
+ return (insn & 0x3B000000) == 0x39000000
+}
+
+// hasWriteback reports whether a load/store instruction writes back to Rn.
+// This covers pre-index and post-index addressing modes.
+func hasWriteback(insn uint32) bool {
+ // Load/store register (unscaled immediate, pre-index, post-index)
+ // bits [29:24] == 111000, bits [11:10] distinguish:
+ // 00 = unscaled, 01 = post-index, 11 = pre-index
+ if (insn&0x3B200000) == 0x38000000 && (insn&0xC00) != 0 {
+ return true
+ }
+ // Load/store pair (pre/post-index)
+ // bits [29:23]: 0x28800000 pattern with bit[23] or writeback bit set
+ if (insn & 0x3B800000) == 0x28800000 {
+ return true // post-index pair
+ }
+ if (insn & 0x3B800000) == 0x29800000 {
+ return true // pre-index pair
+ }
+ return false
+}
+
+// isValidPos2 checks whether insn is a valid instruction at position 2 of the
+// erratum sequence: a load/store that is not exclusive and does not write back
+// to register rn.
+func isValidPos2(insn uint32, rn uint32) bool {
+ if !isLoadStoreClass(insn) {
+ return false
+ }
+ if isLoadStoreExclusive(insn) {
+ return false
+ }
+ if hasWriteback(insn) && getRn(insn) == rn {
+ return false
+ }
+ return true
+}
+
+// isValidPos4 checks whether insn is a valid instruction at position 3 or 4
+// (the dependent instruction): an unsigned-immediate load/store using rn as
+// the base register.
+func isValidPos4(insn uint32, rn uint32) bool {
+ return isLoadStoreRegisterUnsigned(insn) && getRn(insn) == rn
+}
+
+// isErratum843419Reloc reports whether rt is a relocation that produces an
+// ADRP instruction on ARM64.
+func isErratum843419Reloc(rt objabi.RelocType) bool {
+ switch rt {
+ case objabi.R_ADDRARM64,
+ objabi.R_ARM64_PCREL_LDST8,
+ objabi.R_ARM64_PCREL_LDST16,
+ objabi.R_ARM64_PCREL_LDST32,
+ objabi.R_ARM64_PCREL_LDST64:
+ return true
+ }
+ return false
+}
+
+// is843419Sequence reads up to 4 instructions starting at the ADRP and checks
+// whether they form an erratum 843419 triggering sequence. It requires access
+// to instructions that may be in the NEXT symbol (across a page boundary), so
+// it takes a function to read instruction words by virtual address.
+//
+// Returns true if the sequence matches.
+func is843419Sequence(adrpInsn uint32, pageOffset uint64, readInsn func(off int) (uint32, bool)) bool {
+ rn := getRt(adrpInsn) // destination register of ADRP
+
+ // Read Pos2 (instruction after ADRP).
+ pos2, ok := readInsn(4)
+ if !ok {
+ return false
+ }
+ if !isValidPos2(pos2, rn) {
+ return false
+ }
+
+ // 3-instruction variant: ADRP at 0xFFC, Pos2 at next page, Pos3 is the
+ // dependent load/store.
+ if pageOffset == 0xFFC {
+ pos3, ok := readInsn(8)
+ if ok && isValidPos4(pos3, rn) {
+ return true
+ }
+ }
+
+ // 4-instruction variant: ADRP at 0xFF8 or 0xFFC, Pos3 is any non-branch,
+ // Pos4 is the dependent load/store.
+ pos3, ok := readInsn(8)
+ if !ok {
+ return false
+ }
+ if isBranch(pos3) {
+ return false
+ }
+ pos4, ok := readInsn(12)
+ if !ok {
+ return false
+ }
+ return isValidPos4(pos4, rn)
+}
+
+// erratum843419Check checks whether the ADRP instruction for relocation ri in
+// symbol s will land at a dangerous page offset (0xFF8 or 0xFFC within a 4KB
+// page) AND the following instructions match the erratum 843419 triggering
+// pattern. If so, it generates a veneer that performs the ADRP+op at a safe
+// address and patches the original site to branch to the veneer.
+func erratum843419Check(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) {
+ relocs := ldr.Relocs(s)
+ r := relocs.At(ri)
+
+ adrpAddr := ldr.SymValue(s) + int64(r.Off())
+ pageOffset := uint64(adrpAddr) & 0xFFF
+
+ // With -debugtramp=2, generate veneers for ALL ADRP relocations to stress
+ // test the veneer mechanism. Otherwise, only check dangerous page offsets.
+ forceVeneer := *ld.FlagDebugTramp >= 2
+ if !forceVeneer && pageOffset != 0xFF8 && pageOffset != 0xFFC {
+ return
+ }
+
+ sb := ldr.MakeSymbolUpdater(s)
+ sdata := sb.Data()
+ off := r.Off()
+
+ if !forceVeneer {
+ // Check whether the instruction sequence actually matches the erratum.
+ ioff := int(off)
+ adrpInsn := ctxt.Arch.ByteOrder.Uint32(sdata[ioff:])
+
+ if !isADRP(adrpInsn) {
+ return
+ }
+
+ readInsn := func(delta int) (uint32, bool) {
+ pos := ioff + delta
+ if pos+4 > len(sdata) {
+ return 0, false
+ }
+ return ctxt.Arch.ByteOrder.Uint32(sdata[pos:]), true
+ }
+
+ if !is843419Sequence(adrpInsn, pageOffset, readInsn) {
+ return
+ }
+ }
+
+ rt := r.Type()
+ add := r.Add()
+
+ // Create a veneer symbol.
+ name := fmt.Sprintf("%s+%d-erratum843419", ldr.SymName(s), off)
+ veneer := ldr.LookupOrCreateSym(name, ldr.SymVersion(s))
+ ldr.SetAttrReachable(veneer, true)
+ if ldr.SymType(veneer) != 0 {
+ return // already created
+ }
+
+ vb := ldr.MakeSymbolUpdater(veneer)
+ ctxt.AddTramp(vb, ldr.SymType(s))
+ // Align veneer to 16 bytes so its ADRP (at offset 0) never lands on a
+ // page offset of 0xFF8 or 0xFFC. The highest 16-aligned offset in a 4KB
+ // page is 0xFF0, which is safe.
+ vb.SetAlign(16)
+
+ // Build veneer: ADRP Xd, target + original_second_insn + B back
+ vb.SetSize(12)
+ P := make([]byte, 12)
+
+ // Read original instructions from the symbol data.
+ origADRP := ctxt.Arch.ByteOrder.Uint32(sdata[off:])
+ origOP := ctxt.Arch.ByteOrder.Uint32(sdata[off+4:])
+
+ // Veneer instruction 0: ADRP with cleared immediate (relocation fills it).
+ adrpRd := origADRP & 0x1F
+ ctxt.Arch.ByteOrder.PutUint32(P[0:], 0x90000000|adrpRd)
+ // Veneer instruction 1: copy the original second instruction as-is.
+ ctxt.Arch.ByteOrder.PutUint32(P[4:], origOP)
+ // Veneer instruction 2: B (unconditional branch, placeholder for relocation).
+ ctxt.Arch.ByteOrder.PutUint32(P[8:], 0x14000000)
+ vb.SetData(P)
+
+ // Relocation 0: ADRP+op pair in the veneer, same type and target as original.
+ rAdrp, _ := vb.AddRel(rt)
+ rAdrp.SetOff(0)
+ rAdrp.SetSiz(8)
+ rAdrp.SetSym(rs)
+ rAdrp.SetAdd(add)
+
+ // Relocation 1: B back to the instruction after the original pair.
+ rBack, _ := vb.AddRel(objabi.R_CALLARM64)
+ rBack.SetOff(8)
+ rBack.SetSiz(4)
+ rBack.SetSym(s)
+ rBack.SetAdd(int64(off) + 8)
+
+ // Patch original site: replace ADRP with B to veneer, replace second insn
+ // with NOP. The B will be resolved by a R_CALLARM64 relocation.
+ sb.MakeWritable()
+ sb.SetUint32(ctxt.Arch, int64(off), 0x14000000) // B (placeholder)
+ sb.SetUint32(ctxt.Arch, int64(off)+4, 0xd503201f) // NOP
+
+ // Replace the original relocation with R_CALLARM64 to branch to the veneer.
+ rels := sb.Relocs()
+ rel := rels.At(ri)
+ rel.SetType(objabi.R_CALLARM64)
+ rel.SetSym(veneer)
+ rel.SetAdd(0)
+ rel.SetSiz(4)
+
+ if *ld.FlagDebugTramp > 0 && ctxt.Debugvlog > 0 {
+ ctxt.Logf("erratum 843419 veneer for %s+%#x at page offset %#x\n",
+ ldr.SymName(s), off, pageOffset)
+ }
+}
diff --git a/src/cmd/link/internal/arm64/erratum843419_test.go b/src/cmd/link/internal/arm64/erratum843419_test.go
new file mode 100644
index 0000000..7a46969
--- /dev/null
+++ b/src/cmd/link/internal/arm64/erratum843419_test.go
@@ -0,0 +1,151 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package arm64
+
+import "testing"
+
+func TestIsADRP(t *testing.T) {
+ tests := []struct {
+ name string
+ insn uint32
+ want bool
+ }{
+ {"adrp x0", 0x90000000, true},
+ {"adrp x27", 0x9000001b, true},
+ {"adrp x27 with imm", 0xd000a3fb, true}, // from real binary
+ {"adrp x27 with imm2", 0xf000a47b, true}, // from real binary
+ {"add x0, x0, #0", 0x91000000, false},
+ {"ldr x1, [x27]", 0xf9400361, false},
+ {"b #0", 0x14000000, false},
+ {"nop", 0xd503201f, false},
+ {"str x3, [x27, #1080]", 0xf9021f63, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isADRP(tt.insn); got != tt.want {
+ t.Errorf("isADRP(%#x) = %v, want %v", tt.insn, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestIsBranch(t *testing.T) {
+ tests := []struct {
+ name string
+ insn uint32
+ want bool
+ }{
+ {"b #0", 0x14000000, true},
+ {"bl #0", 0x94000000, true},
+ {"cbz x0, #0", 0xb4000000, true},
+ {"cbnz w0, #0", 0x35000000, true},
+ {"b.eq #0", 0x54000000, true},
+ {"tbz x0, #0, #0", 0x36000000, true},
+ {"br x5", 0xd61f00a0, true},
+ {"blr x5", 0xd63f00a0, true},
+ {"ret", 0xd65f03c0, true},
+ {"adrp x0", 0x90000000, false},
+ {"ldr x1, [x27]", 0xf9400361, false},
+ {"nop", 0xd503201f, false},
+ {"add x0, x0, #1", 0x91000400, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isBranch(tt.insn); got != tt.want {
+ t.Errorf("isBranch(%#x) = %v, want %v", tt.insn, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestIsLoadStoreRegisterUnsigned(t *testing.T) {
+ tests := []struct {
+ name string
+ insn uint32
+ want bool
+ }{
+ // From real erratum matches in elma binary:
+ {"ldr w2, [x27, #608]", 0xb9426362, true},
+ {"str x2, [x27, #1888]", 0xf903b362, true},
+ {"ldr x1, [x27, #1856]", 0xf943a361, true},
+ {"str x3, [x27, #1080]", 0xf9021f63, true},
+ // Non-unsigned-immediate:
+ {"ldp x5, x0, [x0, #64]", 0xa94400a5, false},
+ {"ldxr x0, [x1]", 0xc85f7c20, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := isLoadStoreRegisterUnsigned(tt.insn); got != tt.want {
+ t.Errorf("isLoadStoreRegisterUnsigned(%#x) = %v, want %v", tt.insn, got, tt.want)
+ }
+ })
+ }
+}
+
+func TestIs843419Sequence(t *testing.T) {
+ // Real erratum match from elma 1.6.0-hotfix binary:
+ // 0x7faffc: d000a3fb adrp x27, 0x1c78000
+ // 0x7fb000: f943a361 ldr x1, [x27, #1856]
+ // 0x7fb004: d000a57b adrp x27, 0x1ca9000
+ // 0x7fb008: b9426362 ldr w2, [x27, #608]
+ match1 := []uint32{0xd000a3fb, 0xf943a361, 0xd000a57b, 0xb9426362}
+
+ // Real erratum match from elma 1.6.0-hotfix binary:
+ // 0x81cff8: f000a47b adrp x27, 0x1cab000
+ // 0x81cffc: f9021f63 str x3, [x27, #1080]
+ // 0x81d000: f000a31b adrp x27, 0x1c80000
+ // 0x81d004: f903b362 str x2, [x27, #1888]
+ match2 := []uint32{0xf000a47b, 0xf9021f63, 0xf000a31b, 0xf903b362}
+
+ // Non-match: ADRP followed immediately by dependent load (no gap).
+ // This was the writeBarrier example that the Google engineer correctly
+ // identified as NOT matching the erratum.
+ noMatch := []uint32{0xd000a3fb, 0xb9426362, 0x14000000, 0xd503201f}
+
+ tests := []struct {
+ name string
+ insns []uint32
+ pageOffset uint64
+ want bool
+ }{
+ {"real match 1 (4-insn at 0xFFC)", match1, 0xFFC, true},
+ {"real match 2 (4-insn at 0xFF8)", match2, 0xFF8, true},
+ {"no match: dependent load at pos2", noMatch, 0xFF8, false},
+ // Note: is843419Sequence does NOT check the page offset itself.
+ // The caller (erratum843419Check) filters by page offset before calling.
+ // So this test verifies the instruction pattern only.
+ {"3-insn variant at 0xFFC", []uint32{
+ 0x90000000 | 27, // adrp x27
+ 0x91000000, // add x0, x0, #0 (load/store class? no — this is NOT ls class)
+ 0xb9400360, // ldr w0, [x27]
+ }, 0xFFC, false}, // add is not load/store class, so pos2 fails
+ {"3-insn variant valid at 0xFFC", []uint32{
+ 0x90000000 | 27, // adrp x27
+ 0xf9400001, // ldr x1, [x0] — load, does not write x27
+ 0xb9400360, // ldr w0, [x27] — unsigned imm load, base=x27
+ }, 0xFFC, true},
+ {"4-insn with branch at pos3", []uint32{
+ 0x90000000 | 27, // adrp x27
+ 0xf9400001, // ldr x1, [x0]
+ 0x14000010, // b #64 — branch at pos3 blocks 4-insn variant
+ 0xb9400360, // ldr w0, [x27]
+ }, 0xFF8, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ readInsn := func(delta int) (uint32, bool) {
+ idx := delta / 4
+ if idx < 0 || idx >= len(tt.insns) {
+ return 0, false
+ }
+ return tt.insns[idx], true
+ }
+ got := is843419Sequence(tt.insns[0], tt.pageOffset, readInsn)
+ if got != tt.want {
+ t.Errorf("is843419Sequence(pageOffset=%#x) = %v, want %v", tt.pageOffset, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index 773efb4..f5f625d 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -69,26 +69,32 @@
return 0
}

- n := uint64(0)
+ nCall := uint64(0)
+ nADRP := uint64(0)
relocs := ldr.Relocs(s)
for ri := 0; ri < relocs.Count(); ri++ {
r := relocs.At(ri)
if r.Type().IsDirectCallOrJump() {
- n++
+ nCall++
+ }
+ if ctxt.IsARM64() && isADRPReloc(r.Type()) {
+ nADRP++
}
}

switch {
case ctxt.IsARM():
- return n * 20 // Trampolines in ARM range from 3 to 5 instructions.
+ return nCall * 20 // Trampolines in ARM range from 3 to 5 instructions.
case ctxt.IsARM64():
- return n * 12 // Trampolines in ARM64 are 3 instructions.
+ // Trampolines for calls are 3 instructions.
+ // Erratum 843419 veneers are also 3 instructions.
+ return nCall*12 + nADRP*12
case ctxt.IsLOONG64():
- return n * 12 // Trampolines in LOONG64 are 3 instructions.
+ return nCall * 12 // Trampolines in LOONG64 are 3 instructions.
case ctxt.IsPPC64():
- return n * 16 // Trampolines in PPC64 are 4 instructions.
+ return nCall * 16 // Trampolines in PPC64 are 4 instructions.
case ctxt.IsRISCV64():
- return n * 8 // Trampolines in RISCV64 are 2 instructions.
+ return nCall * 8 // Trampolines in RISCV64 are 2 instructions.
}
panic("unreachable")
}
@@ -97,6 +103,9 @@
// ARM, LOONG64, PPC64, PPC64LE and RISCV64 support trampoline insertion for internal
// and external linking. On PPC64 and PPC64LE the text sections might be split
// but will still insert trampolines where necessary.
+//
+// On ARM64, this also handles Cortex-A53 erratum 843419 by checking ADRP
+// relocations for dangerous page offsets and generating veneers if needed.
func trampoline(ctxt *Link, s loader.Sym) {
if thearch.Trampoline == nil {
return // no need or no support of trampolines on this arch
@@ -107,6 +116,17 @@
for ri := 0; ri < relocs.Count(); ri++ {
r := relocs.At(ri)
rt := r.Type()
+
+ // On ARM64, pass ADRP relocations to the arch hook for erratum 843419
+ // checking. These are separate from the call/jump trampoline logic.
+ if ctxt.IsARM64() && isADRPReloc(rt) {
+ rs := r.Sym()
+ if rs != 0 && ldr.AttrReachable(rs) {
+ thearch.Trampoline(ctxt, ldr, ri, rs, s)
+ }
+ continue
+ }
+
if !rt.IsDirectCallOrJump() && !isPLTCall(ctxt.Arch, rt) {
continue
}
@@ -137,6 +157,20 @@
}
}

+// isADRPReloc reports whether rt is an ARM64 relocation that produces an ADRP
+// instruction.
+func isADRPReloc(rt objabi.RelocType) bool {
+ switch rt {
+ case objabi.R_ADDRARM64,
+ objabi.R_ARM64_PCREL_LDST8,
+ objabi.R_ARM64_PCREL_LDST16,
+ objabi.R_ARM64_PCREL_LDST32,
+ objabi.R_ARM64_PCREL_LDST64:
+ return true
+ }
+ return false
+}
+
// whether rt is a (host object) relocation that will be turned into
// a call to PLT.
func isPLTCall(arch *sys.Arch, rt objabi.RelocType) bool {
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
index ac9a7a9..74f3ad1 100644
--- a/src/cmd/link/link_test.go
+++ b/src/cmd/link/link_test.go
@@ -2472,3 +2472,75 @@
}
}
}
+
+func TestErratum843419(t *testing.T) {
+ // Test that the linker generates erratum 843419 veneers for ARM64 binaries.
+ // The erratum affects ADRP instructions at page offsets 0xFF8/0xFFC on
+ // Cortex-A53 cores. We use -debugtramp=2 to force veneer generation for
+ // all ADRP relocations so the test works regardless of binary layout.
+ if runtime.GOARCH != "arm64" {
+ t.Skipf("erratum 843419 only applies to arm64")
+ }
+
+ testenv.MustHaveGoBuild(t)
+ t.Parallel()
+
+ tmpdir := t.TempDir()
+ src := filepath.Join(tmpdir, "main.go")
+ err := os.WriteFile(src, []byte(testErratum843419Src), 0666)
+ if err != nil {
+ t.Fatal(err)
+ }
+ exe := filepath.Join(tmpdir, "main.exe")
+
+ // Build with -debugtramp=2 to force veneer generation for all ADRP
+ // relocations regardless of page offset, for testing purposes.
+ cmd := goCmd(t, "build", "-ldflags=-debugtramp=2", "-o", exe, src)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("build failed: %v\n%s", err, out)
+ }
+
+ // Verify the binary runs correctly.
+ cmd = testenv.Command(t, exe)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("executable failed to run: %v\n%s", err, out)
+ }
+ if string(out) != "OK\n" {
+ t.Errorf("unexpected output: %q, want %q", string(out), "OK\n")
+ }
+
+ // Check that erratum843419 veneers were generated.
+ out, err = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", exe).CombinedOutput()
+ if err != nil {
+ t.Fatalf("nm failed: %v\n%s", err, out)
+ }
+ if !bytes.Contains(out, []byte("erratum843419")) {
+ t.Errorf("no erratum843419 veneers found in binary; expected veneer symbols in nm output")
+ }
+}
+
+const testErratum843419Src = `
+package main
+
+import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "crypto/sha256"
+)
+
+func main() {
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+ hash := sha256.Sum256([]byte("erratum 843419 test"))
+ _, err = ecdsa.SignASN1(rand.Reader, key, hash[:])
+ if err != nil {
+ panic(err)
+ }
+ println("OK")
+}
+`

Change information

Files:
  • M src/cmd/link/internal/arm64/asm.go
  • A src/cmd/link/internal/arm64/erratum843419.go
  • A src/cmd/link/internal/arm64/erratum843419_test.go
  • M src/cmd/link/internal/ld/data.go
  • M src/cmd/link/link_test.go
Change size: L
Delta: 5 files changed, 594 insertions(+), 7 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: Idf00a4d810fc9a2a3bfada3dd7f6618d01da86e9
Gerrit-Change-Number: 764141
Gerrit-PatchSet: 1
Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Gopher Robot (Gerrit)

unread,
Apr 8, 2026, 3:59:23 PM (9 hours ago) Apr 8
to Luiz, Gerrit Bot, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Gopher Robot added 1 comment

Patchset-level comments
File-level comment, Patchset 1 (Latest):
Gopher Robot . unresolved

I spotted some possible problems with your PR:

  1. You have a long 258 character line in the commit message body. Please add line breaks to long lines that should be wrapped. Lines in the commit message body should be wrapped at ~76 characters unless needed for things like URLs or tables. (Note: GitHub might render long lines as soft-wrapped, so double-check in the Gerrit commit message shown above.)

Please address any problems by updating the GitHub PR.

When complete, mark this comment as 'Done' and click the [blue 'Reply' button](https://go.dev/wiki/GerritBot#i-left-a-reply-to-a-comment-in-gerrit-but-no-one-but-me-can-see-it) above. These findings are based on heuristics; if a finding does not apply, briefly reply here saying so.

To update the commit title or commit message body shown here in Gerrit, you must edit the GitHub PR title and PR description (the first comment) in the GitHub web interface using the 'Edit' button or 'Edit' menu entry there. Note: pushing a new commit to the PR will not automatically update the commit message used by Gerrit.

For more details, see:

(In general for Gerrit code reviews, the change author is expected to [log in to Gerrit](https://go-review.googlesource.com/login/) with a Gmail or other Google account and then close out each piece of feedback by marking it as 'Done' if implemented as suggested or otherwise reply to each review comment. See the [Review](https://go.dev/doc/contribute#review) section of the Contributing Guide for details.)

Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: Idf00a4d810fc9a2a3bfada3dd7f6618d01da86e9
    Gerrit-Change-Number: 764141
    Gerrit-PatchSet: 1
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-Comment-Date: Wed, 08 Apr 2026 19:59:17 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Gopher Robot (Gerrit)

    unread,
    Apr 8, 2026, 4:03:19 PM (9 hours ago) Apr 8
    to Luiz, Gerrit Bot, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

    Message from Gopher Robot

    Congratulations on opening your first change. Thank you for your contribution!

    Next steps:
    A maintainer will review your change and provide feedback. See
    https://go.dev/doc/contribute#review for more info and tips to get your
    patch through code review.

    Most changes in the Go project go through a few rounds of revision. This can be
    surprising to people new to the project. The careful, iterative review process
    is our way of helping mentor contributors and ensuring that their contributions
    have a lasting impact.

    During May-July and Nov-Jan the Go project is in a code freeze, during which
    little code gets reviewed or merged. If a reviewer responds with a comment like
    R=go1.11 or adds a tag like "wait-release", it means that this CL will be
    reviewed as part of the next development cycle. See https://go.dev/s/release
    for more details.

    Open in Gerrit

    Related details

    Attention set is empty
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: Idf00a4d810fc9a2a3bfada3dd7f6618d01da86e9
    Gerrit-Change-Number: 764141
    Gerrit-PatchSet: 1
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-Comment-Date: Wed, 08 Apr 2026 20:03:14 +0000
    Gerrit-HasComments: No
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Jorropo (Gerrit)

    unread,
    Apr 8, 2026, 4:04:46 PM (9 hours ago) Apr 8
    to Luiz, Gerrit Bot, goph...@pubsubhelper.golang.org, Gopher Robot, golang-co...@googlegroups.com

    Jorropo added 1 comment

    Patchset-level comments
    Jorropo . resolved

    Would it be worth it to not perform the work around if GOARM64 is set to a level higher than v8.0-a (as Cortex-A53 implements v8.0-a at most) ?

    Open in Gerrit

    Related details

    Attention set is empty
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: Idf00a4d810fc9a2a3bfada3dd7f6618d01da86e9
    Gerrit-Change-Number: 764141
    Gerrit-PatchSet: 1
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-CC: Jorropo <jorro...@gmail.com>
    Gerrit-Comment-Date: Wed, 08 Apr 2026 20:04:39 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Jorropo (Gerrit)

    unread,
    Apr 8, 2026, 4:04:51 PM (9 hours ago) Apr 8
    to Luiz, Gerrit Bot, goph...@pubsubhelper.golang.org, Gopher Robot, golang-co...@googlegroups.com

    Jorropo voted Commit-Queue+1

    Commit-Queue+1
    Open in Gerrit

    Related details

    Attention set is empty
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: Idf00a4d810fc9a2a3bfada3dd7f6618d01da86e9
    Gerrit-Change-Number: 764141
    Gerrit-PatchSet: 1
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-Reviewer: Jorropo <jorro...@gmail.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-Comment-Date: Wed, 08 Apr 2026 20:04:44 +0000
    Gerrit-HasComments: No
    Gerrit-Has-Labels: Yes
    unsatisfied_requirement
    open
    diffy
    Reply all
    Reply to author
    Forward
    0 new messages