Skip to content

Commit 1a0afcf

Browse files
committed
Implement -bundle_loader
Differential Revision: https://reviews.llvm.org/D95913 Usage: -bundle_loader <executable> This option specifies the executable that will load the build output file being linked. When building a bundle, users can use the --bundle_loader to specify an executable that contains symbols referenced, but not implemented in the bundle.
1 parent 4544a63 commit 1a0afcf

File tree

9 files changed

+128
-31
lines changed

9 files changed

+128
-31
lines changed

lld/MachO/Driver.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ static std::vector<ArchiveMember> getArchiveMembers(MemoryBufferRef mb) {
261261
return v;
262262
}
263263

264-
static InputFile *addFile(StringRef path, bool forceLoadArchive) {
264+
static InputFile *addFile(StringRef path, bool forceLoadArchive,
265+
bool isBundleLoader = false) {
265266
Optional<MemoryBufferRef> buffer = readFile(path);
266267
if (!buffer)
267268
return nullptr;
@@ -325,6 +326,16 @@ static InputFile *addFile(StringRef path, bool forceLoadArchive) {
325326
case file_magic::bitcode:
326327
newFile = make<BitcodeFile>(mbref);
327328
break;
329+
case file_magic::macho_executable:
330+
case file_magic::macho_bundle:
331+
// We only allow executable and bundle type here if it is used
332+
// as a bundle loader.
333+
if (!isBundleLoader)
334+
error(path + ": unhandled file type");
335+
if (Optional<DylibFile *> dylibFile =
336+
loadDylib(mbref, nullptr, isBundleLoader))
337+
newFile = *dylibFile;
338+
break;
328339
default:
329340
error(path + ": unhandled file type");
330341
}
@@ -747,6 +758,11 @@ bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
747758
config->printEachFile = args.hasArg(OPT_t);
748759
config->printWhyLoad = args.hasArg(OPT_why_load);
749760
config->outputType = getOutputType(args);
761+
if (const opt::Arg *arg = args.getLastArg(OPT_bundle_loader)) {
762+
if (config->outputType != MH_BUNDLE)
763+
error("-bundle_loader can only be used with MachO bundle output");
764+
addFile(arg->getValue(), false, true);
765+
}
750766
config->ltoObjPath = args.getLastArgValue(OPT_object_path_lto);
751767
config->ltoNewPassManager =
752768
args.hasFlag(OPT_no_lto_legacy_pass_manager, OPT_lto_legacy_pass_manager,
@@ -796,6 +812,7 @@ bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
796812
const auto &opt = arg->getOption();
797813
warnIfDeprecatedOption(opt);
798814
warnIfUnimplementedOption(opt);
815+
799816
// TODO: are any of these better handled via filtered() or getLastArg()?
800817
switch (opt.getID()) {
801818
case OPT_INPUT:

lld/MachO/Driver.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ std::string createResponseFile(const llvm::opt::InputArgList &args);
4444
llvm::Optional<std::string> resolveDylibPath(llvm::StringRef path);
4545

4646
llvm::Optional<DylibFile *> loadDylib(llvm::MemoryBufferRef mbref,
47-
DylibFile *umbrella = nullptr);
47+
DylibFile *umbrella = nullptr,
48+
bool isBundleLoader = false);
4849

4950
llvm::Optional<InputFile *> loadArchiveMember(MemoryBufferRef, uint32_t modTime,
5051
StringRef archiveName,

lld/MachO/DriverUtils.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ Optional<std::string> macho::resolveDylibPath(StringRef path) {
176176
static DenseMap<CachedHashStringRef, DylibFile *> loadedDylibs;
177177

178178
Optional<DylibFile *> macho::loadDylib(MemoryBufferRef mbref,
179-
DylibFile *umbrella) {
179+
DylibFile *umbrella,
180+
bool isBundleLoader) {
180181
StringRef path = mbref.getBufferIdentifier();
181182
DylibFile *&file = loadedDylibs[CachedHashStringRef(path)];
182183
if (file)
@@ -190,11 +191,13 @@ Optional<DylibFile *> macho::loadDylib(MemoryBufferRef mbref,
190191
": " + toString(result.takeError()));
191192
return {};
192193
}
193-
file = make<DylibFile>(**result, umbrella);
194+
file = make<DylibFile>(**result, umbrella, isBundleLoader);
194195
} else {
195196
assert(magic == file_magic::macho_dynamically_linked_shared_lib ||
196-
magic == file_magic::macho_dynamically_linked_shared_lib_stub);
197-
file = make<DylibFile>(mbref, umbrella);
197+
magic == file_magic::macho_dynamically_linked_shared_lib_stub ||
198+
magic == file_magic::macho_executable ||
199+
magic == file_magic::macho_bundle);
200+
file = make<DylibFile>(mbref, umbrella, isBundleLoader);
198201
}
199202
return file;
200203
}

lld/MachO/InputFiles.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -605,8 +605,11 @@ void loadReexport(StringRef path, DylibFile *umbrella) {
605605
inputFiles.insert(*reexport);
606606
}
607607

608-
DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella)
609-
: InputFile(DylibKind, mb), refState(RefState::Unreferenced) {
608+
DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella,
609+
bool isBundleLoader)
610+
: InputFile(DylibKind, mb), refState(RefState::Unreferenced),
611+
isBundleLoader(isBundleLoader) {
612+
assert(!isBundleLoader || !umbrella);
610613
if (umbrella == nullptr)
611614
umbrella = this;
612615

@@ -619,7 +622,9 @@ DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella)
619622
currentVersion = read32le(&c->dylib.current_version);
620623
compatibilityVersion = read32le(&c->dylib.compatibility_version);
621624
dylibName = reinterpret_cast<const char *>(cmd) + read32le(&c->dylib.name);
622-
} else {
625+
} else if (!isBundleLoader) {
626+
// macho_executable and macho_bundle don't have LC_ID_DYLIB,
627+
// so it's OK.
623628
error("dylib " + toString(this) + " missing LC_ID_DYLIB load command");
624629
return;
625630
}
@@ -658,8 +663,12 @@ DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella)
658663
}
659664
}
660665

