1//===-- AArch64MachObjectWriter.cpp - ARM Mach Object Writer --------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "MCTargetDesc/AArch64FixupKinds.h"
10#include "MCTargetDesc/AArch64MCAsmInfo.h"
11#include "MCTargetDesc/AArch64MCTargetDesc.h"
12#include "llvm/ADT/Twine.h"
13#include "llvm/BinaryFormat/MachO.h"
14#include "llvm/MC/MCAsmInfo.h"
15#include "llvm/MC/MCAsmInfoDarwin.h"
16#include "llvm/MC/MCAssembler.h"
17#include "llvm/MC/MCContext.h"
18#include "llvm/MC/MCExpr.h"
19#include "llvm/MC/MCFixup.h"
20#include "llvm/MC/MCMachObjectWriter.h"
21#include "llvm/MC/MCSection.h"
22#include "llvm/MC/MCSectionMachO.h"
23#include "llvm/MC/MCSymbol.h"
24#include "llvm/MC/MCValue.h"
25#include "llvm/Support/Casting.h"
26#include "llvm/Support/MathExtras.h"
27#include <cassert>
28#include <cstdint>
29
30using namespace llvm;
31
32namespace {
33
34class AArch64MachObjectWriter : public MCMachObjectTargetWriter {
35 bool getAArch64FixupKindMachOInfo(const MCFixup &Fixup, unsigned &RelocType,
36 AArch64::Specifier Spec, unsigned &Log2Size,
37 const MCAssembler &Asm);
38
39public:
40 AArch64MachObjectWriter(uint32_t CPUType, uint32_t CPUSubtype, bool IsILP32)
41 : MCMachObjectTargetWriter(!IsILP32 /* is64Bit */, CPUType, CPUSubtype) {}
42
43 void recordRelocation(MachObjectWriter *Writer, MCAssembler &Asm,
44 const MCFragment *Fragment, const MCFixup &Fixup,
45 MCValue Target, uint64_t &FixedValue) override;
46};
47
48} // end anonymous namespace
49
50bool AArch64MachObjectWriter::getAArch64FixupKindMachOInfo(
51 const MCFixup &Fixup, unsigned &RelocType, AArch64::Specifier Spec,
52 unsigned &Log2Size, const MCAssembler &Asm) {
53 RelocType = unsigned(MachO::ARM64_RELOC_UNSIGNED);
54 Log2Size = ~0U;
55
56 switch (Fixup.getTargetKind()) {
57 default:
58 return false;
59
60 case FK_Data_1:
61 Log2Size = Log2_32(Value: 1);
62 return true;
63 case FK_Data_2:
64 Log2Size = Log2_32(Value: 2);
65 return true;
66 case FK_Data_4:
67 Log2Size = Log2_32(Value: 4);
68 if (Spec == AArch64::S_MACHO_GOT)
69 RelocType = unsigned(MachO::ARM64_RELOC_POINTER_TO_GOT);
70 return true;
71 case FK_Data_8:
72 Log2Size = Log2_32(Value: 8);
73 if (Spec == AArch64::S_MACHO_GOT)
74 RelocType = unsigned(MachO::ARM64_RELOC_POINTER_TO_GOT);
75 return true;
76 case AArch64::fixup_aarch64_add_imm12:
77 case AArch64::fixup_aarch64_ldst_imm12_scale1:
78 case AArch64::fixup_aarch64_ldst_imm12_scale2:
79 case AArch64::fixup_aarch64_ldst_imm12_scale4:
80 case AArch64::fixup_aarch64_ldst_imm12_scale8:
81 case AArch64::fixup_aarch64_ldst_imm12_scale16:
82 Log2Size = Log2_32(Value: 4);
83 switch (Spec) {
84 default:
85 return false;
86 case AArch64::S_MACHO_PAGEOFF:
87 RelocType = unsigned(MachO::ARM64_RELOC_PAGEOFF12);
88 return true;
89 case AArch64::S_MACHO_GOTPAGEOFF:
90 RelocType = unsigned(MachO::ARM64_RELOC_GOT_LOAD_PAGEOFF12);
91 return true;
92 case AArch64::S_MACHO_TLVPPAGEOFF:
93 RelocType = unsigned(MachO::ARM64_RELOC_TLVP_LOAD_PAGEOFF12);
94 return true;
95 }
96 case AArch64::fixup_aarch64_pcrel_adrp_imm21:
97 Log2Size = Log2_32(Value: 4);
98 // This encompasses the relocation for the whole 21-bit value.
99 switch (Spec) {
100 default:
101 reportError(L: Fixup.getLoc(), Msg: "ADR/ADRP relocations must be GOT relative");
102 return false;
103 case AArch64::S_MACHO_PAGE:
104 RelocType = unsigned(MachO::ARM64_RELOC_PAGE21);
105 return true;
106 case AArch64::S_MACHO_GOTPAGE:
107 RelocType = unsigned(MachO::ARM64_RELOC_GOT_LOAD_PAGE21);
108 return true;
109 case AArch64::S_MACHO_TLVPPAGE:
110 RelocType = unsigned(MachO::ARM64_RELOC_TLVP_LOAD_PAGE21);
111 return true;
112 }
113 return true;
114 case AArch64::fixup_aarch64_pcrel_branch26:
115 case AArch64::fixup_aarch64_pcrel_call26:
116 Log2Size = Log2_32(Value: 4);
117 RelocType = unsigned(MachO::ARM64_RELOC_BRANCH26);
118 return true;
119 }
120}
121
122static bool canUseLocalRelocation(const MCSectionMachO &Section,
123 const MCSymbol &Symbol, unsigned Log2Size) {
124 // Debug info sections can use local relocations.
125 if (Section.hasAttribute(Value: MachO::S_ATTR_DEBUG))
126 return true;
127
128 // Otherwise, only pointer sized relocations are supported.
129 if (Log2Size != 3)
130 return false;
131
132 // But only if they don't point to a few forbidden sections.
133 if (!Symbol.isInSection())
134 return true;
135 const MCSectionMachO &RefSec = cast<MCSectionMachO>(Val&: Symbol.getSection());
136 if (RefSec.getType() == MachO::S_CSTRING_LITERALS)
137 return false;
138
139 if (RefSec.getSegmentName() == "__DATA" &&
140 (RefSec.getName() == "__cfstring" ||
141 RefSec.getName() == "__objc_classrefs"))
142 return false;
143
144 return true;
145}
146
147void AArch64MachObjectWriter::recordRelocation(
148 MachObjectWriter *Writer, MCAssembler &Asm, const MCFragment *Fragment,
149 const MCFixup &Fixup, MCValue Target, uint64_t &FixedValue) {
150 unsigned IsPCRel = Fixup.isPCRel();
151
152 // See <reloc.h>.
153 uint32_t FixupOffset = Asm.getFragmentOffset(F: *Fragment);
154 unsigned Log2Size = 0;
155 int64_t Value = 0;
156 unsigned Index = 0;
157 unsigned Type = 0;
158 unsigned Kind = Fixup.getKind();
159 const MCSymbol *RelSymbol = nullptr;
160
161 FixupOffset += Fixup.getOffset();
162
163 // AArch64 pcrel relocation addends do not include the section offset.
164 if (IsPCRel)
165 FixedValue += FixupOffset;
166
167 // ADRP fixups use relocations for the whole symbol value and only
168 // put the addend in the instruction itself. Clear out any value the
169 // generic code figured out from the sybmol definition.
170 if (Kind == AArch64::fixup_aarch64_pcrel_adrp_imm21)
171 FixedValue = 0;
172
173 // imm19 relocations are for conditional branches, which require
174 // assembler local symbols. If we got here, that's not what we have,
175 // so complain loudly.
176 if (Kind == AArch64::fixup_aarch64_pcrel_branch19) {
177 reportError(L: Fixup.getLoc(), Msg: "conditional branch requires assembler-local"
178 " label. '" +
179 Target.getAddSym()->getName() +
180 "' is external.");
181 return;
182 }
183
184 // 14-bit branch relocations should only target internal labels, and so
185 // should never get here.
186 if (Kind == AArch64::fixup_aarch64_pcrel_branch14) {
187 reportError(L: Fixup.getLoc(), Msg: "Invalid relocation on conditional branch!");
188 return;
189 }
190
191 if (!getAArch64FixupKindMachOInfo(Fixup, RelocType&: Type, Spec: Target.getSpecifier(),
192 Log2Size, Asm)) {
193 reportError(L: Fixup.getLoc(), Msg: "unknown AArch64 fixup kind!");
194 return;
195 }
196
197 Value = Target.getConstant();
198
199 if (Target.isAbsolute()) { // constant
200 // FIXME: Should this always be extern?
201 // SymbolNum of 0 indicates the absolute section.
202 Type = MachO::ARM64_RELOC_UNSIGNED;
203
204 if (IsPCRel) {
205 reportError(L: Fixup.getLoc(), Msg: "PC relative absolute relocation!");
206 return;
207
208 // FIXME: x86_64 sets the type to a branch reloc here. Should we do
209 // something similar?
210 }
211 } else if (auto *B = Target.getSubSym()) { // A - B + constant
212 const MCSymbol *A = Target.getAddSym();
213 const MCSymbol *A_Base = Writer->getAtom(S: *A);
214 const MCSymbol *B_Base = Writer->getAtom(S: *B);
215
216 // Check for "_foo@got - .", which comes through here as:
217 // Ltmp0:
218 // ... _foo@got - Ltmp0
219 if (Target.getSpecifier() == AArch64::S_MACHO_GOT &&
220 Asm.getSymbolOffset(S: *B) ==
221 Asm.getFragmentOffset(F: *Fragment) + Fixup.getOffset()) {
222 // SymB is the PC, so use a PC-rel pointer-to-GOT relocation.
223 Type = MachO::ARM64_RELOC_POINTER_TO_GOT;
224 IsPCRel = 1;
225 MachO::any_relocation_info MRE;
226 MRE.r_word0 = FixupOffset;
227 MRE.r_word1 = (IsPCRel << 24) | (Log2Size << 25) | (Type << 28);
228 Writer->addRelocation(RelSymbol: A_Base, Sec: Fragment->getParent(), MRE);
229 return;
230 } else if (Target.getSpecifier() != AArch64::S_None) {
231 // Otherwise, neither symbol can be modified.
232 reportError(L: Fixup.getLoc(), Msg: "unsupported relocation of modified symbol");
233 return;
234 }
235
236 // We don't support PCrel relocations of differences.
237 if (IsPCRel) {
238 reportError(L: Fixup.getLoc(), Msg: "unsupported pc-relative relocation of "
239 "difference");
240 return;
241 }
242
243 // AArch64 always uses external relocations. If there is no symbol to use as
244 // a base address (a local symbol with no preceding non-local symbol),
245 // error out.
246 //
247 // FIXME: We should probably just synthesize an external symbol and use
248 // that.
249 if (!A_Base) {
250 reportError(L: Fixup.getLoc(),
251 Msg: "unsupported relocation of local symbol '" + A->getName() +
252 "'. Must have non-local symbol earlier in section.");
253 return;
254 }
255 if (!B_Base) {
256 reportError(L: Fixup.getLoc(),
257 Msg: "unsupported relocation of local symbol '" + B->getName() +
258 "'. Must have non-local symbol earlier in section.");
259 return;
260 }
261
262 if (A_Base == B_Base && A_Base) {
263 reportError(L: Fixup.getLoc(), Msg: "unsupported relocation with identical base");
264 return;
265 }
266
267 Value +=
268 (!A->getFragment() ? 0 : Writer->getSymbolAddress(S: *A)) -
269 (!A_Base || !A_Base->getFragment() ? 0
270 : Writer->getSymbolAddress(S: *A_Base));
271 Value -=
272 (!B->getFragment() ? 0 : Writer->getSymbolAddress(S: *B)) -
273 (!B_Base || !B_Base->getFragment() ? 0
274 : Writer->getSymbolAddress(S: *B_Base));
275
276 Type = MachO::ARM64_RELOC_UNSIGNED;
277
278 MachO::any_relocation_info MRE;
279 MRE.r_word0 = FixupOffset;
280 MRE.r_word1 = (IsPCRel << 24) | (Log2Size << 25) | (Type << 28);
281 Writer->addRelocation(RelSymbol: A_Base, Sec: Fragment->getParent(), MRE);
282
283 RelSymbol = B_Base;
284 Type = MachO::ARM64_RELOC_SUBTRACTOR;
285 } else { // A + constant
286 const MCSymbol *Symbol = Target.getAddSym();
287 const MCSectionMachO &Section =
288 static_cast<const MCSectionMachO &>(*Fragment->getParent());
289
290 bool CanUseLocalRelocation =
291 canUseLocalRelocation(Section, Symbol: *Symbol, Log2Size);
292 if (Symbol->isTemporary() && (Value || !CanUseLocalRelocation)) {
293 // Make sure that the symbol is actually in a section here. If it isn't,
294 // emit an error and exit.
295 if (!Symbol->isInSection()) {
296 reportError(L: Fixup.getLoc(),
297 Msg: "unsupported relocation of local symbol '" +
298 Symbol->getName() +
299 "'. Must have non-local symbol earlier in section.");
300 return;
301 }
302 const MCSection &Sec = Symbol->getSection();
303 if (!MCAsmInfoDarwin::isSectionAtomizableBySymbols(Section: Sec))
304 Symbol->setUsedInReloc();
305 }
306
307 const MCSymbol *Base = Writer->getAtom(S: *Symbol);
308
309 // If the symbol is a variable it can either be in a section and
310 // we have a base or it is absolute and should have been expanded.
311 assert(!Symbol->isVariable() || Base);
312
313 // Relocations inside debug sections always use local relocations when
314 // possible. This seems to be done because the debugger doesn't fully
315 // understand relocation entries and expects to find values that
316 // have already been fixed up.
317 if (Symbol->isInSection()) {
318 if (Section.hasAttribute(Value: MachO::S_ATTR_DEBUG))
319 Base = nullptr;
320 }
321
322 // AArch64 uses external relocations as much as possible. For debug
323 // sections, and for pointer-sized relocations (.quad), we allow section
324 // relocations. It's code sections that run into trouble.
325 if (Base) {
326 RelSymbol = Base;
327
328 // Add the local offset, if needed.
329 if (Base != Symbol)
330 Value += Asm.getSymbolOffset(S: *Symbol) - Asm.getSymbolOffset(S: *Base);
331 } else if (Symbol->isInSection()) {
332 if (!CanUseLocalRelocation) {
333 reportError(L: Fixup.getLoc(),
334 Msg: "unsupported relocation of local symbol '" +
335 Symbol->getName() +
336 "'. Must have non-local symbol earlier in section.");
337 return;
338 }
339 // Adjust the relocation to be section-relative.
340 // The index is the section ordinal (1-based).
341 const MCSection &Sec = Symbol->getSection();
342 Index = Sec.getOrdinal() + 1;
343 Value += Writer->getSymbolAddress(S: *Symbol);
344
345 if (IsPCRel)
346 Value -= Writer->getFragmentAddress(Asm, Fragment) + Fixup.getOffset() +
347 (1ULL << Log2Size);
348 } else {
349 llvm_unreachable(
350 "This constant variable should have been expanded during evaluation");
351 }
352 }
353
354 // If the relocation kind is Branch26, Page21, or Pageoff12, any addend
355 // is represented via an Addend relocation, not encoded directly into
356 // the instruction.
357 if ((Type == MachO::ARM64_RELOC_BRANCH26 ||
358 Type == MachO::ARM64_RELOC_PAGE21 ||
359 Type == MachO::ARM64_RELOC_PAGEOFF12) &&
360 Value) {
361 if (!isInt<24>(x: Value)) {
362 reportError(L: Fixup.getLoc(), Msg: "addend too big for relocation");
363 return;
364 }
365
366 MachO::any_relocation_info MRE;
367 MRE.r_word0 = FixupOffset;
368 MRE.r_word1 =
369 (Index << 0) | (IsPCRel << 24) | (Log2Size << 25) | (Type << 28);
370 Writer->addRelocation(RelSymbol, Sec: Fragment->getParent(), MRE);
371
372 // Now set up the Addend relocation.
373 Type = MachO::ARM64_RELOC_ADDEND;
374 Index = Value;
375 RelSymbol = nullptr;
376 IsPCRel = 0;
377 Log2Size = 2;
378
379 // Put zero into the instruction itself. The addend is in the relocation.
380 Value = 0;
381 }
382
383 if (Target.getSpecifier() == AArch64::S_AUTH ||
384 Target.getSpecifier() == AArch64::S_AUTHADDR) {
385 auto *Expr = cast<AArch64AuthMCExpr>(Val: Fixup.getValue());
386
387 assert(Type == MachO::ARM64_RELOC_UNSIGNED);
388
389 if (IsPCRel) {
390 reportError(L: Fixup.getLoc(), Msg: "invalid PC relative auth relocation");
391 return;
392 }
393
394 if (Log2Size != 3) {
395 reportError(L: Fixup.getLoc(),
396 Msg: "invalid auth relocation size, must be 8 bytes");
397 return;
398 }
399
400 if (Target.getSubSym()) {
401 reportError(L: Fixup.getLoc(),
402 Msg: "invalid auth relocation, can't reference two symbols");
403 return;
404 }
405
406 uint16_t Discriminator = Expr->getDiscriminator();
407 AArch64PACKey::ID Key = Expr->getKey();
408
409 if (!isInt<32>(x: Value)) {
410 reportError(L: Fixup.getLoc(), Msg: "addend too big for relocation");
411 return;
412 }
413
414 Type = MachO::ARM64_RELOC_AUTHENTICATED_POINTER;
415 Value = (uint32_t(Value)) | (uint64_t(Discriminator) << 32) |
416 (uint64_t(Expr->hasAddressDiversity()) << 48) |
417 (uint64_t(Key) << 49) | (1ULL << 63);
418 }
419
420 // If there's any addend left to handle, encode it in the instruction.
421 FixedValue = Value;
422
423 // struct relocation_info (8 bytes)
424 MachO::any_relocation_info MRE;
425 MRE.r_word0 = FixupOffset;
426 MRE.r_word1 =
427 (Index << 0) | (IsPCRel << 24) | (Log2Size << 25) | (Type << 28);
428 Writer->addRelocation(RelSymbol, Sec: Fragment->getParent(), MRE);
429}
430
431std::unique_ptr<MCObjectTargetWriter>
432llvm::createAArch64MachObjectWriter(uint32_t CPUType, uint32_t CPUSubtype,
433 bool IsILP32) {
434 return std::make_unique<AArch64MachObjectWriter>(args&: CPUType, args&: CPUSubtype,
435 args&: IsILP32);
436}
437