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 @@ -6,7 +6,10 @@ // //===----------------------------------------------------------------------===// +#include "Symbols.h" +#include "SyntheticSections.h" #include "Target.h" + #include "lld/Common/ErrorHandler.h" #include "llvm/BinaryFormat/MachO.h" #include "llvm/Support/Endian.h" @@ -20,14 +23,20 @@ struct X86_64 : TargetInfo { X86_64(); + uint64_t getImplicitAddend(const uint8_t *loc, uint8_t type) const override; void relocateOne(uint8_t *loc, uint8_t type, uint64_t val) const override; + + void writeStub(uint8_t *buf, const DylibSymbol &) const override; + void writeStubHelperHeader(uint8_t *buf) const override; + void writeStubHelperEntry(uint8_t *buf, const DylibSymbol &, + uint64_t entryAddr) const override; + + void prepareDylibSymbolRelocation(DylibSymbol &, uint8_t type) override; + uint64_t getDylibSymbolVA(const DylibSymbol &, uint8_t type) const override; }; -X86_64::X86_64() { - cpuType = CPU_TYPE_X86_64; - cpuSubtype = CPU_SUBTYPE_X86_64_ALL; -} +} // namespace uint64_t X86_64::getImplicitAddend(const uint8_t *loc, uint8_t type) const { switch (type) { @@ -56,7 +65,98 @@ } } -} // namespace +// The following methods emit a number of assembly sequences with RIP-relative +// addressing. Note that RIP-relative addressing on X86-64 has the RIP pointing +// to the next instruction, not the current instruction, so we always have to +// account for the current instruction's size when calculating offsets. +// writeRipRelative helps with that. +// +// bufAddr: The virtual address corresponding to buf[0]. +// bufOff: The offset within buf of the next instruction. +// destAddr: The destination address that the current instruction references. +static void writeRipRelative(uint8_t *buf, uint64_t bufAddr, uint64_t bufOff, + uint64_t destAddr) { + uint64_t rip = bufAddr + bufOff; + // For the instructions we care about, the RIP-relative address is always + // stored in the last 4 bytes of the instruction. + write32le(buf + bufOff - 4, destAddr - rip); +} + +static constexpr uint8_t stub[] = { + 0xff, 0x25, 0, 0, 0, 0, // jmpq *__la_symbol_ptr(%rip) +}; + +void X86_64::writeStub(uint8_t *buf, const DylibSymbol &sym) const { + memcpy(buf, stub, 2); // just copy the two nonzero bytes + uint64_t stubAddr = in.stubs->addr + sym.stubsIndex * sizeof(stub); + writeRipRelative(buf, stubAddr, sizeof(stub), + in.lazyPointers->addr + sym.stubsIndex * WordSize); +} + +static constexpr uint8_t stubHelperHeader[] = { + 0x4c, 0x8d, 0x1d, 0, 0, 0, 0, // 0x0: leaq ImageLoaderCache(%rip), %r11 + 0x41, 0x53, // 0x7: pushq %r11 + 0xff, 0x25, 0, 0, 0, 0, // 0x9: jmpq *dyld_stub_binder@GOT(%rip) + 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->addr); + writeRipRelative(buf, in.stubHelper->addr, 0xf, + in.got->addr + + in.stubHelper->stubBinder->gotIndex * WordSize); +} + +void X86_64::writeStubHelperEntry(uint8_t *buf, const DylibSymbol &sym, + uint64_t entryAddr) const { + memcpy(buf, stubHelperEntry, sizeof(stubHelperEntry)); + write32le(buf + 1, sym.lazyBindOffset); + writeRipRelative(buf, entryAddr, sizeof(stubHelperEntry), + in.stubHelper->addr); +} + +void X86_64::prepareDylibSymbolRelocation(DylibSymbol &sym, uint8_t type) { + switch (type) { + case X86_64_RELOC_GOT_LOAD: + in.got->addEntry(sym); + break; + case X86_64_RELOC_BRANCH: + in.stubs->addEntry(sym); + break; + case X86_64_RELOC_GOT: + fatal("TODO: Unhandled dylib symbol relocation X86_64_RELOC_GOT"); + default: + llvm_unreachable("Unexpected dylib relocation type"); + } +} + +uint64_t X86_64::getDylibSymbolVA(const DylibSymbol &sym, uint8_t type) const { + switch (type) { + case X86_64_RELOC_GOT_LOAD: + return in.got->addr + sym.gotIndex * WordSize; + case X86_64_RELOC_BRANCH: + return in.stubs->addr + sym.stubsIndex * sizeof(stub); + case X86_64_RELOC_GOT: + fatal("TODO: Unhandled dylib symbol relocation X86_64_RELOC_GOT"); + default: + llvm_unreachable("Unexpected dylib relocation type"); + } +} + +X86_64::X86_64() { + cpuType = CPU_TYPE_X86_64; + cpuSubtype = CPU_SUBTYPE_X86_64_ALL; + + stubSize = sizeof(stub); + stubHelperHeaderSize = sizeof(stubHelperHeader); + stubHelperEntrySize = sizeof(stubHelperEntry); +} TargetInfo *macho::createX86_64TargetInfo() { static X86_64 t; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -157,6 +157,14 @@ } } + // dyld requires us to load libSystem. Since we may run tests on non-OSX + // systems which do not have libSystem, we mock it out here. + // TODO: Replace this with a stub tbd file once we have TAPI support. + if (StringRef(getenv("LLD_IN_TEST")) == "1" && + config->outputType == MH_EXECUTE) { + inputFiles.push_back(DylibFile::createLibSystemMock()); + } + if (config->outputType == MH_EXECUTE && !isa(config->entry)) { error("undefined symbol: " + config->entry->getName()); return false; diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h --- a/lld/MachO/InputFiles.h +++ b/lld/MachO/InputFiles.h @@ -63,6 +63,11 @@ explicit DylibFile(MemoryBufferRef mb); static bool classof(const InputFile *f) { return f->kind() == DylibKind; } + // Do not use this constructor!! This is meant only for createLibSystemMock(), + // but it cannot be made private as we call it via make(). + DylibFile(); + static DylibFile *createLibSystemMock(); + StringRef dylibName; uint64_t ordinal = 0; // Ordinal numbering starts from 1, so 0 is a sentinel }; diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -263,6 +263,16 @@ } } +DylibFile::DylibFile() : InputFile(DylibKind, MemoryBufferRef()) {} + +DylibFile *DylibFile::createLibSystemMock() { + auto *file = make(); + file->mb = MemoryBufferRef("", "/usr/lib/libSystem.B.dylib"); + file->dylibName = "/usr/lib/libSystem.B.dylib"; + file->symbols.push_back(symtab->addDylib("dyld_stub_binder", file)); + return file; +} + // Returns "" or "baz.o". std::string lld::toString(const InputFile *file) { return file ? std::string(file->getName()) : ""; diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp --- a/lld/MachO/InputSection.cpp +++ b/lld/MachO/InputSection.cpp @@ -9,7 +9,6 @@ #include "InputSection.h" #include "OutputSegment.h" #include "Symbols.h" -#include "SyntheticSections.h" #include "Target.h" #include "lld/Common/Memory.h" #include "llvm/Support/Endian.h" @@ -33,7 +32,7 @@ uint64_t va = 0; if (auto *s = r.target.dyn_cast()) { if (auto *dylibSymbol = dyn_cast(s)) { - va = in.got->addr + dylibSymbol->gotIndex * WordSize; + va = target->getDylibSymbolVA(*dylibSymbol, r.type); } else { va = s->getVA(); } diff --git a/lld/MachO/OutputSegment.h b/lld/MachO/OutputSegment.h --- a/lld/MachO/OutputSegment.h +++ b/lld/MachO/OutputSegment.h @@ -17,8 +17,9 @@ namespace segment_names { -constexpr const char *text = "__TEXT"; constexpr const char *pageZero = "__PAGEZERO"; +constexpr const char *text = "__TEXT"; +constexpr const char *data = "__DATA"; constexpr const char *linkEdit = "__LINKEDIT"; } // namespace segment_names diff --git a/lld/MachO/Symbols.h b/lld/MachO/Symbols.h --- a/lld/MachO/Symbols.h +++ b/lld/MachO/Symbols.h @@ -77,6 +77,8 @@ DylibFile *file; uint32_t gotIndex = UINT32_MAX; + uint32_t stubsIndex = UINT32_MAX; + uint32_t lazyBindOffset = UINT32_MAX; }; inline uint64_t Symbol::getVA() const { diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -24,6 +24,7 @@ constexpr const char *pageZero = "__pagezero"; constexpr const char *header = "__mach_header"; constexpr const char *binding = "__binding"; +constexpr const char *lazyBinding = "__lazy_binding"; constexpr const char *export_ = "__export"; constexpr const char *symbolTable = "__symbol_table"; constexpr const char *stringTable = "__string_table"; @@ -63,7 +64,6 @@ public: GotSection(); - void addEntry(DylibSymbol &sym); const llvm::SetVector &getEntries() const { return entries; } @@ -77,6 +77,8 @@ // runtime by dyld. } + void addEntry(DylibSymbol &sym); + private: llvm::SetVector entries; }; @@ -97,6 +99,96 @@ SmallVector contents; }; +// The following sections implement lazy symbol binding -- very similar to the +// PLT mechanism in ELF. +// +// ELF's .plt section is broken up into two sections in Mach-O: StubsSection and +// StubHelperSection. Calls to functions in dylibs will end up calling into +// StubsSection, which contains indirect jumps to addresses stored in the +// LazyPointerSection (the counterpart to ELF's .plt.got). +// +// Initially, the LazyPointerSection contains addresses that point into one of +// the entry points in the middle of the StubHelperSection. The code in +// StubHelperSection will push on the stack an offset into the +// LazyBindingSection. The push is followed by a jump to the beginning of the +// StubHelperSection (similar to PLT0), which then calls into dyld_stub_binder. +// dyld_stub_binder is a non-lazily-bound symbol, so this call looks it up in +// the GOT. +// +// The stub binder will look up the bind opcodes in the LazyBindingSection at +// the given offset. The bind opcodes will tell the binder to update the address +// in the LazyPointerSection to point to the symbol, so that subsequent calls +// don't have to redo the symbol resolution. The binder will then jump to the +// resolved symbol. + +class StubsSection : public InputSection { +public: + StubsSection(); + size_t getSize() const override; + bool isNeeded() const override { return !entries.empty(); } + void writeTo(uint8_t *buf) override; + + const llvm::SetVector &getEntries() const { return entries; } + + void addEntry(DylibSymbol &sym); + +private: + llvm::SetVector entries; +}; + +class StubHelperSection : public InputSection { +public: + StubHelperSection(); + size_t getSize() const override; + bool isNeeded() const override; + void writeTo(uint8_t *buf) override; + + void setupSymbols(); + + DylibSymbol *stubBinder = nullptr; +}; + +// This section contains space for just a single word, and will be used by dyld +// to cache an address to the image loader it uses. Note that unlike the other +// synthetic sections, which should become OutputSections once we create that +// abstraction, the ImageLoaderCacheSection should be an InputSection that +// gets merged into the __data OutputSection. +class ImageLoaderCacheSection : public InputSection { +public: + ImageLoaderCacheSection(); + size_t getSize() const override { return WordSize; } + bool isNeeded() const override; + // Nothing to write, this contains zeros at link time; it's populated at + // runtime by dyld. + void writeTo(uint8_t *buf) override {} +}; + +class LazyPointerSection : public InputSection { +public: + LazyPointerSection(); + size_t getSize() const override; + bool isNeeded() const override; + void writeTo(uint8_t *buf) override; +}; + +class LazyBindingSection : public InputSection { +public: + LazyBindingSection(); + void finalizeContents(); + size_t getSize() const override { return contents.size(); } + uint32_t encode(const DylibSymbol &); + // Like other sections in __LINKEDIT, the lazy binding section is special: its + // offsets are recorded in the LC_DYLD_INFO_ONLY load command, instead of in + // section headers. + bool isHidden() const override { return true; } + bool isNeeded() const override; + void writeTo(uint8_t *buf) override; + +private: + SmallVector contents; + llvm::raw_svector_ostream os{contents}; +}; + // Stores a trie that describes the set of exported symbols. class ExportSection : public InputSection { public: @@ -159,6 +251,10 @@ struct InStruct { GotSection *got = nullptr; + LazyPointerSection *lazyPointers = nullptr; + StubsSection *stubs = nullptr; + StubHelperSection *stubHelper = nullptr; + ImageLoaderCacheSection *imageLoaderCache = nullptr; }; extern InStruct in; diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -22,6 +22,7 @@ using namespace llvm; using namespace llvm::MachO; using namespace llvm::support; +using namespace llvm::support::endian; namespace lld { namespace macho { @@ -51,6 +52,8 @@ hdr->ncmds = loadCommands.size(); hdr->sizeofcmds = sizeOfCmds; hdr->flags = MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL; + if (config->outputType == MH_DYLIB) + hdr->flags |= MH_NO_REEXPORTED_DYLIBS; uint8_t *p = reinterpret_cast(hdr + 1); for (LoadCommand *lc : loadCommands) { @@ -131,6 +134,142 @@ memcpy(buf, contents.data(), contents.size()); } +StubsSection::StubsSection() { + segname = segment_names::text; + name = "__stubs"; +} + +size_t StubsSection::getSize() const { + return entries.size() * target->stubSize; +} + +void StubsSection::writeTo(uint8_t *buf) { + size_t off = 0; + for (const DylibSymbol *sym : in.stubs->getEntries()) { + target->writeStub(buf + off, *sym); + off += target->stubSize; + } +} + +void StubsSection::addEntry(DylibSymbol &sym) { + if (entries.insert(&sym)) + sym.stubsIndex = entries.size() - 1; +} + +StubHelperSection::StubHelperSection() { + segname = segment_names::text; + name = "__stub_helper"; +} + +size_t StubHelperSection::getSize() const { + return target->stubHelperHeaderSize + + in.stubs->getEntries().size() * target->stubHelperEntrySize; +} + +bool StubHelperSection::isNeeded() const { + return !in.stubs->getEntries().empty(); +} + +void StubHelperSection::writeTo(uint8_t *buf) { + target->writeStubHelperHeader(buf); + size_t off = target->stubHelperHeaderSize; + for (const DylibSymbol *sym : in.stubs->getEntries()) { + target->writeStubHelperEntry(buf + off, *sym, addr + off); + off += target->stubHelperEntrySize; + } +} + +void StubHelperSection::setupSymbols() { + stubBinder = dyn_cast_or_null(symtab->find("dyld_stub_binder")); + if (stubBinder == nullptr) { + error("symbol dyld_stub_binder not found (normally in libSystem.dylib). " + "Needed to perform lazy binding."); + return; + } + in.got->addEntry(*stubBinder); + + symtab->addDefined("__dyld_private", in.imageLoaderCache, 0); +} + +ImageLoaderCacheSection::ImageLoaderCacheSection() { + segname = segment_names::data; + name = "__data"; +} + +bool ImageLoaderCacheSection::isNeeded() const { + return !in.stubs->getEntries().empty(); +} + +LazyPointerSection::LazyPointerSection() { + segname = segment_names::data; + name = "__la_symbol_ptr"; + align = 8; + flags = S_LAZY_SYMBOL_POINTERS; +} + +size_t LazyPointerSection::getSize() const { + return in.stubs->getEntries().size() * WordSize; +} + +bool LazyPointerSection::isNeeded() const { + return !in.stubs->getEntries().empty(); +} + +void LazyPointerSection::writeTo(uint8_t *buf) { + size_t off = 0; + for (const DylibSymbol *sym : in.stubs->getEntries()) { + uint64_t stubHelperOffset = target->stubHelperHeaderSize + + sym->stubsIndex * target->stubHelperEntrySize; + write64le(buf + off, in.stubHelper->addr + stubHelperOffset); + off += WordSize; + } +} + +LazyBindingSection::LazyBindingSection() { + segname = segment_names::linkEdit; + name = section_names::lazyBinding; +} + +bool LazyBindingSection::isNeeded() const { return in.stubs->isNeeded(); } + +void LazyBindingSection::finalizeContents() { + // TODO: Just precompute output size here instead of writing to a temporary + // buffer + for (DylibSymbol *sym : in.stubs->getEntries()) + sym->lazyBindOffset = encode(*sym); +} + +void LazyBindingSection::writeTo(uint8_t *buf) { + memcpy(buf, contents.data(), contents.size()); +} + +// Unlike the non-lazy binding section, the bind opcodes in this section aren't +// interpreted all at once. Rather, dyld will start interpreting opcodes at a +// given offset, typically only binding a single symbol before it finds a +// BIND_OPCODE_DONE terminator. As such, unlike in the non-lazy-binding case, +// we cannot encode just the differences between symbols; we have to emit the +// complete bind information for each symbol. +uint32_t LazyBindingSection::encode(const DylibSymbol &sym) { + uint32_t opstreamOffset = contents.size(); + OutputSegment *dataSeg = in.lazyPointers->parent; + os << static_cast(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | + dataSeg->index); + uint64_t offset = in.lazyPointers->addr - dataSeg->firstSection()->addr + + sym.stubsIndex * WordSize; + encodeULEB128(offset, os); + if (sym.file->ordinal <= BIND_IMMEDIATE_MASK) { + os << static_cast(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | + sym.file->ordinal); + } else { + error("TODO: Support larger dylib symbol ordinals"); + return opstreamOffset; + } + os << static_cast(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM) + << sym.getName() << '\0' << static_cast(BIND_OPCODE_DO_BIND) + << static_cast(BIND_OPCODE_DONE); + return opstreamOffset; +} + ExportSection::ExportSection() { segname = segment_names::linkEdit; name = section_names::export_; diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h --- a/lld/MachO/Target.h +++ b/lld/MachO/Target.h @@ -9,11 +9,14 @@ #ifndef LLD_MACHO_TARGET_H #define LLD_MACHO_TARGET_H +#include #include namespace lld { namespace macho { +class DylibSymbol; + enum { // We are currently only supporting 64-bit targets since macOS and iOS are // deprecating 32-bit apps. @@ -30,8 +33,27 @@ uint8_t type) const = 0; virtual void relocateOne(uint8_t *loc, uint8_t type, uint64_t val) const = 0; + // Write code for lazy binding. See the comments on StubsSection for more + // details. + virtual void writeStub(uint8_t *buf, const DylibSymbol &) const = 0; + virtual void writeStubHelperHeader(uint8_t *buf) const = 0; + virtual void writeStubHelperEntry(uint8_t *buf, const DylibSymbol &, + uint64_t entryAddr) const = 0; + + // Dylib symbols are referenced via either the GOT or the stubs section, + // depending on the relocation type. prepareDylibSymbolRelocation() will set + // up the GOT/stubs entries, and getDylibSymbolVA() will return the addresses + // of those entries. + virtual void prepareDylibSymbolRelocation(DylibSymbol &, uint8_t type) = 0; + virtual uint64_t getDylibSymbolVA(const DylibSymbol &, + uint8_t type) const = 0; + uint32_t cpuType; uint32_t cpuSubtype; + + size_t stubSize; + size_t stubHelperHeaderSize; + size_t stubHelperEntrySize; }; TargetInfo *createX86_64TargetInfo(); diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -53,6 +53,7 @@ uint64_t fileOff = 0; MachHeaderSection *headerSection = nullptr; BindingSection *bindingSection = nullptr; + LazyBindingSection *lazyBindingSection = nullptr; ExportSection *exportSection = nullptr; StringTableSection *stringTableSection = nullptr; SymtabSection *symtabSection = nullptr; @@ -61,8 +62,11 @@ // LC_DYLD_INFO_ONLY stores the offsets of symbol import/export information. class LCDyldInfo : public LoadCommand { public: - LCDyldInfo(BindingSection *bindingSection, ExportSection *exportSection) - : bindingSection(bindingSection), exportSection(exportSection) {} + LCDyldInfo(BindingSection *bindingSection, + LazyBindingSection *lazyBindingSection, + ExportSection *exportSection) + : bindingSection(bindingSection), lazyBindingSection(lazyBindingSection), + exportSection(exportSection) {} uint32_t getSize() const override { return sizeof(dyld_info_command); } @@ -74,6 +78,10 @@ c->bind_off = bindingSection->getFileOffset(); c->bind_size = bindingSection->getFileSize(); } + if (lazyBindingSection->isNeeded()) { + c->lazy_bind_off = lazyBindingSection->getFileOffset(); + c->lazy_bind_size = lazyBindingSection->getFileSize(); + } if (exportSection->isNeeded()) { c->export_off = exportSection->getFileOffset(); c->export_size = exportSection->getFileSize(); @@ -81,6 +89,7 @@ } BindingSection *bindingSection; + LazyBindingSection *lazyBindingSection; ExportSection *exportSection; }; @@ -334,12 +343,12 @@ for (Reloc &r : sect->relocs) if (auto *s = r.target.dyn_cast()) if (auto *dylibSymbol = dyn_cast(s)) - in.got->addEntry(*dylibSymbol); + target->prepareDylibSymbolRelocation(*dylibSymbol, r.type); } void Writer::createLoadCommands() { headerSection->addLoadCommand( - make(bindingSection, exportSection)); + make(bindingSection, lazyBindingSection, exportSection)); headerSection->addLoadCommand( make(symtabSection, stringTableSection)); headerSection->addLoadCommand(make()); @@ -371,16 +380,12 @@ dylibFile->ordinal = dylibOrdinal++; } } - - // TODO: dyld requires libSystem to be loaded. libSystem is a universal - // binary and we don't have support for that yet, so mock it out here. - headerSection->addLoadCommand( - make("/usr/lib/libSystem.B.dylib")); } void Writer::createHiddenSections() { headerSection = createInputSection(); bindingSection = createInputSection(); + lazyBindingSection = createInputSection(); stringTableSection = createInputSection(); symtabSection = createInputSection(*stringTableSection); exportSection = createInputSection(); @@ -459,6 +464,9 @@ void Writer::run() { scanRelocations(); + if (in.stubHelper->isNeeded()) + in.stubHelper->setupSymbols(); + createHiddenSections(); // Sort and assign sections to their respective segments. No more sections can // be created after this method runs. @@ -479,6 +487,7 @@ // Fill __LINKEDIT contents. bindingSection->finalizeContents(); + lazyBindingSection->finalizeContents(); exportSection->finalizeContents(); symtabSection->finalizeContents(); @@ -501,4 +510,8 @@ void macho::createSyntheticSections() { in.got = createInputSection(); + in.lazyPointers = createInputSection(); + in.stubs = createInputSection(); + in.stubHelper = createInputSection(); + in.imageLoaderCache = createInputSection(); } diff --git a/lld/test/MachO/Inputs/libgoodbye.s b/lld/test/MachO/Inputs/libgoodbye.s --- a/lld/test/MachO/Inputs/libgoodbye.s +++ b/lld/test/MachO/Inputs/libgoodbye.s @@ -1,5 +1,14 @@ .section __TEXT,__cstring -.globl _goodbye_world +.globl _goodbye_world, _print_goodbye _goodbye_world: .asciz "Goodbye world!\n" + +.text +_print_goodbye: + movl $0x2000004, %eax # write() syscall + mov $1, %rdi # stdout + leaq _goodbye_world(%rip), %rsi + mov $15, %rdx # length of str + syscall + ret diff --git a/lld/test/MachO/Inputs/libhello.s b/lld/test/MachO/Inputs/libhello.s --- a/lld/test/MachO/Inputs/libhello.s +++ b/lld/test/MachO/Inputs/libhello.s @@ -1,8 +1,17 @@ .section __TEXT,__cstring -.globl _hello_world, _hello_its_me +.globl _hello_world, _hello_its_me, _print_hello _hello_world: .asciz "Hello world!\n" _hello_its_me: .asciz "Hello, it's me\n" + +.text +_print_hello: + movl $0x2000004, %eax # write() syscall + mov $1, %rdi # stdout + leaq _hello_world(%rip), %rsi + mov $13, %rdx # length of str + syscall + ret diff --git a/lld/test/MachO/dylink-lazy.s b/lld/test/MachO/dylink-lazy.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/dylink-lazy.s @@ -0,0 +1,59 @@ +# REQUIRES: x86 +# RUN: mkdir -p %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %p/Inputs/libhello.s \ +# RUN: -o %t/libhello.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %p/Inputs/libgoodbye.s \ +# RUN: -o %t/libgoodbye.o +# RUN: lld -flavor darwinnew -dylib -install_name \ +# RUN: @executable_path/libhello.dylib %t/libhello.o -o %t/libhello.dylib +# RUN: lld -flavor darwinnew -dylib -install_name \ +# RUN: @executable_path/libgoodbye.dylib %t/libgoodbye.o -o %t/libgoodbye.dylib + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/dylink-lazy.o +# RUN: lld -flavor darwinnew -o %t/dylink-lazy -L%t -lhello -lgoodbye %t/dylink-lazy.o + +## When looking at the __stubs section alone, we are unable to easily tell which +## symbol each entry points to. So we call objdump twice in order to get the +## disassembly of __text and the bind tables first, which allow us to check for +## matching entries in __stubs. +# RUN: (llvm-objdump -d --no-show-raw-insn --syms --bind --lazy-bind %t/dylink-lazy; \ +# RUN: llvm-objdump -D --no-show-raw-insn %t/dylink-lazy) | FileCheck %s + +# CHECK-LABEL: SYMBOL TABLE: +# CHECK: {{0*}}[[#%x, IMGLOADER:]] {{.*}} __DATA,__data __dyld_private + +# CHECK-LABEL: Disassembly of section __TEXT,__text: +# CHECK: callq 0x[[#%x, HELLO_STUB:]] +# CHECK-NEXT: callq 0x[[#%x, GOODBYE_STUB:]] + +# CHECK-LABEL: Bind table: +# CHECK: __DATA_CONST __got 0x[[#%x, BINDER:]] pointer 0 libSystem dyld_stub_binder + +# CHECK-LABEL: Lazy bind table: +# CHECK-DAG: __DATA __la_symbol_ptr 0x{{0*}}[[#%x, HELLO_LAZY_PTR:]] libhello _print_hello +# CHECK-DAG: __DATA __la_symbol_ptr 0x{{0*}}[[#%x, GOODBYE_LAZY_PTR:]] libgoodbye _print_goodbye + +# CHECK-LABEL: Disassembly of section __TEXT,__stubs: +# CHECK-DAG: [[#%x, HELLO_STUB]]: jmpq *[[#%u, HELLO_LAZY_PTR - HELLO_STUB - 6]](%rip) +# CHECK-DAG: [[#%x, GOODBYE_STUB]]: jmpq *[[#%u, GOODBYE_LAZY_PTR - GOODBYE_STUB - 6]](%rip) + +# CHECK-LABEL: Disassembly of section __TEXT,__stub_helper: +# CHECK: {{0*}}[[#%x, STUB_HELPER_ENTRY:]] <__stub_helper>: +# CHECK-NEXT: leaq [[#%u, IMGLOADER - STUB_HELPER_ENTRY - 7]](%rip), %r11 +# CHECK-NEXT: pushq %r11 +# CHECK-NEXT: jmpq *[[#%u, BINDER_OFF:]](%rip) +# CHECK-NEXT: [[#%x, BINDER - BINDER_OFF]]: nop +# CHECK-NEXT: pushq $0 +# CHECK-NEXT: jmp 0x[[#STUB_HELPER_ENTRY]] +# CHECK-NEXT: pushq $ +# CHECK-NEXT: jmp 0x[[#STUB_HELPER_ENTRY]] + +.text +.globl _main + +_main: + sub $8, %rsp # 16-byte-align the stack; dyld checks for this + callq _print_hello + callq _print_goodbye + add $8, %rsp + ret