Skip to content

Commit 0d30e92

Browse files
committed
[lld-macho] Add support for emitting chained fixups
This commit adds support for chained fixups, which were introduced in Apple's late 2020 OS releases. This format replaces the dyld opcodes used for supplying rebase and binding information, and encodes most of that data directly in the memory location that will have the fixup applied. This reduces binary size and is a requirement for page-in linking, which will be available starting with macOS 13. A high-level overview of the format and my implementation can be found in SyntheticSections.h. This feature is currently gated behind the `-fixup_chains` flag, and will be enabled by default for supported targets in a later commit. Like in ld64, lazy binding is disabled when chained fixups are in use, and the `-init_offsets` transformation is performed by default. Differential Revision: https://reviews.llvm.org/D132560
1 parent 8344dfa commit 0d30e92

24 files changed

+902
-94
lines changed

lld/MachO/Arch/ARM.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct ARM : TargetInfo {
3232
void relocateOne(uint8_t *loc, const Reloc &, uint64_t va,
3333
uint64_t pc) const override;
3434

35-
void writeStub(uint8_t *buf, const Symbol &) const override;
35+
void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
3636
void writeStubHelperHeader(uint8_t *buf) const override;
3737
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
3838
uint64_t entryAddr) const override;
@@ -140,7 +140,7 @@ void ARM::relocateOne(uint8_t *loc, const Reloc &r, uint64_t value,
140140
}
141141
}
142142

