| 1 | //===- X86_64.cpp ---------------------------------------------------------===// |
| 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 "InputFiles.h" |
| 10 | #include "Symbols.h" |
| 11 | #include "SyntheticSections.h" |
| 12 | #include "Target.h" |
| 13 | |
| 14 | #include "lld/Common/ErrorHandler.h" |
| 15 | #include "mach-o/compact_unwind_encoding.h" |
| 16 | #include "llvm/BinaryFormat/MachO.h" |
| 17 | #include "llvm/Support/Endian.h" |
| 18 | |
| 19 | using namespace llvm::MachO; |
| 20 | using namespace llvm::support::endian; |
| 21 | using namespace lld; |
| 22 | using namespace lld::macho; |
| 23 | |
| 24 | namespace { |
| 25 | |
| 26 | struct X86_64 : TargetInfo { |
| 27 | X86_64(); |
| 28 | |
| 29 | int64_t getEmbeddedAddend(MemoryBufferRef, uint64_t offset, |
| 30 | const relocation_info) const override; |
| 31 | void relocateOne(uint8_t *loc, const Reloc &, uint64_t va, |
| 32 | uint64_t relocVA) const override; |
| 33 | |
| 34 | void writeStub(uint8_t *buf, const Symbol &, |
| 35 | uint64_t pointerVA) const override; |
| 36 | void writeStubHelperHeader(uint8_t *buf) const override; |
| 37 | void writeStubHelperEntry(uint8_t *buf, const Symbol &, |
| 38 | uint64_t entryAddr) const override; |
| 39 | |
| 40 | void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, |
| 41 | uint64_t &stubOffset, uint64_t selrefVA, |
| 42 | Symbol *objcMsgSend) const override; |
| 43 | |
| 44 | void relaxGotLoad(uint8_t *loc, uint8_t type) const override; |
| 45 | uint64_t getPageSize() const override { return 4 * 1024; } |
| 46 | |
| 47 | void handleDtraceReloc(const Symbol *sym, const Reloc &r, |
| 48 | uint8_t *loc) const override; |
| 49 | }; |
| 50 | } // namespace |
| 51 | |
| 52 | static constexpr std::array<RelocAttrs, 10> relocAttrsArray{._M_elems: { |
| 53 | #define B(x) RelocAttrBits::x |
| 54 | {.name: "UNSIGNED" , |
| 55 | B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4) | B(BYTE8)}, |
| 56 | {.name: "SIGNED" , B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)}, |
| 57 | {.name: "BRANCH" , B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE4)}, |
| 58 | {.name: "GOT_LOAD" , B(PCREL) | B(EXTERN) | B(GOT) | B(LOAD) | B(BYTE4)}, |
| 59 | {.name: "GOT" , B(PCREL) | B(EXTERN) | B(GOT) | B(POINTER) | B(BYTE4)}, |
| 60 | {.name: "SUBTRACTOR" , B(SUBTRAHEND) | B(EXTERN) | B(BYTE4) | B(BYTE8)}, |
| 61 | {.name: "SIGNED_1" , B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)}, |
| 62 | {.name: "SIGNED_2" , B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)}, |
| 63 | {.name: "SIGNED_4" , B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)}, |
| 64 | {.name: "TLV" , B(PCREL) | B(EXTERN) | B(TLV) | B(LOAD) | B(BYTE4)}, |
| 65 | #undef B |
| 66 | }}; |
| 67 | |
| 68 | static int pcrelOffset(uint8_t type) { |
| 69 | switch (type) { |
| 70 | case X86_64_RELOC_SIGNED_1: |
| 71 | return 1; |
| 72 | case X86_64_RELOC_SIGNED_2: |
| 73 | return 2; |
| 74 | case X86_64_RELOC_SIGNED_4: |
| 75 | return 4; |
| 76 | default: |
| 77 | return 0; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | int64_t X86_64::getEmbeddedAddend(MemoryBufferRef mb, uint64_t offset, |
| 82 | relocation_info rel) const { |
| 83 | auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart()); |
| 84 | const uint8_t *loc = buf + offset + rel.r_address; |
| 85 | |
| 86 | switch (rel.r_length) { |
| 87 | case 2: |
| 88 | return static_cast<int32_t>(read32le(P: loc)) + pcrelOffset(type: rel.r_type); |
| 89 | case 3: |
| 90 | return read64le(P: loc) + pcrelOffset(type: rel.r_type); |
| 91 | default: |
| 92 | llvm_unreachable("invalid r_length" ); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | void X86_64::relocateOne(uint8_t *loc, const Reloc &r, uint64_t value, |
| 97 | uint64_t relocVA) const { |
| 98 | if (r.pcrel) { |
| 99 | uint64_t pc = relocVA + 4 + pcrelOffset(type: r.type); |
| 100 | value -= pc; |
| 101 | } |
| 102 | |
| 103 | switch (r.length) { |
| 104 | case 2: |
| 105 | if (r.type == X86_64_RELOC_UNSIGNED) |
| 106 | checkUInt(loc, d: r, v: value, bits: 32); |
| 107 | else |
| 108 | checkInt(loc, d: r, v: value, bits: 32); |
| 109 | write32le(P: loc, V: value); |
| 110 | break; |
| 111 | case 3: |
| 112 | write64le(P: loc, V: value); |
| 113 | break; |
| 114 | default: |
| 115 | llvm_unreachable("invalid r_length" ); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | // The following methods emit a number of assembly sequences with RIP-relative |
| 120 | // addressing. Note that RIP-relative addressing on X86-64 has the RIP pointing |
| 121 | // to the next instruction, not the current instruction, so we always have to |
| 122 | // account for the current instruction's size when calculating offsets. |
| 123 | // writeRipRelative helps with that. |
| 124 | // |
| 125 | // bufAddr: The virtual address corresponding to buf[0]. |
| 126 | // bufOff: The offset within buf of the next instruction. |
| 127 | // destAddr: The destination address that the current instruction references. |
| 128 | static void writeRipRelative(SymbolDiagnostic d, uint8_t *buf, uint64_t bufAddr, |
| 129 | uint64_t bufOff, uint64_t destAddr) { |
| 130 | uint64_t rip = bufAddr + bufOff; |
| 131 | checkInt(loc: buf, d, v: destAddr - rip, bits: 32); |
| 132 | // For the instructions we care about, the RIP-relative address is always |
| 133 | // stored in the last 4 bytes of the instruction. |
| 134 | write32le(P: buf + bufOff - 4, V: destAddr - rip); |
| 135 | } |
| 136 | |
| 137 | static constexpr uint8_t stub[] = { |
| 138 | 0xff, 0x25, 0, 0, 0, 0, // jmpq *__la_symbol_ptr(%rip) |
| 139 | }; |
| 140 | |
| 141 | void X86_64::writeStub(uint8_t *buf, const Symbol &sym, |
| 142 | uint64_t pointerVA) const { |
| 143 | memcpy(dest: buf, src: stub, n: 2); // just copy the two nonzero bytes |
| 144 | uint64_t stubAddr = in.stubs->addr + sym.stubsIndex * sizeof(stub); |
| 145 | writeRipRelative(d: {.symbol: &sym, .reason: "stub" }, buf, bufAddr: stubAddr, bufOff: sizeof(stub), destAddr: pointerVA); |
| 146 | } |
| 147 | |
| 148 | static constexpr uint8_t [] = { |
| 149 | 0x4c, 0x8d, 0x1d, 0, 0, 0, 0, // 0x0: leaq ImageLoaderCache(%rip), %r11 |
| 150 | 0x41, 0x53, // 0x7: pushq %r11 |
| 151 | 0xff, 0x25, 0, 0, 0, 0, // 0x9: jmpq *dyld_stub_binder@GOT(%rip) |
| 152 | 0x90, // 0xf: nop |
| 153 | }; |
| 154 | |
| 155 | void X86_64::(uint8_t *buf) const { |
| 156 | memcpy(dest: buf, src: stubHelperHeader, n: sizeof(stubHelperHeader)); |
| 157 | SymbolDiagnostic d = {.symbol: nullptr, .reason: "stub helper header" }; |
| 158 | writeRipRelative(d, buf, bufAddr: in.stubHelper->addr, bufOff: 7, |
| 159 | destAddr: in.imageLoaderCache->getVA()); |
| 160 | writeRipRelative(d, buf, bufAddr: in.stubHelper->addr, bufOff: 0xf, |
| 161 | destAddr: in.got->addr + |
| 162 | in.stubHelper->stubBinder->gotIndex * LP64::wordSize); |
| 163 | } |
| 164 | |
| 165 | static constexpr uint8_t stubHelperEntry[] = { |
| 166 | 0x68, 0, 0, 0, 0, // 0x0: pushq <bind offset> |
| 167 | 0xe9, 0, 0, 0, 0, // 0x5: jmp <__stub_helper> |
| 168 | }; |
| 169 | |
| 170 | void X86_64::writeStubHelperEntry(uint8_t *buf, const Symbol &sym, |
| 171 | uint64_t entryAddr) const { |
| 172 | memcpy(dest: buf, src: stubHelperEntry, n: sizeof(stubHelperEntry)); |
| 173 | write32le(P: buf + 1, V: sym.lazyBindOffset); |
| 174 | writeRipRelative(d: {.symbol: &sym, .reason: "stub helper" }, buf, bufAddr: entryAddr, |
| 175 | bufOff: sizeof(stubHelperEntry), destAddr: in.stubHelper->addr); |
| 176 | } |
| 177 | |
| 178 | static constexpr uint8_t objcStubsFastCode[] = { |
| 179 | 0x48, 0x8b, 0x35, 0, 0, 0, 0, // 0x0: movq selrefs@selector(%rip), %rsi |
| 180 | 0xff, 0x25, 0, 0, 0, 0, // 0x7: jmpq *_objc_msgSend@GOT(%rip) |
| 181 | }; |
| 182 | |
| 183 | void X86_64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, |
| 184 | uint64_t &stubOffset, uint64_t selrefVA, |
| 185 | Symbol *objcMsgSend) const { |
| 186 | uint64_t objcMsgSendAddr = in.got->addr; |
| 187 | uint64_t objcMsgSendIndex = objcMsgSend->gotIndex; |
| 188 | |
| 189 | memcpy(dest: buf, src: objcStubsFastCode, n: sizeof(objcStubsFastCode)); |
| 190 | SymbolDiagnostic d = {.symbol: sym, .reason: sym->getName()}; |
| 191 | uint64_t stubAddr = stubsAddr + stubOffset; |
| 192 | writeRipRelative(d, buf, bufAddr: stubAddr, bufOff: 7, destAddr: selrefVA); |
| 193 | writeRipRelative(d, buf, bufAddr: stubAddr, bufOff: 0xd, |
| 194 | destAddr: objcMsgSendAddr + objcMsgSendIndex * LP64::wordSize); |
| 195 | stubOffset += target->objcStubsFastSize; |
| 196 | } |
| 197 | |
| 198 | void X86_64::relaxGotLoad(uint8_t *loc, uint8_t type) const { |
| 199 | // Convert MOVQ to LEAQ |
| 200 | if (loc[-2] != 0x8b) |
| 201 | error(msg: getRelocAttrs(type).name + " reloc requires MOVQ instruction" ); |
| 202 | loc[-2] = 0x8d; |
| 203 | } |
| 204 | |
| 205 | X86_64::X86_64() : TargetInfo(LP64()) { |
| 206 | cpuType = CPU_TYPE_X86_64; |
| 207 | cpuSubtype = CPU_SUBTYPE_X86_64_ALL; |
| 208 | |
| 209 | modeDwarfEncoding = UNWIND_X86_MODE_DWARF; |
| 210 | subtractorRelocType = X86_64_RELOC_SUBTRACTOR; |
| 211 | unsignedRelocType = X86_64_RELOC_UNSIGNED; |
| 212 | |
| 213 | stubSize = sizeof(stub); |
| 214 | stubHelperHeaderSize = sizeof(stubHelperHeader); |
| 215 | stubHelperEntrySize = sizeof(stubHelperEntry); |
| 216 | |
| 217 | objcStubsFastSize = sizeof(objcStubsFastCode); |
| 218 | objcStubsFastAlignment = 1; |
| 219 | |
| 220 | relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()}; |
| 221 | } |
| 222 | |
| 223 | TargetInfo *macho::createX86_64TargetInfo() { |
| 224 | static X86_64 t; |
| 225 | return &t; |
| 226 | } |
| 227 | |
| 228 | void X86_64::handleDtraceReloc(const Symbol *sym, const Reloc &r, |
| 229 | uint8_t *loc) const { |
| 230 | assert(r.type == X86_64_RELOC_BRANCH); |
| 231 | |
| 232 | if (config->outputType == MH_OBJECT) |
| 233 | return; |
| 234 | |
| 235 | if (sym->getName().starts_with(Prefix: "___dtrace_probe" )) { |
| 236 | // change call site to a NOP |
| 237 | loc[-1] = 0x90; |
| 238 | write32le(P: loc, V: 0x00401F0F); |
| 239 | } else if (sym->getName().starts_with(Prefix: "___dtrace_isenabled" )) { |
| 240 | // change call site to a clear eax |
| 241 | loc[-1] = 0x33; |
| 242 | write32le(P: loc, V: 0x909090C0); |
| 243 | } else { |
| 244 | error(msg: "Unrecognized dtrace symbol prefix: " + toString(*sym)); |
| 245 | } |
| 246 | } |
| 247 | |