661-
DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella)
662-
: InputFile(DylibKind, interface), refState(RefState::Unreferenced) {
666+
DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella,
667+
bool isBundleLoader)
668+
: InputFile(DylibKind, interface), refState(RefState::Unreferenced),
669+
isBundleLoader(isBundleLoader) {
670+
// FIXME: Add test for the missing TBD code path.
671+
663672
if (umbrella == nullptr)
664673
umbrella = this;
665674

lld/MachO/InputFiles.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,28 @@ class DylibFile : public InputFile {
125125
// the root dylib to ensure symbols in the child library are correctly bound
126126
// to the root. On the other hand, if a dylib is being directly loaded
127127
// (through an -lfoo flag), then `umbrella` should be a nullptr.
128-
explicit DylibFile(MemoryBufferRef mb, DylibFile *umbrella = nullptr);
128+
explicit DylibFile(MemoryBufferRef mb, DylibFile *umbrella = nullptr,
129+
bool isBundleLoader = false);
129130

130131
explicit DylibFile(const llvm::MachO::InterfaceFile &interface,
131-
DylibFile *umbrella = nullptr);
132+
DylibFile *umbrella = nullptr,
133+
bool isBundleLoader = false);
132134

133135
static bool classof(const InputFile *f) { return f->kind() == DylibKind; }
134136

135137
StringRef dylibName;
136138
uint32_t compatibilityVersion = 0;
137139
uint32_t currentVersion = 0;
138-
uint64_t ordinal = 0; // Ordinal numbering starts from 1, so 0 is a sentinel
140+
int64_t ordinal = 0; // Ordinal numbering starts from 1, so 0 is a sentinel
139141
RefState refState;
140142
bool reexport = false;
141143
bool forceWeakImport = false;
144+
145+
// An executable can be used as a bundle loader that will load the output
146+
// file being linked, and that contains symbols referenced, but not
147+
// implemented in the bundle. When used like this, it is very similar
148+
// to a Dylib, so we re-used the same class to represent it.
149+
bool isBundleLoader;
142150
};
143151

144152
// .a file

lld/MachO/Options.td

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,6 @@ def grp_bundle : OptionGroup<"bundle">, HelpText<"CREATING A BUNDLE">;
396396
def bundle_loader : Separate<["-"], "bundle_loader">,
397397
MetaVarName<"<executable>">,
398398
HelpText<"Resolve undefined symbols from <executable>">,
399-
Flags<[HelpHidden]>,
400399
Group<grp_bundle>;
401400

