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 | |
30 | using namespace llvm; |
31 | |
32 | namespace { |
33 | |
34 | class AArch64MachObjectWriter : public MCMachObjectTargetWriter { |
35 | bool getAArch64FixupKindMachOInfo(const MCFixup &Fixup, unsigned &RelocType, |
36 | AArch64::Specifier Spec, unsigned &Log2Size, |
37 | const MCAssembler &Asm); |
38 | |
39 | public: |
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 | |
50 | bool 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 | |
122 | static 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 | |
147 | void 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 | |
431 | std::unique_ptr<MCObjectTargetWriter> |
432 | llvm::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 | |