[clang] [lld] [llvm] [clang][lld] Apple Kext support (PR #204805)
Visual Ehrmanntraut via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 10 09:21:05 PDT 2026
https://github.com/VisualEhrmanntraut updated https://github.com/llvm/llvm-project/pull/204805
>From d575e43643f7c90a1a2728352a24867d7105d9e9 Mon Sep 17 00:00:00 2001
From: Visual <affects-boson6g at icloud.com>
Date: Sat, 10 Oct 2026 19:19:40 +0300
Subject: [PATCH] [lld-macho][clang][llvm-objcopy] Add x86_64 kext bundle
support
---
clang/lib/Driver/ToolChains/Darwin.cpp | 21 ++-
.../InstallAPI/DiagnosticBuilderWrappers.cpp | 3 +
clang/test/Driver/darwin-kext.c | 21 +++
lld/MachO/Driver.cpp | 44 ++++-
lld/MachO/InputSection.cpp | 6 +
lld/MachO/InputSection.h | 3 +-
lld/MachO/Options.td | 7 +-
lld/MachO/OutputSegment.cpp | 20 ++-
lld/MachO/Symbols.h | 6 +-
lld/MachO/SyntheticSections.cpp | 102 +++++++++--
lld/MachO/SyntheticSections.h | 47 +++++
lld/MachO/Writer.cpp | 56 +++++-
lld/test/MachO/kext.s | 156 ++++++++++++++++
llvm/include/llvm/TextAPI/FileTypes.h | 3 +
llvm/lib/ObjCopy/MachO/MachOLayoutBuilder.cpp | 28 ++-
llvm/lib/ObjCopy/MachO/MachOObjcopy.cpp | 10 ++
llvm/lib/ObjCopy/MachO/MachOObject.cpp | 15 ++
llvm/lib/ObjCopy/MachO/MachOObject.h | 4 +
llvm/lib/ObjCopy/MachO/MachOReader.cpp | 63 +++++--
llvm/lib/ObjCopy/MachO/MachOReader.h | 1 +
llvm/lib/ObjCopy/MachO/MachOWriter.cpp | 69 +++++--
llvm/lib/ObjCopy/MachO/MachOWriter.h | 3 +
llvm/lib/TextAPI/BinaryReader/DylibReader.cpp | 3 +
.../llvm-objcopy/MachO/kext-relocations.test | 170 ++++++++++++++++++
24 files changed, 783 insertions(+), 78 deletions(-)
create mode 100644 clang/test/Driver/darwin-kext.c
create mode 100644 lld/test/MachO/kext.s
create mode 100644 llvm/test/tools/llvm-objcopy/MachO/kext-relocations.test
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index 0fb94d8712e69..67027edae1457 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -345,8 +345,13 @@ void darwin::Linker::AddLinkArgs(Compilation &C, const ArgList &Args,
// FIXME: Why do this only on this path?
Args.AddLastArg(CmdArgs, options::OPT_force__cpusubtype__ALL);
- Args.AddLastArg(CmdArgs, options::OPT_bundle);
- Args.AddAllArgs(CmdArgs, options::OPT_bundle__loader);
+ const bool IsKext = Args.hasArg(options::OPT_fapple_kext);
+ if (IsKext) {
+ CmdArgs.push_back("-kext");
+ } else {
+ Args.AddLastArg(CmdArgs, options::OPT_bundle);
+ Args.AddAllArgs(CmdArgs, options::OPT_bundle__loader);
+ }
Args.AddAllArgs(CmdArgs, options::OPT_client__name);
Arg *A;
@@ -356,14 +361,17 @@ void darwin::Linker::AddLinkArgs(Compilation &C, const ArgList &Args,
D.Diag(diag::err_drv_argument_only_allowed_with) << A->getAsString(Args)
<< "-dynamiclib";
- Args.AddLastArg(CmdArgs, options::OPT_force__flat__namespace);
+ if (!IsKext)
+ Args.AddLastArg(CmdArgs, options::OPT_force__flat__namespace);
Args.AddLastArg(CmdArgs, options::OPT_keep__private__externs);
- Args.AddLastArg(CmdArgs, options::OPT_private__bundle);
+ if (!IsKext)
+ Args.AddLastArg(CmdArgs, options::OPT_private__bundle);
} else {
CmdArgs.push_back("-dylib");
Arg *A;
- if ((A = Args.getLastArg(options::OPT_bundle)) ||
+ if ((A = Args.getLastArg(options::OPT_fapple_kext)) ||
+ (A = Args.getLastArg(options::OPT_bundle)) ||
(A = Args.getLastArg(options::OPT_bundle__loader)) ||
(A = Args.getLastArg(options::OPT_client__name)) ||
(A = Args.getLastArg(options::OPT_force__flat__namespace)) ||
@@ -4084,6 +4092,9 @@ void Darwin::addStartObjectFileArgs(const ArgList &Args,
if (isTargetFirmware())
return MachO::addStartObjectFileArgs(Args, CmdArgs);
+ if (Args.hasArg(options::OPT_fapple_kext))
+ return;
+
// Derived from startfile spec.
if (Args.hasArg(options::OPT_dynamiclib))
addDynamicLibLinkArgs(*this, Args, CmdArgs);
diff --git a/clang/lib/InstallAPI/DiagnosticBuilderWrappers.cpp b/clang/lib/InstallAPI/DiagnosticBuilderWrappers.cpp
index 37b428216c91e..4e093392f5c34 100644
--- a/clang/lib/InstallAPI/DiagnosticBuilderWrappers.cpp
+++ b/clang/lib/InstallAPI/DiagnosticBuilderWrappers.cpp
@@ -57,6 +57,9 @@ const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB,
case FileType::MachO_Bundle:
DB.AddString("mach-o bundle");
return DB;
+ case FileType::MachO_KextBundle:
+ DB.AddString("mach-o kext bundle");
+ return DB;
case FileType::MachO_DynamicLibrary:
DB.AddString("mach-o dynamic library");
return DB;
diff --git a/clang/test/Driver/darwin-kext.c b/clang/test/Driver/darwin-kext.c
new file mode 100644
index 0000000000000..475f1c4477c4e
--- /dev/null
+++ b/clang/test/Driver/darwin-kext.c
@@ -0,0 +1,21 @@
+// Check that -fapple-kext links a kext bundle, without start files or runtime
+// libraries. The linker looks up undefined symbols of kexts dynamically on its
+// own, so no -undefined is passed.
+
+// RUN: %clang --target=x86_64-apple-macos11 -fapple-kext -### %s 2>&1 | \
+// RUN: FileCheck %s
+// CHECK: {{ld(.exe)?"}}
+// CHECK-SAME: "-static" "-arch" "x86_64" "-kext"
+// CHECK-NOT: "-kext"
+// CHECK-NOT: "-undefined"
+// CHECK-NOT: crt
+// CHECK-NOT: "-lSystem"
+
+// RUN: %clang --target=x86_64-apple-macos11 -fapple-kext -bundle \
+// RUN: -bundle_loader foo -### %s 2>&1 | FileCheck %s --check-prefix=BUNDLE
+// BUNDLE: {{ld(.exe)?"}}
+// BUNDLE-NOT: "-bundle
+
+// RUN: not %clang --target=x86_64-apple-macos11 -fapple-kext -dynamiclib \
+// RUN: -### %s 2>&1 | FileCheck %s --check-prefix=DYLIB
+// DYLIB: error: invalid argument '-fapple-kext' not allowed with '-dynamiclib'
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 0dfced91c511d..4ab98dec12c76 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -73,11 +73,14 @@ std::unique_ptr<DependencyTracker> macho::depTracker;
static HeaderFileType getOutputType(const InputArgList &args) {
// TODO: -r, -dylinker, -preload...
- Arg *outputArg = args.getLastArg(OPT_bundle, OPT_dylib, OPT_execute);
+ Arg *outputArg =
+ args.getLastArg(OPT_kext, OPT_bundle, OPT_dylib, OPT_execute);
if (outputArg == nullptr)
return MH_EXECUTE;
switch (outputArg->getOption().getID()) {
+ case OPT_kext:
+ return MH_KEXT_BUNDLE;
case OPT_bundle:
return MH_BUNDLE;
case OPT_dylib:
@@ -101,6 +104,10 @@ static std::optional<StringRef> findLibrary(StringRef name) {
// (crt1.o)
if (name.ends_with(".o"))
return findPathCombination(name, config->librarySearchPaths, {""});
+ // Kexts cannot link against dylibs.
+ if (config->outputType == MH_KEXT_BUNDLE)
+ return findPathCombination("lib" + name, config->librarySearchPaths,
+ {".a"});
if (config->searchDylibsFirst) {
if (std::optional<StringRef> path =
findPathCombination("lib" + name, config->librarySearchPaths,
@@ -527,6 +534,10 @@ static InputFile *processFile(std::optional<MemoryBufferRef> buffer,
case file_magic::macho_dynamically_linked_shared_lib:
case file_magic::macho_dynamically_linked_shared_lib_stub:
case file_magic::tapi_file:
+ if (config->outputType == MH_KEXT_BUNDLE) {
+ warn("ignoring unexpected dylib '" + path + "'");
+ break;
+ }
if (DylibFile *dylibFile =
loadDylib(mbref, nullptr, /*isBundleLoader=*/false, isExplicit))
newFile = dylibFile;
@@ -995,6 +1006,11 @@ static TargetInfo *createTargetInfo(InputArgList &args) {
static UndefinedSymbolTreatment
getUndefinedSymbolTreatment(const ArgList &args) {
+ // The kernel linker resolves undefined symbols of kexts. Like ld64, ignore
+ // -undefined.
+ if (config->outputType == MH_KEXT_BUNDLE)
+ return UndefinedSymbolTreatment::dynamic_lookup;
+
StringRef treatmentStr = args.getLastArgValue(OPT_undefined);
auto treatment =
StringSwitch<UndefinedSymbolTreatment>(treatmentStr)
@@ -1067,6 +1083,9 @@ static void warnIfDeprecatedOption(const Option &opt) {
static void warnIfUnimplementedOption(const Option &opt) {
if (!opt.getGroup().isValid() || !opt.hasFlag(DriverFlag::HelpHidden))
return;
+ // Clang links kexts with -static, which has no effect on them.
+ if (opt.getID() == OPT_static && config->outputType == MH_KEXT_BUNDLE)
+ return;
switch (opt.getGroup().getID()) {
case OPT_grp_deprecated:
// warn about deprecated options elsewhere
@@ -1258,6 +1277,9 @@ static bool dataConstDefault(const InputArgList &args) {
switch (config->outputType) {
case MH_EXECUTE:
return !(args.hasArg(OPT_no_pie) && supportsNoPie());
+ case MH_KEXT_BUNDLE:
+ // ld64 places constant data of x86_64 kexts in __DATA.
+ return false;
case MH_BUNDLE:
// FIXME: return false when -final_name ...
// has prefix "/System/Library/UserEventPlugins/"
@@ -1280,6 +1302,13 @@ static bool shouldEmitChainedFixups(const InputArgList &args) {
return false;
bool requested = arg && arg->getOption().matches(OPT_fixup_chains);
+ if (config->outputType == MH_KEXT_BUNDLE) {
+ if (requested)
+ error("-fixup_chains is incompatible with -kext");
+
+ return false;
+ }
+
if (!config->isPic) {
if (requested)
error("-fixup_chains is incompatible with -no_pie");
@@ -1884,7 +1913,14 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
pie = true;
}
- config->isPic = config->outputType == MH_DYLIB ||
+ // arm64 kexts need __TEXT_EXEC, stubs and split segment info, and arm64e
+ // ones pointer authentication too.
+ if (config->outputType == MH_KEXT_BUNDLE &&
+ !is_contained({AK_x86_64, AK_x86_64h}, config->arch()))
+ fatal("-kext is only supported for x86_64 targets");
+
+ config->isPic = config->outputType == MH_KEXT_BUNDLE ||
+ config->outputType == MH_DYLIB ||
config->outputType == MH_BUNDLE ||
(config->outputType == MH_EXECUTE && pie);
@@ -2024,9 +2060,11 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
config->demangle = args.hasArg(OPT_demangle);
config->implicitDylibs = !args.hasArg(OPT_no_implicit_dylibs);
config->emitFunctionStarts =
+ config->outputType != MH_KEXT_BUNDLE &&
args.hasFlag(OPT_function_starts, OPT_no_function_starts, true);
config->emitDataInCodeInfo =
- args.hasFlag(OPT_data_in_code_info, OPT_no_data_in_code_info, true);
+ args.hasFlag(OPT_data_in_code_info, OPT_no_data_in_code_info,
+ config->outputType != MH_KEXT_BUNDLE);
config->emitChainedFixups = shouldEmitChainedFixups(args);
config->emitInitOffsets =
config->emitChainedFixups || args.hasArg(OPT_init_offsets);
diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index 6ea4b0ddd58e4..cd261102f72f3 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -252,6 +252,12 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
if (target->hasAttr(r.type, RelocAttrBits::LOAD) &&
!referentSym->isInGot())
target->relaxGotLoad(loc, r.type);
+ // The kernel linker binds these using the external relocations, which
+ // take the addend from the relocated location.
+ if (config->outputType == MH_KEXT_BUNDLE && needsBinding(referentSym) &&
+ !target->hasAttr(r.type, RelocAttrBits::GOT) &&
+ !target->hasAttr(r.type, RelocAttrBits::TLV))
+ continue;
// For dtrace symbols, do not handle them as normal undefined symbols
if (referentSym->getName().starts_with("___dtrace_")) {
// Change dtrace call site to pre-defined instructions
diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index 49ea57a24129c..f7966540894e6 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -380,7 +380,8 @@ constexpr const char unwindInfo[] = "__unwind_info";
constexpr const char weakBinding[] = "__weak_binding";
constexpr const char zeroFill[] = "__zerofill";
constexpr const char addrSig[] = "__llvm_addrsig";
-
+constexpr const char extRelocs[] = "__ext_relocs";
+constexpr const char localRelocs[] = "__local_relocs";
} // namespace section_names
void addInputSection(InputSection *inputSection);
diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td
index 32b6ce0a64a07..c8f6199d87892 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -244,6 +244,9 @@ def dylib : Flag<["-"], "dylib">,
def bundle : Flag<["-"], "bundle">,
HelpText<"Produce a bundle">,
Group<grp_kind>;
+def kext : Flag<["-"], "kext">,
+ HelpText<"Produce a kext bundle">,
+ Group<grp_kind>;
def r : Flag<["-"], "r">,
HelpText<"Merge multiple object files into one, retaining relocations">,
Flags<[HelpHidden]>,
@@ -1432,10 +1435,6 @@ def no_keep_dwarf_unwind : Flag<["-"], "no_keep_dwarf_unwind">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
-def kext : Flag<["-"], "kext">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
- Group<grp_undocumented>;
def kext_objects_dir : Flag<["-"], "kext_objects_dir">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
diff --git a/lld/MachO/OutputSegment.cpp b/lld/MachO/OutputSegment.cpp
index 5824b5c7772b3..d705a0e3994d2 100644
--- a/lld/MachO/OutputSegment.cpp
+++ b/lld/MachO/OutputSegment.cpp
@@ -155,15 +155,17 @@ static int sectionOrder(OutputSection *osec) {
}
} else if (segname == segment_names::linkEdit) {
return StringSwitch<int>(osec->name)
- .Case(section_names::chainFixups, -11)
- .Case(section_names::rebase, -10)
- .Case(section_names::binding, -9)
- .Case(section_names::weakBinding, -8)
- .Case(section_names::lazyBinding, -7)
- .Case(section_names::export_, -6)
- .Case(section_names::functionStarts, -5)
- .Case(section_names::dataInCode, -4)
- .Case(section_names::symbolTable, -3)
+ .Case(section_names::chainFixups, -13)
+ .Case(section_names::rebase, -12)
+ .Case(section_names::binding, -11)
+ .Case(section_names::weakBinding, -10)
+ .Case(section_names::lazyBinding, -9)
+ .Case(section_names::export_, -8)
+ .Case(section_names::localRelocs, -7)
+ .Case(section_names::functionStarts, -6)
+ .Case(section_names::dataInCode, -5)
+ .Case(section_names::symbolTable, -4)
+ .Case(section_names::extRelocs, -3)
.Case(section_names::indirectSymbolTable, -2)
.Case(section_names::stringTable, -1)
.Case(section_names::codeSignature, std::numeric_limits<int>::max())
diff --git a/lld/MachO/Symbols.h b/lld/MachO/Symbols.h
index cdeef93475222..1ef3cf99b4859 100644
--- a/lld/MachO/Symbols.h
+++ b/lld/MachO/Symbols.h
@@ -391,8 +391,12 @@ T *replaceSymbol(Symbol *s, ArgT &&...arg) {
inline bool needsBinding(const Symbol *sym) {
if (isa<DylibSymbol>(sym))
return true;
+ // Weak definitions in a kext are not coalesced by the kernel linker, so
+ // references to them are rebased rather than bound.
if (const auto *defined = dyn_cast<Defined>(sym))
- return defined->isExternalWeakDef() || defined->interposable;
+ return (defined->isExternalWeakDef() &&
+ config->outputType != llvm::MachO::MH_KEXT_BUNDLE) ||
+ defined->interposable;
return false;
}
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 808f32fb2b094..9ba8099154537 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -141,10 +141,10 @@ void MachHeaderSection::writeTo(uint8_t *buf) const {
if (config->outputType == MH_DYLIB && config->applicationExtension)
hdr->flags |= MH_APP_EXTENSION_SAFE;
- if (in.exports->hasWeakSymbol || hasNonWeakDefinition())
+ if (in.exports && (in.exports->hasWeakSymbol || hasNonWeakDefinition()))
hdr->flags |= MH_WEAK_DEFINES;
- if (in.exports->hasWeakSymbol || hasWeakBinding())
+ if (in.exports && (in.exports->hasWeakSymbol || hasWeakBinding()))
hdr->flags |= MH_BINDS_TO_WEAK;
for (const OutputSegment *seg : outputSegments) {
@@ -303,7 +303,9 @@ void RebaseSection::writeTo(uint8_t *buf) const {
GotSection::GotSection()
: SyntheticSection(segment_names::data, section_names::got) {
align = target->wordSize;
- flags = S_NON_LAZY_SYMBOL_POINTERS;
+ // ld64 marks the GOT of kexts as a regular section.
+ flags = config->outputType == MH_KEXT_BUNDLE ? S_REGULAR
+ : S_NON_LAZY_SYMBOL_POINTERS;
}
void macho::addNonLazyBindingEntries(const Symbol *sym,
@@ -319,6 +321,18 @@ void macho::addNonLazyBindingEntries(const Symbol *sym,
return;
}
+ if (config->outputType == MH_KEXT_BUNDLE) {
+ if (needsBinding(sym))
+ in.extRelocs->addEntry(sym, isec, offset, target->unsignedRelocType,
+ /*pcrel=*/false, target->p2WordSize);
+ else if (isa<Defined>(sym))
+ in.localRelocs->addEntry(sym, isec, offset, target->unsignedRelocType,
+ /*pcrel=*/false, target->p2WordSize);
+ else
+ llvm_unreachable("cannot bind to an undefined symbol");
+ return;
+ }
+
if (const auto *dysym = dyn_cast<DylibSymbol>(sym)) {
in.binding->addEntry(dysym, isec, offset, addend);
if (dysym->isWeakDef())
@@ -808,6 +822,61 @@ void StubHelperSection::setUp() {
dyldPrivate->used = true;
}
+RelocSection::RelocSection(const char *name)
+ : LinkEditSection(segment_names::linkEdit, name) {}
+
+void RelocSection::addEntry(Referent referent, const InputSection *isec,
+ uint32_t offset, uint8_t type, bool pcrel,
+ uint8_t length) {
+ assert(!isFinal && "RelocSection entry added after finalizeContents");
+ entries.push_back({referent, isec, offset, type, pcrel, length});
+}
+
+// Must be called after the symbol table is finalized, as external relocations
+// refer to symbol table indices.
+void RelocSection::finalizeContents() {
+ assert(!isFinal && "RelocSection finalized twice");
+ isFinal = true;
+
+ const bool external = isExternal();
+ raw_svector_ostream os(contents);
+ for (const Entry &e : entries) {
+ uint32_t symbolNum;
+ if (external) {
+ symbolNum = cast<const Symbol *>(e.referent)->symtabIndex;
+ } else {
+ // Local relocations name the section of their target.
+ const InputSection *targetIsec =
+ dyn_cast<const InputSection *>(e.referent);
+ if (!targetIsec)
+ targetIsec = cast<Defined>(cast<const Symbol *>(e.referent))->isec();
+ symbolNum = targetIsec ? targetIsec->parent->index : R_ABS;
+ }
+
+ // Relocation addresses are relative to the first segment.
+ char buf[2 * sizeof(uint32_t)];
+ write32le(buf, e.isec->getVA(e.offset) - in.header->addr);
+ write32le(buf + sizeof(uint32_t),
+ (symbolNum & 0x00ffffff) |
+ (static_cast<uint32_t>(e.pcrel) << 24) |
+ (static_cast<uint32_t>(e.length) << 25) |
+ (static_cast<uint32_t>(external) << 27) |
+ (static_cast<uint32_t>(e.type) << 28));
+ os.write(buf, sizeof(buf));
+ }
+}
+
+void RelocSection::writeTo(uint8_t *buf) const {
+ assert(isFinal && "RelocSection contents written before finalization");
+ memcpy(buf, contents.data(), contents.size());
+}
+
+ExternalRelocSection::ExternalRelocSection()
+ : RelocSection(section_names::extRelocs) {}
+
+LocalRelocSection::LocalRelocSection()
+ : RelocSection(section_names::localRelocs) {}
+
llvm::DenseMap<llvm::CachedHashStringRef, ConcatInputSection *>
ObjCSelRefsHelper::methnameToSelref;
void ObjCSelRefsHelper::initialize() {
@@ -1494,25 +1563,26 @@ IndirectSymtabSection::IndirectSymtabSection()
section_names::indirectSymbolTable) {}
uint32_t IndirectSymtabSection::getNumSymbols() const {
- uint32_t size = in.got->getEntries().size() + in.stubs->getEntries().size();
- if (!config->emitChainedFixups)
- size += in.stubs->getEntries().size();
+ uint32_t size = in.got->getEntries().size();
+ if (in.stubs)
+ size += in.stubs->getEntries().size() * (config->emitChainedFixups ? 1 : 2);
return size;
}
bool IndirectSymtabSection::isNeeded() const {
- return in.got->isNeeded() || in.stubs->isNeeded();
+ return in.got->isNeeded() || (in.stubs && in.stubs->isNeeded());
}
void IndirectSymtabSection::finalizeContents() {
uint32_t off = 0;
in.got->reserved1 = off;
off += in.got->getEntries().size();
- in.stubs->reserved1 = off;
- if (in.lazyPointers) {
+ if (in.stubs) {
+ in.stubs->reserved1 = off;
off += in.stubs->getEntries().size();
- in.lazyPointers->reserved1 = off;
}
+ if (in.lazyPointers)
+ in.lazyPointers->reserved1 = off;
}
static uint32_t indirectValue(const Symbol *sym) {
@@ -1527,9 +1597,11 @@ void IndirectSymtabSection::writeTo(uint8_t *buf) const {
write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
++off;
}
- for (const Symbol *sym : in.stubs->getEntries()) {
- write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
- ++off;
+ if (in.stubs) {
+ for (const Symbol *sym : in.stubs->getEntries()) {
+ write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
+ ++off;
+ }
}
if (in.lazyPointers) {
@@ -2324,6 +2396,10 @@ void macho::createSyntheticSymbols() {
/*referencedDynamically=*/true);
break;
+ case MH_KEXT_BUNDLE:
+ // ld64 defines no header symbol for kexts.
+ break;
+
// The following symbols are N_SECT symbols, even though the header is not
// part of any section and that they are private to the bundle/dylib/object
// they are part of.
diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index 2cc71414d56e7..6b553f9d50848 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -288,6 +288,51 @@ class StubsSection final : public SyntheticSection {
llvm::SetVector<Symbol *> entries;
};
+// Stores the external or local relocation entries of a kext bundle, which the
+// kernel linker uses in place of dyld's binding and rebase opcodes.
+class RelocSection : public LinkEditSection {
+public:
+ // The symbol an external relocation binds to, or the symbol or section a
+ // local relocation points into.
+ using Referent = llvm::PointerUnion<const Symbol *, const InputSection *>;
+
+ RelocSection(const char *name);
+ void addEntry(Referent referent, const InputSection *isec, uint32_t offset,
+ uint8_t type, bool pcrel, uint8_t length);
+ bool isNeeded() const override { return !entries.empty(); }
+ void finalizeContents() override;
+ uint64_t getRawSize() const override { return contents.size(); }
+ void writeTo(uint8_t *buf) const override;
+ uint32_t getNumEntries() const { return entries.size(); }
+
+ virtual bool isExternal() const = 0;
+
+private:
+ struct Entry {
+ Referent referent;
+ const InputSection *isec;
+ uint32_t offset;
+ uint8_t type;
+ bool pcrel;
+ uint8_t length;
+ };
+ bool isFinal = false;
+ std::vector<Entry> entries;
+ SmallVector<char, 128> contents;
+};
+
+class ExternalRelocSection final : public RelocSection {
+public:
+ ExternalRelocSection();
+ bool isExternal() const override { return true; }
+};
+
+class LocalRelocSection final : public RelocSection {
+public:
+ LocalRelocSection();
+ bool isExternal() const override { return false; }
+};
+
class StubHelperSection final : public SyntheticSection {
public:
StubHelperSection();
@@ -844,6 +889,8 @@ struct InStruct {
InitOffsetsSection *initOffsets = nullptr;
ObjCMethListSection *objcMethList = nullptr;
ChainedFixupsSection *chainedFixups = nullptr;
+ ExternalRelocSection *extRelocs = nullptr;
+ LocalRelocSection *localRelocs = nullptr;
CStringSection *getOrCreateCStringSection(StringRef name,
bool forceDedupStrings = false) {
diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp
index b97203d656ebd..1a3f95ab48534 100644
--- a/lld/MachO/Writer.cpp
+++ b/lld/MachO/Writer.cpp
@@ -212,6 +212,15 @@ class LCDysymtab final : public LoadCommand {
c->indirectsymoff = indirectSymtabSection->fileOff;
c->nindirectsyms = indirectSymtabSection->getNumSymbols();
+
+ if (in.extRelocs && in.extRelocs->isNeeded()) {
+ c->extreloff = in.extRelocs->fileOff;
+ c->nextrel = in.extRelocs->getNumEntries();
+ }
+ if (in.localRelocs && in.localRelocs->isNeeded()) {
+ c->locreloff = in.localRelocs->fileOff;
+ c->nlocrel = in.localRelocs->getNumEntries();
+ }
}
SymtabSection *symtabSection;
@@ -684,8 +693,13 @@ static void prepareSymbolRelocation(Symbol *sym, const InputSection *isec,
const RelocAttrs &relocAttrs = target->getRelocAttrs(r.type);
if (relocAttrs.hasAttr(RelocAttrBits::BRANCH)) {
- if (needsBinding(sym))
- in.stubs->addEntry(sym);
+ if (needsBinding(sym)) {
+ // Kexts have no stubs: the kernel linker patches the branch itself.
+ if (config->outputType == MH_KEXT_BUNDLE)
+ in.extRelocs->addEntry(sym, isec, r.offset, r.type, r.pcrel, r.length);
+ else
+ in.stubs->addEntry(sym);
+ }
} else if (relocAttrs.hasAttr(RelocAttrBits::GOT)) {
if (relocAttrs.hasAttr(RelocAttrBits::POINTER) || needsBinding(sym))
addNonLazyPointerEntry(sym);
@@ -698,6 +712,11 @@ static void prepareSymbolRelocation(Symbol *sym, const InputSection *isec,
// need of rebase opcodes.
if (!(isThreadLocalVariables(isec->getFlags()) && isa<Defined>(sym)))
addNonLazyBindingEntries(sym, isec, r.offset, r.addend);
+ } else if (config->outputType == MH_KEXT_BUNDLE && needsBinding(sym)) {
+ // The kernel linker can only bind branches and pointers.
+ error(isec->getLocation(r.offset) + ": " + relocAttrs.name +
+ " relocation to " + toString(*sym) +
+ " cannot be bound in a kext; access it through the GOT instead");
}
}
@@ -741,6 +760,10 @@ void Writer::scanRelocations() {
if (!r.pcrel) {
if (config->emitChainedFixups)
in.chainedFixups->addRebase(isec, r.offset);
+ else if (config->outputType == MH_KEXT_BUNDLE)
+ in.localRelocs->addEntry(cast<InputSection *>(r.referent), isec,
+ r.offset, r.type, /*pcrel=*/false,
+ r.length);
else
in.rebase->addEntry(isec, r.offset);
}
@@ -831,7 +854,7 @@ template <class LP> void Writer::createLoadCommands() {
if (config->emitChainedFixups) {
in.header->addLoadCommand(make<LCChainedFixups>(in.chainedFixups));
in.header->addLoadCommand(make<LCExportsTrie>(in.exports));
- } else {
+ } else if (config->outputType != MH_KEXT_BUNDLE) {
in.header->addLoadCommand(make<LCDyldInfo>(
in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports));
}
@@ -857,6 +880,7 @@ template <class LP> void Writer::createLoadCommands() {
in.header->addLoadCommand(make<LCSubClient>(client));
break;
case MH_BUNDLE:
+ case MH_KEXT_BUNDLE:
break;
default:
llvm_unreachable("unhandled output file type");
@@ -867,10 +891,13 @@ template <class LP> void Writer::createLoadCommands() {
in.header->addLoadCommand(uuidCommand);
}
- if (useLCBuildVersion(config->platformInfo))
- in.header->addLoadCommand(make<LCBuildVersion>(config->platformInfo));
- else
- in.header->addLoadCommand(make<LCMinVersion>(config->platformInfo));
+ // ld64 does not record the platform of kexts.
+ if (config->outputType != MH_KEXT_BUNDLE) {
+ if (useLCBuildVersion(config->platformInfo))
+ in.header->addLoadCommand(make<LCBuildVersion>(config->platformInfo));
+ else
+ in.header->addLoadCommand(make<LCMinVersion>(config->platformInfo));
+ }
if (config->secondaryPlatformInfo) {
in.header->addLoadCommand(
@@ -1089,6 +1116,7 @@ template <class LP> void Writer::createOutputSections() {
break;
case MH_DYLIB:
case MH_BUNDLE:
+ case MH_KEXT_BUNDLE:
break;
default:
llvm_unreachable("unhandled output file type");
@@ -1208,6 +1236,11 @@ void Writer::finalizeLinkEditSegment() {
osec->finalizeContents();
});
+ if (in.extRelocs)
+ in.extRelocs->finalizeContents();
+ if (in.localRelocs)
+ in.localRelocs->finalizeContents();
+
// Now that __LINKEDIT is filled out, do a proper calculation of its
// addresses and offsets.
linkEditSegment->addr = addr;
@@ -1436,6 +1469,9 @@ void macho::createSyntheticSections() {
in.wordLiteralSection = make<WordLiteralSection>();
if (config->emitChainedFixups) {
in.chainedFixups = make<ChainedFixupsSection>();
+ } else if (config->outputType == MH_KEXT_BUNDLE) {
+ in.extRelocs = make<ExternalRelocSection>();
+ in.localRelocs = make<LocalRelocSection>();
} else {
in.rebase = make<RebaseSection>();
in.binding = make<BindingSection>();
@@ -1444,9 +1480,11 @@ void macho::createSyntheticSections() {
in.lazyPointers = make<LazyPointerSection>();
in.stubHelper = make<StubHelperSection>();
}
- in.exports = make<ExportSection>();
+ if (config->outputType != MH_KEXT_BUNDLE)
+ in.exports = make<ExportSection>();
in.got = make<GotSection>();
- in.stubs = make<StubsSection>();
+ if (config->outputType != MH_KEXT_BUNDLE)
+ in.stubs = make<StubsSection>();
in.objcStubs = make<ObjCStubsSection>();
in.unwindInfo = makeUnwindInfoSection();
in.objCImageInfo = make<ObjCImageInfoSection>();
diff --git a/lld/test/MachO/kext.s b/lld/test/MachO/kext.s
new file mode 100644
index 0000000000000..f2059f388b40f
--- /dev/null
+++ b/lld/test/MachO/kext.s
@@ -0,0 +1,156 @@
+# REQUIRES: x86
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11 %t/main.s -o %t/main.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11 %t/signed.s -o %t/signed.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11 %t/lib.s -o %t/lib.o
+
+## Undefined symbols are looked up dynamically regardless of -undefined, and
+## -static, which clang passes for kexts, is accepted without a warning.
+# RUN: %no-lsystem-lld -kext -static -undefined error -o %t/main.kext %t/main.o
+# RUN: llvm-objdump --macho -r --private-headers %t/main.kext | \
+# RUN: FileCheck %s --implicit-check-not="cmd LC_"
+# RUN: llvm-objdump --macho -d --no-show-raw-insn %t/main.kext | \
+# RUN: FileCheck %s --check-prefix=DISASM
+# RUN: llvm-objdump --macho -s --section=__DATA,__data %t/main.kext | \
+# RUN: FileCheck %s --check-prefix=DATA
+# RUN: llvm-nm -m %t/main.kext | FileCheck %s --check-prefix=SYMS
+
+## Branches and pointers to undefined symbols, including their GOT slots, are
+## bound through external relocations. Local relocations name the section that
+## they point into, and references to weak definitions are not bound.
+# CHECK: External relocation information 3 entries
+# CHECK-NEXT: address pcrel length extern type scattered symbolnum/value
+# CHECK-NEXT: [[#%.8x,GOT:]] False quad True UNSIGND False _ext_data
+# CHECK-NEXT: [[#%.8x,CALL:]] True long True BRANCH False _ext_func
+# CHECK-NEXT: [[#%.8x,EXTPTR:]] False quad True UNSIGND False _ext_data
+# CHECK-NEXT: Local relocation information 3 entries
+# CHECK-NEXT: address pcrel length extern type scattered symbolnum/value
+# CHECK-NEXT: [[#%.8x,EXTPTR-16]] False quad False UNSIGND False 1 (__TEXT,__text)
+# CHECK-NEXT: [[#%.8x,EXTPTR-8]] False quad False UNSIGND False 2 (__TEXT,__cstring)
+# CHECK-NEXT: [[#%.8x,EXTPTR+8]] False quad False UNSIGND False 4 (__DATA,__data)
+
+## Like ld64, there is no __DATA_CONST segment, and no LC_BUILD_VERSION,
+## LC_FUNCTION_STARTS, or dyld info. The GOT is a regular section.
+# CHECK: filetype ncmds sizeofcmds flags
+# CHECK-NEXT: KEXTBUNDLE 6 {{[0-9]+}} NOUNDEFS DYLDLINK TWOLEVEL{{$}}
+# CHECK: cmd LC_SEGMENT_64
+# CHECK-NEXT: cmdsize
+# CHECK-NEXT: segname __TEXT
+# CHECK-NEXT: vmaddr 0x0000000000000000
+# CHECK: sectname __text
+# CHECK-NEXT: segname __TEXT
+# CHECK-NEXT: addr 0x[[#%.16x,CALL-1]]
+# CHECK: sectname __cstring
+# CHECK: cmd LC_SEGMENT_64
+# CHECK-NEXT: cmdsize
+# CHECK-NEXT: segname __DATA
+# CHECK: sectname __got
+# CHECK-NEXT: segname __DATA
+# CHECK-NEXT: addr 0x[[#%.16x,GOT]]
+# CHECK: type S_REGULAR
+# CHECK-NEXT: attributes (none)
+# CHECK-NEXT: reserved1 0
+# CHECK: sectname __data
+# CHECK-NEXT: segname __DATA
+# CHECK-NEXT: addr 0x[[#%.16x,EXTPTR-16]]
+# CHECK: cmd LC_SEGMENT_64
+# CHECK-NEXT: cmdsize
+# CHECK-NEXT: segname __LINKEDIT
+# CHECK: cmd LC_SYMTAB
+# CHECK: cmd LC_DYSYMTAB
+# CHECK: nindirectsyms 1
+# CHECK-NEXT: extreloff
+# CHECK-NEXT: nextrel 3
+# CHECK-NEXT: locreloff
+# CHECK-NEXT: nlocrel 3
+# CHECK: cmd LC_UUID
+
+## The load from the GOT of a weak definition is relaxed, and calls to it are
+## direct.
+# DISASM: _start_fn:
+# DISASM-NEXT: callq _ext_func
+# DISASM-NEXT: movq 0x{{[0-9a-f]+}}(%rip), %rax
+# DISASM-NEXT: leaq _weak_data(%rip), %rax
+# DISASM-NEXT: callq _weak_fn
+
+## The addend of an external relocation is left in place.
+# DATA: Contents of (__DATA,__data) section
+# DATA-NEXT: {{^[0-9a-f]+}}
+# DATA-NEXT: {{^[0-9a-f]+}} 08 00 00 00 00 00 00 00
+
+## ld64 defines no header symbol for kexts.
+# SYMS-NOT: __mh_
+# SYMS: (undefined) external _ext_data (dynamically looked up)
+# SYMS: (undefined) external _ext_func (dynamically looked up)
+
+## Like ld64, kexts are never linked against dylibs.
+# RUN: %no-lsystem-lld -dylib -o %t/libfoo.dylib %t/lib.o
+# RUN: llvm-ar rcs %t/libbar.a %t/lib.o
+# RUN: not %no-lsystem-lld -kext -o /dev/null %t/main.o -L%t -lfoo 2>&1 | \
+# RUN: FileCheck %s --check-prefix=NOLIB
+# RUN: %no-lsystem-lld -kext -o /dev/null %t/main.o -L%t -lbar
+# RUN: %no-arg-lld -arch x86_64 -platform_version macos 11.0 11.0 -kext \
+# RUN: -o /dev/null %t/main.o %t/libfoo.dylib 2>&1 | \
+# RUN: FileCheck %s --check-prefix=DYLIB -DFILE=%t/libfoo.dylib
+# NOLIB: error: library not found for -lfoo
+# DYLIB: warning: ignoring unexpected dylib '[[FILE]]'
+
+# RUN: not %no-lsystem-lld -kext -o /dev/null %t/signed.o 2>&1 | \
+# RUN: FileCheck %s --check-prefix=SIGNED
+# SIGNED: error: {{.*}}signed.o:(symbol _f+0x3): SIGNED relocation to _ext cannot be bound in a kext; access it through the GOT instead
+
+# RUN: not %no-lsystem-lld -kext -fixup_chains -o /dev/null %t/main.o 2>&1 | \
+# RUN: FileCheck %s --check-prefix=CHAINED
+# CHAINED: error: -fixup_chains is incompatible with -kext
+
+# RUN: not %no-lsystem-lld -kext -arch arm64 -o /dev/null %t/main.o 2>&1 | \
+# RUN: FileCheck %s --check-prefix=ARM64
+# ARM64: error: -kext is only supported for x86_64 targets
+
+#--- main.s
+.text
+.globl _start_fn
+_start_fn:
+ callq _ext_func
+ movq _ext_data at GOTPCREL(%rip), %rax
+ movq _weak_data at GOTPCREL(%rip), %rax
+ callq _weak_fn
+ retq
+
+.globl _weak_fn
+.weak_definition _weak_fn
+_weak_fn:
+ retq
+
+.cstring
+Lstr:
+ .asciz "kext"
+
+.data
+_local_ptr:
+ .quad _start_fn
+_section_ptr:
+ .quad Lstr
+_ext_ptr:
+ .quad _ext_data + 8
+_weak_ptr:
+ .quad _weak_data
+
+.globl _weak_data
+.weak_definition _weak_data
+_weak_data:
+ .quad 0
+
+.subsections_via_symbols
+
+#--- signed.s
+.text
+.globl _f
+_f:
+ leaq _ext(%rip), %rax
+ retq
+
+#--- lib.s
+.globl _ext_func
+_ext_func:
+ retq
diff --git a/llvm/include/llvm/TextAPI/FileTypes.h b/llvm/include/llvm/TextAPI/FileTypes.h
index 5876e9d5a5304..b8d35130a39d7 100644
--- a/llvm/include/llvm/TextAPI/FileTypes.h
+++ b/llvm/include/llvm/TextAPI/FileTypes.h
@@ -40,6 +40,9 @@ enum FileType : unsigned {
/// Text-based stub file (.tbd) version 5.0
TBD_V5 = 1U << 7,
+ /// \brief MachO Kernel Extension Bundle file.
+ MachO_KextBundle = 1U << 8,
+
All = ~0U,
LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/All),
diff --git a/llvm/lib/ObjCopy/MachO/MachOLayoutBuilder.cpp b/llvm/lib/ObjCopy/MachO/MachOLayoutBuilder.cpp
index 8cd418276f851..5dcbedc5237d7 100644
--- a/llvm/lib/ObjCopy/MachO/MachOLayoutBuilder.cpp
+++ b/llvm/lib/ObjCopy/MachO/MachOLayoutBuilder.cpp
@@ -263,9 +263,9 @@ Error MachOLayoutBuilder::layoutTail(uint64_t Offset) {
// The order of LINKEDIT elements is as follows:
// rebase info, binding info, weak binding info, lazy binding info, export
- // trie, chained fixups, dyld exports trie, function starts, data-in-code,
- // symbol table, indirect symbol table, symbol table strings,
- // dylib codesign drs, and code signature.
+ // trie, chained fixups, dyld exports trie, local relocations, function
+ // starts, data-in-code, symbol table, external relocations, indirect symbol
+ // table, symbol table strings, dylib codesign drs, and code signature.
auto updateOffset = [&Offset, LinkEditAlign](uint64_t Size) {
uint64_t PreviousOffset = Offset;
// Match ld64 and lld-macho behavior by aligning all LINKEDIT entries to
@@ -289,6 +289,10 @@ Error MachOLayoutBuilder::layoutTail(uint64_t Offset) {
updateOffset(O.ChainedFixups.Data.size());
auto [StartOfDyldExportsTrie, DyldExportsTrieSize] =
updateOffset(DyldExportsTrieRawSize);
+ uint64_t StartOfLocalRelocations =
+ updateOffset(sizeof(MachO::any_relocation_info) *
+ O.LocalRelocations.size())
+ .first;
auto [StartOfFunctionStarts, FunctionStartsSize] =
updateOffset(O.FunctionStarts.Data.size());
auto [StartOfDataInCode, DataInCodeSize] =
@@ -297,6 +301,10 @@ Error MachOLayoutBuilder::layoutTail(uint64_t Offset) {
updateOffset(O.LinkerOptimizationHint.Data.size());
uint64_t StartOfSymbols =
updateOffset(NListSize * O.SymTable.Symbols.size()).first;
+ uint64_t StartOfExternalRelocations =
+ updateOffset(sizeof(MachO::any_relocation_info) *
+ O.ExternalRelocations.size())
+ .first;
uint64_t StartOfIndirectSymbols =
updateOffset(sizeof(uint32_t) * O.IndirectSymTable.Symbols.size()).first;
auto [StartOfSymbolStrings, SymbolStringsSize] =
@@ -372,13 +380,23 @@ Error MachOLayoutBuilder::layoutTail(uint64_t Offset) {
MLC.symtab_command_data.strsize = SymbolStringsSize;
break;
case MachO::LC_DYSYMTAB: {
+ // Dynamic relocations are only read from linked images; relocations of
+ // an MH_OBJECT file are attached to its sections.
+ const bool IsObjectFile =
+ O.Header.FileType == MachO::HeaderFileType::MH_OBJECT;
if (MLC.dysymtab_command_data.ntoc != 0 ||
MLC.dysymtab_command_data.nmodtab != 0 ||
MLC.dysymtab_command_data.nextrefsyms != 0 ||
- MLC.dysymtab_command_data.nlocrel != 0 ||
- MLC.dysymtab_command_data.nextrel != 0)
+ (IsObjectFile && (MLC.dysymtab_command_data.nlocrel != 0 ||
+ MLC.dysymtab_command_data.nextrel != 0)))
return createStringError(llvm::errc::not_supported,
"shared library is not yet supported");
+ MLC.dysymtab_command_data.extreloff =
+ O.ExternalRelocations.size() ? StartOfExternalRelocations : 0;
+ MLC.dysymtab_command_data.nextrel = O.ExternalRelocations.size();
+ MLC.dysymtab_command_data.locreloff =
+ O.LocalRelocations.size() ? StartOfLocalRelocations : 0;
+ MLC.dysymtab_command_data.nlocrel = O.LocalRelocations.size();
MLC.dysymtab_command_data.indirectsymoff =
O.IndirectSymTable.Symbols.size() ? StartOfIndirectSymbols : 0;
MLC.dysymtab_command_data.nindirectsyms =
diff --git a/llvm/lib/ObjCopy/MachO/MachOObjcopy.cpp b/llvm/lib/ObjCopy/MachO/MachOObjcopy.cpp
index 1bca325198e0d..9c100a56eb038 100644
--- a/llvm/lib/ObjCopy/MachO/MachOObjcopy.cpp
+++ b/llvm/lib/ObjCopy/MachO/MachOObjcopy.cpp
@@ -89,6 +89,11 @@ static void markSymbols(const CommonConfig &Config, Object &Obj) {
if (ISE.Symbol)
(*ISE.Symbol)->Referenced = true;
+ // Nor those referenced from external relocations, which --strip-all keeps.
+ for (const RelocationInfo &R : Obj.ExternalRelocations)
+ if (R.Symbol && *R.Symbol)
+ (*R.Symbol)->Referenced = true;
+
// --strip-all removes relocations, so their symbols need not be preserved.
if (Config.StripAll)
return;
@@ -144,6 +149,11 @@ static void updateAndRemoveSymbols(const CommonConfig &Config,
return false;
if (N->n_desc & MachO::REFERENCED_DYNAMICALLY)
return false;
+ // The kernel linker looks up the external symbols of a kext, such as
+ // _kmod_info, and binds other kexts to them.
+ if (Obj.Header.FileType == MachO::HeaderFileType::MH_KEXT_BUNDLE &&
+ N->isExternalSymbol())
+ return false;
if (Config.StripAll)
return true;
if (Config.DiscardMode == DiscardType::All && !(N->n_type & MachO::N_EXT))
diff --git a/llvm/lib/ObjCopy/MachO/MachOObject.cpp b/llvm/lib/ObjCopy/MachO/MachOObject.cpp
index e5f36c538ff7d..73bd769f1c904 100644
--- a/llvm/lib/ObjCopy/MachO/MachOObject.cpp
+++ b/llvm/lib/ObjCopy/MachO/MachOObject.cpp
@@ -123,6 +123,13 @@ Error Object::removeSections(
OldIndexToSection[(*I)->Index] = I->get();
(*I)->Index = NextSectionIndex++;
}
+ for (auto I = It, End = LC.Sections.end(); I != End; ++I)
+ for (const RelocationInfo &R : LocalRelocations)
+ if (R.Sec && *R.Sec == I->get())
+ return createStringError(std::errc::invalid_argument,
+ "section '%s' cannot be removed because it "
+ "is referenced by a local relocation",
+ (*I)->CanonicalName.c_str());
LC.Sections.erase(It, LC.Sections.end());
}
@@ -147,6 +154,14 @@ Error Object::removeSections(
(*R.Symbol)->Name.c_str(),
*((*R.Symbol)->section()),
Sec->CanonicalName.c_str());
+ for (const RelocationInfo &R : ExternalRelocations)
+ if (R.Symbol && *R.Symbol && DeadSymbols.count(*R.Symbol))
+ return createStringError(std::errc::invalid_argument,
+ "symbol '%s' defined in section with index "
+ "'%u' cannot be removed because it is "
+ "referenced by an external relocation",
+ (*R.Symbol)->Name.c_str(),
+ *((*R.Symbol)->section()));
SymTable.removeSymbols(IsDead);
for (std::unique_ptr<SymbolEntry> &S : SymTable.Symbols)
if (S->section())
diff --git a/llvm/lib/ObjCopy/MachO/MachOObject.h b/llvm/lib/ObjCopy/MachO/MachOObject.h
index 9895585fcab14..811cbdcf3b21d 100644
--- a/llvm/lib/ObjCopy/MachO/MachOObject.h
+++ b/llvm/lib/ObjCopy/MachO/MachOObject.h
@@ -309,6 +309,10 @@ struct Object {
LazyBindInfo LazyBinds;
ExportInfo Exports;
IndirectSymbolTable IndirectSymTable;
+ /// External and local relocation entries of a linked image (e.g. a kext
+ /// bundle), described by the LC_DYSYMTAB load command.
+ std::vector<RelocationInfo> ExternalRelocations;
+ std::vector<RelocationInfo> LocalRelocations;
LinkData DataInCode;
LinkData LinkerOptimizationHint;
LinkData FunctionStarts;
diff --git a/llvm/lib/ObjCopy/MachO/MachOReader.cpp b/llvm/lib/ObjCopy/MachO/MachOReader.cpp
index dd4d60deccbb8..6db24f9b1fd6a 100644
--- a/llvm/lib/ObjCopy/MachO/MachOReader.cpp
+++ b/llvm/lib/ObjCopy/MachO/MachOReader.cpp
@@ -45,6 +45,22 @@ static Section constructSectionCommon(const SectionType &Sec, uint32_t Index) {
return S;
}
+static RelocationInfo
+constructRelocationInfo(const object::MachOObjectFile &MachOObj,
+ object::DataRefImpl Rel) {
+ RelocationInfo R;
+ R.Symbol = nullptr; // We'll fill this field later.
+ R.Info = MachOObj.getRelocation(Rel);
+ R.Scattered = MachOObj.isRelocationScattered(R.Info);
+ unsigned Type = MachOObj.getAnyRelocationType(R.Info);
+ // TODO Support CPU_TYPE_ARM.
+ R.IsAddend =
+ !R.Scattered && (MachOObj.getHeader().cputype == MachO::CPU_TYPE_ARM64 &&
+ Type == MachO::ARM64_RELOC_ADDEND);
+ R.Extern = !R.Scattered && MachOObj.getPlainRelocationExternal(R.Info);
+ return R;
+}
+
Section constructSection(const MachO::section &Sec, uint32_t Index) {
return constructSectionCommon(Sec, Index);
}
@@ -90,22 +106,12 @@ Expected<std::vector<std::unique_ptr<Section>>> static extractSections(
S.Content =
StringRef(reinterpret_cast<const char *>(Data->data()), Data->size());
- const uint32_t CPUType = MachOObj.getHeader().cputype;
S.Relocations.reserve(S.NReloc);
for (auto RI = MachOObj.section_rel_begin(SecRef->getRawDataRefImpl()),
RE = MachOObj.section_rel_end(SecRef->getRawDataRefImpl());
- RI != RE; ++RI) {
- RelocationInfo R;
- R.Symbol = nullptr; // We'll fill this field later.
- R.Info = MachOObj.getRelocation(RI->getRawDataRefImpl());
- R.Scattered = MachOObj.isRelocationScattered(R.Info);
- unsigned Type = MachOObj.getAnyRelocationType(R.Info);
- // TODO Support CPU_TYPE_ARM.
- R.IsAddend = !R.Scattered && (CPUType == MachO::CPU_TYPE_ARM64 &&
- Type == MachO::ARM64_RELOC_ADDEND);
- R.Extern = !R.Scattered && MachOObj.getPlainRelocationExternal(R.Info);
- S.Relocations.push_back(R);
- }
+ RI != RE; ++RI)
+ S.Relocations.push_back(
+ constructRelocationInfo(MachOObj, RI->getRawDataRefImpl()));
assert(S.NReloc == S.Relocations.size() &&
"Incorrect number of relocations");
@@ -243,6 +249,22 @@ void MachOReader::readSymbolTable(Object &O) const {
}
}
+void MachOReader::readDynamicRelocations(Object &O) const {
+ // In an MH_OBJECT file, getRelocation() interprets the reference as a
+ // section relocation, and such files have no dynamic relocations anyway.
+ if (!O.DySymTabCommandIndex ||
+ O.Header.FileType == MachO::HeaderFileType::MH_OBJECT)
+ return;
+
+ for (const object::RelocationRef &Rel : MachOObj.external_relocations())
+ O.ExternalRelocations.push_back(
+ constructRelocationInfo(MachOObj, Rel.getRawDataRefImpl()));
+ for (auto RI = MachOObj.locrel_begin(), RE = MachOObj.locrel_end(); RI != RE;
+ ++RI)
+ O.LocalRelocations.push_back(
+ constructRelocationInfo(MachOObj, RI->getRawDataRefImpl()));
+}
+
void MachOReader::setSymbolInRelocationInfo(Object &O) const {
std::vector<const Section *> Sections;
for (auto &LC : O.LoadCommands)
@@ -265,6 +287,20 @@ void MachOReader::setSymbolInRelocationInfo(Object &O) const {
Reloc.Sec = Sections[SymbolNum - 1];
}
}
+
+ // A local relocation may use R_ABS instead of a section number; such
+ // entries have no section and are written back unchanged.
+ for (std::vector<RelocationInfo> *Relocs :
+ {&O.ExternalRelocations, &O.LocalRelocations})
+ for (RelocationInfo &Reloc : *Relocs)
+ if (!Reloc.Scattered && !Reloc.IsAddend) {
+ const uint32_t SymbolNum =
+ Reloc.getPlainRelocationSymbolNum(MachOObj.isLittleEndian());
+ if (Reloc.Extern)
+ Reloc.Symbol = O.SymTable.getSymbolByIndex(SymbolNum);
+ else if (SymbolNum >= 1 && SymbolNum <= Sections.size())
+ Reloc.Sec = Sections[SymbolNum - 1];
+ }
}
void MachOReader::readRebaseInfo(Object &O) const {
@@ -368,6 +404,7 @@ Expected<std::unique_ptr<Object>> MachOReader::create() const {
if (Error E = readLoadCommands(*Obj))
return std::move(E);
readSymbolTable(*Obj);
+ readDynamicRelocations(*Obj);
setSymbolInRelocationInfo(*Obj);
readRebaseInfo(*Obj);
readBindInfo(*Obj);
diff --git a/llvm/lib/ObjCopy/MachO/MachOReader.h b/llvm/lib/ObjCopy/MachO/MachOReader.h
index a88885d568179..ea84abd12d941 100644
--- a/llvm/lib/ObjCopy/MachO/MachOReader.h
+++ b/llvm/lib/ObjCopy/MachO/MachOReader.h
@@ -33,6 +33,7 @@ class MachOReader : public Reader {
void readHeader(Object &O) const;
Error readLoadCommands(Object &O) const;
void readSymbolTable(Object &O) const;
+ void readDynamicRelocations(Object &O) const;
void setSymbolInRelocationInfo(Object &O) const;
void readRebaseInfo(Object &O) const;
void readBindInfo(Object &O) const;
diff --git a/llvm/lib/ObjCopy/MachO/MachOWriter.cpp b/llvm/lib/ObjCopy/MachO/MachOWriter.cpp
index d83a82cee73ae..c895bcdd7002e 100644
--- a/llvm/lib/ObjCopy/MachO/MachOWriter.cpp
+++ b/llvm/lib/ObjCopy/MachO/MachOWriter.cpp
@@ -102,6 +102,14 @@ size_t MachOWriter::totalSize() const {
if (DySymTabCommand.indirectsymoff)
Ends.push_back(DySymTabCommand.indirectsymoff +
sizeof(uint32_t) * O.IndirectSymTable.Symbols.size());
+ if (DySymTabCommand.extreloff)
+ Ends.push_back(DySymTabCommand.extreloff +
+ sizeof(MachO::any_relocation_info) *
+ O.ExternalRelocations.size());
+ if (DySymTabCommand.locreloff)
+ Ends.push_back(DySymTabCommand.locreloff +
+ sizeof(MachO::any_relocation_info) *
+ O.LocalRelocations.size());
}
for (std::optional<size_t> LinkEditDataCommandIndex :
@@ -260,22 +268,27 @@ void MachOWriter::writeSections() {
assert((Sec->Size == Sec->Content.size()) && "Incorrect section size");
memcpy(Buf->getBufferStart() + Sec->Offset, Sec->Content.data(),
Sec->Content.size());
- for (size_t Index = 0; Index < Sec->Relocations.size(); ++Index) {
- RelocationInfo RelocInfo = Sec->Relocations[Index];
- if (!RelocInfo.Scattered && !RelocInfo.IsAddend) {
- const uint32_t SymbolNum = RelocInfo.Extern
- ? (*RelocInfo.Symbol)->Index
- : (*RelocInfo.Sec)->Index;
- RelocInfo.setPlainRelocationSymbolNum(SymbolNum, IsLittleEndian);
- }
- if (IsLittleEndian != sys::IsLittleEndianHost)
- MachO::swapStruct(
- reinterpret_cast<MachO::any_relocation_info &>(RelocInfo.Info));
- memcpy(Buf->getBufferStart() + Sec->RelOff +
- Index * sizeof(MachO::any_relocation_info),
- &RelocInfo.Info, sizeof(RelocInfo.Info));
- }
+ writeRelocations(Sec->Relocations, Sec->RelOff);
+ }
+}
+
+void MachOWriter::writeRelocations(ArrayRef<RelocationInfo> Relocations,
+ uint64_t Offset) {
+ for (size_t Index = 0; Index < Relocations.size(); ++Index) {
+ RelocationInfo RelocInfo = Relocations[Index];
+ if (!RelocInfo.Scattered && !RelocInfo.IsAddend &&
+ (RelocInfo.Extern || RelocInfo.Sec)) {
+ const uint32_t SymbolNum = RelocInfo.Extern ? (*RelocInfo.Symbol)->Index
+ : (*RelocInfo.Sec)->Index;
+ RelocInfo.setPlainRelocationSymbolNum(SymbolNum, IsLittleEndian);
}
+ if (IsLittleEndian != sys::IsLittleEndianHost)
+ MachO::swapStruct(
+ reinterpret_cast<MachO::any_relocation_info &>(RelocInfo.Info));
+ memcpy(Buf->getBufferStart() + Offset +
+ Index * sizeof(MachO::any_relocation_info),
+ &RelocInfo.Info, sizeof(RelocInfo.Info));
+ }
}
template <typename NListType>
@@ -407,6 +420,26 @@ void MachOWriter::writeIndirectSymbolTable() {
}
}
+void MachOWriter::writeExternalRelocations() {
+ if (!O.DySymTabCommandIndex)
+ return;
+
+ const MachO::dysymtab_command &DySymTabCommand =
+ O.LoadCommands[*O.DySymTabCommandIndex]
+ .MachOLoadCommand.dysymtab_command_data;
+ writeRelocations(O.ExternalRelocations, DySymTabCommand.extreloff);
+}
+
+void MachOWriter::writeLocalRelocations() {
+ if (!O.DySymTabCommandIndex)
+ return;
+
+ const MachO::dysymtab_command &DySymTabCommand =
+ O.LoadCommands[*O.DySymTabCommandIndex]
+ .MachOLoadCommand.dysymtab_command_data;
+ writeRelocations(O.LocalRelocations, DySymTabCommand.locreloff);
+}
+
void MachOWriter::writeLinkData(std::optional<size_t> LCIndex,
const LinkData &LD) {
if (!LCIndex)
@@ -641,6 +674,12 @@ void MachOWriter::writeTail() {
if (DySymTabCommand.indirectsymoff)
Queue.emplace_back(DySymTabCommand.indirectsymoff,
&MachOWriter::writeIndirectSymbolTable);
+ if (DySymTabCommand.extreloff)
+ Queue.emplace_back(DySymTabCommand.extreloff,
+ &MachOWriter::writeExternalRelocations);
+ if (DySymTabCommand.locreloff)
+ Queue.emplace_back(DySymTabCommand.locreloff,
+ &MachOWriter::writeLocalRelocations);
}
std::initializer_list<std::pair<std::optional<size_t>, WriteHandlerType>>
diff --git a/llvm/lib/ObjCopy/MachO/MachOWriter.h b/llvm/lib/ObjCopy/MachO/MachOWriter.h
index 5da29dff656a7..b72596d8ed2cf 100644
--- a/llvm/lib/ObjCopy/MachO/MachOWriter.h
+++ b/llvm/lib/ObjCopy/MachO/MachOWriter.h
@@ -39,6 +39,7 @@ class MachOWriter {
void writeLoadCommands();
template <typename StructType>
void writeSectionInLoadCommand(const Section &Sec, uint8_t *&Out);
+ void writeRelocations(ArrayRef<RelocationInfo> Relocations, uint64_t Offset);
void writeSections();
void writeSymbolTable();
void writeStringTable();
@@ -48,6 +49,8 @@ class MachOWriter {
void writeLazyBindInfo();
void writeExportInfo();
void writeIndirectSymbolTable();
+ void writeExternalRelocations();
+ void writeLocalRelocations();
void writeLinkData(std::optional<size_t> LCIndex, const LinkData &LD);
void writeCodeSignatureData();
void writeDataInCodeData();
diff --git a/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp b/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp
index f55bc9c1a28c2..b072f6349e317 100644
--- a/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp
+++ b/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp
@@ -157,6 +157,9 @@ static Error readMachOHeader(MachOObjectFile *Obj, RecordsSlice &Slice) {
case MachO::MH_BUNDLE:
BA.File = FileType::MachO_Bundle;
break;
+ case MachO::MH_KEXT_BUNDLE:
+ BA.File = FileType::MachO_KextBundle;
+ break;
}
if (H.flags & MachO::MH_TWOLEVEL)
diff --git a/llvm/test/tools/llvm-objcopy/MachO/kext-relocations.test b/llvm/test/tools/llvm-objcopy/MachO/kext-relocations.test
new file mode 100644
index 0000000000000..ca96d22980c84
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/MachO/kext-relocations.test
@@ -0,0 +1,170 @@
+## Check that the external and local relocations of a kext bundle are kept,
+## and that the symbols and sections they refer to are renumbered.
+
+## The input is linked from:
+## .text
+## .globl _f
+## _f:
+## callq _ext_func
+## retq
+## _local_fn:
+## retq
+## .section __TEXT,__unused
+## .quad 0
+## .data
+## .globl _p
+## _p:
+## .quad _ext_data
+## _q:
+## .quad _local_fn
+## _r:
+## .quad _q
+
+# RUN: yaml2obj %s -o %t
+
+# RUN: llvm-objcopy %t %t.copy
+# RUN: llvm-objdump --macho -r %t.copy | FileCheck %s -DDATA=3
+
+## Stripping removes the local symbols, but keeps the external ones, which the
+## kernel linker looks up.
+# RUN: llvm-strip %t -o %t.strip
+# RUN: llvm-objdump --macho -r %t.strip | FileCheck %s -DDATA=3
+# RUN: llvm-nm -m %t.strip | FileCheck %s --check-prefix=SYMS
+# RUN: llvm-strip -x %t -o %t.strip-x
+# RUN: cmp %t.strip %t.strip-x
+
+# CHECK: External relocation information 2 entries
+# CHECK-NEXT: address pcrel length extern type scattered symbolnum/value
+# CHECK-NEXT: 00000271 True long True BRANCH False _ext_func
+# CHECK-NEXT: 00001000 False quad True UNSIGND False _ext_data
+# CHECK-NEXT: Local relocation information 2 entries
+# CHECK-NEXT: address pcrel length extern type scattered symbolnum/value
+# CHECK-NEXT: 00001008 False quad False UNSIGND False 1 (__TEXT,__text)
+# CHECK-NEXT: 00001010 False quad False UNSIGND False [[DATA]] (__DATA,__data)
+
+# SYMS: (undefined) external _ext_data
+# SYMS-NEXT: (undefined) external _ext_func
+# SYMS-NEXT: (__TEXT,__text) external _f
+# SYMS-NEXT: (__DATA,__data) external _p
+# SYMS-EMPTY:
+
+# RUN: llvm-objcopy --remove-section __TEXT,__unused %t %t.removed
+# RUN: llvm-objdump --macho -r %t.removed | FileCheck %s -DDATA=2
+
+# RUN: not llvm-objcopy --remove-section __TEXT,__text %t /dev/null 2>&1 | \
+# RUN: FileCheck %s --check-prefix=ERROR -DFILE=%t
+# ERROR: error: '[[FILE]]': section '__TEXT,__text' cannot be removed because it is referenced by a local relocation
+
+--- !mach-o
+FileHeader:
+ magic: 0xFEEDFACF
+ cputype: 0x1000007
+ cpusubtype: 0x3
+ filetype: 0xB
+ ncmds: 5
+ sizeofcmds: 560
+ flags: 0x85
+ reserved: 0x0
+LoadCommands:
+ - cmd: LC_SEGMENT_64
+ cmdsize: 232
+ segname: __TEXT
+ vmaddr: 0
+ vmsize: 4096
+ fileoff: 0
+ filesize: 4096
+ maxprot: 5
+ initprot: 5
+ nsects: 2
+ flags: 0
+ Sections:
+ - sectname: __text
+ segname: __TEXT
+ addr: 0x270
+ size: 7
+ offset: 0x270
+ align: 0
+ reloff: 0x0
+ nreloc: 0
+ flags: 0x80000400
+ reserved1: 0x0
+ reserved2: 0x0
+ reserved3: 0x0
+ content: E800000000C3C3
+ - sectname: __unused
+ segname: __TEXT
+ addr: 0x277
+ size: 8
+ offset: 0x277
+ align: 0
+ reloff: 0x0
+ nreloc: 0
+ flags: 0x0
+ reserved1: 0x0
+ reserved2: 0x0
+ reserved3: 0x0
+ content: '0000000000000000'
+ - cmd: LC_SEGMENT_64
+ cmdsize: 152
+ segname: __DATA
+ vmaddr: 4096
+ vmsize: 4096
+ fileoff: 4096
+ filesize: 4096
+ maxprot: 3
+ initprot: 3
+ nsects: 1
+ flags: 0
+ Sections:
+ - sectname: __data
+ segname: __DATA
+ addr: 0x1000
+ size: 24
+ offset: 0x1000
+ align: 0
+ reloff: 0x0
+ nreloc: 0
+ flags: 0x0
+ reserved1: 0x0
+ reserved2: 0x0
+ reserved3: 0x0
+ content: '000000000000000076020000000000000810000000000000'
+ - cmd: LC_SEGMENT_64
+ cmdsize: 72
+ segname: __LINKEDIT
+ vmaddr: 8192
+ vmsize: 192
+ fileoff: 8192
+ filesize: 192
+ maxprot: 1
+ initprot: 1
+ nsects: 0
+ flags: 0
+ - cmd: LC_SYMTAB
+ cmdsize: 24
+ symoff: 8208
+ nsyms: 7
+ stroff: 8336
+ strsize: 48
+ - cmd: LC_DYSYMTAB
+ cmdsize: 80
+ ilocalsym: 0
+ nlocalsym: 3
+ iextdefsym: 3
+ nextdefsym: 2
+ iundefsym: 5
+ nundefsym: 2
+ tocoff: 0
+ ntoc: 0
+ modtaboff: 0
+ nmodtab: 0
+ extrefsymoff: 0
+ nextrefsyms: 0
+ indirectsymoff: 0
+ nindirectsyms: 0
+ extreloff: 8320
+ nextrel: 2
+ locreloff: 8192
+ nlocrel: 2
+__LINKEDIT: 08100000010000061010000003000006020000000E01000076020000000000000C0000000E03000008100000000000000F0000000E0300001010000000000000120000000F0100007002000000000000150000000F030000001000000000000018000000010000FE000000000000000022000000010000FE0000000000000000710200000600002D001000000500000E20005F6C6F63616C5F666E005F71005F72005F66005F70005F6578745F64617461005F6578745F66756E630000000000
+...
More information about the cfe-commits
mailing list