[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