[llvm-branch-commits] [lld] [lld-macho] Add arm64e pointer authentication support (PR #234002)
Oskar Wirga via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Oct 20 14:50:56 PDT 2026
https://github.com/oskarwirga created https://github.com/llvm/llvm-project/pull/234002
This replaces #188378.
This PR adds an arm64e target to lld that
links pointer-authenticated code the way ld64 does: it parses
`ARM64_RELOC_AUTHENTICATED_POINTER` and writes authenticated chained rebases
and binds. The behaviour was checked against Xcode 27's ld and tested extensively.
There are seven commits:
1. Add an arm64e target. `-arch arm64e` selects it. The CPU subtype carries
ptrauth ABI version 1 for macOS and Mac Catalyst executables and
version 0 for everything else.
2. Use chained fixups for arm64e. Every arm64e output uses chained
fixups. `DYLD_CHAINED_PTR_ARM64E_USERLAND24` is used from the same deployment
targets as in ld64 and `DYLD_CHAINED_PTR_ARM64E` below them. Errors on: invalid rebase
targets or more than 65535 imports in the 16-bit format.
3. Link arm64e authenticated pointers. The extern-only
`ARM64_RELOC_AUTHENTICATED_POINTER` is parsed and its signing schema kept
in `Relocation`, which stays 24 bytes. Error on payloads without the
auth bit or with reserved bits set. Auth'd
rebases store an offset of at most 4 GiB from the image base,
auth'd binds keep their addends in the imports table, and
auth'd pointers in `__thread_vars` stay signed pointers.
4. Keep differently signed data apart in ICF. This matters even without
`--icf`, because `__cfstring` sections are always deduplicated.
5. Sign personality pointers through `__auth_got`. The unwinder
authenticates them against their slot address, and an image with an
unsigned slot crashes on its first throw.
6. Don't mix arm64 and arm64e objects, in either direction, and keep
`arm64e.x1` objects out of both, as ld64 does. `-arch_errors_fatal` makes
it an error.
7. Don't merge ObjC categories for arm64e. lld warns and skips the merge,
because merging does not yet preserve the signing schemas.
The tests check the decoded fixups (`llvm-objdump --macho --dyld-info`, from
earlier in the stack) against ld64's output for the same inputs, and every
negative check fails without its change.
Known differences from ld64:
- Calls still go through unsigned `__stubs`/`__got`; later PR adds
`__auth_stubs`.
- `-preload`: lld reports an error for authenticated pointers, while ld64
writes them unsigned and warns.
- GOT references to a personality: ld64 sends every GOT reference to a
personality symbol to `__auth_got`; lld sends only the unwinder's.
Compared with #188378: the copy of ARM64.cpp is
gone, `Symbol` does not grow, the relocation addend accessors and the
`llvm-objdump` decoding are separate PRs, and
`__auth_stubs`, the TLV bootstrap bind, -map and the ABI-marker check follow
in the stack.
Assisted-by: Claude
>From 91fcf8d12049bbcce2bf09b63445155bcf3cf525 Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Tue, 6 Oct 2026 15:48:21 -0700
Subject: [PATCH 1/7] [lld-macho] Add an arm64e target
lld accepts -arch arm64e but links it with the arm64 target, so the output
claims to be arm64. Add an ARM64e target derived from ARM64, select it for
-arch arm64e, and mark the CPU subtype as ld64 does: CPU_SUBTYPE_ARM64E
with ptrauth ABI version 1 for macOS and Mac Catalyst executables, and
version 0 for everything else.
Assisted-by: Claude Code
---
lld/MachO/Arch/ARM64.cpp | 10 ++++++++
lld/MachO/Driver.cpp | 2 ++
lld/MachO/SyntheticSections.cpp | 10 ++++++++
lld/MachO/Target.h | 1 +
lld/test/MachO/arm64e-cpu-subtype.s | 37 +++++++++++++++++++++++++++++
5 files changed, 60 insertions(+)
create mode 100644 lld/test/MachO/arm64e-cpu-subtype.s
diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp
index 7acf48cbbf123..513f2a947e7e5 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -42,6 +42,9 @@ struct ARM64 : ARM64Common {
uint32_t getICFSafeThunkSize() const override;
};
+struct ARM64e final : ARM64 {
+ ARM64e();
+};
} // namespace
// Random notes on reloc types:
@@ -236,3 +239,10 @@ TargetInfo *macho::createARM64TargetInfo() {
static ARM64 t;
return &t;
}
+
+ARM64e::ARM64e() { cpuSubtype = CPU_SUBTYPE_ARM64E; }
+
+TargetInfo *macho::createARM64eTargetInfo() {
+ static ARM64e t;
+ return &t;
+}
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 0dfced91c511d..3f1ff64a54ccb 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -984,6 +984,8 @@ static TargetInfo *createTargetInfo(InputArgList &args) {
case CPU_TYPE_X86_64:
return createX86_64TargetInfo();
case CPU_TYPE_ARM64:
+ if (config->arch() == AK_arm64e)
+ return createARM64eTargetInfo();
return createARM64TargetInfo();
case CPU_TYPE_ARM64_32:
return createARM64_32TargetInfo();
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 3629f0df1cd75..6c9a1144bc817 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -103,6 +103,16 @@ static uint32_t cpuSubtype() {
config->platformInfo.target.MinDeployment >= VersionTuple(10, 5))
subtype |= CPU_SUBTYPE_LIB64;
+ if (config->arch() == AK_arm64e) {
+ // ld64 marks macOS and Mac Catalyst executables with ptrauth ABI version 1.
+ bool isMacExecutable = config->outputType == MH_EXECUTE &&
+ (config->platform() == PLATFORM_MACOS ||
+ config->platform() == PLATFORM_MACCATALYST);
+ subtype = CPU_SUBTYPE_ARM64E_WITH_PTRAUTH_VERSION(
+ /*PtrAuthABIVersion=*/isMacExecutable ? 1 : 0,
+ /*PtrAuthKernelABIVersion=*/false);
+ }
+
return subtype;
}
diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h
index 0c7c0c3817c97..d13ee50e3f112 100644
--- a/lld/MachO/Target.h
+++ b/lld/MachO/Target.h
@@ -160,6 +160,7 @@ class TargetInfo {
TargetInfo *createX86_64TargetInfo();
TargetInfo *createARM64TargetInfo();
+TargetInfo *createARM64eTargetInfo();
TargetInfo *createARM64_32TargetInfo();
struct LP64 {
diff --git a/lld/test/MachO/arm64e-cpu-subtype.s b/lld/test/MachO/arm64e-cpu-subtype.s
new file mode 100644
index 0000000000000..6d17d0a4cefb9
--- /dev/null
+++ b/lld/test/MachO/arm64e-cpu-subtype.s
@@ -0,0 +1,37 @@
+# REQUIRES: aarch64
+## As with ld64, the arm64e CPU subtype carries ptrauth ABI version 1 for macOS
+## and Mac Catalyst executables, and version 0 for everything else.
+
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t.o %s
+# DEFINE: %{link} = %no-arg-lld -arch arm64e -fatal_warnings %t.o -o %t.out
+# DEFINE: %{subtype} = llvm-objdump --macho --private-header %t.out
+
+# RUN: %{link} -platform_version macos 13.0 13.0 -execute
+# RUN: %{subtype} | FileCheck %s --check-prefixes=PAC01,EXECUTE
+# RUN: %{link} -platform_version mac-catalyst 16.0 16.0 -execute
+# RUN: %{subtype} | FileCheck %s --check-prefixes=PAC01,EXECUTE
+# RUN: %{link} -platform_version ios 16.0 16.0 -execute
+# RUN: %{subtype} | FileCheck %s --check-prefixes=PAC00,EXECUTE
+
+# RUN: %{link} -platform_version macos 13.0 13.0 -dylib
+# RUN: %{subtype} | FileCheck %s --check-prefixes=PAC00,DYLIB
+# RUN: %{link} -platform_version mac-catalyst 16.0 16.0 -dylib
+# RUN: %{subtype} | FileCheck %s --check-prefixes=PAC00,DYLIB
+# RUN: %{link} -platform_version ios 16.0 16.0 -dylib
+# RUN: %{subtype} | FileCheck %s --check-prefixes=PAC00,DYLIB
+
+# RUN: %{link} -platform_version macos 13.0 13.0 -bundle
+# RUN: %{subtype} | FileCheck %s --check-prefixes=PAC00,BUNDLE
+# RUN: %{link} -platform_version ios 16.0 16.0 -bundle
+# RUN: %{subtype} | FileCheck %s --check-prefixes=PAC00,BUNDLE
+
+# PAC00: MH_MAGIC_64 ARM64 E PAC00
+# PAC01: MH_MAGIC_64 ARM64 E PAC01
+# EXECUTE-SAME: EXECUTE
+# DYLIB-SAME: DYLIB
+# BUNDLE-SAME: BUNDLE
+
+.globl _main
+.p2align 2
+_main:
+ ret
>From 39986084260c101c53477d431890c9febb4619d7 Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Tue, 6 Oct 2026 15:52:06 -0700
Subject: [PATCH 2/7] [lld-macho] Use ld64's chained fixups for arm64e
Authenticated pointers can only be encoded as chained fixups, so like
ld64, use them for every arm64e output: also below the deployment targets
at which lld switches other architectures to chained fixups, and when
-no_fixup_chains is given, which ld64 ignores with a warning while keeping
__mod_init_func and DYLD_CHAINED_PTR_ARM64E.
Write rebases and binds in ld64's pointer formats, with an 8-byte stride:
DYLD_CHAINED_PTR_ARM64E_USERLAND24 from the deployment targets at which
ld64 switches to it, and DYLD_CHAINED_PTR_ARM64E below them. As ld64 does,
and with its messages, reject rebase targets that the format cannot hold,
including targets below the image base, and images with more than 65535
imports in DYLD_CHAINED_PTR_ARM64E, whose binds have 16-bit ordinals.
Assisted-by: Claude Code
---
lld/MachO/Config.h | 2 +
lld/MachO/Driver.cpp | 41 ++++-
lld/MachO/SyntheticSections.cpp | 71 ++++++++-
lld/MachO/Writer.cpp | 23 ++-
lld/test/MachO/arm64e-chained-fixups-high8.s | 68 ++++++++
.../MachO/arm64e-chained-fixups-imports.s | 58 +++++++
lld/test/MachO/arm64e-chained-fixups.s | 146 ++++++++++++++++++
lld/test/MachO/arm64e-stubs.s | 42 +++++
8 files changed, 439 insertions(+), 12 deletions(-)
create mode 100644 lld/test/MachO/arm64e-chained-fixups-high8.s
create mode 100644 lld/test/MachO/arm64e-chained-fixups-imports.s
create mode 100644 lld/test/MachO/arm64e-chained-fixups.s
create mode 100644 lld/test/MachO/arm64e-stubs.s
diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h
index a939d7789a758..ac9f0aac7a91f 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -137,6 +137,8 @@ struct Configuration {
bool emitEncryptionInfo = false;
bool emitInitOffsets = false;
bool emitChainedFixups = false;
+ bool noFixupChainsIgnored = false;
+ uint16_t chainedFixupsPointerFormat = 0;
bool emitRelativeMethodLists = false;
bool thinLTOEmitImportsFiles;
bool thinLTOEmitIndexFiles;
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 3f1ff64a54ccb..9485c0c74fd18 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1278,8 +1278,15 @@ static bool dataConstDefault(const InputArgList &args) {
static bool shouldEmitChainedFixups(const InputArgList &args) {
const Arg *arg = args.getLastArg(OPT_fixup_chains, OPT_no_fixup_chains);
- if (arg && arg->getOption().matches(OPT_no_fixup_chains))
- return false;
+ if (arg && arg->getOption().matches(OPT_no_fixup_chains)) {
+ if (config->arch() != AK_arm64e)
+ return false;
+ // Authenticated pointers can only be encoded as chained fixups.
+ warn("bind opcodes are no longer supported with arm64e, switching to "
+ "chained fixups");
+ config->noFixupChainsIgnored = true;
+ return true;
+ }
bool requested = arg && arg->getOption().matches(OPT_fixup_chains);
if (!config->isPic) {
@@ -1289,7 +1296,8 @@ static bool shouldEmitChainedFixups(const InputArgList &args) {
return false;
}
- if (!is_contained({AK_x86_64, AK_x86_64h, AK_arm64}, config->arch())) {
+ if (!is_contained({AK_x86_64, AK_x86_64h, AK_arm64, AK_arm64e},
+ config->arch())) {
if (requested)
error("-fixup_chains is only supported on x86_64 and arm64 targets");
@@ -1303,7 +1311,7 @@ static bool shouldEmitChainedFixups(const InputArgList &args) {
return false;
}
- if (requested)
+ if (requested || config->arch() == AK_arm64e)
return true;
static const MinVersions<9> minVersion = {{
@@ -1320,6 +1328,27 @@ static bool shouldEmitChainedFixups(const InputArgList &args) {
return greaterEqMinVersion(minVersion, false);
}
+static uint16_t getChainedFixupsPointerFormat() {
+ if (config->arch() != AK_arm64e)
+ return DYLD_CHAINED_PTR_64;
+ // Like ld64, keep the original format if -no_fixup_chains was given.
+ if (config->noFixupChainsIgnored)
+ return DYLD_CHAINED_PTR_ARM64E;
+ // The releases from which arm64e uses DYLD_CHAINED_PTR_ARM64E_USERLAND24.
+ static const MinVersions<7> minVersion = {{
+ {PLATFORM_MACOS, VersionTuple(12, 0)},
+ {PLATFORM_IOS, VersionTuple(15, 0)},
+ {PLATFORM_MACCATALYST, VersionTuple(15, 0)},
+ {PLATFORM_TVOS, VersionTuple(15, 0)},
+ {PLATFORM_WATCHOS, VersionTuple(8, 0)},
+ {PLATFORM_XROS, VersionTuple(1, 0)},
+ {PLATFORM_DRIVERKIT, VersionTuple(21, 0)},
+ }};
+ return greaterEqMinVersion(minVersion, true)
+ ? DYLD_CHAINED_PTR_ARM64E_USERLAND24
+ : DYLD_CHAINED_PTR_ARM64E;
+}
+
static bool shouldEmitRelativeMethodLists(const InputArgList &args) {
const Arg *arg = args.getLastArg(OPT_objc_relative_method_lists,
OPT_no_objc_relative_method_lists);
@@ -2030,8 +2059,10 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
config->emitDataInCodeInfo =
args.hasFlag(OPT_data_in_code_info, OPT_no_data_in_code_info, true);
config->emitChainedFixups = shouldEmitChainedFixups(args);
+ config->chainedFixupsPointerFormat = getChainedFixupsPointerFormat();
config->emitInitOffsets =
- config->emitChainedFixups || args.hasArg(OPT_init_offsets);
+ (config->emitChainedFixups && !config->noFixupChainsIgnored) ||
+ args.hasArg(OPT_init_offsets);
config->emitRelativeMethodLists = shouldEmitRelativeMethodLists(args);
config->icfLevel = getICFLevel(args);
config->keepICFStabs = args.hasArg(OPT_keep_icf_stabs);
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 6c9a1144bc817..8706896f782c1 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -356,9 +356,50 @@ void GotSection::addEntry(Symbol *sym) {
}
}
+// Reports, with ld64's message, a rebase target the pointer format can't hold.
+static void reportUnencodableRebase(const uint8_t *buf, bool isAddress,
+ uint64_t value) {
+ uint64_t fileOff = buf - in.bufferStart;
+ std::string loc = "<unknown>";
+ for (const OutputSegment *seg : outputSegments)
+ if (fileOff >= seg->fileOff && fileOff - seg->fileOff < seg->fileSize)
+ loc = (seg->name + "+0x" + utohexstr(fileOff - seg->fileOff)).str();
+ error(Twine(isAddress ? "vmAddress" : "vmOffset") + " (0x" +
+ utohexstr(value) + ") cannot fit in fixup at " + loc);
+}
+
+static void writeArm64eChainedRebase(uint8_t *buf, uint64_t targetVA) {
+ uint64_t imageBase = in.header->addr;
+ // USERLAND24 rebases hold an offset from the image base, ARM64E rebases an
+ // address.
+ uint64_t targetValue =
+ config->chainedFixupsPointerFormat == DYLD_CHAINED_PTR_ARM64E_USERLAND24
+ ? targetVA - imageBase
+ : targetVA;
+ auto *rebase = reinterpret_cast<dyld_chained_ptr_arm64e_rebase *>(buf);
+ rebase->target = targetValue & 0x7ff'ffff'ffff;
+ rebase->high8 = targetValue >> 56;
+ rebase->next = 0;
+ rebase->bind = 0;
+ rebase->auth = 0;
+ // Reject targets that lose bits in the encoding and, like ld64, targets below
+ // the image base.
+ uint64_t encodedValue = rebase->target | (uint64_t(rebase->high8) << 56);
+ uint64_t untaggedTarget = targetVA & ((uint64_t(1) << 56) - 1);
+ if (encodedValue != targetValue || untaggedTarget < imageBase)
+ reportUnencodableRebase(buf,
+ config->chainedFixupsPointerFormat !=
+ DYLD_CHAINED_PTR_ARM64E_USERLAND24,
+ targetValue);
+}
+
void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA) {
assert(config->emitChainedFixups);
assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
+ if (config->arch() == AK_arm64e) {
+ writeArm64eChainedRebase(buf, targetVA);
+ return;
+ }
auto *rebase = reinterpret_cast<dyld_chained_ptr_64_rebase *>(buf);
rebase->target = targetVA & 0xf'ffff'ffff;
rebase->high8 = (targetVA >> 56);
@@ -374,9 +415,31 @@ void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA) {
" does not fit into chained fixup. Re-link with -no_fixup_chains");
}
+// Bind is dyld_chained_ptr_arm64e_bind or dyld_chained_ptr_arm64e_bind24.
+template <class Bind>
+static void writeArm64eChainedBind(uint8_t *buf, const Symbol *sym,
+ int64_t addend) {
+ auto *bind = reinterpret_cast<Bind *>(buf);
+ auto [ordinal, inlineAddend] = in.chainedFixups->getBinding(sym, addend);
+ bind->ordinal = ordinal;
+ bind->zero = 0;
+ bind->addend = inlineAddend;
+ bind->next = 0;
+ bind->bind = 1;
+ bind->auth = 0;
+}
+
static void writeChainedBind(uint8_t *buf, const Symbol *sym, int64_t addend) {
assert(config->emitChainedFixups);
assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
+ if (config->arch() == AK_arm64e) {
+ if (config->chainedFixupsPointerFormat ==
+ DYLD_CHAINED_PTR_ARM64E_USERLAND24)
+ writeArm64eChainedBind<dyld_chained_ptr_arm64e_bind24>(buf, sym, addend);
+ else
+ writeArm64eChainedBind<dyld_chained_ptr_arm64e_bind>(buf, sym, addend);
+ return;
+ }
auto *bind = reinterpret_cast<dyld_chained_ptr_64_bind *>(buf);
auto [ordinal, inlineAddend] = in.chainedFixups->getBinding(sym, addend);
bind->ordinal = ordinal;
@@ -2443,7 +2506,7 @@ size_t ChainedFixupsSection::SegmentInfo::writeTo(uint8_t *buf) const {
segInfo->size = getSize();
segInfo->page_size = target->getPageSize();
// FIXME: Use DYLD_CHAINED_PTR_64_OFFSET on newer OS versions.
- segInfo->pointer_format = DYLD_CHAINED_PTR_64;
+ segInfo->pointer_format = config->chainedFixupsPointerFormat;
segInfo->segment_offset = oseg->addr - in.header->addr;
segInfo->max_valid_pointer = 0; // not used on 64-bit
segInfo->page_count = pageStarts.back().first + 1;
@@ -2545,6 +2608,12 @@ void ChainedFixupsSection::finalizeContents() {
error("cannot encode chained fixups: imported symbols table size " +
Twine(symtabSize) + " exceeds 4 GiB");
+ // DYLD_CHAINED_PTR_ARM64E binds have 16-bit import ordinals.
+ if (config->chainedFixupsPointerFormat == DYLD_CHAINED_PTR_ARM64E &&
+ bindings.size() > UINT16_MAX)
+ error("chained fixups, imports_count (" + Twine(bindings.size()) +
+ ") exceeds max of 65535");
+
bool needsLargeOrdinal = any_of(bindings, [](const auto &p) {
// 0xF1 - 0xFF are reserved for special ordinals in the 8-bit encoding.
return ordinalForSymbol(*p.first.first) > 0xF0;
diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp
index d9fb7a231b97f..926cbd94d5d07 100644
--- a/lld/MachO/Writer.cpp
+++ b/lld/MachO/Writer.cpp
@@ -1308,7 +1308,8 @@ void Writer::buildFixupChains() {
TimeTraceScope timeScope("Build fixup chains");
const uint64_t pageSize = target->getPageSize();
- constexpr uint32_t stride = 4; // for DYLD_CHAINED_PTR_64
+ // All ARM64E userland formats use 8-byte stride; DYLD_CHAINED_PTR_64 uses 4.
+ const uint32_t stride = config->arch() == AK_arm64e ? 8 : 4;
for (size_t i = 0, count = loc.size(); i < count;) {
const OutputSegment *oseg = loc[i].isec->parent->parent;
@@ -1329,14 +1330,24 @@ void Writer::buildFixupChains() {
if (offset < target->wordSize)
return fail("fixups overlap");
+ // arm64e ignores -no_fixup_chains, so don't suggest it there.
if (offset % stride != 0)
- return fail(
- "fixups are unaligned (offset " + Twine(offset) +
- " is not a multiple of the stride). Re-link with -no_fixup_chains");
+ return fail("fixups are unaligned (offset " + Twine(offset) +
+ " is not a multiple of the stride)" +
+ (config->arch() == AK_arm64e
+ ? ""
+ : ". Re-link with -no_fixup_chains"));
// The "next" field is in the same location for bind and rebase entries.
- reinterpret_cast<dyld_chained_ptr_64_bind *>(buf + loc[i - 1].offset)
- ->next = offset / stride;
+ uint8_t *prev = buf + loc[i - 1].offset;
+ if (config->arch() == AK_arm64e) {
+ auto *entry =
+ reinterpret_cast<dyld_chained_ptr_arm64e_auth_bind *>(prev);
+ entry->next = offset / stride;
+ } else {
+ auto *entry = reinterpret_cast<dyld_chained_ptr_64_bind *>(prev);
+ entry->next = offset / stride;
+ }
++i;
}
}
diff --git a/lld/test/MachO/arm64e-chained-fixups-high8.s b/lld/test/MachO/arm64e-chained-fixups-high8.s
new file mode 100644
index 0000000000000..a9c1b67fbed95
--- /dev/null
+++ b/lld/test/MachO/arm64e-chained-fixups-high8.s
@@ -0,0 +1,68 @@
+# REQUIRES: aarch64
+## The top byte of a rebased pointer goes in the high8 field. In an executable,
+## DYLD_CHAINED_PTR_ARM64E_USERLAND24 stores the rest of the target as an
+## offset from the image base, and DYLD_CHAINED_PTR_ARM64E as an address. A
+## tagged target below the image base is rejected in both formats, as ld64
+## does, even though DYLD_CHAINED_PTR_ARM64E could encode its address.
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/tagged.o %t/tagged.s
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/below.o %t/below.s
+
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 %t/tagged.o \
+# RUN: -o %t/userland24
+# RUN: llvm-objdump --macho --syms --chained-fixups %t/userland24 > %t/userland24.txt
+# RUN: llvm-objdump --macho --dyld-info %t/userland24 >> %t/userland24.txt
+# RUN: FileCheck %s --check-prefix=USERLAND24 -D#%x,BASE=0x100000000 \
+# RUN: < %t/userland24.txt
+
+# RUN: %lld -arch arm64e -platform_version macos 11.0 11.0 %t/tagged.o \
+# RUN: -o %t/arm64e
+# RUN: llvm-objdump --macho --syms --chained-fixups %t/arm64e > %t/arm64e.txt
+# RUN: llvm-objdump --macho --dyld-info %t/arm64e >> %t/arm64e.txt
+# RUN: FileCheck %s --check-prefix=ARM64E -D#%x,BASE=0x100000000 \
+# RUN: < %t/arm64e.txt
+
+# RUN: not %lld -arch arm64e -platform_version macos 13.0 13.0 %t/below.o \
+# RUN: -o /dev/null 2>&1 | FileCheck %s --check-prefix=BELOW
+# RUN: not %lld -arch arm64e -platform_version macos 11.0 11.0 %t/below.o \
+# RUN: -o /dev/null 2>&1 | FileCheck %s --check-prefix=BELOW-ARM64E
+
+## `_target + 0x1200000000000000`: target:43 is _target's offset from the image
+## base (USERLAND24) or its address (ARM64E), and high8:8 is 0x12.
+# USERLAND24: pointer_format = 12 (DYLD_CHAINED_PTR_ARM64E_USERLAND24)
+# USERLAND24: [[#%x,TARGET:]] l O __DATA,__data _target
+# USERLAND24: dyld information:
+# USERLAND24: __DATA __data 0x[[#%X,TARGET+8]] 0x[[#%.16X,0x900000000000+TARGET-BASE]] rebase 0x[[#%X,TARGET+0x1200000000000000]]
+
+# ARM64E: pointer_format = 1 (DYLD_CHAINED_PTR_ARM64E)
+# ARM64E: [[#%x,TARGET:]] l O __DATA,__data _target
+# ARM64E: dyld information:
+# ARM64E: __DATA __data 0x[[#%X,TARGET+8]] 0x[[#%.16X,0x900000000000+TARGET]] rebase 0x[[#%X,TARGET+0x1200000000000000]]
+
+# BELOW: error: vmOffset (0x11FFFFFF{{[0-9A-F]+}}) cannot fit in fixup at __DATA+0x0
+# BELOW-ARM64E: error: vmAddress (0x12000000{{[0-9A-F]+}}) cannot fit in fixup at __DATA+0x0
+
+#--- tagged.s
+.globl _main
+.p2align 2
+_main:
+ ret
+
+.data
+.p2align 3
+_target:
+ .quad 0
+_tagged:
+ .quad _target + 0x1200000000000000
+
+#--- below.s
+.globl _main
+.p2align 2
+_main:
+ ret
+
+.data
+.p2align 3
+_below:
+ .quad _main + 0x11ffffff00000000
diff --git a/lld/test/MachO/arm64e-chained-fixups-imports.s b/lld/test/MachO/arm64e-chained-fixups-imports.s
new file mode 100644
index 0000000000000..bd8bac6caf143
--- /dev/null
+++ b/lld/test/MachO/arm64e-chained-fixups-imports.s
@@ -0,0 +1,58 @@
+# REQUIRES: aarch64
+## DYLD_CHAINED_PTR_ARM64E binds have 16-bit import ordinals, so an image in
+## that format can have at most 65535 imports, while the 24-bit ordinals of
+## DYLD_CHAINED_PTR_ARM64E_USERLAND24 have room for more.
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/lib.o %t/lib.s
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/max.o %t/max.s
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/over.o %t/over.s
+# RUN: %lld -arch arm64e -platform_version macos 11.0 11.0 -dylib \
+# RUN: -install_name @executable_path/libmany.dylib %t/lib.o -o %t/libmany.dylib
+
+# RUN: %lld -arch arm64e -platform_version macos 11.0 11.0 -dylib %t/max.o \
+# RUN: %t/libmany.dylib -o %t/max.dylib
+# RUN: llvm-objdump --macho --chained-fixups %t/max.dylib | \
+# RUN: FileCheck %s --check-prefix=MAX
+# RUN: not %lld -arch arm64e -platform_version macos 11.0 11.0 -dylib %t/over.o \
+# RUN: %t/libmany.dylib -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib %t/over.o \
+# RUN: %t/libmany.dylib -o %t/userland24.dylib
+# RUN: llvm-objdump --macho --chained-fixups %t/userland24.dylib | \
+# RUN: FileCheck %s --check-prefix=USERLAND24
+
+# MAX: imports_count = 65535
+# MAX: pointer_format = 1 (DYLD_CHAINED_PTR_ARM64E)
+# ERR: error: chained fixups, imports_count (65536) exceeds max of 65535
+# USERLAND24: imports_count = 65536
+# USERLAND24: pointer_format = 12 (DYLD_CHAINED_PTR_ARM64E_USERLAND24)
+
+#--- lib.s
+.macro def
+.globl _s\@
+_s\@:
+.endm
+.rept 65536
+def
+.endr
+ ret
+
+#--- max.s
+.macro ref
+.quad _s\@
+.endm
+.data
+.p2align 3
+.rept 65535
+ref
+.endr
+
+#--- over.s
+.macro ref
+.quad _s\@
+.endm
+.data
+.p2align 3
+.rept 65536
+ref
+.endr
diff --git a/lld/test/MachO/arm64e-chained-fixups.s b/lld/test/MachO/arm64e-chained-fixups.s
new file mode 100644
index 0000000000000..6fcc565804041
--- /dev/null
+++ b/lld/test/MachO/arm64e-chained-fixups.s
@@ -0,0 +1,146 @@
+# REQUIRES: aarch64
+## Authenticated pointers can only be encoded as chained fixups, so arm64e
+## output always uses them: also below the deployment targets at which lld
+## switches other architectures to chained fixups, and when -no_fixup_chains is
+## given. In the latter case lld warns and, like ld64, uses
+## DYLD_CHAINED_PTR_ARM64E even where DYLD_CHAINED_PTR_ARM64E_USERLAND24 is the
+## default, and keeps __mod_init_func.
+
+# 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: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/arm64.o %t/arm64.s
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/local.o %t/local.s
+# RUN: %lld -arch arm64e -platform_version macos 11.0 11.0 -dylib \
+# RUN: -install_name @executable_path/libfoo.dylib %t/foo.o -o %t/libfoo.dylib
+
+## macOS 11.0 is below the deployment target at which lld uses chained fixups
+## for arm64.
+# RUN: %lld -arch arm64e -platform_version macos 11.0 11.0 -dylib %t/test.o \
+# RUN: %t/libfoo.dylib -o %t/macos11.dylib
+# RUN: llvm-objdump --macho --all-headers %t/macos11.dylib | \
+# RUN: FileCheck %s --check-prefixes=INIT-OFFSETS,CHAINED \
+# RUN: --implicit-check-not=LC_DYLD_INFO --implicit-check-not=__mod_init_func
+# RUN: llvm-objdump --macho --chained-fixups %t/macos11.dylib | \
+# RUN: FileCheck %s --check-prefix=ARM64E
+# RUN: llvm-objdump --macho --dyld-info %t/macos11.dylib | \
+# RUN: FileCheck %s --check-prefix=PTRS
+
+# RUN: %no-fatal-warnings-lld -arch arm64e -platform_version macos 13.0 13.0 \
+# RUN: -dylib -no_fixup_chains %t/test.o %t/libfoo.dylib \
+# RUN: -o %t/no-fixup-chains.dylib 2>&1 | FileCheck %s --check-prefix=WARN
+# RUN: llvm-objdump --macho --all-headers %t/no-fixup-chains.dylib | \
+# RUN: FileCheck %s --check-prefixes=MOD-INIT,CHAINED \
+# RUN: --implicit-check-not=LC_DYLD_INFO --implicit-check-not=__init_offsets
+# RUN: llvm-objdump --macho --chained-fixups %t/no-fixup-chains.dylib | \
+# RUN: FileCheck %s --check-prefix=ARM64E
+# RUN: llvm-objdump --macho --dyld-info %t/no-fixup-chains.dylib | \
+# RUN: FileCheck %s --check-prefix=PTRS
+
+## arm64e chains pointers with an 8-byte stride; don't suggest the ignored
+## -no_fixup_chains when two pointers are 4 bytes out of step.
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/unaligned.o \
+# RUN: %t/unaligned.s
+# RUN: not %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib \
+# RUN: %t/unaligned.o -o /dev/null 2>&1 | FileCheck %s \
+# RUN: --check-prefix=UNALIGNED --implicit-check-not=-no_fixup_chains
+
+## arm64 still honors -no_fixup_chains.
+# RUN: %lld -arch arm64 -platform_version macos 13.0 13.0 -dylib \
+# RUN: -no_fixup_chains %t/arm64.o -o %t/arm64.dylib
+# RUN: llvm-objdump --macho --all-headers %t/arm64.dylib | \
+# RUN: FileCheck %s --check-prefix=OPCODES --implicit-check-not=LC_DYLD_CHAINED
+
+## The releases from which arm64e uses DYLD_CHAINED_PTR_ARM64E_USERLAND24.
+# DEFINE: %{link} = %no-arg-lld -arch arm64e -fatal_warnings -dylib \
+# DEFINE: %t/local.o -o %t/format.dylib
+# DEFINE: %{format} = llvm-objdump --macho --chained-fixups %t/format.dylib
+# RUN: %{link} -platform_version macos 11.0 11.0
+# RUN: %{format} | FileCheck %s --check-prefix=ARM64E
+# RUN: %{link} -platform_version macos 12.0 12.0
+# RUN: %{format} | FileCheck %s --check-prefix=USERLAND24
+# RUN: %{link} -platform_version mac-catalyst 14.0 14.0
+# RUN: %{format} | FileCheck %s --check-prefix=ARM64E
+# RUN: %{link} -platform_version mac-catalyst 15.0 15.0
+# RUN: %{format} | FileCheck %s --check-prefix=USERLAND24
+# RUN: %{link} -platform_version ios 14.0 14.0
+# RUN: %{format} | FileCheck %s --check-prefix=ARM64E
+# RUN: %{link} -platform_version ios 15.0 15.0
+# RUN: %{format} | FileCheck %s --check-prefix=USERLAND24
+# RUN: %{link} -platform_version ios-simulator 16.0 16.0
+# RUN: %{format} | FileCheck %s --check-prefix=USERLAND24
+# RUN: %{link} -platform_version tvos 14.0 14.0
+# RUN: %{format} | FileCheck %s --check-prefix=ARM64E
+# RUN: %{link} -platform_version tvos 15.0 15.0
+# RUN: %{format} | FileCheck %s --check-prefix=USERLAND24
+# RUN: %{link} -platform_version watchos 7.0 7.0
+# RUN: %{format} | FileCheck %s --check-prefix=ARM64E
+# RUN: %{link} -platform_version watchos 8.0 8.0
+# RUN: %{format} | FileCheck %s --check-prefix=USERLAND24
+# RUN: %{link} -platform_version xros 1.0 1.0
+# RUN: %{format} | FileCheck %s --check-prefix=USERLAND24
+# RUN: %{link} -platform_version driverkit 20.0 20.0
+# RUN: %{format} | FileCheck %s --check-prefix=ARM64E
+# RUN: %{link} -platform_version driverkit 21.0 21.0
+# RUN: %{format} | FileCheck %s --check-prefix=USERLAND24
+
+# WARN: warning: bind opcodes are no longer supported with arm64e, switching to chained fixups
+# UNALIGNED: error: __DATA,__data, offset 12: fixups are unaligned (offset 12 is not a multiple of the stride)
+
+# INIT-OFFSETS: sectname __init_offsets
+# MOD-INIT: sectname __mod_init_func
+# CHAINED: cmd LC_DYLD_CHAINED_FIXUPS
+# OPCODES: cmd LC_DYLD_INFO_ONLY
+
+# ARM64E: pointer_format = 1 (DYLD_CHAINED_PTR_ARM64E)
+# USERLAND24: pointer_format = 12 (DYLD_CHAINED_PTR_ARM64E_USERLAND24)
+
+## _local_ptr is a rebase, and _foo_ptr a bind of _foo.
+# PTRS: __DATA __data 0x{{[0-9A-F]+}} 0x{{[0-9A-F]+}} rebase
+# PTRS-NEXT: __DATA __data 0x{{[0-9A-F]+}} 0x{{[0-9A-F]+}} bind 0x0 libfoo _foo
+
+#--- foo.s
+.globl _foo
+_foo:
+ ret
+
+#--- test.s
+.p2align 2
+_init:
+ ret
+
+.globl _local
+_local:
+ ret
+
+.data
+.p2align 3
+.globl _local_ptr, _foo_ptr
+_local_ptr:
+ .quad _local
+_foo_ptr:
+ .quad _foo
+
+.section __DATA,__mod_init_func,mod_init_funcs
+.p2align 3
+ .quad _init
+
+#--- arm64.s
+.data
+.p2align 3
+_ptr:
+ .quad _ptr
+
+#--- local.s
+.data
+.p2align 3
+_ptr:
+ .quad _ptr
+
+#--- unaligned.s
+.data
+.p2align 3
+_a:
+ .quad _a
+ .long 0
+ .quad _a
diff --git a/lld/test/MachO/arm64e-stubs.s b/lld/test/MachO/arm64e-stubs.s
new file mode 100644
index 0000000000000..4ed21ab9e0d6f
--- /dev/null
+++ b/lld/test/MachO/arm64e-stubs.s
@@ -0,0 +1,42 @@
+# REQUIRES: aarch64
+## On arm64e, calls to dylib functions go through __stubs, which load the
+## target from an unsigned __got slot, as on arm64.
+
+# 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 -o %t/test
+# RUN: llvm-objdump --macho --section-headers %t/test > %t/out
+# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn \
+# RUN: --section=__TEXT,__stubs %t/test >> %t/out
+# RUN: llvm-objdump --macho --dyld-info %t/test >> %t/out
+# RUN: FileCheck %s < %t/out --implicit-check-not=__auth
+
+# CHECK: __stubs
+# CHECK-NEXT: __got {{[0-9a-f]+}} {{0*}}[[#%x,GOT:]] DATA
+
+# CHECK-LABEL: _main:
+# CHECK-NEXT: bl {{.*}} ; symbol stub for: _foo
+# CHECK-LABEL: Contents of (__TEXT,__stubs) section
+# CHECK-NEXT: adrp x16, {{.*}} ; 0x[[#GOT]]
+# CHECK-NEXT: ldr x16, [x16] ; literal pool symbol address: _foo
+# CHECK-NEXT: br x16
+
+## The __got slot is an unauthenticated bind of _foo.
+# CHECK-LABEL: dyld information:
+# CHECK: __DATA_CONST __got 0x[[#%X,GOT]] 0x4000000000000000 bind 0x0 libfoo _foo
+
+#--- foo.s
+.globl _foo
+_foo:
+ ret
+
+#--- test.s
+.globl _main
+.p2align 2
+_main:
+ bl _foo
+ ret
>From 1036729281ac34e48f67a3334f1d1f7a352f1d84 Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Tue, 6 Oct 2026 15:55:07 -0700
Subject: [PATCH 3/7] [lld-macho] Link arm64e authenticated pointers
Parse ARM64_RELOC_AUTHENTICATED_POINTER, which keeps the pointer's signing
schema (key, diversity and address diversity) in its upper half, and write
it as an authenticated chained rebase or bind. Relocation stores the schema
next to a 32-bit addend, so its size stays 24 bytes.
As in ld64, accept only the extern form of the relocation and only a payload
with the authentication bit set and the reserved bits clear, store the
target of an authenticated rebase as an offset of at most 4 GiB from the
image base, put the addends of authenticated binds, which have no addend
field, in the imports table, and keep authenticated pointers in
__thread_vars signed pointers rather than offsets into the thread-local
data.
Assisted-by: Claude Code
---
lld/MachO/Arch/ARM64.cpp | 12 +-
lld/MachO/Arch/ARM64Common.cpp | 5 +-
lld/MachO/InputFiles.cpp | 29 ++++
lld/MachO/InputSection.cpp | 21 ++-
lld/MachO/Relocations.h | 56 ++++++-
lld/MachO/SyntheticSections.cpp | 77 +++++++--
lld/MachO/SyntheticSections.h | 13 +-
lld/MachO/Writer.cpp | 9 +-
lld/MachO/Writer.h | 3 +-
.../MachO/arm64e-auth-pointer-invalid.yaml | 153 ++++++++++++++++++
lld/test/MachO/arm64e-auth-pointer.s | 104 ++++++++++++
lld/test/MachO/arm64e-auth-reloc-local.s | 117 ++++++++++++++
lld/test/MachO/arm64e-chained-fixups.s | 18 ++-
lld/test/MachO/arm64e-tlv-auth.s | 42 +++++
14 files changed, 612 insertions(+), 47 deletions(-)
create mode 100644 lld/test/MachO/arm64e-auth-pointer-invalid.yaml
create mode 100644 lld/test/MachO/arm64e-auth-pointer.s
create mode 100644 lld/test/MachO/arm64e-auth-reloc-local.s
create mode 100644 lld/test/MachO/arm64e-tlv-auth.s
diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp
index 513f2a947e7e5..76b549981c4d0 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -53,7 +53,7 @@ struct ARM64e final : ARM64 {
// absolute version of this relocation. The semantics of the absolute relocation
// are weird -- it results in the value of the GOT slot being written, instead
// of the address. Let's not support it unless we find a real-world use case.
-static constexpr std::array<RelocAttrs, 11> relocAttrsArray{{
+static constexpr std::array<RelocAttrs, 12> relocAttrsArray{{
#define B(x) RelocAttrBits::x
{"UNSIGNED",
B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4) | B(BYTE8)},
@@ -69,6 +69,9 @@ static constexpr std::array<RelocAttrs, 11> relocAttrsArray{{
{"TLVP_LOAD_PAGEOFF12",
B(ABSOLUTE) | B(EXTERN) | B(TLV) | B(LOAD) | B(BYTE4)},
{"ADDEND", B(ADDEND)},
+ // arm64e only.
+ {"AUTHENTICATED_POINTER",
+ B(ABSOLUTE) | B(UNSIGNED) | B(EXTERN) | B(BYTE8) | B(AUTH)},
#undef B
}};
@@ -232,7 +235,7 @@ ARM64::ARM64() : ARM64Common(LP64()) {
stubHelperHeaderSize = sizeof(stubHelperHeaderCode);
stubHelperEntrySize = sizeof(stubHelperEntryCode);
- relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
+ relocAttrs = ArrayRef(relocAttrsArray).drop_back();
}
TargetInfo *macho::createARM64TargetInfo() {
@@ -240,7 +243,10 @@ TargetInfo *macho::createARM64TargetInfo() {
return &t;
}
-ARM64e::ARM64e() { cpuSubtype = CPU_SUBTYPE_ARM64E; }
+ARM64e::ARM64e() {
+ cpuSubtype = CPU_SUBTYPE_ARM64E;
+ relocAttrs = relocAttrsArray;
+}
TargetInfo *macho::createARM64eTargetInfo() {
static ARM64e t;
diff --git a/lld/MachO/Arch/ARM64Common.cpp b/lld/MachO/Arch/ARM64Common.cpp
index 599f1e18efda6..f99fd7f75fd81 100644
--- a/lld/MachO/Arch/ARM64Common.cpp
+++ b/lld/MachO/Arch/ARM64Common.cpp
@@ -19,7 +19,8 @@ using namespace lld::macho;
int64_t ARM64Common::getEmbeddedAddend(MemoryBufferRef mb, uint64_t offset,
const relocation_info rel) const {
if (rel.r_type != ARM64_RELOC_UNSIGNED &&
- rel.r_type != ARM64_RELOC_SUBTRACTOR) {
+ rel.r_type != ARM64_RELOC_SUBTRACTOR &&
+ rel.r_type != ARM64_RELOC_AUTHENTICATED_POINTER) {
// All other reloc types should use the ADDEND relocation to store their
// addends.
// TODO(gkm): extract embedded addend just so we can assert that it is 0
@@ -28,6 +29,8 @@ int64_t ARM64Common::getEmbeddedAddend(MemoryBufferRef mb, uint64_t offset,
const auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
const uint8_t *loc = buf + offset + rel.r_address;
+ if (rel.r_type == ARM64_RELOC_AUTHENTICATED_POINTER)
+ return static_cast<int32_t>(read32le(loc));
switch (rel.r_length) {
case 2:
return static_cast<int32_t>(read32le(loc));
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 19850aaaec5f0..f6aabe39bf135 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -505,6 +505,18 @@ static Defined *findSymbolAtOffset(const ConcatInputSection *isec,
return d;
}
+namespace {
+// The in-object form of the target of an ARM64_RELOC_AUTHENTICATED_POINTER.
+struct AuthEmbeddedPointer {
+ uint64_t addend : 32;
+ uint64_t diversity : 16;
+ uint64_t addrDiv : 1;
+ uint64_t key : 2;
+ uint64_t reserved : 12;
+ uint64_t auth : 1;
+};
+} // namespace
+
template <class SectionHeader>
static bool validateRelocationInfo(InputFile *file, const SectionHeader &sec,
relocation_info rel) {
@@ -530,6 +542,14 @@ static bool validateRelocationInfo(InputFile *file, const SectionHeader &sec,
error(message("has invalid width of " + std::to_string(1 << rel.r_length) +
" bytes"));
}
+ if (valid && relocAttrs.hasAttr(RelocAttrBits::AUTH)) {
+ auto ptr = bit_cast<AuthEmbeddedPointer>(
+ read64le(file->mb.getBufferStart() + sec.offset + rel.r_address));
+ if (!ptr.auth)
+ error(message("requires bit 63 to be set"));
+ else if (ptr.reserved)
+ error(message("requires bits 51..62 to be zero"));
+ }
return valid;
}
@@ -589,6 +609,15 @@ void ObjFile::parseRelocations(ArrayRef<SectionHeader> sectionHeaders,
r.pcrel = relInfo.r_pcrel;
r.length = relInfo.r_length;
r.offset = relInfo.r_address;
+
+ if (target->hasAttr(relInfo.r_type, RelocAttrBits::AUTH)) {
+ auto ptr = bit_cast<AuthEmbeddedPointer>(
+ read64le(buf + sec.offset + relInfo.r_address));
+ r.setAuthInfo({static_cast<uint16_t>(ptr.diversity),
+ static_cast<PtrAuthKey>(ptr.key),
+ static_cast<bool>(ptr.addrDiv)});
+ }
+
if (relInfo.r_extern) {
r.referent = symbols[relInfo.r_symbolnum];
r.setAddend(isSubtrahend ? 0 : totalAddend);
diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index ce9fdda29cc95..f60d590c12af6 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -260,14 +260,15 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
}
referentVA = resolveSymbolOffsetVA(referentSym, r.type, r.getAddend());
- if (isThreadLocalVariables(getFlags()) && isa<Defined>(referentSym)) {
- // References from thread-local variable sections are treated as offsets
- // relative to the start of the thread-local data memory area, which
- // is initialized via copying all the TLV data sections (which are all
- // contiguous).
+ // References from thread-local variable sections are treated as offsets
+ // relative to the start of the thread-local data memory area, which
+ // is initialized via copying all the TLV data sections (which are all
+ // contiguous). Authenticated pointers are pointers, not offsets.
+ if (isThreadLocalVariables(getFlags()) && isa<Defined>(referentSym) &&
+ !target->hasAttr(r.type, RelocAttrBits::AUTH)) {
referentVA -= firstTLVDataSection->addr;
} else if (needsFixup) {
- writeChainedFixup(loc, referentSym, r.getAddend());
+ writeChainedFixup(loc, referentSym, r.getAddend(), r.getAuthInfo());
continue;
}
} else if (auto *referentIsec = r.referent.dyn_cast<InputSection *>()) {
@@ -275,10 +276,16 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
referentVA = referentIsec->getVA(r.getAddend());
if (needsFixup) {
- writeChainedRebase(loc, referentVA);
+ writeChainedRebase(loc, referentVA, r.getAuthInfo());
continue;
}
}
+ // Only a chained fixup can encode an authenticated pointer.
+ if (target->hasAttr(r.type, RelocAttrBits::AUTH)) {
+ error(getLocation(r.offset) +
+ ": authenticated pointer requires chained fixups");
+ continue;
+ }
target->relocateOne(loc, r, referentVA, getVA() + r.offset);
}
}
diff --git a/lld/MachO/Relocations.h b/lld/MachO/Relocations.h
index 675e10b3a487b..a46ae7d06d73c 100644
--- a/lld/MachO/Relocations.h
+++ b/lld/MachO/Relocations.h
@@ -41,7 +41,8 @@ enum class RelocAttrBits {
LOAD = 1 << 13, // Relaxable indirect load
POINTER = 1 << 14, // Non-relaxable indirect load (pointer is taken)
UNSIGNED = 1 << 15, // *_UNSIGNED relocs
- LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue*/ (1 << 16) - 1),
+ AUTH = 1 << 16, // *_AUTHENTICATED_POINTER relocs
+ LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue*/ (1 << 17) - 1),
};
// Note: SUBTRACTOR always pairs with UNSIGNED (a delta between two symbols).
@@ -51,11 +52,40 @@ struct RelocAttrs {
bool hasAttr(RelocAttrBits b) const { return (bits & b) == b; }
};
+// Matches ptrauth_key in <ptrauth.h>.
+enum PtrAuthKey : uint8_t {
+ PtrAuthKeyIA,
+ PtrAuthKeyIB,
+ PtrAuthKeyDA,
+ PtrAuthKeyDB
+};
+
+// The signing schema of an authenticated pointer.
+struct AuthInfo {
+ uint16_t diversity;
+ PtrAuthKey key;
+ bool addrDiv;
+
+ bool operator==(const AuthInfo &other) const {
+ return diversity == other.diversity && key == other.key &&
+ addrDiv == other.addrDiv;
+ }
+ bool operator!=(const AuthInfo &other) const { return !(*this == other); }
+};
+
+struct AuthReloc {
+ int32_t addend;
+ AuthInfo info;
+};
+static_assert(sizeof(AuthReloc) == sizeof(int64_t),
+ "AuthReloc must match int64_t size");
+
class Relocation {
public:
uint8_t type = llvm::MachO::GENERIC_RELOC_INVALID;
bool pcrel = false;
uint8_t length = 0;
+ bool hasAuth = false;
// The offset from the start of the subsection that this relocation belongs
// to.
uint32_t offset = 0;
@@ -69,8 +99,23 @@ class Relocation {
: type(type), pcrel(pcrel), length(length), offset(offset),
referent(referent), addend(addend) {}
- int64_t getAddend() const { return addend; }
- void setAddend(int64_t value) { addend = value; }
+ const AuthInfo *getAuthInfo() const {
+ return hasAuth ? &authData.info : nullptr;
+ }
+ void setAuthInfo(AuthInfo info) {
+ assert(!hasAuth);
+ authData = {static_cast<int32_t>(addend), info};
+ hasAuth = true;
+ }
+ int64_t getAddend() const {
+ return hasAuth ? static_cast<int64_t>(authData.addend) : addend;
+ }
+ void setAddend(int64_t a) {
+ if (hasAuth)
+ authData.addend = static_cast<int32_t>(a);
+ else
+ addend = a;
+ }
InputSection *getReferentInputSection() const;
@@ -81,7 +126,10 @@ class Relocation {
private:
// Adding this offset to the address of the referent symbol or subsection
// gives the destination that this relocation refers to.
- int64_t addend = 0;
+ union {
+ int64_t addend = 0;
+ AuthReloc authData; // Valid when hasAuth.
+ };
};
bool validateSymbolRelocation(const Symbol *, const InputSection *,
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 8706896f782c1..2b76bb7f04138 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -318,10 +318,10 @@ GotSection::GotSection()
void macho::addNonLazyBindingEntries(const Symbol *sym,
const InputSection *isec, uint64_t offset,
- int64_t addend) {
+ int64_t addend, bool forceOutlineAuth) {
if (config->emitChainedFixups) {
if (needsBinding(sym))
- in.chainedFixups->addBinding(sym, isec, offset, addend);
+ in.chainedFixups->addBinding(sym, isec, offset, addend, forceOutlineAuth);
else if (isa<Defined>(sym))
in.chainedFixups->addRebase(isec, offset);
else
@@ -368,8 +368,24 @@ static void reportUnencodableRebase(const uint8_t *buf, bool isAddress,
utohexstr(value) + ") cannot fit in fixup at " + loc);
}
-static void writeArm64eChainedRebase(uint8_t *buf, uint64_t targetVA) {
+static void writeArm64eChainedRebase(uint8_t *buf, uint64_t targetVA,
+ const AuthInfo *auth) {
uint64_t imageBase = in.header->addr;
+ if (auth) {
+ auto *rebase = reinterpret_cast<dyld_chained_ptr_arm64e_auth_rebase *>(buf);
+ uint64_t runtimeOffset = targetVA - imageBase;
+ if (!isUInt<32>(runtimeOffset))
+ reportUnencodableRebase(buf, /*isAddress=*/false, runtimeOffset);
+ rebase->target = runtimeOffset;
+ rebase->diversity = auth->diversity;
+ rebase->addrDiv = auth->addrDiv;
+ rebase->key = auth->key;
+ rebase->next = 0;
+ rebase->bind = 0;
+ rebase->auth = 1;
+ return;
+ }
+
// USERLAND24 rebases hold an offset from the image base, ARM64E rebases an
// address.
uint64_t targetValue =
@@ -393,11 +409,12 @@ static void writeArm64eChainedRebase(uint8_t *buf, uint64_t targetVA) {
targetValue);
}
-void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA) {
+void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA,
+ const AuthInfo *auth) {
assert(config->emitChainedFixups);
assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
if (config->arch() == AK_arm64e) {
- writeArm64eChainedRebase(buf, targetVA);
+ writeArm64eChainedRebase(buf, targetVA, auth);
return;
}
auto *rebase = reinterpret_cast<dyld_chained_ptr_64_rebase *>(buf);
@@ -429,12 +446,38 @@ static void writeArm64eChainedBind(uint8_t *buf, const Symbol *sym,
bind->auth = 0;
}
-static void writeChainedBind(uint8_t *buf, const Symbol *sym, int64_t addend) {
+// AuthBind is dyld_chained_ptr_arm64e_auth_bind or
+// dyld_chained_ptr_arm64e_auth_bind24, neither of which has room for an
+// addend.
+template <class AuthBind>
+static void writeArm64eChainedAuthBind(uint8_t *buf, const Symbol *sym,
+ int64_t addend, const AuthInfo &auth) {
+ auto *bind = reinterpret_cast<AuthBind *>(buf);
+ bind->ordinal =
+ in.chainedFixups->getBinding(sym, addend, /*forceOutline=*/true).first;
+ bind->zero = 0;
+ bind->diversity = auth.diversity;
+ bind->addrDiv = auth.addrDiv;
+ bind->key = auth.key;
+ bind->next = 0;
+ bind->bind = 1;
+ bind->auth = 1;
+}
+
+static void writeChainedBind(uint8_t *buf, const Symbol *sym, int64_t addend,
+ const AuthInfo *auth) {
assert(config->emitChainedFixups);
assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
if (config->arch() == AK_arm64e) {
- if (config->chainedFixupsPointerFormat ==
- DYLD_CHAINED_PTR_ARM64E_USERLAND24)
+ bool isUserland24 = config->chainedFixupsPointerFormat ==
+ DYLD_CHAINED_PTR_ARM64E_USERLAND24;
+ if (auth && isUserland24)
+ writeArm64eChainedAuthBind<dyld_chained_ptr_arm64e_auth_bind24>(
+ buf, sym, addend, *auth);
+ else if (auth)
+ writeArm64eChainedAuthBind<dyld_chained_ptr_arm64e_auth_bind>(
+ buf, sym, addend, *auth);
+ else if (isUserland24)
writeArm64eChainedBind<dyld_chained_ptr_arm64e_bind24>(buf, sym, addend);
else
writeArm64eChainedBind<dyld_chained_ptr_arm64e_bind>(buf, sym, addend);
@@ -449,11 +492,12 @@ static void writeChainedBind(uint8_t *buf, const Symbol *sym, int64_t addend) {
bind->bind = 1;
}
-void macho::writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend) {
+void macho::writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend,
+ const AuthInfo *auth) {
if (needsBinding(sym))
- writeChainedBind(buf, sym, addend);
+ writeChainedBind(buf, sym, addend, auth);
else
- writeChainedRebase(buf, sym->getVA() + addend);
+ writeChainedRebase(buf, sym->getVA() + addend, auth);
}
void GotSection::writeTo(uint8_t *buf) const {
@@ -2438,9 +2482,10 @@ bool ChainedFixupsSection::isNeeded() const {
void ChainedFixupsSection::addBinding(const Symbol *sym,
const InputSection *isec, uint64_t offset,
- int64_t addend) {
+ int64_t addend, bool forceOutline) {
locations.emplace_back(isec, offset);
- int64_t outlineAddend = (addend < 0 || addend > 0xFF) ? addend : 0;
+ int64_t outlineAddend =
+ (forceOutline || addend < 0 || addend > 0xFF) ? addend : 0;
auto [it, inserted] = bindings.insert(
{{sym, outlineAddend}, static_cast<uint32_t>(bindings.size())});
@@ -2455,8 +2500,10 @@ void ChainedFixupsSection::addBinding(const Symbol *sym,
}
std::pair<uint32_t, uint8_t>
-ChainedFixupsSection::getBinding(const Symbol *sym, int64_t addend) const {
- int64_t outlineAddend = (addend < 0 || addend > 0xFF) ? addend : 0;
+ChainedFixupsSection::getBinding(const Symbol *sym, int64_t addend,
+ bool forceOutline) const {
+ int64_t outlineAddend =
+ (forceOutline || addend < 0 || addend > 0xFF) ? addend : 0;
auto it = bindings.find({sym, outlineAddend});
assert(it != bindings.end() && "binding not found in the imports table");
if (outlineAddend == 0)
diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index 2cc71414d56e7..a5f8763ffbbf7 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -774,13 +774,14 @@ class ChainedFixupsSection final : public LinkEditSection {
locations.emplace_back(isec, offset);
}
void addBinding(const Symbol *dysym, const InputSection *isec,
- uint64_t offset, int64_t addend = 0);
+ uint64_t offset, int64_t addend = 0,
+ bool forceOutline = false);
void setHasNonWeakDefinition() { hasNonWeakDef = true; }
// Returns an (ordinal, inline addend) tuple used by dyld_chained_ptr_64_bind.
- std::pair<uint32_t, uint8_t> getBinding(const Symbol *sym,
- int64_t addend) const;
+ std::pair<uint32_t, uint8_t> getBinding(const Symbol *sym, int64_t addend,
+ bool forceOutline = false) const;
const std::vector<Location> &getLocations() const { return locations; }
@@ -816,8 +817,10 @@ class ChainedFixupsSection final : public LinkEditSection {
llvm::MachO::ChainedImportFormat importFormat;
};
-void writeChainedRebase(uint8_t *buf, uint64_t targetVA);
-void writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend);
+void writeChainedRebase(uint8_t *buf, uint64_t targetVA,
+ const AuthInfo *auth = nullptr);
+void writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend,
+ const AuthInfo *auth = nullptr);
struct InStruct {
const uint8_t *bufferStart = nullptr;
diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp
index 926cbd94d5d07..a09fc8fdbcd96 100644
--- a/lld/MachO/Writer.cpp
+++ b/lld/MachO/Writer.cpp
@@ -695,9 +695,12 @@ static void prepareSymbolRelocation(Symbol *sym, const InputSection *isec,
} else if (relocAttrs.hasAttr(RelocAttrBits::UNSIGNED)) {
// References from thread-local variable sections are treated as offsets
// relative to the start of the referent section, and therefore have no
- // need of rebase opcodes.
- if (!(isThreadLocalVariables(isec->getFlags()) && isa<Defined>(sym)))
- addNonLazyBindingEntries(sym, isec, r.offset, r.getAddend());
+ // need of rebase opcodes. Authenticated pointers are pointers, not offsets.
+ bool isAuth = relocAttrs.hasAttr(RelocAttrBits::AUTH);
+ if (isAuth ||
+ !(isThreadLocalVariables(isec->getFlags()) && isa<Defined>(sym)))
+ addNonLazyBindingEntries(sym, isec, r.offset, r.getAddend(),
+ /*forceOutlineAuth=*/isAuth);
}
}
diff --git a/lld/MachO/Writer.h b/lld/MachO/Writer.h
index 066a0fd5fd3aa..aa24df9f0912a 100644
--- a/lld/MachO/Writer.h
+++ b/lld/MachO/Writer.h
@@ -31,7 +31,8 @@ void createSyntheticSections();
// Add bindings for symbols that need weak or non-lazy bindings.
void addNonLazyBindingEntries(const Symbol *, const InputSection *,
- uint64_t offset, int64_t addend = 0);
+ uint64_t offset, int64_t addend = 0,
+ bool forceOutlineAuth = false);
extern OutputSection *firstTLVDataSection;
diff --git a/lld/test/MachO/arm64e-auth-pointer-invalid.yaml b/lld/test/MachO/arm64e-auth-pointer-invalid.yaml
new file mode 100644
index 0000000000000..a4681c92b98ad
--- /dev/null
+++ b/lld/test/MachO/arm64e-auth-pointer-invalid.yaml
@@ -0,0 +1,153 @@
+# REQUIRES: aarch64
+# RUN: yaml2obj -D CONTENT=0000000034120580 %s -o %t.valid.o
+# RUN: %no-arg-lld -arch arm64e -dylib -platform_version ios 17.0 17.0 %t.valid.o -o %t.dylib
+# RUN: yaml2obj -D CONTENT=0000000034120500 %s -o %t.no-auth.o
+# RUN: not %no-arg-lld -arch arm64e -dylib -platform_version ios 17.0 17.0 %t.no-auth.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=AUTH
+# RUN: yaml2obj -D CONTENT=0000000034120D80 %s -o %t.bit51.o
+# RUN: not %no-arg-lld -arch arm64e -dylib -platform_version ios 17.0 17.0 %t.bit51.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=RESERVED
+# RUN: yaml2obj -D CONTENT=00000000341205C0 %s -o %t.bit62.o
+# RUN: not %no-arg-lld -arch arm64e -dylib -platform_version ios 17.0 17.0 %t.bit62.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=RESERVED
+
+## Invalid embedded signing schemas must not become unsigned pointers or have
+## their reserved bits silently discarded.
+# AUTH: error: AUTHENTICATED_POINTER relocation requires bit 63 to be set at offset 0 of __DATA,__data in
+# RESERVED: error: AUTHENTICATED_POINTER relocation requires bits 51..62 to be zero at offset 0 of __DATA,__data in
+
+--- !mach-o
+FileHeader:
+ magic: 0xFEEDFACF
+ cputype: 0x100000C
+ cpusubtype: 0x80000002
+ filetype: 0x1
+ ncmds: 4
+ sizeofcmds: 440
+ flags: 0x0
+ reserved: 0x0
+LoadCommands:
+ - cmd: LC_SEGMENT_64
+ cmdsize: 312
+ segname: ''
+ vmaddr: 0
+ vmsize: 16
+ fileoff: 472
+ filesize: 16
+ maxprot: 7
+ initprot: 7
+ nsects: 3
+ flags: 0
+ Sections:
+ - sectname: __text
+ segname: __TEXT
+ addr: 0x0
+ size: 0
+ offset: 0x1D8
+ align: 0
+ reloff: 0x0
+ nreloc: 0
+ flags: 0x80000000
+ reserved1: 0x0
+ reserved2: 0x0
+ reserved3: 0x0
+ content: ''
+ - sectname: __const
+ segname: __DATA
+ addr: 0x0
+ size: 8
+ offset: 0x1D8
+ align: 3
+ reloff: 0x0
+ nreloc: 0
+ flags: 0x0
+ reserved1: 0x0
+ reserved2: 0x0
+ reserved3: 0x0
+ content: '0000000000000000'
+ - sectname: __data
+ segname: __DATA
+ addr: 0x8
+ size: 8
+ offset: 0x1E0
+ align: 3
+ reloff: 0x1E8
+ nreloc: 1
+ flags: 0x0
+ reserved1: 0x0
+ reserved2: 0x0
+ reserved3: 0x0
+ content: '[[CONTENT]]'
+ relocations:
+ - address: 0x0
+ symbolnum: 4
+ pcrel: false
+ length: 3
+ extern: true
+ type: 11
+ scattered: false
+ value: 0
+ - cmd: LC_BUILD_VERSION
+ cmdsize: 24
+ platform: 2
+ minos: 1114112
+ sdk: 0
+ ntools: 0
+ - cmd: LC_SYMTAB
+ cmdsize: 24
+ symoff: 496
+ nsyms: 5
+ stroff: 576
+ strsize: 32
+ - cmd: LC_DYSYMTAB
+ cmdsize: 80
+ ilocalsym: 0
+ nlocalsym: 3
+ iextdefsym: 3
+ nextdefsym: 2
+ iundefsym: 5
+ nundefsym: 0
+ tocoff: 0
+ ntoc: 0
+ modtaboff: 0
+ nmodtab: 0
+ extrefsymoff: 0
+ nextrefsyms: 0
+ indirectsymoff: 0
+ nindirectsyms: 0
+ extreloff: 0
+ nextrel: 0
+ locreloff: 0
+ nlocrel: 0
+LinkEditData:
+ NameList:
+ - n_strx: 26
+ n_type: 0xE
+ n_sect: 1
+ n_desc: 0
+ n_value: 0
+ - n_strx: 20
+ n_type: 0xE
+ n_sect: 2
+ n_desc: 0
+ n_value: 0
+ - n_strx: 14
+ n_type: 0xE
+ n_sect: 3
+ n_desc: 0
+ n_value: 8
+ - n_strx: 9
+ n_type: 0xF
+ n_sect: 3
+ n_desc: 0
+ n_value: 8
+ - n_strx: 1
+ n_type: 0xF
+ n_sect: 2
+ n_desc: 0
+ n_value: 0
+ StringTable:
+ - ''
+ - _target
+ - _ptr
+ - ltmp2
+ - ltmp1
+ - ltmp0
+...
diff --git a/lld/test/MachO/arm64e-auth-pointer.s b/lld/test/MachO/arm64e-auth-pointer.s
new file mode 100644
index 0000000000000..64ca66a7b42bf
--- /dev/null
+++ b/lld/test/MachO/arm64e-auth-pointer.s
@@ -0,0 +1,104 @@
+# REQUIRES: aarch64
+## Authenticated pointers become authenticated chained rebases and binds that
+## keep their key, diversity, address diversity and addend, next to
+## unauthenticated ones. The words and imports below are the ones Xcode 27's ld
+## writes for the same object.
+
+# 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 11.0 11.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 -o %t/userland24
+# RUN: llvm-objdump --macho --chained-fixups --syms %t/userland24 > %t/out
+# RUN: llvm-objdump --macho --dyld-info %t/userland24 >> %t/out
+# RUN: FileCheck %s --check-prefixes=CHECK,USERLAND24 -D#%x,BASE=0x100000000 \
+# RUN: < %t/out
+
+# RUN: %lld -arch arm64e -platform_version macos 11.0 11.0 %t/test.o \
+# RUN: %t/libfoo.dylib -o %t/arm64e
+# RUN: llvm-objdump --macho --chained-fixups --syms %t/arm64e > %t/out
+# RUN: llvm-objdump --macho --dyld-info %t/arm64e >> %t/out
+# RUN: FileCheck %s --check-prefixes=CHECK,ARM64E -D#%x,BASE=0x100000000 \
+# RUN: < %t/out
+
+## An authenticated rebase stores its target as a 32-bit offset from the image
+## base, which the target below doesn't fit in.
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/far.o %t/far.s
+# RUN: not %lld -arch arm64e -platform_version macos 13.0 13.0 %t/far.o \
+# RUN: -o /dev/null 2>&1 | FileCheck %s --check-prefix=FAR
+# FAR: error: vmOffset (0x100008000) cannot fit in fixup at __DATA+0x0
+
+## Authenticated binds have no addend field, so their addends go in the
+## imports table. The unauthenticated bind shares import 0 and keeps its
+## addend inline.
+# CHECK: imports_format = 2 (DYLD_CHAINED_IMPORT_ADDEND)
+# USERLAND24: pointer_format = 12 (DYLD_CHAINED_PTR_ARM64E_USERLAND24)
+# ARM64E: pointer_format = 1 (DYLD_CHAINED_PTR_ARM64E)
+# CHECK: dyld chained import addend[0]
+# CHECK: name_offset = {{[0-9]+}} (_foo)
+# CHECK-NEXT: addend = 0
+# CHECK: dyld chained import addend[1]
+# CHECK: name_offset = {{[0-9]+}} (_foo)
+# CHECK-NEXT: addend = -4
+# CHECK: dyld chained import addend[2]
+# CHECK: name_offset = {{[0-9]+}} (_foo)
+# CHECK-NEXT: addend = 16
+# CHECK: [[#%x,LOCAL:]] l F __TEXT,__text _local
+
+## An authenticated rebase stores its target as an offset from the image base
+## in both formats, and an authenticated bind its import's ordinal, with no room
+## for an addend. Only the unauthenticated rebase differs between the formats:
+## USERLAND24 stores the offset, ARM64E the address.
+# CHECK-LABEL: dyld information:
+# CHECK-NEXT: segment section address pointer type div addr key addend dylib symbol/vm address
+# CHECK-NEXT: __DATA __data 0x[[#%X,BASE+0x4000]] 0x[[#%.16X,0x8008000000000000+LOCAL-BASE]] rebase 0x0000 false IA 0x[[#%X,LOCAL]]
+# CHECK-NEXT: __DATA __data 0x[[#%X,BASE+0x4008]] 0x[[#%.16X,0x800B123400000000+LOCAL+8-BASE]] rebase 0x1234 true IB 0x[[#%X,LOCAL+8]]
+# CHECK-NEXT: __DATA __data 0x[[#%X,BASE+0x4010]] 0xC00C002A00000000 bind 0x002A false DA 0x0 libfoo _foo
+# CHECK-NEXT: __DATA __data 0x[[#%X,BASE+0x4018]] 0xC00FFFFF00000002 bind 0xFFFF true DB 0x10 libfoo _foo
+# CHECK-NEXT: __DATA __data 0x[[#%X,BASE+0x4020]] 0xC008000100000001 bind 0x0001 false IA 0xFFFFFFFFFFFFFFFC libfoo _foo
+# USERLAND24-NEXT: __DATA __data 0x[[#%X,BASE+0x4028]] 0x[[#%.16X,0x8000000000000+LOCAL-BASE]] rebase 0x[[#%X,LOCAL]]
+# ARM64E-NEXT: __DATA __data 0x[[#%X,BASE+0x4028]] 0x[[#%.16X,0x8000000000000+LOCAL]] rebase 0x[[#%X,LOCAL]]
+# CHECK-NEXT: __DATA __data 0x[[#%X,BASE+0x4030]] 0x4000000800000000 bind 0x8 libfoo _foo
+
+#--- foo.s
+.globl _foo
+_foo:
+ ret
+
+#--- test.s
+.globl _main
+.p2align 2
+_main:
+ ret
+_local:
+ ret
+
+.data
+.p2align 3
+ .quad _local at AUTH(ia,0)
+ .quad (_local + 8)@AUTH(ib,0x1234,addr)
+ .quad _foo at AUTH(da,42)
+ .quad (_foo + 16)@AUTH(db,0xffff,addr)
+ .quad (_foo - 4)@AUTH(ia,1)
+ .quad _local
+ .quad _foo + 8
+
+#--- far.s
+.globl _main
+.p2align 2
+_main:
+ ret
+
+.data
+.p2align 3
+ .quad _far at AUTH(da,0)
+
+.zerofill __DATA,__bss,_big,0x100000000
+
+.section __FAR,__data
+.p2align 3
+_far:
+ .quad 0
diff --git a/lld/test/MachO/arm64e-auth-reloc-local.s b/lld/test/MachO/arm64e-auth-reloc-local.s
new file mode 100644
index 0000000000000..b55bb92e2566f
--- /dev/null
+++ b/lld/test/MachO/arm64e-auth-reloc-local.s
@@ -0,0 +1,117 @@
+# REQUIRES: aarch64
+## The assembler emits an authenticated pointer to a local symbol as an extern
+## relocation, which keeps its signing schema. ld64 rejects the non-extern
+## (section-relative) form, and so does lld.
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/local.o %t/local.s
+# RUN: llvm-objdump --macho -r %t/local.o | FileCheck %s --check-prefix=OBJ
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib %t/local.o \
+# RUN: -o %t/local.dylib
+# RUN: llvm-objdump --macho --syms %t/local.dylib > %t/out
+# RUN: llvm-objdump --macho --dyld-info %t/local.dylib >> %t/out
+# RUN: FileCheck %s < %t/out
+
+# RUN: yaml2obj %t/nonextern.yaml -o %t/nonextern.o
+# RUN: not %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib \
+# RUN: %t/nonextern.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR
+
+# OBJ: 00000000 False ?( 3) True 11 (?) False _target
+
+# CHECK: [[#%x,TARGET:]] l O __DATA_CONST,__const _target
+## An authenticated rebase of _target with key DA, diversity 0x1234 and
+## address diversity.
+# CHECK-LABEL: dyld information:
+# CHECK: __DATA __data 0x{{[0-9A-F]+}} 0x{{[0-9A-F]+}} rebase 0x1234 true DA 0x[[#%X,TARGET]]
+
+# ERR: error: AUTHENTICATED_POINTER relocation must be extern at offset 0 of __DATA,__data in {{.*}}nonextern.o
+
+#--- local.s
+.section __DATA,__const
+.p2align 3
+_target:
+ .quad 0
+
+.section __DATA,__data
+.p2align 3
+.globl _ptr
+_ptr:
+ .quad _target at AUTH(da,0x1234,addr)
+
+## local.s with the relocation turned into a section-relative one.
+#--- nonextern.yaml
+--- !mach-o
+FileHeader:
+ magic: 0xFEEDFACF
+ cputype: 0x100000C
+ cpusubtype: 0x80000002
+ filetype: 0x1
+ ncmds: 2
+ sizeofcmds: 256
+ flags: 0x0
+ reserved: 0x0
+LoadCommands:
+ - cmd: LC_SEGMENT_64
+ cmdsize: 232
+ segname: ''
+ vmaddr: 0
+ vmsize: 16
+ fileoff: 288
+ filesize: 16
+ maxprot: 7
+ initprot: 7
+ nsects: 2
+ flags: 0
+ Sections:
+ - sectname: __const
+ segname: __DATA
+ addr: 0x0
+ size: 8
+ offset: 0x120
+ align: 3
+ reloff: 0x0
+ nreloc: 0
+ flags: 0x0
+ reserved1: 0x0
+ reserved2: 0x0
+ reserved3: 0x0
+ content: '0000000000000000'
+ - sectname: __data
+ segname: __DATA
+ addr: 0x8
+ size: 8
+ offset: 0x128
+ align: 3
+ reloff: 0x130
+ nreloc: 1
+ flags: 0x0
+ reserved1: 0x0
+ reserved2: 0x0
+ reserved3: 0x0
+ content: '0000000034120580'
+ relocations:
+ - address: 0x0
+ symbolnum: 1
+ pcrel: false
+ length: 3
+ extern: false
+ type: 11
+ scattered: false
+ value: 0
+ - cmd: LC_SYMTAB
+ cmdsize: 24
+ symoff: 312
+ nsyms: 1
+ stroff: 328
+ strsize: 6
+LinkEditData:
+ NameList:
+ - n_strx: 1
+ n_type: 0xF
+ n_sect: 2
+ n_desc: 0
+ n_value: 8
+ StringTable:
+ - ''
+ - _ptr
+...
diff --git a/lld/test/MachO/arm64e-chained-fixups.s b/lld/test/MachO/arm64e-chained-fixups.s
index 6fcc565804041..217d09af462b0 100644
--- a/lld/test/MachO/arm64e-chained-fixups.s
+++ b/lld/test/MachO/arm64e-chained-fixups.s
@@ -24,7 +24,7 @@
# RUN: llvm-objdump --macho --chained-fixups %t/macos11.dylib | \
# RUN: FileCheck %s --check-prefix=ARM64E
# RUN: llvm-objdump --macho --dyld-info %t/macos11.dylib | \
-# RUN: FileCheck %s --check-prefix=PTRS
+# RUN: FileCheck %s --check-prefix=AUTH
# RUN: %no-fatal-warnings-lld -arch arm64e -platform_version macos 13.0 13.0 \
# RUN: -dylib -no_fixup_chains %t/test.o %t/libfoo.dylib \
@@ -35,7 +35,7 @@
# RUN: llvm-objdump --macho --chained-fixups %t/no-fixup-chains.dylib | \
# RUN: FileCheck %s --check-prefix=ARM64E
# RUN: llvm-objdump --macho --dyld-info %t/no-fixup-chains.dylib | \
-# RUN: FileCheck %s --check-prefix=PTRS
+# RUN: FileCheck %s --check-prefix=AUTH
## arm64e chains pointers with an 8-byte stride; don't suggest the ignored
## -no_fixup_chains when two pointers are 4 bytes out of step.
@@ -95,9 +95,11 @@
# ARM64E: pointer_format = 1 (DYLD_CHAINED_PTR_ARM64E)
# USERLAND24: pointer_format = 12 (DYLD_CHAINED_PTR_ARM64E_USERLAND24)
-## _local_ptr is a rebase, and _foo_ptr a bind of _foo.
-# PTRS: __DATA __data 0x{{[0-9A-F]+}} 0x{{[0-9A-F]+}} rebase
-# PTRS-NEXT: __DATA __data 0x{{[0-9A-F]+}} 0x{{[0-9A-F]+}} bind 0x0 libfoo _foo
+## _local_ptr is an authenticated rebase with key IA and diversity 42, and
+## _foo_ptr an authenticated bind with key DA, diversity 7 and address
+## diversity.
+# AUTH: __DATA __data 0x{{[0-9A-F]+}} 0x8008002A{{[0-9A-F]+}} rebase 0x002A false IA
+# AUTH-NEXT: __DATA __data 0x{{[0-9A-F]+}} 0xC005000700000000 bind 0x0007 true DA 0x0 libfoo _foo
#--- foo.s
.globl _foo
@@ -117,9 +119,9 @@ _local:
.p2align 3
.globl _local_ptr, _foo_ptr
_local_ptr:
- .quad _local
+ .quad _local at AUTH(ia,42)
_foo_ptr:
- .quad _foo
+ .quad _foo at AUTH(da,7,addr)
.section __DATA,__mod_init_func,mod_init_funcs
.p2align 3
@@ -135,7 +137,7 @@ _ptr:
.data
.p2align 3
_ptr:
- .quad _ptr
+ .quad _ptr at AUTH(da,0)
#--- unaligned.s
.data
diff --git a/lld/test/MachO/arm64e-tlv-auth.s b/lld/test/MachO/arm64e-tlv-auth.s
new file mode 100644
index 0000000000000..ae4a65144b45b
--- /dev/null
+++ b/lld/test/MachO/arm64e-tlv-auth.s
@@ -0,0 +1,42 @@
+# REQUIRES: aarch64
+## Only the plain pointers in __thread_vars are offsets into the thread-local
+## data. An authenticated pointer there is a signed pointer to its target, which
+## ld64 emits as an authenticated rebase.
+
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos %s -o %t.o
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib %t.o \
+# RUN: -o %t.dylib
+# RUN: llvm-objdump --macho --section-headers %t.dylib > %t.out
+# RUN: llvm-objdump --macho --dyld-info %t.dylib >> %t.out
+# RUN: llvm-objdump --macho -s --section __DATA,__thread_vars %t.dylib >> %t.out
+# RUN: FileCheck %s < %t.out
+
+## Without chained fixups, which -preload turns off, the pointer cannot be
+## encoded.
+# RUN: not %no-fatal-warnings-lld -arch arm64e -platform_version macos 13.0 13.0 \
+# RUN: -dylib -preload %t.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR
+
+# CHECK: __thread_vars 00000018 {{0*}}[[#%x,VARS:]] DATA
+# CHECK-NEXT: __thread_data 00000008 {{0*}}[[#%x,DATA:]] DATA
+
+## The first word is an authenticated rebase of _tls with key DA. The third,
+## the offset of _tls in the thread-local data, is not a fixup.
+# CHECK-LABEL: dyld information:
+# CHECK: __DATA __thread_vars 0x[[#%X,VARS]] 0x{{[0-9A-F]+}} rebase 0x0000 false DA 0x[[#%X,DATA]]
+# CHECK-NOT: __thread_vars
+# CHECK-LABEL: Contents of (__DATA,__thread_vars) section
+# CHECK: {{0*}}[[#VARS+16]] 00000000 00000000
+
+# ERR: error: {{.*}}.o:(symbol _desc+0x0): authenticated pointer requires chained fixups
+
+.section __DATA,__thread_data,thread_local_regular
+.p2align 3
+_tls:
+ .quad 42
+
+.section __DATA,__thread_vars,thread_local_variables
+.globl _desc
+_desc:
+ .quad _tls at AUTH(da,0)
+ .quad 0
+ .quad _tls
>From beb79cc8cde22925b175d51376cfb26663883811 Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Tue, 6 Oct 2026 15:58:11 -0700
Subject: [PATCH 4/7] [lld-macho] Keep differently signed arm64e data apart in
ICF
Sections that differ only in the signing schema of an authenticated pointer
are not identical. Compare the schemas in ICF. This matters even without
--icf, because __cfstring sections, whose isa pointers arm64e compilers
sign, are always deduplicated.
Assisted-by: Claude Code
---
lld/MachO/ICF.cpp | 4 +++
lld/test/MachO/arm64e-icf.s | 60 +++++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+)
create mode 100644 lld/test/MachO/arm64e-icf.s
diff --git a/lld/MachO/ICF.cpp b/lld/MachO/ICF.cpp
index e5f9a37fcaf81..c0fd0b91b4878 100644
--- a/lld/MachO/ICF.cpp
+++ b/lld/MachO/ICF.cpp
@@ -148,6 +148,10 @@ bool ICF::equalsConstant(const ConcatInputSection *ia,
return false;
if (ra.offset != rb.offset)
return false;
+ if (ra.hasAuth != rb.hasAuth)
+ return false;
+ if (ra.hasAuth && *ra.getAuthInfo() != *rb.getAuthInfo())
+ return false;
if (isa<Symbol *>(ra.referent) != isa<Symbol *>(rb.referent))
return false;
diff --git a/lld/test/MachO/arm64e-icf.s b/lld/test/MachO/arm64e-icf.s
new file mode 100644
index 0000000000000..c5fcd46a94722
--- /dev/null
+++ b/lld/test/MachO/arm64e-icf.s
@@ -0,0 +1,60 @@
+# REQUIRES: aarch64
+## Identical sections are only folded if their authenticated pointers have the
+## same signing schema. __cfstring sections are always deduplicated, ignoring
+## the bytes that relocations overwrite, so only the relocations can tell these
+## apart; arm64e compilers sign each CFString's isa pointer.
+
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t.o %s
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 %t.o -o %t.out
+# RUN: llvm-objdump --macho --syms %t.out | FileCheck %s
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 --icf=all %t.o \
+# RUN: -o %t.icf.out
+# RUN: llvm-objdump --macho --syms %t.icf.out | FileCheck %s
+
+# CHECK: [[#%x,SAME:]] l O __DATA_CONST,__cfstring _same_a
+# CHECK-NEXT: {{0*}}[[#SAME]] l O __DATA_CONST,__cfstring _same_b
+# CHECK-NEXT: {{0*}}[[#SAME+0x20]] l O __DATA_CONST,__cfstring _other_diversity
+# CHECK-NEXT: {{0*}}[[#SAME+0x40]] l O __DATA_CONST,__cfstring _unsigned
+
+.globl _main
+.p2align 2
+_main:
+ ret
+
+.section __DATA,__cfstring
+.p2align 3
+_same_a:
+ .quad ___CFConstantStringClassReference at AUTH(da,27361,addr)
+ .long 1992
+ .space 4
+ .quad L_.str
+ .quad 3
+_same_b:
+ .quad ___CFConstantStringClassReference at AUTH(da,27361,addr)
+ .long 1992
+ .space 4
+ .quad L_.str
+ .quad 3
+_other_diversity:
+ .quad ___CFConstantStringClassReference at AUTH(da,27362,addr)
+ .long 1992
+ .space 4
+ .quad L_.str
+ .quad 3
+_unsigned:
+ .quad ___CFConstantStringClassReference
+ .long 1992
+ .space 4
+ .quad L_.str
+ .quad 3
+
+.section __TEXT,__cstring,cstring_literals
+L_.str:
+ .asciz "abc"
+
+.data
+.globl ___CFConstantStringClassReference
+___CFConstantStringClassReference:
+ .quad 0
+
+.subsections_via_symbols
>From c871816efed584dcc794bc5f49801b662b8624db Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Tue, 6 Oct 2026 16:02:01 -0700
Subject: [PATCH 5/7] [lld-macho] Sign arm64e personality pointers through
__auth_got
The arm64e unwinder authenticates the personality pointers that it loads
through the GOT, from the personality array in __unwind_info and from a
CIE, against the address of their slot with the IA key. An image with an
unsigned slot crashes on its first throw. Like ld64, put these GOT entries
in __auth_got, whose pointers dyld signs that way. Other GOT references to
the same symbol keep their unsigned __got slot.
Assisted-by: Claude Code
---
lld/MachO/InputSection.cpp | 10 ++-
lld/MachO/InputSection.h | 1 +
lld/MachO/OutputSegment.cpp | 1 +
lld/MachO/SyntheticSections.cpp | 37 +++++++-
lld/MachO/SyntheticSections.h | 24 ++++++
lld/MachO/UnwindInfoSection.cpp | 26 ++++--
lld/MachO/Writer.cpp | 10 ++-
lld/test/MachO/arm64e-auth-got.s | 140 +++++++++++++++++++++++++++++++
8 files changed, 240 insertions(+), 9 deletions(-)
create mode 100644 lld/test/MachO/arm64e-auth-got.s
diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index f60d590c12af6..24e9283d05db5 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -258,7 +258,10 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
target->handleDtraceReloc(referentSym, r, loc);
continue;
}
- referentVA = resolveSymbolOffsetVA(referentSym, r.type, r.getAddend());
+ if (target->hasAttr(r.type, RelocAttrBits::GOT) && usesAuthGot(this))
+ referentVA = in.authGot->getVA(referentSym) + r.getAddend();
+ else
+ referentVA = resolveSymbolOffsetVA(referentSym, r.type, r.getAddend());
// References from thread-local variable sections are treated as offsets
// relative to the start of the thread-local data memory area, which
@@ -412,6 +415,11 @@ bool macho::isEhFrameSection(const InputSection *isec) {
isec->getSegName() == segment_names::text;
}
+bool macho::usesAuthGot(const InputSection *isec) {
+ // arm64e's unwinder expects signed personality pointers.
+ return config->arch() == AK_arm64e && isEhFrameSection(isec);
+}
+
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 49ea57a24129c..bdbf53aa588a6 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -310,6 +310,7 @@ bool isClassRefsSection(const InputSection *);
bool isSelRefsSection(const InputSection *);
bool isEhFrameSection(const InputSection *);
bool isGccExceptTabSection(const InputSection *);
+bool usesAuthGot(const InputSection *);
extern std::vector<ConcatInputSection *> inputSections;
// This is used as a counter for specyfing input order for input sections
diff --git a/lld/MachO/OutputSegment.cpp b/lld/MachO/OutputSegment.cpp
index 5824b5c7772b3..6d8eadb703f5a 100644
--- a/lld/MachO/OutputSegment.cpp
+++ b/lld/MachO/OutputSegment.cpp
@@ -148,6 +148,7 @@ static int sectionOrder(OutputSection *osec) {
return std::numeric_limits<int>::max();
default:
return StringSwitch<int>(osec->name)
+ .Case(section_names::authGot, -4)
.Case(section_names::got, -3)
.Case(section_names::lazySymbolPtr, -2)
.Case(section_names::const_, -1)
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 2b76bb7f04138..c774cb60ed795 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -511,6 +511,28 @@ void GotSection::writeTo(uint8_t *buf) const {
}
}
+AuthGotSection::AuthGotSection()
+ : SyntheticSection(segment_names::data, section_names::authGot) {
+ align = target->wordSize;
+ flags = S_NON_LAZY_SYMBOL_POINTERS;
+}
+
+void AuthGotSection::addEntry(Symbol *sym) {
+ auto [it, inserted] = indices.try_emplace(sym, entries.size());
+ if (!inserted)
+ return;
+ entries.push_back(sym);
+ addNonLazyBindingEntries(sym, isec, it->second * target->wordSize, 0,
+ /*forceOutlineAuth=*/true);
+}
+
+void AuthGotSection::writeTo(uint8_t *buf) const {
+ static constexpr AuthInfo auth = {/*diversity=*/0, PtrAuthKeyIA,
+ /*addrDiv=*/true};
+ for (const auto &[i, entry] : llvm::enumerate(entries))
+ writeChainedFixup(&buf[i * target->wordSize], entry, 0, &auth);
+}
+
BindingSection::BindingSection()
: LinkEditSection(segment_names::linkEdit, section_names::binding) {}
@@ -1612,19 +1634,26 @@ IndirectSymtabSection::IndirectSymtabSection()
uint32_t IndirectSymtabSection::getNumSymbols() const {
uint32_t size = in.got->getEntries().size() + in.stubs->getEntries().size();
+ if (in.authGot)
+ size += in.authGot->getEntries().size();
if (!config->emitChainedFixups)
size += in.stubs->getEntries().size();
return size;
}
bool IndirectSymtabSection::isNeeded() const {
- return in.got->isNeeded() || in.stubs->isNeeded();
+ return in.got->isNeeded() || in.stubs->isNeeded() ||
+ (in.authGot && in.authGot->isNeeded());
}
void IndirectSymtabSection::finalizeContents() {
uint32_t off = 0;
in.got->reserved1 = off;
off += in.got->getEntries().size();
+ if (in.authGot) {
+ in.authGot->reserved1 = off;
+ off += in.authGot->getEntries().size();
+ }
in.stubs->reserved1 = off;
if (in.lazyPointers) {
off += in.stubs->getEntries().size();
@@ -1644,6 +1673,12 @@ void IndirectSymtabSection::writeTo(uint8_t *buf) const {
write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
++off;
}
+ if (in.authGot) {
+ for (const Symbol *sym : in.authGot->getEntries()) {
+ write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
+ ++off;
+ }
+ }
for (const Symbol *sym : in.stubs->getEntries()) {
write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
++off;
diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index a5f8763ffbbf7..9fb6006f97d42 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -126,6 +126,29 @@ class GotSection final : public SyntheticSection {
llvm::SetVector<const Symbol *> entries;
};
+// The arm64e __auth_got section, whose pointers dyld signs with the IA key
+// and address diversity.
+class AuthGotSection final : public SyntheticSection {
+public:
+ AuthGotSection();
+ ArrayRef<const Symbol *> getEntries() const { return entries; }
+ bool isNeeded() const override { return !entries.empty(); }
+ uint64_t getSize() const override {
+ return entries.size() * target->wordSize;
+ }
+ void writeTo(uint8_t *buf) const override;
+ void addEntry(Symbol *sym);
+ bool contains(const Symbol *sym) const { return indices.contains(sym); }
+ uint64_t getVA(const Symbol *sym) const {
+ return addr + indices.at(sym) * target->wordSize;
+ }
+
+private:
+ // Kept here rather than in Symbol, so that other targets don't pay for it.
+ llvm::DenseMap<const Symbol *, uint32_t> indices;
+ std::vector<const Symbol *> entries;
+};
+
struct Location {
const InputSection *isec;
uint64_t offset;
@@ -837,6 +860,7 @@ struct InStruct {
LazyBindingSection *lazyBinding = nullptr;
ExportSection *exports = nullptr;
GotSection *got = nullptr;
+ AuthGotSection *authGot = nullptr;
LazyPointerSection *lazyPointers = nullptr;
StubsSection *stubs = nullptr;
StubHelperSection *stubHelper = nullptr;
diff --git a/lld/MachO/UnwindInfoSection.cpp b/lld/MachO/UnwindInfoSection.cpp
index 811260096b2dc..2b4bc56404592 100644
--- a/lld/MachO/UnwindInfoSection.cpp
+++ b/lld/MachO/UnwindInfoSection.cpp
@@ -220,6 +220,22 @@ void UnwindInfoSectionImpl::prepare() {
}
}
+// arm64e's unwinder expects signed personality pointers.
+static void addPersonalityGotEntry(Symbol *s) {
+ if (config->arch() == AK_arm64e)
+ in.authGot->addEntry(s);
+ else
+ in.got->addEntry(s);
+}
+
+static bool hasPersonalityGotEntry(const Symbol *s) {
+ return config->arch() == AK_arm64e ? in.authGot->contains(s) : s->isInGot();
+}
+
+static uint64_t getPersonalityGotVA(const Symbol *s) {
+ return config->arch() == AK_arm64e ? in.authGot->getVA(s) : s->getGotVA();
+}
+
// Compact unwind relocations have different semantics, so we handle them in a
// separate code path from regular relocations. First, we do not wish to add
// rebase opcodes for __LD,__compact_unwind, because that section doesn't
@@ -283,7 +299,7 @@ void UnwindInfoSectionImpl::prepareRelocations(ConcatInputSection *isec) {
personalityTable[{defined->isec(), defined->value}];
if (personality == nullptr) {
personality = defined;
- in.got->addEntry(defined);
+ addPersonalityGotEntry(defined);
} else if (personality != defined) {
r.referent = personality;
}
@@ -291,7 +307,7 @@ void UnwindInfoSectionImpl::prepareRelocations(ConcatInputSection *isec) {
}
assert(isa<DylibSymbol>(s));
- in.got->addEntry(s);
+ addPersonalityGotEntry(s);
continue;
}
@@ -308,7 +324,7 @@ void UnwindInfoSectionImpl::prepareRelocations(ConcatInputSection *isec) {
Defined *const *gotEntry =
llvm::find_if(referentIsec->symbols, [&](Defined const *d) {
return d->value == static_cast<uint64_t>(r.getAddend()) &&
- d->isInGot();
+ hasPersonalityGotEntry(d);
});
if (gotEntry != referentIsec->symbols.end()) {
s = *gotEntry;
@@ -322,7 +338,7 @@ void UnwindInfoSectionImpl::prepareRelocations(ConcatInputSection *isec) {
/*isReferencedDynamically=*/false,
/*noDeadStrip=*/false);
s->used = true;
- in.got->addEntry(s);
+ addPersonalityGotEntry(s);
}
}
r.referent = s;
@@ -642,7 +658,7 @@ void UnwindInfoSectionImpl::writeTo(uint8_t *buf) const {
// Personalities
for (const Symbol *personality : personalities)
- *i32p++ = personality->getGotVA() - in.header->addr;
+ *i32p++ = getPersonalityGotVA(personality) - in.header->addr;
// FIXME: LD64 checks and warns aboutgaps or overlapse in cuEntries address
// ranges. We should do the same too
diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp
index a09fc8fdbcd96..97db232fbfcbb 100644
--- a/lld/MachO/Writer.cpp
+++ b/lld/MachO/Writer.cpp
@@ -687,8 +687,12 @@ static void prepareSymbolRelocation(Symbol *sym, const InputSection *isec,
if (needsBinding(sym))
in.stubs->addEntry(sym);
} else if (relocAttrs.hasAttr(RelocAttrBits::GOT)) {
- if (relocAttrs.hasAttr(RelocAttrBits::POINTER) || needsBinding(sym))
- addNonLazyPointerEntry(sym);
+ if (relocAttrs.hasAttr(RelocAttrBits::POINTER) || needsBinding(sym)) {
+ if (usesAuthGot(isec))
+ in.authGot->addEntry(sym);
+ else
+ addNonLazyPointerEntry(sym);
+ }
} else if (relocAttrs.hasAttr(RelocAttrBits::TLV)) {
if (needsBinding(sym))
addNonLazyPointerEntry(sym);
@@ -1460,6 +1464,8 @@ void macho::createSyntheticSections() {
}
in.exports = make<ExportSection>();
in.got = make<GotSection>();
+ if (config->arch() == AK_arm64e)
+ in.authGot = make<AuthGotSection>();
in.stubs = make<StubsSection>();
in.objcStubs = make<ObjCStubsSection>();
in.unwindInfo = makeUnwindInfoSection();
diff --git a/lld/test/MachO/arm64e-auth-got.s b/lld/test/MachO/arm64e-auth-got.s
new file mode 100644
index 0000000000000..cb8de0db1a05b
--- /dev/null
+++ b/lld/test/MachO/arm64e-auth-got.s
@@ -0,0 +1,140 @@
+# REQUIRES: aarch64
+## On arm64e, personality pointers are loaded from __auth_got, where dyld signs
+## them with the IA key and address diversity: the unwinder authenticates them
+## against the address of their slot. That applies to the personality array in
+## __unwind_info and to a CIE's personality in __eh_frame. Other GOT references
+## to the same symbol, `sym at GOT - .` and GOT loads, use an unsigned __got slot.
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/pers.o %t/pers.s
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos \
+# RUN: -emit-compact-unwind-non-canonical=true -o %t/cu.o %t/cu.s
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos \
+# RUN: --emit-dwarf-unwind=always -o %t/dwarf.o %t/dwarf.s
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib \
+# RUN: -install_name @executable_path/libpers.dylib %t/pers.o -o %t/libpers.dylib
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 %t/cu.o %t/dwarf.o \
+# RUN: %t/libpers.dylib -o %t/test
+# RUN: llvm-objdump --macho --section-headers --unwind-info %t/test > %t/out
+# RUN: llvm-dwarfdump --eh-frame %t/test >> %t/out
+# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn %t/test >> %t/out
+# RUN: llvm-objdump --macho -s --section __TEXT,__const %t/test >> %t/out
+# RUN: llvm-objdump --macho --dyld-info %t/test >> %t/out
+# RUN: FileCheck %s -D#%x,BASE=0x100000000 < %t/out
+
+## A personality defined in the same object is referenced from
+## __compact_unwind through a section relocation. It gets an __auth_got slot
+## of its own even though it already has an unsigned __got slot.
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos \
+# RUN: -emit-compact-unwind-non-canonical=true -o %t/local.o %t/local.s
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 %t/local.o \
+# RUN: -o %t/local
+# RUN: llvm-objdump --macho --section-headers --syms --unwind-info \
+# RUN: %t/local > %t/local-out
+# RUN: llvm-objdump --macho -s --section __TEXT,__const %t/local >> %t/local-out
+# RUN: llvm-objdump --macho --dyld-info %t/local >> %t/local-out
+# RUN: FileCheck %s --check-prefix=LOCAL -D#%x,BASE=0x100000000 < %t/local-out
+
+# CHECK: __const {{[0-9a-f]+}} {{0*}}[[#%x,CONST:]] DATA
+# CHECK: __auth_got 00000008 {{0*}}[[#%x,AUTH_GOT:]] DATA
+# CHECK-NEXT: __got 00000008 {{0*}}[[#%x,GOT:]] DATA
+
+# CHECK: Personality functions: (count = 1)
+# CHECK-NEXT: personality[1]: 0x{{0*}}[[#AUTH_GOT-BASE]]
+# CHECK: CIE
+# CHECK: Personality Address: {{0*}}[[#AUTH_GOT]]
+
+# CHECK-LABEL: _main:
+# CHECK: adrp x0, {{.*}} ; 0x[[#%x,GOT_PAGE:]]
+# CHECK-NEXT: ldr x0, [x0, #[[#GOT-GOT_PAGE]]] ; literal pool symbol address: _pers
+
+## `_pers at GOT - .`
+# CHECK-LABEL: Contents of (__TEXT,__const) section
+# CHECK-NEXT: {{0*}}[[#CONST]] [[#%.8x,GOT-CONST]]
+## An authenticated bind with key IA, diversity 0 and address diversity in
+## __auth_got, and an unauthenticated bind in __got.
+# CHECK-LABEL: dyld information:
+# CHECK: __DATA_CONST __auth_got 0x[[#%X,AUTH_GOT]] 0xC009000000000000 bind 0x0000 true IA 0x0 libpers _pers
+# CHECK-NEXT: __DATA_CONST __got 0x[[#%X,GOT]] 0x4000000000000000 bind 0x0 libpers _pers
+
+# LOCAL: __const 00000004 {{0*}}[[#%x,CONST:]] DATA
+# LOCAL: __auth_got 00000008 {{0*}}[[#%x,AUTH_GOT:]] DATA
+# LOCAL-NEXT: __got 00000008 {{0*}}[[#%x,GOT:]] DATA
+# LOCAL: [[#%x,MY_PERS:]] g F __TEXT,__text _my_pers
+# LOCAL: Personality functions: (count = 1)
+# LOCAL-NEXT: personality[1]: 0x{{0*}}[[#AUTH_GOT-BASE]]
+# LOCAL-LABEL: Contents of (__TEXT,__const) section
+# LOCAL-NEXT: {{0*}}[[#CONST]] [[#%.8x,GOT-CONST]]
+## Both slots rebase _my_pers; only the __auth_got one is authenticated.
+# LOCAL-LABEL: dyld information:
+# LOCAL: __DATA_CONST __auth_got 0x[[#%X,AUTH_GOT]] 0x{{[0-9A-F]+}} rebase 0x0000 true IA 0x[[#%X,MY_PERS]]
+# LOCAL-NEXT: __DATA_CONST __got 0x[[#%X,GOT]] 0x{{[0-9A-F]+}} rebase 0x[[#%X,MY_PERS]]
+
+#--- pers.s
+.globl _pers
+.p2align 2
+_pers:
+ ret
+
+#--- cu.s
+.globl _main
+.p2align 2
+_main:
+ .cfi_startproc
+ .cfi_personality 155, _pers
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+ .cfi_def_cfa w29, 16
+ .cfi_offset w30, -8
+ .cfi_offset w29, -16
+ bl _dwarf
+ adrp x0, _pers at GOTPAGE
+ ldr x0, [x0, _pers at GOTPAGEOFF]
+ ldp x29, x30, [sp], #16
+ ret
+ .cfi_endproc
+
+.section __TEXT,__const
+.p2align 2
+_pers_got_ref:
+ .long _pers at GOT - .
+
+#--- dwarf.s
+.globl _dwarf
+.p2align 2
+_dwarf:
+ .cfi_startproc
+ .cfi_personality 155, _pers
+ stp x29, x30, [sp, #-16]!
+ .cfi_def_cfa_offset 16
+ .cfi_offset w30, -8
+ .cfi_offset w29, -16
+ ldp x29, x30, [sp], #16
+ ret
+ .cfi_endproc
+
+#--- local.s
+.globl _main, _my_pers
+.p2align 2
+_main:
+ .cfi_startproc
+ .cfi_personality 155, _my_pers
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+ .cfi_def_cfa w29, 16
+ .cfi_offset w30, -8
+ .cfi_offset w29, -16
+ ldp x29, x30, [sp], #16
+ ret
+ .cfi_endproc
+
+.p2align 2
+_my_pers:
+ ret
+
+.section __TEXT,__const
+.p2align 2
+_my_pers_got_ref:
+ .long _my_pers at GOT - .
+
+.subsections_via_symbols
>From 3897f540ae5b621532aafa59884a3ec3f1978e26 Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Wed, 7 Oct 2026 10:00:35 -0400
Subject: [PATCH 6/7] [lld-macho] Don't mix arm64 and arm64e objects
arm64 and arm64e share a CPU type, but as in ld64, an arm64e link ignores
arm64 objects and an arm64 link ignores arm64e objects with a warning, and
-arch_errors_fatal makes both an error. Like ld64, both also ignore
arm64e.x1 objects, which have the arm64e ABI but need a newer CPU than
arm64e. The inputs of an -arch arm64e.x1 link are not checked.
Assisted-by: Claude Code
---
lld/MachO/InputFiles.cpp | 9 +++-
lld/test/MachO/arm64e-reject-mixed.s | 62 ++++++++++++++++++++++++++++
2 files changed, 70 insertions(+), 1 deletion(-)
create mode 100644 lld/test/MachO/arm64e-reject-mixed.s
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index f6aabe39bf135..930322e4462e6 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -189,7 +189,14 @@ static bool compatWithTargetArch(const InputFile *file, const Header *hdr) {
uint32_t cpuType;
std::tie(cpuType, std::ignore) = getCPUTypeFromArchitecture(config->arch());
- if (hdr->cputype != cpuType) {
+ // arm64 and arm64e are ABI incompatible. arm64e.x1 has the arm64e ABI but
+ // needs a newer CPU, so neither takes it.
+ uint32_t subtype = hdr->cpusubtype & ~CPU_SUBTYPE_MASK;
+ bool isArm64e = subtype == CPU_SUBTYPE_ARM64E;
+ if (hdr->cputype != cpuType ||
+ (is_contained({AK_arm64, AK_arm64e}, config->arch()) &&
+ (subtype == CPU_SUBTYPE_ARM64E_X1 ||
+ isArm64e != (config->arch() == AK_arm64e)))) {
Architecture arch =
getArchitectureFromCpuType(hdr->cputype, hdr->cpusubtype);
auto msg = config->errorForArchMismatch
diff --git a/lld/test/MachO/arm64e-reject-mixed.s b/lld/test/MachO/arm64e-reject-mixed.s
new file mode 100644
index 0000000000000..5d61825d95d87
--- /dev/null
+++ b/lld/test/MachO/arm64e-reject-mixed.s
@@ -0,0 +1,62 @@
+# REQUIRES: aarch64
+## arm64 and arm64e share a CPU type but are ABI incompatible: an arm64e link
+## ignores arm64 objects and an arm64 link ignores arm64e objects, and both
+## reject them under -arch_errors_fatal.
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/test.o %t/test.s
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/arm64.o %t/lib.s
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/arm64e.o %t/lib.s
+
+# RUN: not %no-fatal-warnings-lld -arch arm64e -platform_version macos 13.0 13.0 \
+# RUN: %t/test.o %t/arm64.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=WARN
+# RUN: not %lld -arch arm64e -platform_version macos 13.0 13.0 \
+# RUN: -arch_errors_fatal %t/test.o %t/arm64.o -o /dev/null 2>&1 | \
+# RUN: FileCheck %s --check-prefix=ERR
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 %t/test.o \
+# RUN: %t/arm64e.o -o /dev/null
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/test-arm64.o \
+# RUN: %t/test.s
+# RUN: not %no-fatal-warnings-lld -arch arm64 -platform_version macos 13.0 13.0 \
+# RUN: %t/test-arm64.o %t/arm64e.o -o /dev/null 2>&1 | \
+# RUN: FileCheck %s --check-prefix=WARN-ARM64
+# RUN: not %lld -arch arm64 -platform_version macos 13.0 13.0 \
+# RUN: -arch_errors_fatal %t/test-arm64.o %t/arm64e.o -o /dev/null 2>&1 | \
+# RUN: FileCheck %s --check-prefix=ERR-ARM64
+# RUN: %lld -arch arm64 -platform_version macos 13.0 13.0 %t/test-arm64.o \
+# RUN: %t/arm64.o -o /dev/null
+
+## arm64e.x1 objects have the arm64e ABI but need a newer CPU than arm64e.
+# RUN: llvm-mc -filetype=obj -triple=arm64e.x1-apple-macos -o %t/arm64e-x1.o \
+# RUN: %t/lib.s
+# RUN: not %no-fatal-warnings-lld -arch arm64 -platform_version macos 13.0 13.0 \
+# RUN: %t/test-arm64.o %t/arm64e-x1.o -o /dev/null 2>&1 | \
+# RUN: FileCheck %s --check-prefix=WARN-X1-ARM64
+# RUN: not %no-fatal-warnings-lld -arch arm64e -platform_version macos 13.0 13.0 \
+# RUN: %t/test.o %t/arm64e-x1.o -o /dev/null 2>&1 | \
+# RUN: FileCheck %s --check-prefix=WARN-X1-ARM64E
+
+# WARN: warning: {{.*}}arm64.o has architecture arm64 which is incompatible with target architecture arm64e
+# WARN: error: undefined symbol: _helper
+# ERR: error: {{.*}}arm64.o has architecture arm64 which is incompatible with target architecture arm64e
+
+# WARN-ARM64: warning: {{.*}}arm64e.o has architecture arm64e which is incompatible with target architecture arm64
+# WARN-ARM64: error: undefined symbol: _helper
+# ERR-ARM64: error: {{.*}}arm64e.o has architecture arm64e which is incompatible with target architecture arm64
+
+# WARN-X1-ARM64: warning: {{.*}}arm64e-x1.o has architecture arm64e.x1 which is incompatible with target architecture arm64
+# WARN-X1-ARM64E: warning: {{.*}}arm64e-x1.o has architecture arm64e.x1 which is incompatible with target architecture arm64e
+
+#--- test.s
+.globl _main
+.p2align 2
+_main:
+ bl _helper
+ ret
+
+#--- lib.s
+.globl _helper
+.p2align 2
+_helper:
+ ret
>From 70acb5a3baae60df3f1e34a2a78f50db6ce0e1bb Mon Sep 17 00:00:00 2001
From: Oskar Wirga <oskar at wirga.com>
Date: Wed, 7 Oct 2026 10:01:55 -0400
Subject: [PATCH 7/7] [lld-macho] Don't merge ObjC categories for arm64e
Category merging rewrites category data without regard to the signing
schemas of the authenticated pointers in it, which produces a binary that
crashes when the runtime authenticates them. Until merging preserves the
schemas, warn about -objc_category_merging on arm64e and leave the
categories as they are.
Assisted-by: Claude Code
---
lld/MachO/Driver.cpp | 9 +-
lld/test/MachO/arm64e-objc-category-merging.s | 104 ++++++++++++++++++
2 files changed, 111 insertions(+), 2 deletions(-)
create mode 100644 lld/test/MachO/arm64e-objc-category-merging.s
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 9485c0c74fd18..0940cdbad3d1e 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -2552,8 +2552,13 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
// Category merging uses "->live = false" to erase old category data, so
// it has to run after dead-stripping (markLive).
if (args.hasFlag(OPT_objc_category_merging, OPT_no_objc_category_merging,
- false))
- objc::mergeCategories();
+ false)) {
+ // Merging doesn't preserve the signing schemas in arm64e category data.
+ if (config->arch() == AK_arm64e)
+ warn("-objc_category_merging is not supported on arm64e, ignoring");
+ else
+ objc::mergeCategories();
+ }
computeColdness();
diff --git a/lld/test/MachO/arm64e-objc-category-merging.s b/lld/test/MachO/arm64e-objc-category-merging.s
new file mode 100644
index 0000000000000..f9f80ca1a134e
--- /dev/null
+++ b/lld/test/MachO/arm64e-objc-category-merging.s
@@ -0,0 +1,104 @@
+# REQUIRES: aarch64
+## Category merging does not preserve the signing schemas of the pointers in
+## arm64e category data, so an arm64e link warns about -objc_category_merging
+## and keeps the categories as they are.
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/base.o %t/base.s
+# RUN: %lld -arch arm64e -platform_version macos 13.0 13.0 -dylib \
+# RUN: -install_name @executable_path/libbase.dylib %t/base.o -o %t/libbase.dylib
+# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-macos -o %t/cats.o %t/cats.s
+# RUN: %no-fatal-warnings-lld -arch arm64e -platform_version macos 13.0 13.0 \
+# RUN: -dylib -objc_category_merging %t/libbase.dylib %t/cats.o \
+# RUN: -o %t/cats.dylib 2>&1 | FileCheck %s --check-prefix=WARN
+# RUN: llvm-objdump --macho --section-headers --syms %t/cats.dylib > %t/out
+# RUN: llvm-objdump --macho --dyld-info %t/cats.dylib >> %t/out
+# RUN: FileCheck %s < %t/out
+
+# WARN: warning: -objc_category_merging is not supported on arm64e, ignoring
+
+## Both categories are still listed, and each keeps its signed method list
+## pointer (key DA, diversity 0xC310, address diversity).
+# CHECK: __objc_catlist 00000010
+# CHECK: [[#%x,CAT1:]] l O __DATA,__objc_const __OBJC_$_CATEGORY_MyBaseClass_$_Category01
+# CHECK: [[#%x,CAT2:]] l O __DATA,__objc_const __OBJC_$_CATEGORY_MyBaseClass_$_Category02
+# CHECK-LABEL: dyld information:
+# CHECK: 0x[[#%X,CAT1+16]] 0x{{[0-9A-F]+}} rebase 0xC310 true DA
+# CHECK: 0x[[#%X,CAT2+16]] 0x{{[0-9A-F]+}} rebase 0xC310 true DA
+
+#--- base.s
+.section __DATA,__objc_data
+.globl _OBJC_CLASS_$_MyBaseClass
+_OBJC_CLASS_$_MyBaseClass:
+ .quad 0
+
+#--- cats.s
+.p2align 2
+"-[MyBaseClass(Category01) cat01_InstanceMethod]":
+ ret
+"-[MyBaseClass(Category02) cat02_InstanceMethod]":
+ ret
+
+.section __TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_:
+ .asciz "Category01"
+l_OBJC_CLASS_NAME_.1:
+ .asciz "Category02"
+
+.section __TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_:
+ .asciz "cat01_InstanceMethod"
+l_OBJC_METH_VAR_NAME_.2:
+ .asciz "cat02_InstanceMethod"
+
+.section __TEXT,__objc_methtype,cstring_literals
+l_OBJC_METH_VAR_TYPE_:
+ .asciz "v16 at 0:8"
+
+.section __DATA,__objc_const
+.p2align 3
+__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category01:
+ .long 24
+ .long 1
+ .quad l_OBJC_METH_VAR_NAME_
+ .quad l_OBJC_METH_VAR_TYPE_
+ .quad "-[MyBaseClass(Category01) cat01_InstanceMethod]"@AUTH(ia,0,addr)
+__OBJC_$_CATEGORY_MyBaseClass_$_Category01:
+ .quad l_OBJC_CLASS_NAME_
+ .quad _OBJC_CLASS_$_MyBaseClass
+ .quad __OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category01 at AUTH(da,0xc310,addr)
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad 0
+ .long 64
+ .space 4
+__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02:
+ .long 24
+ .long 1
+ .quad l_OBJC_METH_VAR_NAME_.2
+ .quad l_OBJC_METH_VAR_TYPE_
+ .quad "-[MyBaseClass(Category02) cat02_InstanceMethod]"@AUTH(ia,0,addr)
+__OBJC_$_CATEGORY_MyBaseClass_$_Category02:
+ .quad l_OBJC_CLASS_NAME_.1
+ .quad _OBJC_CLASS_$_MyBaseClass
+ .quad __OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02 at AUTH(da,0xc310,addr)
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad 0
+ .long 64
+ .space 4
+
+.section __DATA,__objc_catlist,regular,no_dead_strip
+.p2align 3
+l_OBJC_LABEL_CATEGORY_$:
+ .quad __OBJC_$_CATEGORY_MyBaseClass_$_Category01
+ .quad __OBJC_$_CATEGORY_MyBaseClass_$_Category02
+
+.section __DATA,__objc_imageinfo,regular,no_dead_strip
+L_OBJC_IMAGE_INFO:
+ .long 0
+ .long 96
+
+.subsections_via_symbols
More information about the llvm-branch-commits
mailing list