diff --git a/lld/MachO/Arch/ARM.cpp b/lld/MachO/Arch/ARM.cpp --- a/lld/MachO/Arch/ARM.cpp +++ b/lld/MachO/Arch/ARM.cpp @@ -37,6 +37,11 @@ void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; + void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, uint64_t selectorOffset, + uint64_t gotFileOffset, + uint64_t msgSendOffset) const override; + void relaxGotLoad(uint8_t *loc, uint8_t type) const override; const RelocAttrs &getRelocAttrs(uint8_t type) const override; uint64_t getPageSize() const override { return 4 * 1024; } @@ -153,6 +158,13 @@ fatal("TODO: implement this"); } +void ARM::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, + uint64_t selectorOffset, uint64_t gotFileOffset, + uint64_t msgSendOffset) const { + fatal("TODO: implement this"); +} + void ARM::relaxGotLoad(uint8_t *loc, uint8_t type) const { fatal("TODO: implement this"); } diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp --- a/lld/MachO/Arch/ARM64.cpp +++ b/lld/MachO/Arch/ARM64.cpp @@ -34,6 +34,10 @@ void writeStubHelperHeader(uint8_t *buf) const override; void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; + void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, uint64_t selectorOffset, + uint64_t gotFileOffset, + uint64_t msgSendOffset) const override; const RelocAttrs &getRelocAttrs(uint8_t type) const override; void populateThunk(InputSection *thunk, Symbol *funcSym) override; }; @@ -106,6 +110,31 @@ ::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA); } +static constexpr uint32_t objcMsgSendStubCode[] = { + 0x90000001, // 00: adrp x1, __objc_selrefs + 0xf9400021, // 04: ldr x1, [x1, #selector "foo"] + 0x90000010, // 00: adrp x16, _got + 0xf9400210, // 04: ldr x16, [x16, #_objc_msgSend] + 0xd61f0200, // 08: br x16 +}; + +void ARM64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, + uint64_t selectorOffset, + uint64_t gotFileOffset, + uint64_t msgSendOffset) const { + SymbolDiagnostic d = {sym, sym->getName()}; + auto *buf32 = reinterpret_cast(buf); + encodePage21(&buf32[0], d, objcMsgSendStubCode[0], selrefsFileOffset); + encodePageOff12(&buf32[1], objcMsgSendStubCode[1], selectorOffset); + encodePage21(&buf32[2], d, objcMsgSendStubCode[2], gotFileOffset); + encodePage21(&buf32[3], d, objcMsgSendStubCode[3], msgSendOffset); + buf32[4] = objcMsgSendStubCode[4]; + buf32[5] = 0xd4200020; // TODO: Why does ld64 have this extra? + buf32[6] = 0xd4200020; + buf32[7] = 0xd4200020; +} + // A thunk is the relaxed variation of stubCode. We don't need the // extra indirection through a lazy pointer because the target address // is known at link time. diff --git a/lld/MachO/Arch/ARM64_32.cpp b/lld/MachO/Arch/ARM64_32.cpp --- a/lld/MachO/Arch/ARM64_32.cpp +++ b/lld/MachO/Arch/ARM64_32.cpp @@ -33,6 +33,10 @@ void writeStubHelperHeader(uint8_t *buf) const override; void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; + void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, uint64_t selectorOffset, + uint64_t gotFileOffset, + uint64_t msgSendOffset) const override; const RelocAttrs &getRelocAttrs(uint8_t type) const override; }; @@ -101,6 +105,14 @@ ::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA); } +void ARM64_32::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, + uint64_t selectorOffset, + uint64_t gotFileOffset, + uint64_t msgSendOffset) const { + fatal("TODO: implement this"); +} + ARM64_32::ARM64_32() : ARM64Common(ILP32()) { cpuType = CPU_TYPE_ARM64_32; cpuSubtype = CPU_SUBTYPE_ARM64_V8; 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 @@ -36,6 +36,11 @@ void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; + void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, uint64_t selectorOffset, + uint64_t gotFileOffset, + uint64_t msgSendOffset) const override; + void relaxGotLoad(uint8_t *loc, uint8_t type) const override; const RelocAttrs &getRelocAttrs(uint8_t type) const override; uint64_t getPageSize() const override { return 4 * 1024; } @@ -175,6 +180,14 @@ sizeof(stubHelperEntry), in.stubHelper->addr); } +void X86_64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, + uint64_t selectorOffset, + uint64_t gotFileOffset, + uint64_t msgSendOffset) const { + fatal("TODO: implement this"); +} + void X86_64::relaxGotLoad(uint8_t *loc, uint8_t type) const { // Convert MOVQ to LEAQ if (loc[-2] != 0x8b) diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h --- a/lld/MachO/InputSection.h +++ b/lld/MachO/InputSection.h @@ -313,6 +313,12 @@ constexpr const char objcClassRefs[] = "__objc_classrefs"; constexpr const char objcConst[] = "__objc_const"; constexpr const char objcImageInfo[] = "__objc_imageinfo"; +constexpr const char objcStubs[] = "__objc_stubs"; +// FIXME: This 2 sections are using a separate name to avoid conflicts, we +// should handle these sections in object files +constexpr const char objcSelrefs[] = + "__objc_selrefs"; // FIXME: This needs to be merged in some cases +constexpr const char objcMethname[] = "__objc_methname2"; constexpr const char objcNonLazyCatList[] = "__objc_nlcatlist"; constexpr const char objcNonLazyClassList[] = "__objc_nlclslist"; constexpr const char objcProtoList[] = "__objc_protolist"; diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -312,6 +312,44 @@ Defined *dyldPrivate = nullptr; }; +class ObjCStubsSection final : public SyntheticSection { +public: + ObjCStubsSection(); + void addEntry(Symbol *sym); + uint64_t getSize() const override; + bool isNeeded() const override { return !symbolsWithOffsets.empty(); } + void finalize() override { isec->isFinal = true; } + void writeTo(uint8_t *buf) const override; + +private: + llvm::DenseMap symbolsWithOffsets; +}; + +class ObjCSelrefsSection final : public SyntheticSection { +public: + ObjCSelrefsSection(); + uint32_t addEntry(uint32_t offset); + uint64_t getSize() const override { return offsets.size() * align; } + bool isNeeded() const override { return !offsets.empty(); } + void writeTo(uint8_t *buf) const override; + +private: + std::vector offsets; +}; + +class ObjCMethnameSection final : public SyntheticSection { +public: + ObjCMethnameSection(); + uint32_t addMethname(StringRef name); + uint64_t getSize() const override { return size; } + bool isNeeded() const override { return !strings.empty(); } + void writeTo(uint8_t *buf) const override; + +private: + std::vector strings; + size_t size = 0; +}; + // Note that this section may also be targeted by non-lazy bindings. In // particular, this happens when branch relocations target weak symbols. class LazyPointerSection final : public SyntheticSection { @@ -615,6 +653,9 @@ LazyPointerSection *lazyPointers = nullptr; StubsSection *stubs = nullptr; StubHelperSection *stubHelper = nullptr; + ObjCStubsSection *objcStubs = nullptr; + ObjCSelrefsSection *objcSelrefs = nullptr; + ObjCMethnameSection *objcMethname = nullptr; UnwindInfoSection *unwindInfo = nullptr; ConcatInputSection *imageLoaderCache = nullptr; }; diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -640,6 +640,101 @@ dyldPrivate->used = true; } +ObjCStubsSection::ObjCStubsSection() + : SyntheticSection(segment_names::text, section_names::objcStubs) { + flags = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS; + // TODO: align changes on setting? + // 32 is correct for arm64 + default option + // x86_64 aligns to 1 for the default option + align = 32; +} + +void ObjCStubsSection::addEntry(Symbol *sym) { + // TODO: only do this once + Symbol *objc = + symtab->addUndefined("_objc_msgSend", nullptr, /*isWeakRef=*/false); + in.got->addEntry(objc); + + const int prefixLength = strlen("_objc_msgSend$"); + uint32_t methOffset = + in.objcMethname->addMethname(sym->getName().drop_front(prefixLength)); + uint32_t selectorOffset = in.objcSelrefs->addEntry(methOffset); + + // FIXME: Collect these, sort, replace later? + Defined *newSym = replaceSymbol( + sym, sym->getName(), nullptr, isec, + /*value=*/symbolsWithOffsets.size() * 32, + /*size=*/32, // TODO: Extra size and values + /*isWeakDef=*/false, /*isExternal=*/true, /*isPrivateExtern=*/true, + /*includeInSymtab=*/true, /*isThumb=*/false, + /*isReferencedDynamically=*/false, /*noDeadStrip=*/false); + symbolsWithOffsets.insert(std::make_pair(newSym, selectorOffset)); +} + +uint64_t ObjCStubsSection::getSize() const { + // FIXME: based on settings + // return 12; // small + // also based on arch seems like? + return 32 * symbolsWithOffsets.size(); // fast // TODO: Extract 32 to target +} + +void ObjCStubsSection::writeTo(uint8_t *buf) const { + uint64_t off = 0; + for (auto p : symbolsWithOffsets) { + Defined *sym = p.first; + uint32_t selectorOffset = p.second; + + target->writeObjCMsgSendStub( + buf + off, sym, + in.objcSelrefs->fileOff, // TODO: is this right? + in.objcSelrefs->addr + selectorOffset, // TODO: is this right? + in.got->fileOff, + 0x0); // TODO: needs to actually reference objc_msgsend + + off += 8 * sizeof(uint32_t); // TODO: extract 8 to target size + } +} + +ObjCSelrefsSection::ObjCSelrefsSection() + : SyntheticSection(segment_names::data, section_names::objcSelrefs) { + flags = S_LITERAL_POINTERS | S_ATTR_NO_DEAD_STRIP; + align = 8; +} + +uint32_t ObjCSelrefsSection::addEntry(uint32_t offset) { + size_t thisOffset = offsets.size() * align; + offsets.push_back(offset); + in.rebase->addEntry(isec, thisOffset); + return thisOffset; +} + +void ObjCSelrefsSection::writeTo(uint8_t *buf) const { + for (size_t i = 0, n = offsets.size(); i < n; ++i) { + write64le(&buf[i * align], in.objcMethname->addr + offsets[i]); + } +} + +ObjCMethnameSection::ObjCMethnameSection() + : SyntheticSection(segment_names::text, section_names::objcMethname) { + flags = S_CSTRING_LITERALS; + align = 1; +} + +uint32_t ObjCMethnameSection::addMethname(StringRef name) { + uint32_t strx = size; + strings.push_back(name); + size += name.size() + 1; // account for null terminator + return strx; +} + +void ObjCMethnameSection::writeTo(uint8_t *buf) const { + uint32_t off = 0; + for (StringRef str : strings) { + memcpy(buf + off, str.data(), str.size()); + off += str.size() + 1; // account for null terminator + } +} + LazyPointerSection::LazyPointerSection() : SyntheticSection(segment_names::data, section_names::lazySymbolPtr) { align = target->wordSize; diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h --- a/lld/MachO/Target.h +++ b/lld/MachO/Target.h @@ -57,6 +57,12 @@ virtual void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const = 0; + virtual void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t selrefsFileOffset, + uint64_t selectorOffset, + uint64_t gotFileOffset, + uint64_t msgSendOffset) const = 0; + // Symbols may be referenced via either the GOT or the stubs section, // depending on the relocation type. prepareSymbolRelocation() will set up the // GOT/stubs entries, and resolveSymbolVA() will return the addresses of those diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -695,6 +695,9 @@ continue; dysym->getFile()->refState = std::max(dysym->getFile()->refState, dysym->getRefState()); + } else if (const auto *undefined = dyn_cast(sym)) { + if (sym->getName().startswith("_objc_msgSend$")) + in.objcStubs->addEntry(sym); } } @@ -1207,6 +1210,9 @@ in.lazyPointers = make(); in.stubs = make(); in.stubHelper = make(); + in.objcStubs = make(); + in.objcSelrefs = make(); + in.objcMethname = make(); in.unwindInfo = makeUnwindInfoSection(); // This section contains space for just a single word, and will be used by