402401
def grp_object : OptionGroup<"object">, HelpText<"CREATING AN OBJECT FILE">;

lld/MachO/SyntheticSections.cpp

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ struct Binding {
215215
OutputSegment *segment = nullptr;
216216
uint64_t offset = 0;
217217
int64_t addend = 0;
218-
uint8_t ordinal = 0;
218+
int16_t ordinal = 0;
219219
};
220220
} // namespace
221221

@@ -262,18 +262,24 @@ static void encodeBinding(const Symbol *sym, const OutputSection *osec,
262262
}
263263

264264
// Non-weak bindings need to have their dylib ordinal encoded as well.
265-
static void encodeDylibOrdinal(const DylibSymbol *dysym, Binding &lastBinding,
265+
static void encodeDylibOrdinal(const DylibSymbol *dysym, Binding *lastBinding,
266266
raw_svector_ostream &os) {
267267
using namespace llvm::MachO;
268-
if (lastBinding.ordinal != dysym->getFile()->ordinal) {
269-
if (dysym->getFile()->ordinal <= BIND_IMMEDIATE_MASK) {
268+
if (lastBinding == nullptr ||
269+
lastBinding->ordinal != dysym->getFile()->ordinal) {
270+
if (dysym->getFile()->ordinal <= 0) {
271+
os << static_cast<uint8_t>(
272+
BIND_OPCODE_SET_DYLIB_SPECIAL_IMM |
273+
(dysym->getFile()->ordinal & BIND_IMMEDIATE_MASK));
274+
} else if (dysym->getFile()->ordinal <= BIND_IMMEDIATE_MASK) {
270275
os << static_cast<uint8_t>(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM |
271276
dysym->getFile()->ordinal);
272277
} else {
273278
os << static_cast<uint8_t>(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
274279
encodeULEB128(dysym->getFile()->ordinal, os);
275280
}
276-
lastBinding.ordinal = dysym->getFile()->ordinal;
281+
if (lastBinding != nullptr)
282+
lastBinding->ordinal = dysym->getFile()->ordinal;
277283
}
278284
}
279285

@@ -309,7 +315,7 @@ void BindingSection::finalizeContents() {
309315
return a.target.getVA() < b.target.getVA();
310316
});
311317
for (const BindingEntry &b : bindings) {
312-
encodeDylibOrdinal(b.dysym, lastBinding, os);
318+
encodeDylibOrdinal(b.dysym, &lastBinding, os);
313319
if (auto *isec = b.target.section.dyn_cast<const InputSection *>()) {
314320
encodeBinding(b.dysym, isec->parent, isec->outSecOff + b.target.offset,
315321
b.addend, /*isWeakBinding=*/false, lastBinding, os);
@@ -529,13 +535,7 @@ uint32_t LazyBindingSection::encode(const DylibSymbol &sym) {
529535
uint64_t offset = in.lazyPointers->addr - dataSeg->firstSection()->addr +
530536
sym.stubsIndex * WordSize;
531537
encodeULEB128(offset, os);
532-
if (sym.getFile()->ordinal <= MachO::BIND_IMMEDIATE_MASK) {
533-
os << static_cast<uint8_t>(MachO::BIND_OPCODE_SET_DYLIB_ORDINAL_IMM |
534-
sym.getFile()->ordinal);
535-
} else {
536-
os << static_cast<uint8_t>(MachO::BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
537-
encodeULEB128(sym.getFile()->ordinal, os);
538-
}
538+
encodeDylibOrdinal(&sym, nullptr, os);
539539

540540
uint8_t flags = MachO::BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM;
541541
if (sym.isWeakRef())
@@ -796,7 +796,12 @@ void SymtabSection::writeTo(uint8_t *buf) const {
796796
nList->n_desc |= defined->isExternalWeakDef() ? MachO::N_WEAK_DEF : 0;
797797
} else if (auto *dysym = dyn_cast<DylibSymbol>(entry.sym)) {
798798
uint16_t n_desc = nList->n_desc;
799-
MachO::SET_LIBRARY_ORDINAL(n_desc, dysym->getFile()->ordinal);
799+
if (dysym->getFile()->isBundleLoader)
800+
MachO::SET_LIBRARY_ORDINAL(n_desc, MachO::EXECUTABLE_ORDINAL);
801+
else
802+
MachO::SET_LIBRARY_ORDINAL(
803+
n_desc, static_cast<uint8_t>(dysym->getFile()->ordinal));
804+
800805
nList->n_type = MachO::N_EXT;
801806
n_desc |= dysym->isWeakRef() ? MachO::N_WEAK_REF : 0;
802807
nList->n_desc = n_desc;

lld/MachO/Writer.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,17 +497,20 @@ void Writer::createLoadCommands() {
497497

498498
in.header->addLoadCommand(make<LCBuildVersion>(config->platform));
499499

500-
uint64_t dylibOrdinal = 1;
500+
int64_t dylibOrdinal = 1;
501501
for (InputFile *file : inputFiles) {
502502
if (auto *dylibFile = dyn_cast<DylibFile>(file)) {
503+
if (dylibFile->isBundleLoader)
504+
dylibFile->ordinal = MachO::BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
505+
else
506+
dylibFile->ordinal = dylibOrdinal++;
503507
LoadCommandType lcType =
504508
dylibFile->forceWeakImport || dylibFile->refState == RefState::Weak
505509
? LC_LOAD_WEAK_DYLIB
506510
: LC_LOAD_DYLIB;
507511
in.header->addLoadCommand(make<LCDylib>(lcType, dylibFile->dylibName,
508512
dylibFile->compatibilityVersion,
509513
dylibFile->currentVersion));
510-
dylibFile->ordinal = dylibOrdinal++;
511514

512515
if (dylibFile->reexport)
513516
in.header->addLoadCommand(

lld/test/MachO/bundle-loader.s

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# REQUIRES: x86
2+
3+
# RUN: rm -rf %t; split-file %s %t
4+
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/2.s -o %t/2.o
5+
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/3.s -o %t/3.o
6+
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o
7+
8+
# RUN: %lld -lSystem -dylib -install_name %t/my_lib.dylib -o %t/mylib.dylib %t/2.o
9+
# RUN: %lld %t/2.o %t/main.o -o %t/main
10+
# RUN: %lld -lSystem -bundle -bundle_loader %t/main -o %t/bundle.bundle %t/3.o %t/mylib.dylib
11+
# Check bundle.bundle to ensure the `my_func` symbol is from executable
12+
# RUN: llvm-nm -m %t/bundle.bundle | FileCheck %s --check-prefix BUNDLE
13+
# BUNDLE: (undefined) external _main (from executable)
14+
# BUNDLE: (undefined) external my_func (from executable)
15+
# RUN: llvm-objdump --macho --lazy-bind %t/bundle.bundle | FileCheck %s --check-prefix BUNDLE-OBJ
16+
# BUNDLE-OBJ: segment section address dylib symbol
17+
# BUNDLE-OBJ: __DATA __la_symbol_ptr 0x{{.*}} my_fun
18+
19+
20+
# RUN: %lld -lSystem -bundle -bundle_loader %t/main -o %t/bundle2.bundle %t/3.o %t/2.o
21+
# Check bundle2.bundle to ensure that _main is still from executable
22+
# but my_func is not.
23+
# RUN: llvm-nm -m %t/bundle2.bundle | FileCheck %s --check-prefix BUNDLE2
24+
# BUNDLE2: (undefined) external _main (from executable)
25+
# BUNDLE2: (__TEXT,__text) external my_func
26+
27+
# Test that bundle_loader can only be used with MachO bundle output.
28+
# RUN: not %lld -lSystem -bundle_loader %t/main -o %t/bundle3.bundle 2>&1 | FileCheck %s --check-prefix ERROR
29+
# ERROR: -bundle_loader can only be used with MachO bundle output
30+
31+
#--- 2.s
32+
# my_lib: This contains the exported function
33+
.globl my_func
34+
my_func:
35+
retq
36+
37+
#--- 3.s
38+
# my_user.s: This is the user/caller of the
39+
# exported function
40+
.text
41+
my_user:
42+
callq my_func()
43+
retq
44+
45+
#--- main.s
46+
# main.s: dummy exec/main loads the exported function.
47+
# This is basically a way to say `my_user` should get
48+
# `my_func` from this executable.
49+
.globl _main
50+
.text
51+
_main:
52+
retq

0 commit comments

Comments
 (0)