[llvm-branch-commits] [lld] [lld-macho] Match ld64 for arm64e TLV bootstrap binds and map files (PR #234004)
Oskar Wirga via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Oct 20 14:50:57 PDT 2026
https://github.com/oskarwirga created https://github.com/llvm/llvm-project/pull/234004
Two small arm64e changes, as separate commits:
1. Sign the arm64e TLV bootstrap pointer as ld64 does. The first word of a
thread-local variable descriptor is bound to `__tlv_bootstrap`, which dyld
replaces with a pointer to `tlv_get_addr` that TLV accesses call with
`blraaz`. On arm64e, write that bind as an auth'd bind with
the IA key and no diversity, even though the object asks for a plain
pointer.
2. List arm64e `__auth_got` entries in the map file. The map file printed the
entries of `__got` but not those of `__auth_got`, which holds the targets of
the stubs and the personalities.
Assisted-by: Claude
>From 1aba06cafe072f8b31d3900ac044e78d34691b94 Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Wed, 7 Oct 2026 10:11:22 -0400
Subject: [PATCH 1/2] [lld-macho] Sign the arm64e TLV bootstrap pointer as ld64
does
The first word of a thread-local variable descriptor is bound to
__tlv_bootstrap, which dyld replaces with a pointer to tlv_get_addr that
TLV accesses call with blraaz. On arm64e, ld64 writes that bind as an
authenticated bind with the IA key and no diversity, although the object
asks for a plain pointer. Do the same.
Assisted-by: Claude Code
---
lld/MachO/InputSection.cpp | 17 ++++++++++++++-
lld/MachO/InputSection.h | 2 ++
lld/MachO/Writer.cpp | 3 ++-
lld/test/MachO/arm64e-tlv-bootstrap.s | 30 +++++++++++++++++++++++++++
4 files changed, 50 insertions(+), 2 deletions(-)
create mode 100644 lld/test/MachO/arm64e-tlv-bootstrap.s
diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index 24e9283d05db5..a1fbd48b9044a 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -271,7 +271,10 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
!target->hasAttr(r.type, RelocAttrBits::AUTH)) {
referentVA -= firstTLVDataSection->addr;
} else if (needsFixup) {
- writeChainedFixup(loc, referentSym, r.getAddend(), r.getAuthInfo());
+ const AuthInfo *auth = r.getAuthInfo();
+ if (!auth)
+ auth = getImplicitAuthInfo(this, referentSym);
+ writeChainedFixup(loc, referentSym, r.getAddend(), auth);
continue;
}
} else if (auto *referentIsec = r.referent.dyn_cast<InputSection *>()) {
@@ -420,6 +423,18 @@ bool macho::usesAuthGot(const InputSection *isec) {
return config->arch() == AK_arm64e && isEhFrameSection(isec);
}
+const AuthInfo *macho::getImplicitAuthInfo(const InputSection *isec,
+ const Symbol *sym) {
+ // The only pointer bound from a TLV descriptor is its thunk, which TLV
+ // accesses call with blraaz.
+ static constexpr AuthInfo tlvThunk = {/*diversity=*/0, PtrAuthKeyIA,
+ /*addrDiv=*/false};
+ if (config->arch() == AK_arm64e && isThreadLocalVariables(isec->getFlags()) &&
+ needsBinding(sym))
+ return &tlvThunk;
+ return nullptr;
+}
+
bool macho::isGccExceptTabSection(const InputSection *isec) {
return isec->getName() == section_names::gccExceptTab &&
isec->getSegName() == segment_names::text;
diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index abb130b4c363e..47e32429ea49b 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -311,6 +311,8 @@ bool isSelRefsSection(const InputSection *);
bool isEhFrameSection(const InputSection *);
bool isGccExceptTabSection(const InputSection *);
bool usesAuthGot(const InputSection *);
+const AuthInfo *getImplicitAuthInfo(const InputSection *isec,
+ const Symbol *sym);
extern std::vector<ConcatInputSection *> inputSections;
// This is used as a counter for specyfing input order for input sections
diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp
index 97db232fbfcbb..bb556c17c52be 100644
--- a/lld/MachO/Writer.cpp
+++ b/lld/MachO/Writer.cpp
@@ -704,7 +704,8 @@ static void prepareSymbolRelocation(Symbol *sym, const InputSection *isec,
if (isAuth ||
!(isThreadLocalVariables(isec->getFlags()) && isa<Defined>(sym)))
addNonLazyBindingEntries(sym, isec, r.offset, r.getAddend(),
- /*forceOutlineAuth=*/isAuth);
+ /*forceOutlineAuth=*/isAuth ||
+ getImplicitAuthInfo(isec, sym));
}
}
diff --git a/lld/test/MachO/arm64e-tlv-bootstrap.s b/lld/test/MachO/arm64e-tlv-bootstrap.s
new file mode 100644
index 0000000000000..44c0cd98db2ab
--- /dev/null
+++ b/lld/test/MachO/arm64e-tlv-bootstrap.s
@@ -0,0 +1,30 @@
+# REQUIRES: aarch64
+## On arm64e, the thunk pointer of a thread-local variable descriptor, a bind to
+## __tlv_bootstrap, is an authenticated bind with the IA key and no diversity,
+## because TLV accesses call it with blraaz. On arm64 it is a plain bind.
+
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t-arm64e.o %s
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib %t-arm64e.o \
+# RUN: -o %t-arm64e.dylib
+# RUN: llvm-objdump --macho --dyld-info %t-arm64e.dylib | \
+# RUN: FileCheck %s --check-prefix=ARM64E
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t-arm64.o %s
+# RUN: %lld -arch arm64 -platform_version macos 13.0 13.0 -dylib %t-arm64.o \
+# RUN: -o %t-arm64.dylib
+# RUN: llvm-objdump --macho --dyld-info %t-arm64.dylib | \
+# RUN: FileCheck %s --check-prefix=ARM64
+
+# ARM64E: __DATA __thread_vars 0x{{[0-9A-F]+}} 0x{{[0-9A-F]+}} bind 0x0000 false IA 0x0 libSystem __tlv_bootstrap
+# ARM64: __DATA __thread_vars 0x{{[0-9A-F]+}} 0x{{[0-9A-F]+}} bind 0x0 libSystem __tlv_bootstrap
+
+.section __DATA,__thread_data,thread_local_regular
+.p2align 2
+_tv$tlv$init:
+ .long 5
+
+.section __DATA,__thread_vars,thread_local_variables
+.globl _tv
+_tv:
+ .quad __tlv_bootstrap
+ .quad 0
+ .quad _tv$tlv$init
>From 53c1025877ca68b256fd578fd40bf8ebed2c5f29 Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Wed, 7 Oct 2026 10:12:44 -0400
Subject: [PATCH 2/2] [lld-macho] List arm64e __auth_got entries in the map
file
The map file printed the entries of __got but not those of arm64e's
__auth_got, which held the targets of the stubs and the personalities.
Print them in the same way.
Assisted-by: Claude Code
---
lld/MachO/MapFile.cpp | 17 +++++++++++------
lld/test/MachO/arm64e-map.s | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+), 6 deletions(-)
create mode 100644 lld/test/MachO/arm64e-map.s
diff --git a/lld/MachO/MapFile.cpp b/lld/MachO/MapFile.cpp
index 93d0e87a402a1..d85e19fc09bae 100644
--- a/lld/MachO/MapFile.cpp
+++ b/lld/MachO/MapFile.cpp
@@ -149,15 +149,15 @@ static void printObjCStubsEntries(raw_fd_ostream &os,
<< sym->getName() << '\n';
}
-static void printNonLazyPointerSection(raw_fd_ostream &os, GotSection *osec) {
+// For printing the entries of the __got and __auth_got sections.
+static void printNonLazyPointer(raw_fd_ostream &os, uint64_t va,
+ const Symbol *sym) {
// ld64 considers stubs to belong to particular files, but considers GOT
// entries to be linker-synthesized. Not sure why they made that decision, but
// I think we can follow suit unless there's demand for better symbol-to-file
// associations.
- for (const Symbol *sym : osec->getEntries())
- os << format("0x%08llX\t0x%08zX\t[ 0] non-lazy-pointer-to-local: %s\n",
- osec->addr + sym->gotIndex * target->wordSize,
- target->wordSize, sym->getName().str().data());
+ os << format("0x%08llX\t0x%08zX\t[ 0] non-lazy-pointer-to-local: %s\n", va,
+ target->wordSize, sym->getName().str().data());
}
static uint64_t getSymSizeForMap(Defined *sym) {
@@ -273,7 +273,12 @@ void macho::writeMapFile() {
os << format("0x%08llX\t0x%08llX\t[ 0] helper helper\n", osec->addr,
osec->getSize());
} else if (osec == in.got) {
- printNonLazyPointerSection(os, in.got);
+ for (const Symbol *sym : in.got->getEntries())
+ printNonLazyPointer(
+ os, in.got->addr + sym->gotIndex * target->wordSize, sym);
+ } else if (in.authGot && osec == in.authGot) {
+ for (const Symbol *sym : in.authGot->getEntries())
+ printNonLazyPointer(os, in.authGot->getVA(sym), sym);
} else if (osec == in.objcMethList) {
printIsecArrSyms(in.objcMethList->getInputs());
}
diff --git a/lld/test/MachO/arm64e-map.s b/lld/test/MachO/arm64e-map.s
new file mode 100644
index 0000000000000..c751368c06d04
--- /dev/null
+++ b/lld/test/MachO/arm64e-map.s
@@ -0,0 +1,36 @@
+# REQUIRES: aarch64
+## The map file lists the entries of arm64e's __auth_got like those of __got.
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/foo.o %t/foo.s
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/test.o %t/test.s
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib \
+# RUN: -install_name @executable_path/libfoo.dylib %t/foo.o -o %t/libfoo.dylib
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 %t/test.o \
+# RUN: %t/libfoo.dylib -map %t/map -o %t/test
+# RUN: llvm-objdump --macho --section-headers %t/test > %t/out
+# RUN: cat %t/map >> %t/out
+# RUN: FileCheck %s < %t/out
+
+# CHECK: __auth_got {{[0-9a-f]+}} {{0*}}[[#%x,AUTH_GOT:]] DATA
+# CHECK: __got {{[0-9a-f]+}} {{0*}}[[#%x,GOT:]] DATA
+# CHECK-LABEL: # Sections:
+# CHECK: 0x[[#%X,AUTH_GOT]] 0x00000008 __DATA_CONST __auth_got
+# CHECK: 0x[[#%X,GOT]] 0x00000008 __DATA_CONST __got
+# CHECK-LABEL: # Symbols:
+# CHECK: 0x[[#%X,AUTH_GOT]] 0x00000008 [ 0] non-lazy-pointer-to-local: _foo
+# CHECK: 0x[[#%X,GOT]] 0x00000008 [ 0] non-lazy-pointer-to-local: _foo
+
+#--- foo.s
+.globl _foo
+_foo:
+ ret
+
+#--- test.s
+.globl _main
+.p2align 2
+_main:
+ bl _foo
+ adrp x0, _foo at GOTPAGE
+ ldr x0, [x0, _foo at GOTPAGEOFF]
+ ret
More information about the llvm-branch-commits
mailing list