https://github.com/VisualEhrmanntraut updated 
https://github.com/llvm/llvm-project/pull/204805

>From d575e43643f7c90a1a2728352a24867d7105d9e9 Mon Sep 17 00:00:00 2001
From: Visual <[email protected]>
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@GOTPCREL(%rip), %rax
+  movq _weak_data@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
+...

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to