diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp new file mode 100644 --- /dev/null +++ b/lld/MachO/Arch/ARM64.cpp @@ -0,0 +1,301 @@ +//===- ARM64.cpp ---------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "InputFiles.h" +#include "Symbols.h" +#include "SyntheticSections.h" +#include "Target.h" + +#include "lld/Common/ErrorHandler.h" +#include "llvm/ADT/Optional.h" +#include "llvm/BinaryFormat/MachO.h" +#include "llvm/Support/Endian.h" + +using namespace llvm::MachO; +using namespace llvm::support::endian; +using namespace lld; +using namespace lld::macho; + +namespace { + +struct ARM64 : TargetInfo { + ARM64(); + + uint64_t getImplicitAddend(MemoryBufferRef, const section_64 &, + const relocation_info &) const override; + void relocateOne(uint8_t *loc, const Reloc &, uint64_t val) const override; + + void writeStub(uint8_t *buf, const macho::Symbol &) const override; + void writeStubHelperHeader(uint8_t *buf) const override; + void writeStubHelperEntry(uint8_t *buf, const DylibSymbol &, + uint64_t entryAddr) const override; + + void prepareSymbolRelocation(lld::macho::Symbol *, const InputSection *, + const Reloc &) override; + uint64_t resolveSymbolVA(uint8_t *buf, const lld::macho::Symbol &, + uint8_t type) const override; +}; + +} // namespace + +// table of relocation validity checks + +struct ARM64RelocAttrs { + RelocationInfoType type; // must match Reloc::type + llvm::Optional tlv; // when applicable, must match DylibSymbol::tlv + bool pcRel; // must match relocation_info::r_pcrel + uint32_t lengths; // must match Reloc::length +}; + +static ARM64RelocAttrs arm64RelocAttrs[] = { + {ARM64_RELOC_UNSIGNED, llvm::None, false, 4 | 8}, + {ARM64_RELOC_SUBTRACTOR, llvm::None, false, 4 | 8}, + {ARM64_RELOC_BRANCH26, false, true, 4}, + {ARM64_RELOC_PAGE21, llvm::None, true, 4}, + {ARM64_RELOC_PAGEOFF12, llvm::None, false, 4}, + {ARM64_RELOC_GOT_LOAD_PAGE21, false, true, 4}, + {ARM64_RELOC_GOT_LOAD_PAGEOFF12, false, false, 4}, + {ARM64_RELOC_POINTER_TO_GOT, false, true, 4 | 8}, + {ARM64_RELOC_TLVP_LOAD_PAGE21, true, true, 4}, + {ARM64_RELOC_TLVP_LOAD_PAGEOFF12, true, false, 4}, + {ARM64_RELOC_ADDEND, llvm::None, false, 4}, +}; + +uint64_t ARM64::getImplicitAddend(MemoryBufferRef mb, const section_64 &sec, + const relocation_info &rel) const { + ARM64RelocAttrs &relocAttrs = arm64RelocAttrs[rel.r_type]; + assert(rel.r_type == relocAttrs.type); + assert(rel.r_pcrel == relocAttrs.pcRel); + const auto *buf = reinterpret_cast(mb.getBufferStart()); + const uint8_t *loc = buf + sec.offset + rel.r_address; + + if (isThreadLocalVariables(sec.flags) && rel.r_type != ARM64_RELOC_UNSIGNED) + error("relocations in thread-local variable sections must be " + "ARM64_RELOC_UNSIGNED"); + + switch (rel.r_length) { + case 0: + return *loc; + case 1: + return read16le(loc); + case 2: + return read32le(loc); + case 3: + return read64le(loc); + default: + llvm_unreachable("invalid r_length"); + } +} + +inline uint64_t bitField(uint64_t value, int right, int width, int left) { + return ((value >> right) & ((1 << width) - 1)) << left; +} + +inline uint64_t encodeBranch26(uint64_t base, uint64_t va) { + // 25 0 + // +-----------+---------------------------------------------------+ + // | | imm26 | + // +-----------+---------------------------------------------------+ + assert((va & 3) == 0); + // Since branch destinations are 4-byte aligned, the 2 least- + // significant bits are 0. They are right shifted off the end. + return (base | bitField(va, 2, 26, 0)); +} + +inline uint64_t encodePage21(uint64_t base, uint64_t va) { + // 30 29 23 5 + // +-+---+---------+-------------------------------------+---------+ + // | |ilo| | immhi | | + // +-+---+---------+-------------------------------------+---------+ + return (base | bitField(va, 12, 2, 29) | bitField(va, 14, 19, 5)); +} + +// TODO(gkm): handle scaled offset? +inline uint64_t encodePageOff12(uint64_t base, uint64_t va) { + // 21 10 + // +-------------------+-----------------------+-------------------+ + // | | imm12 | | + // +-------------------+-----------------------+-------------------+ + return (base | bitField(va, 0, 12, 10)); +} + +// For instruction relocations (load/store & address arithmetic), +// r.addend contains the base encoding--opcode and non-relococatable +// operand fields pre-populated--with the relocatable operand +// field--BRANCH26, PAGE21, PAGEOFF12--zero-filled. + +void ARM64::relocateOne(uint8_t *loc, const Reloc &r, uint64_t value) const { + switch (r.type) { + case ARM64_RELOC_BRANCH26: + value = encodeBranch26(r.addend, value); + break; + case ARM64_RELOC_UNSIGNED: + assert(r.addend == 0); // TODO(gkm): temporary sanity check + break; + case ARM64_RELOC_PAGE21: + case ARM64_RELOC_GOT_LOAD_PAGE21: + case ARM64_RELOC_TLVP_LOAD_PAGE21: + value = encodePage21(r.addend, value); + break; + case ARM64_RELOC_PAGEOFF12: + case ARM64_RELOC_GOT_LOAD_PAGEOFF12: + case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: + value = encodePageOff12(r.addend, value); + break; + case ARM64_RELOC_POINTER_TO_GOT: + case ARM64_RELOC_SUBTRACTOR: + case ARM64_RELOC_ADDEND: + error("TODO(gkm): handle relocation type " + std::to_string(r.type)); + break; + default: + llvm_unreachable("unexpected relocation type"); + } + + switch (r.length) { + case 2: + write32le(loc, value); + break; + case 3: + write64le(loc, value); + break; + default: + llvm_unreachable("invalid r_length"); + } +} + +static constexpr uint32_t stubCode[] = { + 0x90000010, // 00: adrp x16, lazy_pointer@page + 0xf9400210, // 04: ldr x16, [x16, lazy_pointer@pageoff] + 0xd61f0200, // 08: br x16 +}; + +void ARM64::writeStub(uint8_t *buf8, const macho::Symbol &sym) const { + auto *buf32 = reinterpret_cast(buf8); + auto pcVA = [&](int i) { return in.stubs->addr + i * sizeof(uint32_t); }; + uint64_t lazyPointerVA = in.lazyPointers->addr + sym.stubsIndex * WordSize; + buf32[0] = encodePage21(stubCode[0], lazyPointerVA - pcVA(0)); + buf32[1] = encodePageOff12(stubCode[1], lazyPointerVA - pcVA(1)); + buf32[2] = stubCode[2]; +} + +static constexpr uint32_t stubHelperHeaderCode[] = { + 0x90000011, // 00: adrp x17, dyld_imageLoaderCache@page + 0x91000231, // 04: add x17, x17, dyld_imageLoaderCache@pageoff + 0xa9bf47f0, // 08: stp x16/x17, [sp, #-16]! + 0x90000010, // 0c: adrp x16, _fast_lazy_bind@page + 0xf9400210, // 10: ldr x16, [x16,_fast_lazy_bind@pageoff] + 0xd61f0200, // 14: br x16 +}; + +void ARM64::writeStubHelperHeader(uint8_t *buf8) const { + auto *buf32 = reinterpret_cast(buf8); + auto pcVA = [&](int i) { return in.stubHelper->addr + i * sizeof(uint32_t); }; + uint64_t loaderVA = in.imageLoaderCache->getVA(); + buf32[0] = encodePage21(stubHelperHeaderCode[0], loaderVA - pcVA(0)); + buf32[1] = encodePageOff12(stubHelperHeaderCode[1], loaderVA - pcVA(1)); + buf32[2] = stubHelperHeaderCode[2]; + uint64_t binderVA = + in.got->addr + in.stubHelper->stubBinder->gotIndex * WordSize; + buf32[3] = encodePage21(stubHelperHeaderCode[3], binderVA - pcVA(3)); + buf32[4] = encodePageOff12(stubHelperHeaderCode[4], binderVA - pcVA(4)); + buf32[5] = stubHelperHeaderCode[5]; +} + +static constexpr uint32_t stubHelperEntryCode[] = { + 0x18000050, // 00: ldr w16, l0 + 0x14000000, // 04: b helperhelper + 0x00000000, // 08: l0: .long 0 +}; + +void ARM64::writeStubHelperEntry(uint8_t *buf8, const DylibSymbol &sym, + uint64_t entryVA) const { + auto *buf32 = reinterpret_cast(buf8); + auto pcVA = [&](int i) { return in.stubHelper->addr + i * sizeof(uint32_t); }; + buf32[0] = stubHelperEntryCode[0]; + buf32[1] = encodeBranch26(stubHelperEntryCode[1], entryVA - pcVA(1)); + buf32[2] = sym.lazyBindOffset; +} + +void ARM64::prepareSymbolRelocation(lld::macho::Symbol *sym, + const InputSection *isec, const Reloc &r) { + ARM64RelocAttrs &relocAttrs = arm64RelocAttrs[r.type]; + assert(r.type == relocAttrs.type); + assert(relocAttrs.tlv == llvm::None || relocAttrs.tlv == sym->isTlv()); + assert((relocAttrs.lengths & (1 << r.length)) != 0); + + switch (r.type) { + case ARM64_RELOC_BRANCH26: + prepareBranchTarget(sym); + break; + case ARM64_RELOC_UNSIGNED: + addNonLazyBindingEntries(sym, isec, r.offset, r.addend); + break; + case ARM64_RELOC_GOT_LOAD_PAGE21: + if (needsBinding(sym)) + in.got->addEntry(sym); + break; + case ARM64_RELOC_TLVP_LOAD_PAGE21: + if (needsBinding(sym)) + in.tlvPointers->addEntry(sym); + break; + case ARM64_RELOC_POINTER_TO_GOT: + in.got->addEntry(sym); + break; + case ARM64_RELOC_PAGE21: + case ARM64_RELOC_PAGEOFF12: + case ARM64_RELOC_GOT_LOAD_PAGEOFF12: + case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: + case ARM64_RELOC_ADDEND: + case ARM64_RELOC_SUBTRACTOR: + break; + default: + llvm_unreachable("unexpected relocation type"); + } +} + +uint64_t ARM64::resolveSymbolVA(uint8_t *buf, const lld::macho::Symbol &sym, + uint8_t type) const { + switch (type) { + case ARM64_RELOC_BRANCH26: + if (sym.isInStubs()) + return in.stubs->addr + sym.stubsIndex * sizeof(stubCode); + return sym.getVA(); + case ARM64_RELOC_UNSIGNED: + case ARM64_RELOC_PAGE21: + case ARM64_RELOC_PAGEOFF12: + return sym.getVA(); + case ARM64_RELOC_GOT_LOAD_PAGE21: + case ARM64_RELOC_GOT_LOAD_PAGEOFF12: + return in.got->addr + sym.gotIndex * WordSize; + case ARM64_RELOC_TLVP_LOAD_PAGE21: + case ARM64_RELOC_TLVP_LOAD_PAGEOFF12: + if (sym.isInGot()) + return in.tlvPointers->addr + sym.gotIndex * WordSize; + assert(isa(&sym)); + return sym.getVA(); + case ARM64_RELOC_SUBTRACTOR: + error("TODO(gkm): handle relocation type " + std::to_string(type)); + return 0; + default: + llvm_unreachable("unexpected relocation type"); + } +} + +ARM64::ARM64() { + cpuType = CPU_TYPE_ARM64; + cpuSubtype = CPU_SUBTYPE_ARM64_ALL; + + stubSize = sizeof(stubCode); + stubHelperHeaderSize = sizeof(stubHelperHeaderCode); + stubHelperEntrySize = sizeof(stubHelperEntryCode); +} + +TargetInfo *macho::createARM64TargetInfo() { + static ARM64 t; + return &t; +} diff --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp --- a/lld/MachO/Arch/X86_64.cpp +++ b/lld/MachO/Arch/X86_64.cpp @@ -121,7 +121,9 @@ } } -void X86_64::relocateOne(uint8_t *loc, const Reloc &r, uint64_t val) const { +void X86_64::relocateOne(uint8_t *loc, const Reloc &r, uint64_t value) const { + // TODO(gkm): I believe r.addend is always zero. Verify that. + value += r.addend; switch (r.type) { case X86_64_RELOC_BRANCH: case X86_64_RELOC_SIGNED: @@ -134,7 +136,7 @@ // These types are only used for pc-relative relocations, so offset by 4 // since the RIP has advanced by 4 at this point. This is only valid when // r_length = 2, which is enforced by validateLength(). - val -= 4; + value -= 4; break; case X86_64_RELOC_UNSIGNED: break; @@ -145,16 +147,16 @@ switch (r.length) { case 0: - *loc = val; + *loc = value; break; case 1: - write16le(loc, val); + write16le(loc, value); break; case 2: - write32le(loc, val); + write32le(loc, value); break; case 3: - write64le(loc, val); + write64le(loc, value); break; default: llvm_unreachable("invalid r_length"); @@ -196,11 +198,6 @@ 0x90, // 0xf: nop }; -static constexpr uint8_t stubHelperEntry[] = { - 0x68, 0, 0, 0, 0, // 0x0: pushq - 0xe9, 0, 0, 0, 0, // 0x5: jmp <__stub_helper> -}; - void X86_64::writeStubHelperHeader(uint8_t *buf) const { memcpy(buf, stubHelperHeader, sizeof(stubHelperHeader)); writeRipRelative(buf, in.stubHelper->addr, 7, in.imageLoaderCache->getVA()); @@ -209,6 +206,11 @@ in.stubHelper->stubBinder->gotIndex * WordSize); } +static constexpr uint8_t stubHelperEntry[] = { + 0x68, 0, 0, 0, 0, // 0x0: pushq + 0xe9, 0, 0, 0, 0, // 0x5: jmp <__stub_helper> +}; + void X86_64::writeStubHelperEntry(uint8_t *buf, const DylibSymbol &sym, uint64_t entryAddr) const { memcpy(buf, stubHelperEntry, sizeof(stubHelperEntry)); diff --git a/lld/MachO/CMakeLists.txt b/lld/MachO/CMakeLists.txt --- a/lld/MachO/CMakeLists.txt +++ b/lld/MachO/CMakeLists.txt @@ -6,6 +6,7 @@ add_lld_library(lldMachO2 Arch/X86_64.cpp + Arch/ARM64.cpp UnwindInfoSection.cpp Driver.cpp DriverUtils.cpp diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -159,15 +159,16 @@ } static TargetInfo *createTargetInfo(opt::InputArgList &args) { - StringRef arch = args.getLastArgValue(OPT_arch, "x86_64"); - config->arch = llvm::MachO::getArchitectureFromName( - args.getLastArgValue(OPT_arch, arch)); - switch (config->arch) { - case llvm::MachO::AK_x86_64: - case llvm::MachO::AK_x86_64h: + // TODO: should unspecified arch be an error rather than defaulting? + StringRef archName = args.getLastArgValue(OPT_arch, "x86_64"); + config->arch = MachO::getArchitectureFromName(archName); + switch (MachO::getCPUTypeFromArchitecture(config->arch).first) { + case MachO::CPU_TYPE_X86_64: return createX86_64TargetInfo(); + case MachO::CPU_TYPE_ARM64: + return createARM64TargetInfo(); default: - fatal("missing or unsupported -arch " + arch); + fatal("missing or unsupported -arch " + archName); } } @@ -371,7 +372,7 @@ MemoryBufferRef mbref = *buffer; size_t priority = std::numeric_limits::max(); for (StringRef rest : args::getLines(mbref)) { - StringRef arch, objectFile, symbol; + StringRef archName, objectFile, symbol; std::array fields; uint8_t fieldCount = 0; @@ -389,12 +390,18 @@ switch (fieldCount) { case 3: - arch = fields[0]; + archName = fields[0]; + if (!isArchString(archName)) { + error("invalid arch \"" + archName + + "\" in order file: expected one of " + + llvm::join(archNames, ", ")); + continue; + } objectFile = fields[1]; symbol = fields[2]; break; case 2: - (isArchString(fields[0]) ? arch : objectFile) = fields[0]; + (isArchString(fields[0]) ? archName : objectFile) = fields[0]; symbol = fields[1]; break; case 1: @@ -405,16 +412,11 @@ default: llvm_unreachable("too many fields in order file"); } - - if (!arch.empty()) { - if (!isArchString(arch)) { - error("invalid arch \"" + arch + "\" in order file: expected one of " + - llvm::join(archNames, ", ")); - continue; - } - - // TODO: Update when we extend support for other archs - if (arch != "x86_64") + if (!archName.empty()) { + // TODO: should we match cpuType? rather than arch here? + // i.e., arch differs x86_64 for x86_64h, and arm64 vs. arm64e + // whereas cpuType unifies them. + if (config->arch != MachO::getArchitectureFromName(archName)) continue; } diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp --- a/lld/MachO/InputSection.cpp +++ b/lld/MachO/InputSection.cpp @@ -35,9 +35,9 @@ memcpy(buf, data.data(), data.size()); for (Reloc &r : relocs) { - uint64_t referentVA = 0; + uint64_t referentValue = 0; if (auto *referentSym = r.referent.dyn_cast()) { - referentVA = + referentValue = target->resolveSymbolVA(buf + r.offset, *referentSym, r.type); if (isThreadLocalVariables(flags)) { @@ -45,16 +45,15 @@ // as offsets relative to the start of the referent section, // instead of as absolute addresses. if (auto *defined = dyn_cast(referentSym)) - referentVA -= defined->isec->parent->addr; + referentValue -= defined->isec->parent->addr; } } else if (auto *referentIsec = r.referent.dyn_cast()) { - referentVA = referentIsec->getVA(); + referentValue = referentIsec->getVA(); } - uint64_t referentVal = referentVA + r.addend; if (r.pcrel) - referentVal -= getVA() + r.offset; - target->relocateOne(buf + r.offset, r, referentVal); + referentValue -= getVA() + r.offset; + target->relocateOne(buf + r.offset, r, referentValue); } } diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -53,8 +53,8 @@ void MachHeaderSection::writeTo(uint8_t *buf) const { auto *hdr = reinterpret_cast(buf); hdr->magic = MachO::MH_MAGIC_64; - hdr->cputype = MachO::CPU_TYPE_X86_64; - hdr->cpusubtype = MachO::CPU_SUBTYPE_X86_64_ALL | MachO::CPU_SUBTYPE_LIB64; + hdr->cputype = target->cpuType; + hdr->cpusubtype = target->cpuSubtype | MachO::CPU_SUBTYPE_LIB64; hdr->filetype = config->outputType; hdr->ncmds = loadCommands.size(); hdr->sizeofcmds = sizeOfCmds; diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h --- a/lld/MachO/Target.h +++ b/lld/MachO/Target.h @@ -68,6 +68,7 @@ }; TargetInfo *createX86_64TargetInfo(); +TargetInfo *createARM64TargetInfo(); extern TargetInfo *target; diff --git a/lld/test/MachO/relocations-arm64.s b/lld/test/MachO/relocations-arm64.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/relocations-arm64.s @@ -0,0 +1,49 @@ +# REQUIRES: aarch64 +# RUN: rm -fr %t +# RUN: split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-ios %t/main.s -o %t/main.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-ios %t/write.s -o %t/write.o +# RUN: %lld -lSystem -arch arm64 -o %t/main %t/main.o %t/write.o +# RUN: llvm-objdump --section-headers --syms -d %t/main | FileCheck %s + +# CHECK-LABEL: Sections: +# CHECK: __cstring {{[0-9a-f]+}} {{([[:xdigit:]]{13})}}[[#%.3x, CSTRING_OFF:]] DATA + +# CHECK-LABEL: SYMBOL TABLE: +# CHECK: [[#%x,FUNC_ADDR:]] {{.*}} _write + +# CHECK-LABEL: <_main>: +## Test ARM64_RELOC_PAGE21 / ARM64_RELOC_PAGEOFF12 +# CHECK: adrp x1, #0 +# CHECK-NEXT: add x1, x1, #[[#%u, CSTRING_OFF]] +## Test ARM64_RELOC_BRANCH26 +# CHECK: bl 0x[[#%x, FUNC_ADDR]] <_write> + +#--- main.s +.section __TEXT,__text +.globl _main +.p2align 2 +_main: + stp x29, x30, [sp, #-16]! + mov x29, sp +Lloh0: + adrp x1, l_.str@PAGE +Lloh1: + add x1, x1, l_.str@PAGEOFF + orr w0, wzr, #0x1 + mov w2, #13 + bl _write + mov w0, #0 + ldp x29, x30, [sp], #16 + ret + +.section __TEXT,__cstring +l_.str: + .asciz "hello world\n" + +#--- write.s +.globl _write +.p2align 2 +_write: + ret diff --git a/lld/test/MachO/relocations.s b/lld/test/MachO/relocations-x86_84.s rename from lld/test/MachO/relocations.s rename to lld/test/MachO/relocations-x86_84.s