143-
void ARM::writeStub(uint8_t *buf, const Symbol &sym) const {
143+
void ARM::writeStub(uint8_t *buf, const Symbol &sym, uint64_t) const {
144144
fatal("TODO: implement this");
145145
}
146146

lld/MachO/Arch/ARM64.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace {
3131

3232
struct ARM64 : ARM64Common {
3333
ARM64();
34-
void writeStub(uint8_t *buf, const Symbol &) const override;
34+
void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
3535
void writeStubHelperHeader(uint8_t *buf) const override;
3636
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
3737
uint64_t entryAddr) const override;
@@ -77,8 +77,9 @@ static constexpr uint32_t stubCode[] = {
7777
0xd61f0200, // 08: br x16
7878
};
7979

80-
void ARM64::writeStub(uint8_t *buf8, const Symbol &sym) const {
81-
::writeStub<LP64>(buf8, stubCode, sym);
80+
void ARM64::writeStub(uint8_t *buf8, const Symbol &sym,
81+
uint64_t pointerVA) const {
82+
::writeStub(buf8, stubCode, sym, pointerVA);
8283
}
8384

8485
static constexpr uint32_t stubHelperHeaderCode[] = {

lld/MachO/Arch/ARM64Common.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,15 @@ inline uint64_t pageBits(uint64_t address) {
107107
return address & pageMask;
108108
}
109109

110-
template <class LP>
111110
inline void writeStub(uint8_t *buf8, const uint32_t stubCode[3],
112-
const macho::Symbol &sym) {
111+
const macho::Symbol &sym, uint64_t pointerVA) {
113112
auto *buf32 = reinterpret_cast<uint32_t *>(buf8);
114113
constexpr size_t stubCodeSize = 3 * sizeof(uint32_t);
115114
SymbolDiagnostic d = {&sym, "stub"};
116115
uint64_t pcPageBits =
117116
pageBits(in.stubs->addr + sym.stubsIndex * stubCodeSize);
118-
uint64_t lazyPointerVA =
119-
in.lazyPointers->addr + sym.stubsIndex * LP::wordSize;
120-
encodePage21(&buf32[0], d, stubCode[0], pageBits(lazyPointerVA) - pcPageBits);
121-
encodePageOff12(&buf32[1], d, stubCode[1], lazyPointerVA);
117+
encodePage21(&buf32[0], d, stubCode[0], pageBits(pointerVA) - pcPageBits);
118+
encodePageOff12(&buf32[1], d, stubCode[1], pointerVA);
122119
buf32[2] = stubCode[2];
123120
}
124121

lld/MachO/Arch/ARM64_32.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace {
2929

3030
struct ARM64_32 : ARM64Common {
3131
ARM64_32();
32-
void writeStub(uint8_t *buf, const Symbol &) const override;
32+
void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
3333
void writeStubHelperHeader(uint8_t *buf) const override;
3434
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
3535
uint64_t entryAddr) const override;
@@ -70,8 +70,9 @@ static constexpr uint32_t stubCode[] = {
7070
0xd61f0200, // 08: br x16
7171
};
7272

73-
void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym) const {
74-
::writeStub<ILP32>(buf8, stubCode, sym);
73+
void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym,
74+
uint64_t pointerVA) const {
75+
::writeStub(buf8, stubCode, sym, pointerVA);
7576
}
7677

7778
static constexpr uint32_t stubHelperHeaderCode[] = {

lld/MachO/Arch/X86_64.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ struct X86_64 : TargetInfo {
3131
void relocateOne(uint8_t *loc, const Reloc &, uint64_t va,
3232
uint64_t relocVA) const override;
3333

34-
void writeStub(uint8_t *buf, const Symbol &) const override;
34+
void writeStub(uint8_t *buf, const Symbol &,
35+
uint64_t pointerVA) const override;
3536
void writeStubHelperHeader(uint8_t *buf) const override;
3637
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
3738
uint64_t entryAddr) const override;
@@ -138,11 +139,11 @@ static constexpr uint8_t stub[] = {
138139
0xff, 0x25, 0, 0, 0, 0, // jmpq *__la_symbol_ptr(%rip)
139140
};
140141

141-
void X86_64::writeStub(uint8_t *buf, const Symbol &sym) const {
142+
void X86_64::writeStub(uint8_t *buf, const Symbol &sym,
143+
uint64_t pointerVA) const {
142144
memcpy(buf, stub, 2); // just copy the two nonzero bytes
143145
uint64_t stubAddr = in.stubs->addr + sym.stubsIndex * sizeof(stub);
144-
writeRipRelative({&sym, "stub"}, buf, stubAddr, sizeof(stub),
145-
in.lazyPointers->addr + sym.stubsIndex * LP64::wordSize);
146+
writeRipRelative({&sym, "stub"}, buf, stubAddr, sizeof(stub), pointerVA);
146147
}
147148

148149
static constexpr uint8_t stubHelperHeader[] = {

lld/MachO/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ struct Configuration {
132132
bool emitDataInCodeInfo = false;
133133
bool emitEncryptionInfo = false;
134134
bool emitInitOffsets = false;
135+
bool emitChainedFixups = false;
135136
bool timeTraceEnabled = false;
136137
bool dataConst = false;
137138
bool dedupLiterals = true;

lld/MachO/Driver.cpp

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,47 @@ static bool dataConstDefault(const InputArgList &args) {
957957
return false;
958958
}
959959

960+
static bool shouldEmitChainedFixups(const InputArgList &args) {
961+
const Arg *arg = args.getLastArg(OPT_fixup_chains, OPT_no_fixup_chains);
962+
if (arg && arg->getOption().matches(OPT_no_fixup_chains))
963+
return false;
964+
965+
bool isRequested = arg != nullptr;
966+
967+
// Version numbers taken from the Xcode 13.3 release notes.
968+
static const std::array<std::pair<PlatformType, VersionTuple>, 4> minVersion =
969+
{{{PLATFORM_MACOS, VersionTuple(11, 0)},
970+
{PLATFORM_IOS, VersionTuple(13, 4)},
971+
{PLATFORM_TVOS, VersionTuple(14, 0)},
972+
{PLATFORM_WATCHOS, VersionTuple(7, 0)}}};
973+
PlatformType platform = removeSimulator(config->platformInfo.target.Platform);
974+
auto it = llvm::find_if(minVersion,
975+
[&](const auto &p) { return p.first == platform; });
976+
if (it != minVersion.end() && it->second > config->platformInfo.minimum) {
977+
if (!isRequested)
978+
return false;
979+
980+
warn("-fixup_chains requires " + getPlatformName(config->platform()) + " " +
981+
it->second.getAsString() + ", which is newer than target minimum of " +
982+
config->platformInfo.minimum.getAsString());
983+
}
984+
985+
if (!is_contained({AK_x86_64, AK_x86_64h, AK_arm64}, config->arch())) {
986+
if (isRequested)
987+
error("-fixup_chains is only supported on x86_64 and arm64 targets");
988+
return false;
989+
}
990+
991+
if (!config->isPic) {
992+
if (isRequested)
993+
error("-fixup_chains is incompatible with -no_pie");
994+
return false;
995+
}
996+
997+
// TODO: Enable by default once stable.
998+
return isRequested;
999+
}
1000+
9601001
void SymbolPatterns::clear() {
9611002
literals.clear();
9621003
globs.clear();
@@ -1376,6 +1417,11 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
13761417
}
13771418
}
13781419

1420+
config->isPic = config->outputType == MH_DYLIB ||
1421+
config->outputType == MH_BUNDLE ||
1422+
(config->outputType == MH_EXECUTE &&
1423+
args.hasFlag(OPT_pie, OPT_no_pie, true));
1424+
13791425
// Must be set before any InputSections and Symbols are created.
13801426
config->deadStrip = args.hasArg(OPT_dead_strip);
13811427

@@ -1475,7 +1521,9 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
14751521
config->emitBitcodeBundle = args.hasArg(OPT_bitcode_bundle);
14761522
config->emitDataInCodeInfo =
14771523
args.hasFlag(OPT_data_in_code_info, OPT_no_data_in_code_info, true);
1478-
config->emitInitOffsets = args.hasArg(OPT_init_offsets);
1524+
config->emitChainedFixups = shouldEmitChainedFixups(args);
1525+
config->emitInitOffsets =
1526+
config->emitChainedFixups || args.hasArg(OPT_init_offsets);
14791527
config->icfLevel = getICFLevel(args);
14801528
config->dedupLiterals =
14811529
args.hasFlag(OPT_deduplicate_literals, OPT_icf_eq, false) ||
@@ -1698,11 +1746,6 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
16981746
initLLVM(); // must be run before any call to addFile()
16991747
createFiles(args);
17001748

1701-
config->isPic = config->outputType == MH_DYLIB ||
1702-
config->outputType == MH_BUNDLE ||
1703-
(config->outputType == MH_EXECUTE &&
1704-
args.hasFlag(OPT_pie, OPT_no_pie, true));
1705-
17061749
// Now that all dylibs have been loaded, search for those that should be
17071750
// re-exported.
17081751
{

lld/MachO/InputSection.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
181181
const Reloc &r = relocs[i];
182182
uint8_t *loc = buf + r.offset;
183183
uint64_t referentVA = 0;
184+
185+
const bool needsFixup = config->emitChainedFixups &&
186+
target->hasAttr(r.type, RelocAttrBits::UNSIGNED);
184187
if (target->hasAttr(r.type, RelocAttrBits::SUBTRAHEND)) {
185188
const Symbol *fromSym = r.referent.get<Symbol *>();
186189
const Reloc &minuend = relocs[++i];
@@ -205,17 +208,24 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
205208
}
206209
referentVA = resolveSymbolVA(referentSym, r.type) + r.addend;
207210

208-
if (isThreadLocalVariables(getFlags())) {
211+
if (isThreadLocalVariables(getFlags()) && isa<Defined>(referentSym)) {
209212
// References from thread-local variable sections are treated as offsets
210213
// relative to the start of the thread-local data memory area, which
211214
// is initialized via copying all the TLV data sections (which are all
212215
// contiguous).
213-
if (isa<Defined>(referentSym))
214-
referentVA -= firstTLVDataSection->addr;
216+
referentVA -= firstTLVDataSection->addr;
217+
} else if (needsFixup) {
218+
writeChainedFixup(loc, referentSym, r.addend);
219+
continue;
215220
}
216221
} else if (auto *referentIsec = r.referent.dyn_cast<InputSection *>()) {
217222
assert(!::shouldOmitFromOutput(referentIsec));
218223
referentVA = referentIsec->getVA(r.addend);
224+
225+
if (needsFixup) {
226+
writeChainedRebase(loc, referentVA);
227+
continue;
228+
}
219229
}
220230
target->relocateOne(loc, r, referentVA, getVA() + r.offset);
221231
}

lld/MachO/InputSection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ constexpr const char bitcodeBundle[] = "__bundle";
299299
constexpr const char cString[] = "__cstring";
300300
constexpr const char cfString[] = "__cfstring";
301301
constexpr const char cgProfile[] = "__cg_profile";
302+
constexpr const char chainFixups[] = "__chainfixups";
302303
constexpr const char codeSignature[] = "__code_signature";
303304
constexpr const char common[] = "__common";
304305
constexpr const char compactUnwind[] = "__compact_unwind";

lld/MachO/Options.td

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,8 +1230,10 @@ def executable_path : Flag<["-"], "executable_path">,
12301230
Flags<[HelpHidden]>,
12311231
Group<grp_undocumented>;
12321232
def fixup_chains : Flag<["-"], "fixup_chains">,
1233-
HelpText<"This option is undocumented in ld64">,
1234-
Flags<[HelpHidden]>,
1233+
HelpText<"Emit chained fixups">,
1234+
Group<grp_undocumented>;
1235+
def no_fixup_chains : Flag<["-"], "no_fixup_chains">,
1236+
HelpText<"Emit fixup information as classic dyld opcodes">,
12351237
Group<grp_undocumented>;
12361238
def fixup_chains_section : Flag<["-"], "fixup_chains_section">,
12371239
HelpText<"This option is undocumented in ld64">,

0 commit comments

Comments
 (0)