Skip to content

Commit

Permalink
[lld-macho] Support calls to functions in dylibs
Browse files Browse the repository at this point in the history
Summary:
This diff implements 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.

Depends on D78269.

Reviewers: ruiu, pcc, MaskRay, smeenai, alexshap, gkm, Ktwu, christylee

Subscribers: llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D78270
  • Loading branch information
int3 committed May 10, 2020
1 parent db157d2 commit b3e2fc9
Show file tree
Hide file tree
Showing 14 changed files with 481 additions and 21 deletions.
110 changes: 105 additions & 5 deletions lld/MachO/Arch/X86_64.cpp
Expand Up @@ -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"
Expand All @@ -20,14 +23,20 @@ namespace {

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) {
Expand Down Expand Up @@ -62,7 +71,98 @@ void X86_64::relocateOne(uint8_t *loc, uint8_t type, uint64_t val) const {
}
}

} // 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 <bind offset>
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());
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;
Expand Down
8 changes: 8 additions & 0 deletions lld/MachO/Driver.cpp
Expand Up @@ -158,6 +158,14 @@ bool macho::link(llvm::ArrayRef<const char *> argsArr, bool canExitEarly,
}
}

// 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<Defined>(config->entry)) {
error("undefined symbol: " + config->entry->getName());
return false;
Expand Down
10 changes: 10 additions & 0 deletions lld/MachO/InputFiles.cpp
Expand Up @@ -263,6 +263,16 @@ DylibFile::DylibFile(MemoryBufferRef mb) : InputFile(DylibKind, mb) {
}
}

DylibFile::DylibFile() : InputFile(DylibKind, MemoryBufferRef()) {}

DylibFile *DylibFile::createLibSystemMock() {
auto *file = make<DylibFile>();
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 "<internal>" or "baz.o".
std::string lld::toString(const InputFile *file) {
return file ? std::string(file->getName()) : "<internal>";
Expand Down
5 changes: 5 additions & 0 deletions lld/MachO/InputFiles.h
Expand Up @@ -63,6 +63,11 @@ class DylibFile : public InputFile {
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
};
Expand Down
3 changes: 1 addition & 2 deletions lld/MachO/InputSection.cpp
Expand Up @@ -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"
Expand All @@ -35,7 +34,7 @@ void InputSection::writeTo(uint8_t *buf) {
uint64_t va = 0;
if (auto *s = r.target.dyn_cast<Symbol *>()) {
if (auto *dylibSymbol = dyn_cast<DylibSymbol>(s)) {
va = in.got->addr + dylibSymbol->gotIndex * WordSize;
va = target->getDylibSymbolVA(*dylibSymbol, r.type);
} else {
va = s->getVA();
}
Expand Down
3 changes: 2 additions & 1 deletion lld/MachO/OutputSegment.h
Expand Up @@ -18,8 +18,9 @@ namespace macho {

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";
constexpr const char *dataConst = "__DATA_CONST";

Expand Down
2 changes: 2 additions & 0 deletions lld/MachO/Symbols.h
Expand Up @@ -77,6 +77,8 @@ class DylibSymbol : public Symbol {

DylibFile *file;
uint32_t gotIndex = UINT32_MAX;
uint32_t stubsIndex = UINT32_MAX;
uint32_t lazyBindOffset = UINT32_MAX;
};

inline uint64_t Symbol::getVA() const {
Expand Down
128 changes: 128 additions & 0 deletions lld/MachO/SyntheticSections.cpp
Expand Up @@ -22,6 +22,7 @@
using namespace llvm;
using namespace llvm::MachO;
using namespace llvm::support;
using namespace llvm::support::endian;

namespace lld {
namespace macho {
Expand Down Expand Up @@ -56,6 +57,8 @@ void MachHeaderSection::writeTo(uint8_t *buf) const {
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<uint8_t *>(hdr + 1);
for (LoadCommand *lc : loadCommands) {
Expand Down Expand Up @@ -131,6 +134,131 @@ void BindingSection::writeTo(uint8_t *buf) const {
memcpy(buf, contents.data(), contents.size());
}

StubsSection::StubsSection()
: SyntheticSection(segment_names::text, "__stubs") {}

size_t StubsSection::getSize() const {
return entries.size() * target->stubSize;
}

void StubsSection::writeTo(uint8_t *buf) const {
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()
: SyntheticSection(segment_names::text, "__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) const {
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::setup() {
stubBinder = dyn_cast_or_null<DylibSymbol>(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);

inputSections.push_back(in.imageLoaderCache);
symtab->addDefined("__dyld_private", in.imageLoaderCache, 0);
}

ImageLoaderCacheSection::ImageLoaderCacheSection() {
segname = segment_names::data;
name = "__data";
}

LazyPointerSection::LazyPointerSection()
: SyntheticSection(segment_names::data, "__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) const {
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()
: SyntheticSection(segment_names::linkEdit, 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) const {
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<uint8_t>(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<uint8_t>(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM |
sym.file->ordinal);
else
fatal("TODO: Support larger dylib symbol ordinals");

os << static_cast<uint8_t>(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM)
<< sym.getName() << '\0' << static_cast<uint8_t>(BIND_OPCODE_DO_BIND)
<< static_cast<uint8_t>(BIND_OPCODE_DONE);
return opstreamOffset;
}

ExportSection::ExportSection()
: SyntheticSection(segment_names::linkEdit, section_names::export_) {}

Expand Down

0 comments on commit b3e2fc9

Please sign in to comment.