[lld] [llvm] [dyndbg][LLD][ELF] Initial LLD support for dynamic debugging (PR #214188)

Andrew Ng via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 3 06:57:37 PDT 2026


https://github.com/nga888 updated https://github.com/llvm/llvm-project/pull/214188

>From edcd2afb978f0fb99f743be2ae17744bfd48e1d7 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 4 Aug 2026 16:53:52 +0100
Subject: [PATCH 1/7] [dyndbg][LLD][ELF] Initial LLD support for dynamic
 debugging

The key changes are:

* Enable the nested "inner" unoptimized dynamic debugging relocatable
  link within the "outer" optimized link.
* Handle the dependencies between the "inner" unoptimized objects and
  the "outer" optimized objects.
* Make the "inner" unoptimized relocatable link more like a final
  executable link (to reduce output size overheads) except when the
  "outer" is itself a relocatable link.

RFC: https://discourse.llvm.org/t/90113
---
 lld/Common/CommonLinkerContext.cpp            |  14 +
 lld/ELF/Config.h                              |  19 +-
 lld/ELF/Driver.cpp                            |  79 +++-
 lld/ELF/InputFiles.cpp                        |  91 +++-
 lld/ELF/InputFiles.h                          |   6 +
 lld/ELF/LinkerScript.cpp                      |   4 +-
 lld/ELF/MarkLive.cpp                          |  15 +-
 lld/ELF/Symbols.h                             |   5 +
 lld/ELF/SyntheticSections.cpp                 |  37 ++
 lld/ELF/SyntheticSections.h                   |  14 +
 lld/ELF/Writer.cpp                            |  43 +-
 .../ELF/Inputs/trace-symbols-dyndbg-opt.s     |   4 +
 .../ELF/Inputs/trace-symbols-dyndbg-unopt.s   |   5 +
 lld/test/ELF/dynamic-debug.test               | 401 ++++++++++++++++++
 lld/test/ELF/trace-symbols.s                  |  51 ++-
 llvm/include/llvm/BinaryFormat/ELF.h          |   1 +
 16 files changed, 747 insertions(+), 42 deletions(-)
 create mode 100644 lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
 create mode 100644 lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
 create mode 100644 lld/test/ELF/dynamic-debug.test

diff --git a/lld/Common/CommonLinkerContext.cpp b/lld/Common/CommonLinkerContext.cpp
index 12f56bc10ec96..cafd78df4a2ba 100644
--- a/lld/Common/CommonLinkerContext.cpp
+++ b/lld/Common/CommonLinkerContext.cpp
@@ -22,7 +22,14 @@ using namespace lld;
 // state.
 static CommonLinkerContext *lctx;
 
+static uint32_t numNestedCtx;
+
 CommonLinkerContext::CommonLinkerContext() {
+  if (lctx) {
+    ++numNestedCtx;
+    return;
+  }
+
   lctx = this;
   // Fire off the static initializations in CGF's constructor.
   codegen::RegisterCodeGenFlags CGF;
@@ -34,6 +41,13 @@ CommonLinkerContext::~CommonLinkerContext() {
   // new in SpecificAlloc::create().
   for (auto &it : instances)
     it.second->~SpecificAllocBase();
+
+  if (numNestedCtx) {
+    assert(lctx != this);
+    --numNestedCtx;
+    return;
+  }
+
   lctx = nullptr;
 }
 
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 54e0aa58591ad..de61f3674d4ef 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -209,6 +209,7 @@ class LinkerDriver {
   void linkerMain(ArrayRef<const char *> args);
   void addFile(StringRef path, bool withLOption);
   void addLibrary(StringRef name);
+  void addFile(std::unique_ptr<ELFFileBase> ef);
 
 private:
   Ctx &ctx;
@@ -233,8 +234,8 @@ class LinkerDriver {
 
 public:
   // See InputFile::groupId.
-  uint32_t nextGroupId;
-  bool isInGroup;
+  uint32_t nextGroupId = 0;
+  bool isInGroup = false;
   std::unique_ptr<InputFile> armCmseImpLib;
   SmallVector<std::pair<StringRef, unsigned>, 0> archiveFiles;
 };
@@ -646,6 +647,8 @@ struct InStruct {
   std::unique_ptr<SymtabShndxSection> symTabShndx;
   std::unique_ptr<SyntheticSection> hexagonAttributes;
   std::unique_ptr<SyntheticSection> riscvAttributes;
+  std::unique_ptr<SyntheticSection> dynDbg;
+  std::unique_ptr<SyntheticSection> dynDbgNote;
 };
 
 struct Ctx : CommonLinkerContext {
@@ -784,6 +787,18 @@ struct Ctx : CommonLinkerContext {
   llvm::raw_fd_ostream openAuxiliaryFile(llvm::StringRef, std::error_code &);
 
   std::optional<AArch64PauthAbiCoreInfo> aarch64PauthAbiCoreInfo;
+
+  // True if performing the embedded unoptimized dynamic debugging relocatable
+  // link.
+  bool inDynDbgLink = false;
+  // True if performing dynamic debugging style relocatable link rather than a
+  // regular relocatable link.
+  bool dynDbgRelocatable = false;
+  // True if the link contains dynamic debugging.
+  bool hasDynDbg = false;
+  // Pointer to the output of the embedded unoptimized dynamic debugging
+  // relocatable link.
+  std::unique_ptr<llvm::FileOutputBuffer> dynDbgOutput;
 };
 
 // The first two elements of versionDefinitions represent VER_NDX_LOCAL and
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 0393410b35d4d..7130da9608e61 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -113,6 +113,22 @@ llvm::raw_fd_ostream Ctx::openAuxiliaryFile(llvm::StringRef filename,
   return {filename, ec, flags};
 }
 
+static void initContext(Ctx &ctx, LinkerScript &script, StringRef arg0,
+                        llvm::raw_ostream &stdoutOS,
+                        llvm::raw_ostream &stderrOS, bool exitEarly,
+                        bool disableOutput) {
+  ErrorHandler &e = ctx.e;
+  e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
+  e.logName = args::getFilenameWithoutExe(arg0);
+  e.errorLimitExceededMsg = "too many errors emitted, stopping now (use "
+                            "--error-limit=0 to see all errors)";
+  ctx.script = &script;
+  ctx.symAux.emplace_back();
+  ctx.symtab = std::make_unique<SymbolTable>(ctx);
+
+  ctx.arg.progName = arg0;
+}
+
 namespace lld {
 namespace elf {
 bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
@@ -120,19 +136,9 @@ bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
   // This driver-specific context will be freed later by unsafeLldMain().
   auto *context = new Ctx;
   Ctx &ctx = *context;
-
-  context->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
-  context->e.logName = args::getFilenameWithoutExe(args[0]);
-  context->e.errorLimitExceededMsg =
-      "too many errors emitted, stopping now (use "
-      "--error-limit=0 to see all errors)";
-
   LinkerScript script(ctx);
-  ctx.script = &script;
-  ctx.symAux.emplace_back();
-  ctx.symtab = std::make_unique<SymbolTable>(ctx);
-
-  ctx.arg.progName = args[0];
+  initContext(ctx, script, args[0], stdoutOS, stderrOS, exitEarly,
+              disableOutput);
 
   ctx.driver.linkerMain(args);
 
@@ -301,6 +307,11 @@ void LinkerDriver::addLibrary(StringRef name) {
                 {name});
 }
 
+// Add an ELF input file directly.
+void LinkerDriver::addFile(std::unique_ptr<ELFFileBase> ef) {
+  files.push_back(std::move(ef));
+}
+
 // This function is called on startup. We need this for LTO since
 // LTO calls LLVM functions to compile bitcode files to native code.
 // Technically this can be delayed until we read bitcode files, but
@@ -2230,8 +2241,6 @@ void LinkerDriver::createFiles(opt::InputArgList &args) {
 
   // Iterate over argv to process input files and positional arguments.
   std::optional<MemoryBufferRef> defaultScript;
-  nextGroupId = 0;
-  isInGroup = false;
   bool hasInput = false, hasScript = false;
   for (auto *arg : args) {
     switch (arg->getOption().getID()) {
@@ -3240,6 +3249,41 @@ static void postParseObjectFile(ELFFileBase *file) {
   }
 }
 
+template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
+  Ctx dc;
+  LinkerScript script(dc);
+  initContext(dc, script, ctx.arg.progName, ctx.e.outs(), ctx.e.errs(),
+              ctx.e.exitEarly, ctx.e.disableOutput);
+  dc.inDynDbgLink = true;
+  dc.dynDbgRelocatable = !ctx.arg.relocatable;
+
+  for (auto *file : ctx.objectFiles) {
+    auto *obj = cast<ObjFile<ELFT>>(file);
+    if (obj->dynDbgSec) {
+      auto content = obj->dynDbgSec->content();
+      MemoryBufferRef mb({(const char *)content.data(), content.size()},
+                         obj->mb.getBufferIdentifier());
+      dc.driver.addFile(createObjFile(dc, mb));
+    }
+  }
+
+  if (errCount(ctx))
+    return;
+
+  std::vector<const char *> args{
+      dc.arg.progName.data(), "-r", "-o", "-",
+      dc.saver.save(Twine("-O") + Twine(ctx.arg.optimize)).data()};
+  if (ctx.arg.resolveGroups)
+    args.push_back("--force-group-allocation");
+  dc.driver.linkerMain(args);
+  if (errCount(dc) > 0 || !dc.dynDbgOutput) {
+    Err(ctx) << "Failed to create relocatable dynamic debug object";
+    return;
+  }
+
+  ctx.dynDbgOutput.swap(dc.dynDbgOutput);
+}
+
 // Do actual linking. Note that when this function is called,
 // all linker scripts have already been parsed.
 template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
@@ -3533,6 +3577,13 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
     createTaggedSymbols(ctx);
   }
 
+  if (ctx.hasDynDbg) {
+    llvm::TimeTraceScope timeScope("Link dynamic debugging");
+    linkDynamicDebug<ELFT>(ctx);
+    if (errCount(ctx))
+      return;
+  }
+
   // Create synthesized sections such as .got and .plt. This is called before
   // processSectionCommands() so that they can be placed by SECTIONS commands.
   createSyntheticSections<ELFT>(ctx);
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 05c0f3f1c445b..31c22472519d5 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -588,6 +588,7 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
 
     if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS))
       continue;
+
     if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) {
       StringRef signature = getShtGroupSignature(objSections, sec);
       ArrayRef<Elf_Word> entries =
@@ -640,6 +641,26 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
       continue;
     }
 
+    if (sec.sh_type == SHT_LLVM_DYNDBG_ELF) {
+      // Check for presence of dynamic debugging section and create the input
+      // section but mark for discard.
+      StringRef name = check(obj.getSectionName(sec, shstrtab));
+      if (name == dynDbgSecName) {
+        sections[i] = &InputSection::discarded;
+        dynDbgSec = std::make_unique<InputSection>(*this, sec, name);
+        ctx.hasDynDbg = true;
+
+        // If ICF is enabled, warn and disable it because it's incompatible with
+        // dynamic debugging.
+        if (ctx.arg.icf != ICFLevel::None) {
+          Warn(ctx) << "ICF disabled because it is incompatible with dynamic "
+                       "debugging";
+          ctx.arg.icf = ICFLevel::None;
+        }
+      }
+      continue;
+    }
+
     switch (ctx.arg.emachine) {
     case EM_ARM:
       if (sec.sh_type == SHT_ARM_ATTRIBUTES) {
@@ -827,7 +848,9 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
     }
     switch (type) {
     case SHT_GROUP: {
-      if (!ctx.arg.relocatable)
+      // Discard groups for the embedded unoptimized dynamic debugging
+      // relocatable link.
+      if (!ctx.arg.relocatable || ctx.dynDbgRelocatable)
         sections[i] = &InputSection::discarded;
       // Use the verdict parse() recorded for this group instead of repeating
       // the signature hashing and comdatGroups lookup.
@@ -1246,6 +1269,72 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
     sym->isUsedInRegularObj = true;
     sym->referenced = true;
   }
+
+  // Process the undefined symbols of the "inner" embedded unoptimized dynamic
+  // debugging object to ensure that the "outer" ELF contains the dependencies
+  // of the "inner" ELF. Also tag which "outer" global symbols are dynamic
+  // debugging references, i.e. used in an "inner" relocation for a SHT_PROGBITS
+  // and SHF_ALLOC section, via the `isDynDbgRef` flag.
+  if (dynDbgSec) {
+    auto content = dynDbgSec->content();
+    MemoryBufferRef dbgMb({(const char *)content.data(), content.size()},
+                          mb.getBufferIdentifier());
+    std::unique_ptr<ELFFileBase> efb = createObjFile(ctx, dbgMb);
+    ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(efb.get());
+    if (dbgObj) {
+      SmallVector<bool, 0> globalUsed(dbgObj->numSymbols - dbgObj->firstGlobal);
+      ArrayRef<Elf_Shdr> shdrs = dbgObj->template getELFShdrs<ELFT>();
+      for (size_t i = 0, end = shdrs.size(); i != end; ++i) {
+        const Elf_Shdr &sh = shdrs[i];
+        if (!isStaticRelSecType(sh.sh_type))
+          continue;
+
+        const Elf_Shdr &target = shdrs[sh.sh_info];
+        if (target.sh_type != SHT_PROGBITS ||
+            (target.sh_flags & SHF_ALLOC) == 0)
+          continue;
+
+        auto setSymUsed = [&,
+                           firstGlobal = dbgObj->firstGlobal](uint32_t symIdx) {
+          if (symIdx >= firstGlobal)
+            globalUsed[symIdx - firstGlobal] = true;
+        };
+
+        auto isec = std::make_unique<InputSection>(*dbgObj, sh, StringRef());
+        isec->relSecIdx = i;
+        auto relocs = isec->template relsOrRelas<ELFT>(/*supportsCrel=*/false);
+        if (relocs.areRelocsRel()) {
+          for (auto const &r : relocs.rels)
+            setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
+        } else {
+          for (auto const &r : relocs.relas)
+            setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
+        }
+      }
+
+      ArrayRef<Elf_Sym> dbgSyms = dbgObj->template getGlobalELFSyms<ELFT>();
+      assert(dbgSyms.size() == globalUsed.size());
+      for (size_t i = 0, end = dbgSyms.size(); i != end; ++i) {
+        const Elf_Sym &s = dbgSyms[i];
+        if (s.st_shndx != SHN_UNDEF)
+          continue;
+
+        StringRef name = CHECK2(s.getName(dbgObj->stringTable), this);
+        Symbol *sym = symtab->addSymbol(
+            Undefined{this, name, s.getBinding(), s.st_other, s.getType()});
+        sym->isUsedInRegularObj = true;
+        sym->referenced = true;
+        if (globalUsed[i]) {
+          sym->isDynDbgRef = true;
+          if (sym->traced)
+            Msg(ctx) << this << ": dynamic debugging reference to " << name;
+        }
+      }
+    } else {
+      Err(ctx) << this << ": " << dynDbgSecName
+               << " contains an incompatible ELF type";
+    }
+  }
 }
 
 template <class ELFT>
diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h
index 0ded9b2fa38e2..22c29f9ea7909 100644
--- a/lld/ELF/InputFiles.h
+++ b/lld/ELF/InputFiles.h
@@ -268,6 +268,9 @@ template <class ELFT> class ObjFile : public ELFFileBase {
   // Pointer to this input file's .llvm_addrsig section, if it has one.
   const Elf_Shdr *addrsigSec = nullptr;
 
+  // Embedded unoptimized dynamic debug input section.
+  std::unique_ptr<InputSection> dynDbgSec;
+
   // SHT_LLVM_CALL_GRAPH_PROFILE section index.
   uint32_t cgProfileSectionIndex = 0;
 
@@ -389,6 +392,9 @@ std::unique_ptr<ELFFileBase> createObjFile(Ctx &, MemoryBufferRef mb,
 
 std::string replaceThinLTOSuffix(Ctx &, StringRef path);
 
+// Name of embedded unoptimized dynamic debug input/output section.
+constexpr StringRef dynDbgSecName = ".debug_llvm_dyndbg";
+
 } // namespace elf
 } // namespace lld
 
diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index 64fb717e17d9d..d7a057c1915b0 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -67,7 +67,9 @@ StringRef LinkerScript::getOutputSectionName(const InputSectionBase *s) const {
         return ss.save(".rela" + out->name);
       return ss.save(".rel" + out->name);
     }
-    if (ctx.arg.relocatable)
+    // Use default LLD behavior for the embedded unoptimized dynamic debugging
+    // relocatable link.
+    if (ctx.arg.relocatable && !ctx.dynDbgRelocatable)
       return s->name;
   }
 
diff --git a/lld/ELF/MarkLive.cpp b/lld/ELF/MarkLive.cpp
index 2820818133d5c..8e5071cdc0073 100644
--- a/lld/ELF/MarkLive.cpp
+++ b/lld/ELF/MarkLive.cpp
@@ -363,11 +363,20 @@ template <class ELFT, bool TrackWhyLive>
 void MarkLive<ELFT, TrackWhyLive>::run() {
   // Add GC root symbols.
 
-  // Preserve externally-visible symbols if the symbols defined by this
-  // file can interpose other ELF file's symbols at runtime.
-  for (Symbol *sym : ctx.symtab->getSymbols())
+  for (Symbol *sym : ctx.symtab->getSymbols()) {
+    // For now, preserve all symbols required by the embedded unoptimized part
+    // of dynamic debugging. TODO: better support for `--gc-sections`.
+    if (sym->isDynDbgRef) {
+      markSymbol(sym, "dynamic debugging");
+      sym->setFlags(USED);
+      continue;
+    }
+
+    // Preserve externally-visible symbols if the symbols defined by this
+    // file can interpose other ELF file's symbols at runtime.
     if (sym->isExported)
       markSymbol(sym, "externally visible symbol");
+  }
 
   markSymbol(ctx.symtab->find(ctx.arg.entry), "entry point");
   markSymbol(ctx.symtab->find(ctx.arg.init), "initializer function");
diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h
index 0893776882dc6..9a0909037f51e 100644
--- a/lld/ELF/Symbols.h
+++ b/lld/ELF/Symbols.h
@@ -341,6 +341,11 @@ class Symbol {
   LLVM_PREFERRED_TYPE(bool)
   uint8_t referencedAfterWrap : 1;
 
+  // True if this symbol is referenced by the embedded unoptimized part of
+  // dynamic debugging.
+  LLVM_PREFERRED_TYPE(bool)
+  uint8_t isDynDbgRef : 1;
+
   void setFlags(uint16_t bits) {
     flags.fetch_or(bits, std::memory_order_relaxed);
   }
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 6bac881446fd0..84099e6e7503c 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4411,6 +4411,36 @@ size_t MemtagGlobalDescriptors::getSize() const {
   return createMemtagGlobalDescriptors(ctx, symbols);
 }
 
+DynamicDebugSection::DynamicDebugSection(Ctx &ctx)
+    : SyntheticSection(ctx, dynDbgSecName, SHT_LLVM_DYNDBG_ELF, 0, 8) {
+  assert(ctx.dynDbgOutput);
+}
+
+size_t DynamicDebugSection::getSize() const {
+  return ctx.dynDbgOutput->getBufferSize();
+}
+
+void DynamicDebugSection::writeTo(uint8_t *buf) {
+  memcpy(buf, ctx.dynDbgOutput->getBufferStart(),
+         ctx.dynDbgOutput->getBufferSize());
+}
+
+DynamicDebugNote::DynamicDebugNote(Ctx &ctx)
+    : SyntheticSection(ctx, ".note.llvm.dyndbg", SHT_NOTE, 0, 4) {}
+
+size_t DynamicDebugNote::getSize() const {
+  return /*hdrsz=*/12 + /*namesz=*/8 + /*descsz=*/sizeof(uint32_t);
+}
+
+void DynamicDebugNote::writeTo(uint8_t *buf) {
+  write32(ctx, buf, 5);                             // Name size
+  write32(ctx, buf + 4, sizeof(uint32_t));          // Content size
+  write32(ctx, buf + 8, NT_LLVM_DYNAMIC_DEBUGGING); // Type
+  memcpy(buf + 12, "LLVM", 5);                      // Name string
+  uint32_t version = 0;
+  write32(ctx, buf + 20, version);
+}
+
 static OutputSection *findSection(Ctx &ctx, StringRef name) {
   for (SectionCommand *cmd : ctx.script->sectionCommands)
     if (auto *osd = dyn_cast<OutputDesc>(cmd))
@@ -4635,6 +4665,13 @@ template <class ELFT> void elf::createSyntheticSections(Ctx &ctx) {
     add(*ctx.in.shStrTab);
   if (ctx.in.strTab)
     add(*ctx.in.strTab);
+
+  if (ctx.dynDbgOutput) {
+    ctx.in.dynDbg = std::make_unique<DynamicDebugSection>(ctx);
+    add(*ctx.in.dynDbg);
+    ctx.in.dynDbgNote = std::make_unique<DynamicDebugNote>(ctx);
+    add(*ctx.in.dynDbgNote);
+  }
 }
 
 template void elf::splitSections<ELF32LE>(Ctx &);
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index 19d4461348f3f..fefd21b336e24 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -1317,6 +1317,20 @@ class MemtagGlobalDescriptors final : public SyntheticSection {
   SmallVector<const Symbol *, 0> symbols;
 };
 
+class DynamicDebugSection final : public SyntheticSection {
+public:
+  DynamicDebugSection(Ctx &);
+  size_t getSize() const override;
+  void writeTo(uint8_t *buf) override;
+};
+
+class DynamicDebugNote final : public SyntheticSection {
+public:
+  DynamicDebugNote(Ctx &);
+  size_t getSize() const override;
+  void writeTo(uint8_t *buf) override;
+};
+
 template <class ELFT> void createSyntheticSections(Ctx &);
 InputSection *createInterpSection(Ctx &);
 MergeInputSection *createCommentSection(Ctx &);
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 8cf88cd99cf8a..bd0d394386aac 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -365,10 +365,14 @@ template <class ELFT> void Writer<ELFT>::run() {
     if (errCount(ctx))
       return;
 
-    // With -o -, write to lld::outs() (the stdoutOS argument of
+    // Capture output for the embedded unoptimized dynamic debugging relocatable
+    // link.
+    // Otherwise, with -o -, write to lld::outs() (the stdoutOS argument of
     // link()) instead of committing the buffer, which would write to the
     // process's stdout.
-    if (ctx.arg.outputFile == "-") {
+    if (ctx.inDynDbgLink)
+      ctx.dynDbgOutput.swap(buffer);
+    else if (ctx.arg.outputFile == "-") {
       ctx.e.outs() << StringRef(
           reinterpret_cast<const char *>(buffer->getBufferStart()),
           buffer->getBufferSize());
@@ -1914,6 +1918,41 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
     // called after processSymbolAssignments() because it needs to know whether
     // a linker-script-defined symbol is absolute.
     scanRelocations<ELFT>(ctx);
+
+    // Process symbols referenced by the embedded unoptimized part of dynamic
+    // debugging.
+    if (ctx.hasDynDbg) {
+      bool ignoreUnresolved =
+          (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Ignore);
+      bool warnOnly = (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn);
+      for (Symbol *sym : ctx.symtab->getSymbols()) {
+        if (!sym->isDynDbgRef)
+          continue;
+
+        if (sym->isUndefined()) {
+          if (ignoreUnresolved || sym->isWeak())
+            continue;
+
+          static InputSection dummy(ctx.internalFile, dynDbgSecName, 0, 0, 0, 0,
+                                    ArrayRef<uint8_t>());
+          ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(sym->file);
+          InputSectionBase *isec =
+              dbgObj && dbgObj->dynDbgSec ? dbgObj->dynDbgSec.get() : &dummy;
+          ctx.undefErrs.push_back(
+              {cast<Undefined>(sym), {{isec, 0}}, warnOnly});
+          continue;
+        }
+
+        // Ensure there are PLT/GOT entries for references to shared symbols.
+        if (sym->isShared() && sym->isUsedInRegularObj && sym->dsoDefined) {
+          if (sym->isFunc())
+            sym->setFlags(NEEDS_PLT);
+          else if (sym->isObject())
+            sym->setFlags(NEEDS_GOT);
+        }
+      }
+    }
+
     reportUndefinedSymbols(ctx);
     postScanRelocations(ctx);
 
diff --git a/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s b/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
new file mode 100644
index 0000000000000..1fba360bb54ab
--- /dev/null
+++ b/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
@@ -0,0 +1,4 @@
+.globl	baz
+.type	baz, @function
+baz:
+nop
diff --git a/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s b/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
new file mode 100644
index 0000000000000..7688e526340d9
--- /dev/null
+++ b/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
@@ -0,0 +1,5 @@
+.globl	__dyndbg.baz
+.hidden	__dyndbg.baz
+.type	__dyndbg.baz, @function
+__dyndbg.baz:
+callq foo
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
new file mode 100644
index 0000000000000..19a99619e95e7
--- /dev/null
+++ b/lld/test/ELF/dynamic-debug.test
@@ -0,0 +1,401 @@
+REQUIRES: x86
+
+## This test simulates the input from dynamic debugging objects to test ELF LLD's handling
+## of various specific situations that can arise with such objects. These are mainly
+## related to dependencies from the "inner" unoptimized objects.
+
+RUN: rm -rf %t && split-file %s %t
+
+## Create the "outer" optimized objects.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main.s -o %t/main.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+
+## Link only the "outer" optimized objects using `--gc-sections`.
+RUN: ld.lld -e main --gc-sections -o %t/out %t/main.o %t/foo.o
+RUN: llvm-readelf -s %t/out | FileCheck %s
+
+CHECK: Symbol table '.symtab' contains 12 entries:
+CHECK-DAG: {{.*}} 0 FUNC    LOCAL  DEFAULT [[#]] static_func
+CHECK-DAG: {{.*}} 0 OBJECT  LOCAL  DEFAULT [[#]] static_data
+CHECK-DAG: {{.*}} 0 FUNC    LOCAL  HIDDEN  [[#]] static_func.dyndbg.main
+CHECK-DAG: {{.*}} 0 OBJECT  LOCAL  HIDDEN  [[#]] static_data.dyndbg.main
+CHECK-DAG: {{.*}} 0 FUNC    LOCAL  DEFAULT [[#]] static_func
+CHECK-DAG: {{.*}} 0 OBJECT  LOCAL  DEFAULT [[#]] static_data
+CHECK-DAG: {{.*}} 0 FUNC    LOCAL  HIDDEN  [[#]] static_func.dyndbg.foo
+CHECK-DAG: {{.*}} 0 OBJECT  LOCAL  HIDDEN  [[#]] static_data.dyndbg.foo
+CHECK-DAG: {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] func
+CHECK-DAG: {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] main
+CHECK-DAG: {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] foo
+
+## Create and embed the "inner" unoptimized objects to simulate dynamic debugging objects.
+## Note that the embedded `.debug_llvm_dyndbg` section should be aligned to 8.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main-unopt.s -o %t/main-unopt.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo-unopt.s -o %t/foo-unopt.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/main-unopt.o %t/main.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t/main.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/foo-unopt.o %t/foo.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t/foo.o
+
+## Create shared object to test handling of "inner" only dependencies.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/shared.s -o %t/shared.o
+RUN: ld.lld -shared -o %t/shared.so %t/shared.o
+
+## Link simulated dynamic debugging objects.
+RUN: ld.lld -e main -o %t/out-dd %t/main.o %t/foo.o %t/shared.so
+RUN: llvm-readelf -Srsn %t/out-dd | FileCheck -DNSYM=21 --check-prefixes=DD,NO-GC %s
+
+## Link simulated dynamic debugging objects using `--gc-sections`.
+## Note that `--gc-sections` is not yet supported for the "inner" unoptimized relocatable
+## link.
+RUN: ld.lld -e main --gc-sections -o %t/out-dd-gc %t/main.o %t/foo.o %t/shared.so
+RUN: llvm-readelf -Srsn %t/out-dd-gc | FileCheck -DNSYM=19 --check-prefixes=DD,GC %s
+
+## Check dynamic debugging related sections.
+DD: There are 20 section headers, starting at offset {{.*}}:
+DD:     Name               Type            Address          Off    Size   ES Flg Lk    Inf   Al
+DD-DAG: .debug_llvm_dyndbg LLVM_DYNDBG_ELF 0000000000000000 {{.*}} {{.*}} 00      0      0    8
+DD-DAG: .note.llvm.dyndbg  NOTE            0000000000000000 {{.*}} {{.*}} 00      0      0    4
+
+## Check GOT/PLT entries in "outer" ELF for shared symbols only referenced by "inner"
+## unoptimized objects.
+DD: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entries:
+DD: {{.*}} {{.*}} R_X86_64_GLOB_DAT 0000000000000000 shared_data_for_unopt
+DD: Relocation section '.rela.plt' at offset {{.*}} contains 1 entries:
+DD: {{.*}} {{.*}} R_X86_64_JUMP_SLOT 0000000000000000 shared_func_for_unopt
+
+NO-GC: Symbol table '.dynsym' contains 4 entries:
+GC:    Symbol table '.dynsym' contains 3 entries:
+DD-DAG:    0000000000000000     0 OBJECT  GLOBAL DEFAULT   UND shared_data_for_unopt
+DD-DAG:    0000000000000000     0 FUNC    GLOBAL DEFAULT   UND shared_func_for_unopt
+NO-GC-DAG: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT   UND undef_used_by_non_alloc
+
+DD:    Symbol table '.symtab' contains [[NSYM]] entries:
+DD-DAG:    {{.*}} 0 FUNC    LOCAL  DEFAULT [[#]] static_func
+DD-DAG:    {{.*}} 0 OBJECT  LOCAL  DEFAULT [[#]] static_data
+DD-DAG:    {{.*}} 0 FUNC    LOCAL  HIDDEN  [[#]] static_func.dyndbg.main
+DD-DAG:    {{.*}} 0 OBJECT  LOCAL  HIDDEN  [[#]] static_data.dyndbg.main
+DD-DAG:    {{.*}} 0 FUNC    LOCAL  DEFAULT [[#]] static_func
+DD-DAG:    {{.*}} 0 OBJECT  LOCAL  DEFAULT [[#]] static_data
+DD-DAG:    {{.*}} 0 FUNC    LOCAL  HIDDEN  [[#]] static_func.dyndbg.foo
+DD-DAG:    {{.*}} 0 OBJECT  LOCAL  HIDDEN  [[#]] static_data.dyndbg.foo
+DD-DAG:    {{.*}} 0 NOTYPE  LOCAL  HIDDEN  [[#]] _DYNAMIC
+DD-DAG:    {{.*}} 0 FUNC    WEAK   DEFAULT [[#]] comdat_func
+DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] func
+DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] main
+DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] foo
+DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] inlined_in_opt
+NO-GC-DAG: {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] unused_func
+DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] used_by_unused_func
+DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] inlined_for_unopt_and_non_alloc
+DD-DAG:    {{.*}} 0 OBJECT  GLOBAL DEFAULT   UND shared_data_for_unopt
+DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT   UND shared_func_for_unopt
+NO-GC-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND undef_used_by_non_alloc
+REL-DAG:   {{.*}} 0 FILE    LOCAL  DEFAULT   ABS main.o
+REL-DAG:   {{.*}} 0 FILE    LOCAL  DEFAULT   ABS foo.o
+REL-DAG:   {{.*}} 0 FILE    LOCAL  DEFAULT   ABS
+
+DD:      LLVM  0x00000004  Unknown note type: (0x00000004)
+DD-NEXT: description data: 00 00 00 00
+
+## Extract and check "inner" ELF for the default link.
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-dd.o %t/out-dd /dev/null
+RUN: llvm-readelf -Ssg %t/in-dd.o | FileCheck --check-prefix=IN %s
+
+## Extract and check "inner" ELF for the `--gc-sections` link.
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-dd-gc.o %t/out-dd-gc /dev/null
+RUN: llvm-readelf -Ssg %t/in-dd-gc.o | FileCheck --check-prefix=IN %s
+
+IN: There are 9 section headers, starting at offset {{.*}}:
+IN:      Name            Type     Address          Off    Size   ES Flg Lk    Inf   Al
+IN:      .text           PROGBITS 0000000000000000 {{.*}} {{.*}} 00  AX  0      0    4
+IN-NEXT: .rela.text      RELA     0000000000000000 {{.*}} {{.*}} 18   I [[#]] [[#]]  8
+IN-NEXT: .debug_xyz      PROGBITS 0000000000000000 {{.*}} {{.*}} 00      0      0    1
+IN-NEXT: .rela.debug_xyz RELA     0000000000000000 {{.*}} {{.*}} 18   I [[#]] [[#]]  8
+IN-NEXT: .note.GNU-stack PROGBITS 0000000000000000 {{.*}} {{.*}} 00      0      0    1
+IN-NEXT: .symtab         SYMTAB   0000000000000000 {{.*}} {{.*}} 18     [[#]] [[#]]  8
+IN-NEXT: .shstrtab       STRTAB   0000000000000000 {{.*}} {{.*}} 00      0      0    1
+IN-NEXT: .strtab         STRTAB   0000000000000000 {{.*}} {{.*}} 00      0      0    1
+
+IN: Symbol table '.symtab' contains 26 entries:
+IN-DAG: {{.*}} 0 SECTION LOCAL  DEFAULT [[#]] .text
+IN-DAG: {{.*}} 0 SECTION LOCAL  DEFAULT [[#]] .debug_xyz
+IN-DAG: {{.*}} 0 FUNC    GLOBAL HIDDEN  [[#]] __dyndbg.static_func.dyndbg.main
+IN-DAG: {{.*}} 0 FUNC    WEAK   HIDDEN  [[#]] __dyndbg.comdat_func
+IN-DAG: {{.*}} 0 FUNC    GLOBAL HIDDEN  [[#]] __dyndbg.func
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND static_func.dyndbg.main
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND static_data.dyndbg.main
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND inlined_in_opt
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND comdat_func
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND inlined_for_unopt_and_non_alloc
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND shared_data_for_unopt
+IN-DAG: {{.*}} 0 FUNC    GLOBAL HIDDEN  [[#]] __dyndbg.main
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND foo
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND func
+IN-DAG: {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] __dyndbg.inlined_in_opt
+IN-DAG: {{.*}} 0 FUNC    GLOBAL HIDDEN  [[#]] __dyndbg.unused_func
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND used_by_unused_func
+IN-DAG: {{.*}} 0 FUNC    GLOBAL HIDDEN  [[#]] __dyndbg.inlined_for_unopt_and_non_alloc
+IN-DAG: {{.*}} 0 FUNC    GLOBAL HIDDEN  [[#]] __dyndbg.static_func.dyndbg.foo
+IN-DAG: {{.*}} 0 FUNC    GLOBAL HIDDEN  [[#]] __dyndbg.foo
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND static_func.dyndbg.foo
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND static_data.dyndbg.foo
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND shared_func_for_unopt
+IN-DAG: {{.*}} 0 FUNC    GLOBAL HIDDEN  [[#]] __dyndbg.used_by_unused_func
+IN-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND undef_used_by_non_alloc
+## Groups have been resolved.
+IN: There are no section groups in this file.
+
+## Check disabling of incompatible ICF options.
+RUN: ld.lld -e main --icf=all -o %t/out-dd-icf-all %t/main.o %t/foo.o %t/shared.so 2>&1 \
+RUN:   | FileCheck --check-prefix=ICF-WARN %s
+RUN: cmp %t/out-dd-icf-all %t/out-dd
+RUN: ld.lld -e main --icf=safe -o %t/out-dd-icf-safe %t/main.o %t/foo.o %t/shared.so 2>&1 \
+RUN:   | FileCheck --check-prefix=ICF-WARN %s
+RUN: cmp %t/out-dd-icf-safe %t/out-dd
+
+ICF-WARN: ICF disabled because it is incompatible with dynamic debugging
+
+## Check relocatable link of simulated dynamic debugging objects.
+RUN: ld.lld -r -o %t/rel.o %t/main.o %t/foo.o
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/rel-dd.o %t/rel.o /dev/null
+RUN: ld.lld -r -o %t/rel-unopt.o %t/main-unopt.o %t/foo-unopt.o
+RUN: cmp %t/rel-dd.o %t/rel-unopt.o
+
+## Link the above relocatable link output with and without `--gc-sections`.
+RUN: ld.lld -e main -o %t/out-rel-dd %t/rel.o %t/shared.so
+RUN: llvm-readelf -Srsn %t/out-rel-dd | FileCheck -DNSYM=24 --check-prefixes=DD,NO-GC,REL %s
+RUN: ld.lld -e main --gc-sections -o %t/out-rel-dd-gc %t/rel.o %t/shared.so
+RUN: llvm-readelf -Srsn %t/out-rel-dd-gc | FileCheck -DNSYM=22 --check-prefixes=DD,GC,REL %s
+
+## Extract and check "inner" ELF for the above links.
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-rel-dd.o %t/out-rel-dd /dev/null
+RUN: llvm-readelf -Ssg %t/in-rel-dd.o | FileCheck --check-prefix=IN %s
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-rel-dd-gc.o %t/out-rel-dd-gc /dev/null
+RUN: llvm-readelf -Ssg %t/in-rel-dd-gc.o | FileCheck --check-prefix=IN %s
+
+#--- main.s
+# Optimized version of "main"
+
+.local static_func
+.type static_func, at function
+.section .text.static_func,"ax", at progbits
+static_func:
+  ret
+
+.global static_func.dyndbg.main
+.hidden static_func.dyndbg.main
+static_func.dyndbg.main = static_func
+
+.weak comdat_func
+.type comdat_func, at function
+.section .text.comdat_func,"axG", at progbits,comdat_func,comdat
+comdat_func:
+  ret
+
+.global func
+.type func, at function
+.section .text.func,"ax", at progbits
+func:
+  call static_func
+  mov static_data,%rax
+  ret
+
+.global main
+.type main, at function
+.section .text.main,"ax", at progbits
+main:
+  call foo
+  call func
+  ret
+
+.global inlined_in_opt
+.type inlined_in_opt, at function
+.section .text.inlined_in_opt,"ax", at progbits
+inlined_in_opt:
+  ret
+
+.global unused_func
+.type unused_func, at function
+.section .text.unused_func,"ax", at progbits
+unused_func:
+  call used_by_unused_func
+  ret
+
+.global inlined_for_unopt_and_non_alloc
+.type inlined_for_unopt_and_non_alloc, at function
+.section .text.inlined_for_unopt_and_non_alloc,"ax", at progbits
+inlined_for_unopt_and_non_alloc:
+  ret
+
+.local static_data
+.type static_data, at object
+.section .data.static_data,"aw", at progbits
+static_data:
+.asciz "main static data"
+
+.global static_data.dyndbg.main
+.hidden static_data.dyndbg.main
+static_data.dyndbg.main = static_data
+
+#--- main-unopt.s
+# Unoptimized version of "main"
+
+.global __dyndbg.static_func.dyndbg.main
+.hidden __dyndbg.static_func.dyndbg.main
+.type __dyndbg.static_func.dyndbg.main, at function
+.section .text.__dyndbg.static_func.dyndbg.main,"ax", at progbits
+__dyndbg.static_func.dyndbg.main:
+  ret
+
+.weak __dyndbg.comdat_func
+.hidden __dyndbg.comdat_func
+.type __dyndbg.comdat_func, at function
+.section .text.__dyndbg.comdat_func,"axG", at progbits,__dyndbg.comdat_func,comdat
+__dyndbg.comdat_func:
+  ret
+
+.global __dyndbg.func
+.hidden __dyndbg.func
+.type __dyndbg.func, at function
+.section .text.__dyndbg.func,"ax", at progbits
+__dyndbg.func:
+  call static_func.dyndbg.main
+  mov static_data.dyndbg.main,%rax
+
+  # The following is only in the unoptimized due to inlining in the optimized.
+  call inlined_in_opt
+  call comdat_func
+  call inlined_for_unopt_and_non_alloc
+  mov shared_data_for_unopt,%rax
+
+  ret
+
+.global __dyndbg.main
+.hidden __dyndbg.main
+.type __dyndbg.main, at function
+.section .text.__dyndbg.main,"ax", at progbits
+__dyndbg.main:
+  call foo
+  call func
+  ret
+
+.global __dyndbg.inlined_in_opt
+.type __dyndbg.inlined_in_opt, at function
+.section .text.__dyndbg.inlined_in_opt,"ax", at progbits
+__dyndbg.inlined_in_opt:
+  ret
+
+.global __dyndbg.unused_func
+.hidden __dyndbg.unused_func
+.type __dyndbg.unused_func, at function
+.section .text.__dyndbg.unused_func,"ax", at progbits
+__dyndbg.unused_func:
+  call used_by_unused_func
+  ret
+
+.global __dyndbg.inlined_for_unopt_and_non_alloc
+.hidden __dyndbg.inlined_for_unopt_and_non_alloc
+.type __dyndbg.inlined_for_unopt_and_non_alloc, at function
+.section .text.__dyndbg.inlined_for_unopt_and_non_alloc,"ax", at progbits
+__dyndbg.inlined_for_unopt_and_non_alloc:
+  ret
+
+#--- foo.s
+# Optimized version of "foo"
+
+.local static_func
+.type static_func, at function
+.section .text.static_func,"ax", at progbits
+static_func:
+  ret
+
+.global static_func.dyndbg.foo
+.hidden static_func.dyndbg.foo
+static_func.dyndbg.foo = static_func
+
+.weak comdat_func
+.type comdat_func, at function
+.section .text.comdat_func,"axG", at progbits,comdat_func,comdat
+comdat_func:
+  ret
+
+.global foo
+.type foo, at function
+.section .text.foo,"ax", at progbits
+foo:
+  call static_func
+  mov static_data,%rax
+  ret
+
+.global used_by_unused_func
+.type used_by_unused_func, at function
+.section .text.used_by_unused_func,"ax", at progbits
+used_by_unused_func:
+  ret
+
+.local static_data
+.type static_data, at object
+.section .data.static_data,"aw", at progbits
+static_data:
+.asciz "foo static data"
+
+.global static_data.dyndbg.foo
+.hidden static_data.dyndbg.foo
+static_data.dyndbg.foo = static_data
+
+#--- foo-unopt.s
+# Unoptimized version of "foo"
+
+.global __dyndbg.static_func.dyndbg.foo
+.hidden __dyndbg.static_func.dyndbg.foo
+.type __dyndbg.static_func.dyndbg.foo, at function
+.section .text.__dyndbg.static_func.dyndbg.foo,"ax", at progbits
+__dyndbg.static_func.dyndbg.foo:
+  ret
+
+.weak __dyndbg.comdat_func
+.hidden __dyndbg.comdat_func
+.type __dyndbg.comdat_func, at function
+.section .text.__dyndbg.comdat_func,"axG", at progbits,__dyndbg.comdat_func,comdat
+__dyndbg.comdat_func:
+  ret
+
+.global __dyndbg.foo
+.hidden __dyndbg.foo
+.type __dyndbg.foo, at function
+.section .text.__dyndbg.foo,"ax", at progbits
+__dyndbg.foo:
+  call static_func.dyndbg.foo
+  mov static_data.dyndbg.foo,%rax
+
+  # The following is only in the unoptimized due to inlining in the optimized.
+  call shared_func_for_unopt
+  call comdat_func
+
+  ret
+
+.global __dyndbg.used_by_unused_func
+.hidden __dyndbg.used_by_unused_func
+.type __dyndbg.used_by_unused_func, at function
+.section .text.__dyndbg.used_by_unused_func,"ax", at progbits
+__dyndbg.used_by_unused_func:
+  ret
+
+.section .debug_xyz,"", at progbits
+.quad undef_used_by_non_alloc
+.quad inlined_for_unopt_and_non_alloc
+
+#--- shared.s
+.global shared_func_for_unopt
+.type shared_func_for_unopt, at function
+shared_func_for_unopt:
+  ret
+
+.data
+
+.global shared_data_for_unopt
+.type shared_data_for_unopt, at object
+shared_data_for_unopt:
+.word 0xcafe
diff --git a/lld/test/ELF/trace-symbols.s b/lld/test/ELF/trace-symbols.s
index 785414e2bd5b5..ae17899588d31 100644
--- a/lld/test/ELF/trace-symbols.s
+++ b/lld/test/ELF/trace-symbols.s
@@ -3,15 +3,23 @@
 
 # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t
 # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN: %p/Inputs/trace-symbols-foo-weak.s -o %t1
+# RUN:   %p/Inputs/trace-symbols-foo-weak.s -o %t1
 # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN: %p/Inputs/trace-symbols-foo-strong.s -o %t2
+# RUN:   %p/Inputs/trace-symbols-foo-strong.s -o %t2
 # RUN: ld.lld -shared %t1 -o %t1.so
 # RUN: ld.lld -shared %t2 -o %t2.so
 # RUN: rm -f %t1.a
 # RUN: llvm-ar rcs %t1.a %t1
 # RUN: rm -f %t2.a
 # RUN: llvm-ar rcs %t2.a %t2
+## Create dynamic debugging style object.
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
+# RUN:   %p/Inputs/trace-symbols-dyndbg-opt.s -o %t3
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
+# RUN:   %p/Inputs/trace-symbols-dyndbg-unopt.s -o %t-unopt
+# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t-unopt %t3
+# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+# RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t3
 
 # RUN: ld.lld -y foo -shared %t1 %t1.so -o /dev/null | \
 # RUN:   FileCheck --check-prefix=PREEMPT %s --implicit-check-not=foo
@@ -19,75 +27,80 @@
 # PREEMPT-NEXT: trace-symbols.s.tmp1.so: shared definition of foo
 
 # RUN: ld.lld -y foo -trace-symbol common -trace-symbol=hsymbol \
-# RUN:   %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTRFOO %s
+# RUN:   %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTRFOO %s
 # OBJECTRFOO: trace-symbols.s.tmp: reference to foo
 
 # RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN:   %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTDCOMMON %s
+# RUN:   %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTDCOMMON %s
 # OBJECTDCOMMON: trace-symbols.s.tmp1: common definition of common
 
 # RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN:   %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTD1FOO %s
+# RUN:   %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTD1FOO %s
 # OBJECTD1FOO: trace-symbols.s.tmp: reference to foo
 # OBJECTD1FOO: trace-symbols.s.tmp1: common definition of common
 # OBJECTD1FOO: trace-symbols.s.tmp1: definition of foo
 # OBJECTD1FOO: trace-symbols.s.tmp2: definition of foo
 
-# RUN: ld.lld -y foo %t1 %t2 %t -o %t3 | FileCheck -check-prefix=REFLAST %s
+# RUN: ld.lld -y foo %t1 %t2 %t -o /dev/null | FileCheck -check-prefix=REFLAST %s
 # REFLAST: trace-symbols.s.tmp1: definition of foo
 # REFLAST: trace-symbols.s.tmp2: definition of foo
 # REFLAST: trace-symbols.s.tmp: reference to foo
 
 # RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN:   %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTD2FOO %s
+# RUN:   %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTD2FOO %s
 # RUN: ld.lld -y foo -y common --trace-symbol=hsymbol \
-# RUN:   %t %t2 %t1 -o %t3 | FileCheck -check-prefix=OBJECTD2FOO %s
-# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
+# RUN:   %t %t2 %t1 -o /dev/null | FileCheck -check-prefix=OBJECTD2FOO %s
+# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o /dev/null | \
 # RUN:   FileCheck -check-prefix=OBJECTD2FOO %s
 # OBJECTD2FOO: trace-symbols.s.tmp2: definition of foo
 
-# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o %t3 | \
+# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o /dev/null | \
 # RUN:   FileCheck -check-prefix=FOO_AND_COMMON %s
 # FOO_AND_COMMON: trace-symbols.s.tmp: reference to foo
 # FOO_AND_COMMON: trace-symbols.s.tmp2: definition of foo
 # FOO_AND_COMMON: trace-symbols.s.tmp1.a({{.*}}.tmp1): lazy definition of common
 
-# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
+# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o /dev/null | \
 # RUN:   FileCheck -check-prefix=SHLIBDCOMMON %s
 # SHLIBDCOMMON: trace-symbols.s.tmp1.so: shared definition of common
 
-# RUN: ld.lld -y foo -y common %t %t2.so %t1.so -o %t3 | \
+# RUN: ld.lld -y foo -y common %t %t2.so %t1.so -o /dev/null | \
 # RUN:   FileCheck -check-prefix=SHLIBD2FOO %s
-# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
+# RUN: ld.lld -y foo %t %t1.a %t2.so -o /dev/null | \
 # RUN:   FileCheck -check-prefix=NO-SHLIBD2FOO %s
 # SHLIBD2FOO:        trace-symbols.s.tmp2.so: shared definition of foo
 # NO-SHLIBD2FOO-NOT: trace-symbols.s.tmp2.so: definition of foo
 
-# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o %t3 | \
+# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o /dev/null | \
 # RUN:   FileCheck -check-prefix=ARCHIVEDCOMMON %s
 # ARCHIVEDCOMMON-NOT: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of \
 # common
 
-# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
+# RUN: ld.lld -y foo %t %t1.a %t2.so -o /dev/null | \
 # RUN:   FileCheck -check-prefix=ARCHIVED1FOO %s
 # ARCHIVED1FOO: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of foo
 
-# RUN: ld.lld -y foo %t %t1.a %t2.a -o %t3 | \
+# RUN: ld.lld -y foo %t %t1.a %t2.a -o /dev/null | \
 # RUN:   FileCheck -check-prefix=ARCHIVED2FOO %s
 # ARCHIVED2FOO: trace-symbols.s.tmp2.a(trace-symbols.s.tmp2): definition of foo
 
-# RUN: ld.lld -y bar %t %t1.so %t2.so -o %t3 | \
+# RUN: ld.lld -y bar %t %t1.so %t2.so -o /dev/null | \
 # RUN:   FileCheck -check-prefix=SHLIBDBAR %s
 # SHLIBDBAR: trace-symbols.s.tmp2.so: shared definition of bar
 
-# RUN: ld.lld -y foo -y bar %t %t1.so %t2.so -o %t3 | \
+# RUN: ld.lld -y foo -y bar %t %t1.so %t2.so -o /dev/null | \
 # RUN:   FileCheck -check-prefix=SHLIBRBAR %s
 # SHLIBRBAR: trace-symbols.s.tmp1.so: reference to bar
 
-# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o %t3 | \
+# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o /dev/null | \
 # RUN:   FileCheck -check-prefix=STARTLIB %s
 # STARTLIB: trace-symbols.s.tmp1: reference to bar
 
+# RUN: ld.lld -y foo -shared %t2 %t3 -o /dev/null | FileCheck -check-prefix=DYNDBG %s
+# DYNDBG: trace-symbols.s.tmp2: definition of foo
+# DYNDBG: trace-symbols.s.tmp3: reference to foo
+# DYNDBG: trace-symbols.s.tmp3: dynamic debugging reference to foo
+
 ## Check we do not crash when trying to trace special symbol.
 # RUN: ld.lld -trace-symbol=_end %t %t1 %t2 -o /dev/null
 
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
index 8c429a8e1428f..c8e7b36a030d9 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1809,6 +1809,7 @@ enum : unsigned {
 // LLVM-specific notes.
 enum {
   NT_LLVM_HWASAN_GLOBALS = 3,
+  NT_LLVM_DYNAMIC_DEBUGGING = 4,
 };
 
 // GNU note types.

>From 5ad3373aeac6ebe1bd29c45972b26e90a8e61921 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Thu, 6 Aug 2026 19:35:22 +0100
Subject: [PATCH 2/7] Address review comments and suggestions

---
 lld/ELF/Config.h       |  2 +-
 lld/ELF/Driver.cpp     | 37 +++++++++++++++++++++----------------
 lld/ELF/InputFiles.cpp |  5 ++---
 3 files changed, 24 insertions(+), 20 deletions(-)

diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index de61f3674d4ef..cd8cbbb09f60d 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -208,8 +208,8 @@ class LinkerDriver {
   LinkerDriver(LinkerDriver &) = delete;
   void linkerMain(ArrayRef<const char *> args);
   void addFile(StringRef path, bool withLOption);
-  void addLibrary(StringRef name);
   void addFile(std::unique_ptr<ELFFileBase> ef);
+  void addLibrary(StringRef name);
 
 private:
   Ctx &ctx;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 7130da9608e61..d54142b0d91e4 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -298,6 +298,11 @@ void LinkerDriver::addFile(StringRef path, bool withLOption) {
     loadFiles();
 }
 
+// Add an ELF input file directly.
+void LinkerDriver::addFile(std::unique_ptr<ELFFileBase> ef) {
+  files.push_back(std::move(ef));
+}
+
 // Add a given library by searching it from input search paths.
 void LinkerDriver::addLibrary(StringRef name) {
   if (std::optional<std::string> path = searchLibrary(ctx, name))
@@ -307,11 +312,6 @@ void LinkerDriver::addLibrary(StringRef name) {
                 {name});
 }
 
-// Add an ELF input file directly.
-void LinkerDriver::addFile(std::unique_ptr<ELFFileBase> ef) {
-  files.push_back(std::move(ef));
-}
-
 // This function is called on startup. We need this for LTO since
 // LTO calls LLVM functions to compile bitcode files to native code.
 // Technically this can be delayed until we read bitcode files, but
@@ -3249,13 +3249,18 @@ static void postParseObjectFile(ELFFileBase *file) {
   }
 }
 
+// Objects that support Dynamic Debugging contain an ELF Dynamic Debugging
+// section that embeds an unoptimized ELF file with debug information. We
+// extract these embedded ELF files to make a consolidated unoptimized
+// relocatable ELF file to embed in an ELF Dynamic Debugging section in the
+// output. See llvm/docs/DynamicDebugging.md for more details.
 template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
-  Ctx dc;
-  LinkerScript script(dc);
-  initContext(dc, script, ctx.arg.progName, ctx.e.outs(), ctx.e.errs(),
+  Ctx dctx;
+  LinkerScript script(dctx);
+  initContext(dctx, script, ctx.arg.progName, ctx.e.outs(), ctx.e.errs(),
               ctx.e.exitEarly, ctx.e.disableOutput);
-  dc.inDynDbgLink = true;
-  dc.dynDbgRelocatable = !ctx.arg.relocatable;
+  dctx.inDynDbgLink = true;
+  dctx.dynDbgRelocatable = !ctx.arg.relocatable;
 
   for (auto *file : ctx.objectFiles) {
     auto *obj = cast<ObjFile<ELFT>>(file);
@@ -3263,7 +3268,7 @@ template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
       auto content = obj->dynDbgSec->content();
       MemoryBufferRef mb({(const char *)content.data(), content.size()},
                          obj->mb.getBufferIdentifier());
-      dc.driver.addFile(createObjFile(dc, mb));
+      dctx.driver.addFile(createObjFile(dctx, mb));
     }
   }
 
@@ -3271,17 +3276,17 @@ template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
     return;
 
   std::vector<const char *> args{
-      dc.arg.progName.data(), "-r", "-o", "-",
-      dc.saver.save(Twine("-O") + Twine(ctx.arg.optimize)).data()};
+      dctx.arg.progName.data(), "-r", "-o", "-",
+      dctx.saver.save(Twine("-O") + Twine(ctx.arg.optimize)).data()};
   if (ctx.arg.resolveGroups)
     args.push_back("--force-group-allocation");
-  dc.driver.linkerMain(args);
-  if (errCount(dc) > 0 || !dc.dynDbgOutput) {
+  dctx.driver.linkerMain(args);
+  if (errCount(dctx) > 0 || !dctx.dynDbgOutput) {
     Err(ctx) << "Failed to create relocatable dynamic debug object";
     return;
   }
 
-  ctx.dynDbgOutput.swap(dc.dynDbgOutput);
+  ctx.dynDbgOutput.swap(dctx.dynDbgOutput);
 }
 
 // Do actual linking. Note that when this function is called,
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 31c22472519d5..07503d4826674 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -588,7 +588,6 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
 
     if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS))
       continue;
-
     if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) {
       StringRef signature = getShtGroupSignature(objSections, sec);
       ArrayRef<Elf_Word> entries =
@@ -848,8 +847,8 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
     }
     switch (type) {
     case SHT_GROUP: {
-      // Discard groups for the embedded unoptimized dynamic debugging
-      // relocatable link.
+      // Discard groups for non-relocatable links and for embedded unoptimized
+      // dynamic debugging relocatable links.
       if (!ctx.arg.relocatable || ctx.dynDbgRelocatable)
         sections[i] = &InputSection::discarded;
       // Use the verdict parse() recorded for this group instead of repeating

>From 4cd98eac475e68f44ca2d289d1c7a38a526c43c9 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Fri, 7 Aug 2026 15:18:47 +0100
Subject: [PATCH 3/7] Restore code for a change that is no longer required

This change was needed for LLVM 21 to avoid use of uninitialised
variables but this is no longer the case.
---
 lld/ELF/Config.h   | 4 ++--
 lld/ELF/Driver.cpp | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index cd8cbbb09f60d..e617f0c1895da 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -234,8 +234,8 @@ class LinkerDriver {
 
 public:
   // See InputFile::groupId.
-  uint32_t nextGroupId = 0;
-  bool isInGroup = false;
+  uint32_t nextGroupId;
+  bool isInGroup;
   std::unique_ptr<InputFile> armCmseImpLib;
   SmallVector<std::pair<StringRef, unsigned>, 0> archiveFiles;
 };
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index d54142b0d91e4..cb22b5e774077 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2241,6 +2241,8 @@ void LinkerDriver::createFiles(opt::InputArgList &args) {
 
   // Iterate over argv to process input files and positional arguments.
   std::optional<MemoryBufferRef> defaultScript;
+  nextGroupId = 0;
+  isInGroup = false;
   bool hasInput = false, hasScript = false;
   for (auto *arg : args) {
     switch (arg->getOption().getID()) {

>From 2a3d13037ffe767a03cd335ef3363d7eb7dfb564 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Fri, 7 Aug 2026 16:37:42 +0100
Subject: [PATCH 4/7] Add test comment about relocatable link only symbols

---
 lld/test/ELF/dynamic-debug.test | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
index 19a99619e95e7..2cb0585fbf986 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -92,6 +92,8 @@ DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT [[#]] inlined_for_unopt_and_non_alloc
 DD-DAG:    {{.*}} 0 OBJECT  GLOBAL DEFAULT   UND shared_data_for_unopt
 DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT   UND shared_func_for_unopt
 NO-GC-DAG: {{.*}} 0 NOTYPE  GLOBAL DEFAULT   UND undef_used_by_non_alloc
+## The following symbols are only present when linking the relocatable link
+## output of the simulated dynamic debugging objects.
 REL-DAG:   {{.*}} 0 FILE    LOCAL  DEFAULT   ABS main.o
 REL-DAG:   {{.*}} 0 FILE    LOCAL  DEFAULT   ABS foo.o
 REL-DAG:   {{.*}} 0 FILE    LOCAL  DEFAULT   ABS

>From 96383ef7184daa46450a00c65ed31b46d6c4a320 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 11 Aug 2026 16:01:47 +0100
Subject: [PATCH 5/7] Improve dynamic debugging reporting of unresolved symbols

* Matches more closely to existing LLD unresolved behaviour
* Add some comments
* Add testing
---
 lld/ELF/Writer.cpp                      |  30 ++++-
 lld/test/ELF/invalid-dynamic-debug.test | 161 ++++++++++++++++++++++++
 2 files changed, 186 insertions(+), 5 deletions(-)
 create mode 100644 lld/test/ELF/invalid-dynamic-debug.test

diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index bd0d394386aac..ab6221fd70173 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -1920,26 +1920,46 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
     scanRelocations<ELFT>(ctx);
 
     // Process symbols referenced by the embedded unoptimized part of dynamic
-    // debugging.
+    // debugging. Report references to undefined symbols and ensure that
+    // references to shared symbols have PLT/GOT entries as appropriate.
     if (ctx.hasDynDbg) {
       bool ignoreUnresolved =
           (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Ignore);
-      bool warnOnly = (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn);
+      bool warnUnresolved =
+          (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn);
       for (Symbol *sym : ctx.symtab->getSymbols()) {
         if (!sym->isDynDbgRef)
           continue;
 
         if (sym->isUndefined()) {
-          if (ignoreUnresolved || sym->isWeak())
-            continue;
+          bool isWarning;
+
+          // If versioned, issue an error (even if the symbol is weak) because
+          // we don't know the defining filename which is required to construct
+          // a Verneed entry.
+          if (sym->hasVersionSuffix)
+            isWarning = false;
+          else {
+            if (sym->isWeak())
+              continue;
+
+            bool canBeExternal =
+                !sym->isLocal() && sym->visibility() == STV_DEFAULT;
+            if (ignoreUnresolved && canBeExternal)
+              continue;
+
+            isWarning =
+                (warnUnresolved && canBeExternal) || ctx.arg.noinhibitExec;
+          }
 
+          // Report with actual dynamic debugging input section if available.
           static InputSection dummy(ctx.internalFile, dynDbgSecName, 0, 0, 0, 0,
                                     ArrayRef<uint8_t>());
           ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(sym->file);
           InputSectionBase *isec =
               dbgObj && dbgObj->dynDbgSec ? dbgObj->dynDbgSec.get() : &dummy;
           ctx.undefErrs.push_back(
-              {cast<Undefined>(sym), {{isec, 0}}, warnOnly});
+              {cast<Undefined>(sym), {{isec, 0}}, isWarning});
           continue;
         }
 
diff --git a/lld/test/ELF/invalid-dynamic-debug.test b/lld/test/ELF/invalid-dynamic-debug.test
new file mode 100644
index 0000000000000..f1297462036fa
--- /dev/null
+++ b/lld/test/ELF/invalid-dynamic-debug.test
@@ -0,0 +1,161 @@
+REQUIRES: x86
+
+## This test simulates the input from dynamic debugging objects to test ELF LLD's handling
+## of various invalid situations.
+
+RUN: rm -rf %t && split-file %s %t
+
+## Create the "outer" optimized objects.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main.s -o %t/main.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+
+## Create and embed the "inner" unoptimized objects to simulate dynamic debugging objects.
+## Note that the embedded `.debug_llvm_dyndbg` section should be aligned to 8.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main-unopt.s -o %t/main-unopt.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo-unopt.s -o %t/foo-unopt.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/main-unopt.o %t/main.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t/main.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/foo-unopt.o %t/foo.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t/foo.o
+
+## Create object without dynamic debugging.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/no-dyndbg.s -o %t/no-dyndbg.o
+
+## Default link.
+RUN: not ld.lld -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" \
+RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
+
+CHECK:      [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
+CHECK-NEXT: >>> referenced by [[DIR]]/no-dyndbg.o:(.text.bar+0x1)
+CHECK-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+CHECK-EMPTY:
+CHECK-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
+CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-EMPTY:
+CHECK-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
+CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-EMPTY:
+CHECK-NEXT: error: undefined symbol: undef_weak_ver at v1
+CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-EMPTY:
+
+## Default link with reverse input file order.
+RUN: not ld.lld -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" --check-prefix=REVERSE \
+RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
+
+REVERSE:      [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
+REVERSE-NEXT: >>> referenced by [[DIR]]/no-dyndbg.o:(.text.bar+0x1)
+REVERSE-NEXT: >>> referenced by <internal>:(.debug_llvm_dyndbg+0x0)
+REVERSE-EMPTY:
+REVERSE-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
+REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-EMPTY:
+REVERSE-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
+REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-EMPTY:
+REVERSE-NEXT: error: undefined symbol: undef_weak_ver at v1
+REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-EMPTY:
+
+## Links with `--warn-unresolved-symbols`.
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" \
+RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" --check-prefix=REVERSE \
+RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
+
+## Links with `--noinhibit-exec`.
+RUN: not ld.lld --noinhibit-exec -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning -DDIR="%t" \
+RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --noinhibit-exec -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning -DDIR="%t" --check-prefix=REVERSE \
+RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
+
+## Links with `--unresolved-symbols=ignore-all`.
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DFILE="%t/main.o" --check-prefix=ERROR \
+RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DFILE="%t/foo.o" --check-prefix=ERROR \
+RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
+
+ERROR:      error: undefined hidden symbol: undef_hidden
+ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
+ERROR-EMPTY:
+ERROR-NEXT: error: undefined symbol: undef_weak_ver at v1
+ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
+
+#--- main.s
+# Optimized version of "main"
+
+.global main
+.type main, at function
+.section .text.main,"ax", at progbits
+main:
+  ret
+
+#--- main-unopt.s
+# Unoptimized version of "main"
+
+.weak undef_weak
+.weak undef_weak_ver
+.symver undef_weak_ver, undef_weak_ver at v1
+
+.hidden undef_hidden
+
+.global __dyndbg.main
+.hidden __dyndbg.main
+.type __dyndbg.main, at function
+.section .text.__dyndbg.main,"ax", at progbits
+__dyndbg.main:
+  call undef_inner
+  call undef_weak
+  call undef_weak_ver
+  call undef_hidden
+  ret
+
+#--- foo.s
+# Optimized version of "foo"
+
+.global foo
+.type foo, at function
+.section .text.foo,"ax", at progbits
+foo:
+  ret
+
+#--- foo-unopt.s
+# Unoptimized version of "foo"
+
+.weak undef_weak
+.weak undef_weak_ver
+.symver undef_weak_ver, undef_weak_ver at v1
+
+.hidden undef_hidden
+
+.global __dyndbg.foo
+.hidden __dyndbg.foo
+.type __dyndbg.foo, at function
+.section .text.__dyndbg.foo,"ax", at progbits
+__dyndbg.foo:
+  call undef_inner
+  call undef_weak
+  call undef_weak_ver
+  call undef_hidden
+  call undef_no_dyndbg_inner
+  ret
+
+#--- no-dyndbg.s
+# No dynamic debugging
+
+.global bar
+.type bar, at function
+.section .text.bar,"ax", at progbits
+bar:
+  call undef_no_dyndbg_inner
+  ret

>From ec36710d78db5a912132bf16c67f0453d1096854 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Thu, 3 Sep 2026 00:10:29 -0700
Subject: [PATCH 6/7] Simplify dynamic debugging and reject malformed embedded
 objects

Define elf::maybeReportUndefined and call it for dynamic debugging references.

Read the embedded object with llvm::object::ELFFile accessors.

Move the ICF override out of the per-section parse loop, drop numNestedCtx in
favour of comparing against lctx, and derive the note header size from the
name.
---
 lld/Common/CommonLinkerContext.cpp |  16 +---
 lld/ELF/Driver.cpp                 |  40 +++++----
 lld/ELF/InputFiles.cpp             | 134 +++++++++++++----------------
 lld/ELF/InputFiles.h               |   1 +
 lld/ELF/Relocations.cpp            |  15 ++--
 lld/ELF/SyntheticSections.cpp      |  12 +--
 lld/ELF/Writer.cpp                 |  43 +++------
 7 files changed, 115 insertions(+), 146 deletions(-)

diff --git a/lld/Common/CommonLinkerContext.cpp b/lld/Common/CommonLinkerContext.cpp
index cafd78df4a2ba..f883c4cbd9642 100644
--- a/lld/Common/CommonLinkerContext.cpp
+++ b/lld/Common/CommonLinkerContext.cpp
@@ -22,13 +22,10 @@ using namespace lld;
 // state.
 static CommonLinkerContext *lctx;
 
-static uint32_t numNestedCtx;
-
 CommonLinkerContext::CommonLinkerContext() {
-  if (lctx) {
-    ++numNestedCtx;
+  // A nested context, e.g. the dynamic debugging link, does not install itself.
+  if (lctx)
     return;
-  }
 
   lctx = this;
   // Fire off the static initializations in CGF's constructor.
@@ -42,13 +39,8 @@ CommonLinkerContext::~CommonLinkerContext() {
   for (auto &it : instances)
     it.second->~SpecificAllocBase();
 
-  if (numNestedCtx) {
-    assert(lctx != this);
-    --numNestedCtx;
-    return;
-  }
-
-  lctx = nullptr;
+  if (lctx == this)
+    lctx = nullptr;
 }
 
 CommonLinkerContext &lld::commonContext() {
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index cb22b5e774077..84449eb9beb41 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -113,19 +113,15 @@ llvm::raw_fd_ostream Ctx::openAuxiliaryFile(llvm::StringRef filename,
   return {filename, ec, flags};
 }
 
-static void initContext(Ctx &ctx, LinkerScript &script, StringRef arg0,
-                        llvm::raw_ostream &stdoutOS,
-                        llvm::raw_ostream &stderrOS, bool exitEarly,
-                        bool disableOutput) {
-  ErrorHandler &e = ctx.e;
-  e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
-  e.logName = args::getFilenameWithoutExe(arg0);
-  e.errorLimitExceededMsg = "too many errors emitted, stopping now (use "
-                            "--error-limit=0 to see all errors)";
+// Set up the parts of Ctx that both the top-level link and the nested dynamic
+// debugging link need. The caller initializes ctx.e beforehand.
+static void initContext(Ctx &ctx, LinkerScript &script, StringRef arg0) {
+  ctx.e.logName = args::getFilenameWithoutExe(arg0);
+  ctx.e.errorLimitExceededMsg = "too many errors emitted, stopping now (use "
+                                "--error-limit=0 to see all errors)";
   ctx.script = &script;
   ctx.symAux.emplace_back();
   ctx.symtab = std::make_unique<SymbolTable>(ctx);
-
   ctx.arg.progName = arg0;
 }
 
@@ -137,8 +133,8 @@ bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
   auto *context = new Ctx;
   Ctx &ctx = *context;
   LinkerScript script(ctx);
-  initContext(ctx, script, args[0], stdoutOS, stderrOS, exitEarly,
-              disableOutput);
+  ctx.e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
+  initContext(ctx, script, args[0]);
 
   ctx.driver.linkerMain(args);
 
@@ -3259,16 +3255,16 @@ static void postParseObjectFile(ELFFileBase *file) {
 template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
   Ctx dctx;
   LinkerScript script(dctx);
-  initContext(dctx, script, ctx.arg.progName, ctx.e.outs(), ctx.e.errs(),
-              ctx.e.exitEarly, ctx.e.disableOutput);
+  dctx.e.initialize(ctx.e.outs(), ctx.e.errs(), ctx.e.exitEarly,
+                    ctx.e.disableOutput);
+  initContext(dctx, script, ctx.arg.progName);
   dctx.inDynDbgLink = true;
   dctx.dynDbgRelocatable = !ctx.arg.relocatable;
 
   for (auto *file : ctx.objectFiles) {
     auto *obj = cast<ObjFile<ELFT>>(file);
     if (obj->dynDbgSec) {
-      auto content = obj->dynDbgSec->content();
-      MemoryBufferRef mb({(const char *)content.data(), content.size()},
+      MemoryBufferRef mb(toStringRef(obj->dynDbgSec->content()),
                          obj->mb.getBufferIdentifier());
       dctx.driver.addFile(createObjFile(dctx, mb));
     }
@@ -3284,11 +3280,11 @@ template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
     args.push_back("--force-group-allocation");
   dctx.driver.linkerMain(args);
   if (errCount(dctx) > 0 || !dctx.dynDbgOutput) {
-    Err(ctx) << "Failed to create relocatable dynamic debug object";
+    Err(ctx) << "failed to create relocatable dynamic debug object";
     return;
   }
 
-  ctx.dynDbgOutput.swap(dctx.dynDbgOutput);
+  ctx.dynDbgOutput = std::move(dctx.dynDbgOutput);
 }
 
 // Do actual linking. Note that when this function is called,
@@ -3310,6 +3306,14 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
 
   parseFiles(ctx, files);
 
+  // ICF is incompatible with dynamic debugging: the inner ELF references outer
+  // symbols that folding would merge away.
+  if (ctx.hasDynDbg && ctx.arg.icf != ICFLevel::None) {
+    Warn(ctx) << "ICF disabled because it is incompatible with dynamic "
+                 "debugging";
+    ctx.arg.icf = ICFLevel::None;
+  }
+
   // Create dynamic sections for dynamic linking and static PIE.
   ctx.hasDynsym = !ctx.sharedFiles.empty() || ctx.arg.isPic;
   ctx.arg.exportDynamic &= ctx.hasDynsym;
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 07503d4826674..bfa9dbd2f9920 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -641,21 +641,10 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
     }
 
     if (sec.sh_type == SHT_LLVM_DYNDBG_ELF) {
-      // Check for presence of dynamic debugging section and create the input
-      // section but mark for discard.
-      StringRef name = check(obj.getSectionName(sec, shstrtab));
-      if (name == dynDbgSecName) {
+      if (check(obj.getSectionName(sec, shstrtab)) == dynDbgSecName) {
         sections[i] = &InputSection::discarded;
-        dynDbgSec = std::make_unique<InputSection>(*this, sec, name);
+        dynDbgSec = std::make_unique<InputSection>(*this, sec, dynDbgSecName);
         ctx.hasDynDbg = true;
-
-        // If ICF is enabled, warn and disable it because it's incompatible with
-        // dynamic debugging.
-        if (ctx.arg.icf != ICFLevel::None) {
-          Warn(ctx) << "ICF disabled because it is incompatible with dynamic "
-                       "debugging";
-          ctx.arg.icf = ICFLevel::None;
-        }
       }
       continue;
     }
@@ -1269,69 +1258,68 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
     sym->referenced = true;
   }
 
-  // Process the undefined symbols of the "inner" embedded unoptimized dynamic
-  // debugging object to ensure that the "outer" ELF contains the dependencies
-  // of the "inner" ELF. Also tag which "outer" global symbols are dynamic
-  // debugging references, i.e. used in an "inner" relocation for a SHT_PROGBITS
-  // and SHF_ALLOC section, via the `isDynDbgRef` flag.
-  if (dynDbgSec) {
-    auto content = dynDbgSec->content();
-    MemoryBufferRef dbgMb({(const char *)content.data(), content.size()},
-                          mb.getBufferIdentifier());
-    std::unique_ptr<ELFFileBase> efb = createObjFile(ctx, dbgMb);
-    ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(efb.get());
-    if (dbgObj) {
-      SmallVector<bool, 0> globalUsed(dbgObj->numSymbols - dbgObj->firstGlobal);
-      ArrayRef<Elf_Shdr> shdrs = dbgObj->template getELFShdrs<ELFT>();
-      for (size_t i = 0, end = shdrs.size(); i != end; ++i) {
-        const Elf_Shdr &sh = shdrs[i];
-        if (!isStaticRelSecType(sh.sh_type))
-          continue;
-
-        const Elf_Shdr &target = shdrs[sh.sh_info];
-        if (target.sh_type != SHT_PROGBITS ||
-            (target.sh_flags & SHF_ALLOC) == 0)
-          continue;
-
-        auto setSymUsed = [&,
-                           firstGlobal = dbgObj->firstGlobal](uint32_t symIdx) {
-          if (symIdx >= firstGlobal)
-            globalUsed[symIdx - firstGlobal] = true;
-        };
-
-        auto isec = std::make_unique<InputSection>(*dbgObj, sh, StringRef());
-        isec->relSecIdx = i;
-        auto relocs = isec->template relsOrRelas<ELFT>(/*supportsCrel=*/false);
-        if (relocs.areRelocsRel()) {
-          for (auto const &r : relocs.rels)
-            setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
-        } else {
-          for (auto const &r : relocs.relas)
-            setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
-        }
-      }
+  if (dynDbgSec)
+    initDynDbgSymbols();
+}
 
-      ArrayRef<Elf_Sym> dbgSyms = dbgObj->template getGlobalELFSyms<ELFT>();
-      assert(dbgSyms.size() == globalUsed.size());
-      for (size_t i = 0, end = dbgSyms.size(); i != end; ++i) {
-        const Elf_Sym &s = dbgSyms[i];
-        if (s.st_shndx != SHN_UNDEF)
-          continue;
+// Add the undefined symbols of the embedded unoptimized dynamic debugging
+// object so that the outer link resolves the inner link's dependencies. Tag
+// those reached by an inner relocation against a SHT_PROGBITS SHF_ALLOC
+// section with `isDynDbgRef`; the rest are only needed by debug sections.
+template <class ELFT> void ObjFile<ELFT>::initDynDbgSymbols() {
+  MemoryBufferRef dbgMb(toStringRef(dynDbgSec->content()),
+                        mb.getBufferIdentifier());
+  std::unique_ptr<ELFFileBase> efb = createObjFile(ctx, dbgMb);
+  // Compare ekind (note ObjFile<ELFT>::classof only tests InputFile::kind()).
+  if (efb->ekind != ekind) {
+    Err(ctx) << this << ": " << dynDbgSecName
+             << " contains an incompatible ELF type";
+    return;
+  }
+  auto &dbgObj = cast<ObjFile<ELFT>>(*efb);
+  const object::ELFFile<ELFT> obj = dbgObj.getObj();
+
+  ArrayRef<Elf_Sym> dbgSyms = dbgObj.template getGlobalELFSyms<ELFT>();
+  SmallVector<bool, 0> globalUsed(dbgSyms.size());
+  auto setSymUsed = [&, firstGlobal = dbgObj.firstGlobal](uint32_t symIdx) {
+    if (symIdx >= firstGlobal)
+      globalUsed[symIdx - firstGlobal] = true;
+  };
 
-        StringRef name = CHECK2(s.getName(dbgObj->stringTable), this);
-        Symbol *sym = symtab->addSymbol(
-            Undefined{this, name, s.getBinding(), s.st_other, s.getType()});
-        sym->isUsedInRegularObj = true;
-        sym->referenced = true;
-        if (globalUsed[i]) {
-          sym->isDynDbgRef = true;
-          if (sym->traced)
-            Msg(ctx) << this << ": dynamic debugging reference to " << name;
-        }
-      }
+  for (const Elf_Shdr &sh : dbgObj.template getELFShdrs<ELFT>()) {
+    if (!isStaticRelSecType(sh.sh_type))
+      continue;
+    const Elf_Shdr &target = *CHECK2(obj.getSection(sh.sh_info), &dbgObj);
+    if (target.sh_type != SHT_PROGBITS || !(target.sh_flags & SHF_ALLOC))
+      continue;
+    if (sh.sh_type == SHT_CREL) {
+      auto [rels, relas] = CHECK2(obj.crels(sh), &dbgObj);
+      for (const Elf_Rel &r : rels)
+        setSymUsed(r.getSymbol(false));
+      for (const Elf_Rela &r : relas)
+        setSymUsed(r.getSymbol(false));
+    } else if (sh.sh_type == SHT_RELA) {
+      for (const Elf_Rela &r : CHECK2(obj.relas(sh), &dbgObj))
+        setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
     } else {
-      Err(ctx) << this << ": " << dynDbgSecName
-               << " contains an incompatible ELF type";
+      for (const Elf_Rel &r : CHECK2(obj.rels(sh), &dbgObj))
+        setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
+    }
+  }
+
+  for (size_t i = 0, end = dbgSyms.size(); i != end; ++i) {
+    const Elf_Sym &s = dbgSyms[i];
+    if (s.st_shndx != SHN_UNDEF)
+      continue;
+    StringRef name = CHECK2(s.getName(dbgObj.stringTable), this);
+    Symbol *sym = ctx.symtab->addSymbol(
+        Undefined{this, name, s.getBinding(), s.st_other, s.getType()});
+    sym->isUsedInRegularObj = true;
+    sym->referenced = true;
+    if (globalUsed[i]) {
+      sym->isDynDbgRef = true;
+      if (sym->traced)
+        Msg(ctx) << this << ": dynamic debugging reference to " << name;
     }
   }
 }
diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h
index 22c29f9ea7909..50ec881f05b65 100644
--- a/lld/ELF/InputFiles.h
+++ b/lld/ELF/InputFiles.h
@@ -297,6 +297,7 @@ template <class ELFT> class ObjFile : public ELFFileBase {
   void initializeSections(bool ignoreComdats,
                           const llvm::object::ELFFile<ELFT> &obj);
   void initializeSymbols(const llvm::object::ELFFile<ELFT> &obj);
+  void initDynDbgSymbols();
   void initializeJustSymbols();
 
   InputSectionBase *getRelocTarget(uint32_t idx, uint32_t info);
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index d335da63ebf84..9c43f3d5703ee 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -654,12 +654,12 @@ void elf::reportUndefinedSymbols(Ctx &ctx) {
 
 // Report an undefined symbol if necessary.
 // Returns true if the undefined symbol will produce an error message.
-bool RelocScan::maybeReportUndefined(Undefined &sym, uint64_t offset) {
-  std::lock_guard<std::mutex> lock(ctx.relocMutex);
+bool elf::maybeReportUndefined(Ctx &ctx, Undefined &sym, InputSectionBase &sec,
+                               uint64_t offset) {
   // If versioned, issue an error (even if the symbol is weak) because we don't
   // know the defining filename which is required to construct a Verneed entry.
   if (sym.hasVersionSuffix) {
-    ctx.undefErrs.push_back({&sym, {{sec, offset}}, false});
+    ctx.undefErrs.push_back({&sym, {{&sec, offset}}, false});
     return true;
   }
   if (sym.isWeak())
@@ -678,16 +678,21 @@ bool RelocScan::maybeReportUndefined(Undefined &sym, uint64_t offset) {
   // PPC32 .got2 is similar but cannot be fixed. Multiple .got2 is infeasible
   // because .LC0-.LTOC is not representable if the two labels are in different
   // .got2
-  if (sym.discardedSecIdx != 0 && (sec->name == ".got2" || sec->name == ".toc"))
+  if (sym.discardedSecIdx != 0 && (sec.name == ".got2" || sec.name == ".toc"))
     return false;
 
   bool isWarning =
       (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn && canBeExternal) ||
       ctx.arg.noinhibitExec;
-  ctx.undefErrs.push_back({&sym, {{sec, offset}}, isWarning});
+  ctx.undefErrs.push_back({&sym, {{&sec, offset}}, isWarning});
   return !isWarning;
 }
 
+bool RelocScan::maybeReportUndefined(Undefined &sym, uint64_t offset) {
+  std::lock_guard<std::mutex> lock(ctx.relocMutex);
+  return elf::maybeReportUndefined(ctx, sym, *sec, offset);
+}
+
 bool RelocScan::checkTlsLe(uint64_t offset, Symbol &sym, RelType type) {
   if (!ctx.arg.shared)
     return false;
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 84099e6e7503c..503d3cf06c680 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4425,20 +4425,22 @@ void DynamicDebugSection::writeTo(uint8_t *buf) {
          ctx.dynDbgOutput->getBufferSize());
 }
 
+constexpr char dynDbgNoteName[] = "LLVM";
+
 DynamicDebugNote::DynamicDebugNote(Ctx &ctx)
     : SyntheticSection(ctx, ".note.llvm.dyndbg", SHT_NOTE, 0, 4) {}
 
 size_t DynamicDebugNote::getSize() const {
-  return /*hdrsz=*/12 + /*namesz=*/8 + /*descsz=*/sizeof(uint32_t);
+  return sizeof(llvm::ELF::Elf64_Nhdr) + alignTo(sizeof(dynDbgNoteName), 4) +
+         /*descsz=*/sizeof(uint32_t);
 }
 
 void DynamicDebugNote::writeTo(uint8_t *buf) {
-  write32(ctx, buf, 5);                             // Name size
+  write32(ctx, buf, sizeof(dynDbgNoteName));        // Name size
   write32(ctx, buf + 4, sizeof(uint32_t));          // Content size
   write32(ctx, buf + 8, NT_LLVM_DYNAMIC_DEBUGGING); // Type
-  memcpy(buf + 12, "LLVM", 5);                      // Name string
-  uint32_t version = 0;
-  write32(ctx, buf + 20, version);
+  memcpy(buf + 12, dynDbgNoteName, sizeof(dynDbgNoteName));
+  write32(ctx, buf + 12 + alignTo(sizeof(dynDbgNoteName), 4), 0); // Version
 }
 
 static OutputSection *findSection(Ctx &ctx, StringRef name) {
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index ab6221fd70173..e2792dc31d031 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -371,7 +371,7 @@ template <class ELFT> void Writer<ELFT>::run() {
     // link()) instead of committing the buffer, which would write to the
     // process's stdout.
     if (ctx.inDynDbgLink)
-      ctx.dynDbgOutput.swap(buffer);
+      ctx.dynDbgOutput = std::move(buffer);
     else if (ctx.arg.outputFile == "-") {
       ctx.e.outs() << StringRef(
           reinterpret_cast<const char *>(buffer->getBufferStart()),
@@ -1923,43 +1923,20 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
     // debugging. Report references to undefined symbols and ensure that
     // references to shared symbols have PLT/GOT entries as appropriate.
     if (ctx.hasDynDbg) {
-      bool ignoreUnresolved =
-          (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Ignore);
-      bool warnUnresolved =
-          (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn);
+      InputSection *unknownSec = make<InputSection>(
+          ctx.internalFile, dynDbgSecName, 0, 0, 0, 0, ArrayRef<uint8_t>());
       for (Symbol *sym : ctx.symtab->getSymbols()) {
         if (!sym->isDynDbgRef)
           continue;
 
         if (sym->isUndefined()) {
-          bool isWarning;
-
-          // If versioned, issue an error (even if the symbol is weak) because
-          // we don't know the defining filename which is required to construct
-          // a Verneed entry.
-          if (sym->hasVersionSuffix)
-            isWarning = false;
-          else {
-            if (sym->isWeak())
-              continue;
-
-            bool canBeExternal =
-                !sym->isLocal() && sym->visibility() == STV_DEFAULT;
-            if (ignoreUnresolved && canBeExternal)
-              continue;
-
-            isWarning =
-                (warnUnresolved && canBeExternal) || ctx.arg.noinhibitExec;
-          }
-
-          // Report with actual dynamic debugging input section if available.
-          static InputSection dummy(ctx.internalFile, dynDbgSecName, 0, 0, 0, 0,
-                                    ArrayRef<uint8_t>());
-          ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(sym->file);
-          InputSectionBase *isec =
-              dbgObj && dbgObj->dynDbgSec ? dbgObj->dynDbgSec.get() : &dummy;
-          ctx.undefErrs.push_back(
-              {cast<Undefined>(sym), {{isec, 0}}, isWarning});
+          // Report against the referencing dynamic debugging section when the
+          // symbol's file has one.
+          auto *dbgObj = dyn_cast<ObjFile<ELFT>>(sym->file);
+          InputSectionBase *isec = dbgObj && dbgObj->dynDbgSec
+                                       ? dbgObj->dynDbgSec.get()
+                                       : unknownSec;
+          maybeReportUndefined(ctx, cast<Undefined>(*sym), *isec, 0);
           continue;
         }
 

>From 6ff1922cc0f5db1afce378b43661189db967c55d Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Thu, 3 Sep 2026 14:24:14 +0100
Subject: [PATCH 7/7] Change testing to address review comments

* Use `cd %t` to simplify "invalid-dynamic-debug.test"
* Create separate trace symbol test for dynamic debugging and restore
  the existing "trace-symbols.s" test
---
 .../ELF/Inputs/trace-symbols-dyndbg-opt.s     |  4 --
 .../ELF/Inputs/trace-symbols-dyndbg-unopt.s   |  5 --
 lld/test/ELF/invalid-dynamic-debug.test       | 70 +++++++++----------
 lld/test/ELF/trace-symbols-dynamic-debug.test | 38 ++++++++++
 lld/test/ELF/trace-symbols.s                  | 51 +++++---------
 5 files changed, 92 insertions(+), 76 deletions(-)
 delete mode 100644 lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
 delete mode 100644 lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
 create mode 100644 lld/test/ELF/trace-symbols-dynamic-debug.test

diff --git a/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s b/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
deleted file mode 100644
index 1fba360bb54ab..0000000000000
--- a/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
+++ /dev/null
@@ -1,4 +0,0 @@
-.globl	baz
-.type	baz, @function
-baz:
-nop
diff --git a/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s b/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
deleted file mode 100644
index 7688e526340d9..0000000000000
--- a/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
+++ /dev/null
@@ -1,5 +0,0 @@
-.globl	__dyndbg.baz
-.hidden	__dyndbg.baz
-.type	__dyndbg.baz, @function
-__dyndbg.baz:
-callq foo
diff --git a/lld/test/ELF/invalid-dynamic-debug.test b/lld/test/ELF/invalid-dynamic-debug.test
index f1297462036fa..02fbfeae3a66d 100644
--- a/lld/test/ELF/invalid-dynamic-debug.test
+++ b/lld/test/ELF/invalid-dynamic-debug.test
@@ -3,86 +3,86 @@ REQUIRES: x86
 ## This test simulates the input from dynamic debugging objects to test ELF LLD's handling
 ## of various invalid situations.
 
-RUN: rm -rf %t && split-file %s %t
+RUN: rm -rf %t && split-file %s %t && cd %t
 
 ## Create the "outer" optimized objects.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main.s -o %t/main.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux main.s -o main.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo.s -o foo.o
 
 ## Create and embed the "inner" unoptimized objects to simulate dynamic debugging objects.
 ## Note that the embedded `.debug_llvm_dyndbg` section should be aligned to 8.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main-unopt.s -o %t/main-unopt.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo-unopt.s -o %t/foo-unopt.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/main-unopt.o %t/main.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux main-unopt.s -o main-unopt.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo-unopt.s -o foo-unopt.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=main-unopt.o main.o
 RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t/main.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/foo-unopt.o %t/foo.o
+RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 main.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=foo-unopt.o foo.o
 RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t/foo.o
+RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 foo.o
 
 ## Create object without dynamic debugging.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/no-dyndbg.s -o %t/no-dyndbg.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux no-dyndbg.s -o no-dyndbg.o
 
 ## Default link.
-RUN: not ld.lld -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
-RUN:   | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" \
+RUN: not ld.lld -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error \
 RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
 
 CHECK:      [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
-CHECK-NEXT: >>> referenced by [[DIR]]/no-dyndbg.o:(.text.bar+0x1)
-CHECK-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+CHECK-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
+CHECK-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
 CHECK-EMPTY:
 CHECK-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
-CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
 CHECK-EMPTY:
 CHECK-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
-CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
 CHECK-EMPTY:
 CHECK-NEXT: error: undefined symbol: undef_weak_ver at v1
-CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
 CHECK-EMPTY:
 
 ## Default link with reverse input file order.
-RUN: not ld.lld -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
-RUN:   | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" --check-prefix=REVERSE \
+RUN: not ld.lld -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
 RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
 
 REVERSE:      [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
-REVERSE-NEXT: >>> referenced by [[DIR]]/no-dyndbg.o:(.text.bar+0x1)
+REVERSE-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
 REVERSE-NEXT: >>> referenced by <internal>:(.debug_llvm_dyndbg+0x0)
 REVERSE-EMPTY:
 REVERSE-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
-REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
 REVERSE-EMPTY:
 REVERSE-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
-REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
 REVERSE-EMPTY:
 REVERSE-NEXT: error: undefined symbol: undef_weak_ver at v1
-REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
 REVERSE-EMPTY:
 
 ## Links with `--warn-unresolved-symbols`.
-RUN: not ld.lld --warn-unresolved-symbols -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
-RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" \
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error \
 RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --warn-unresolved-symbols -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
-RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" --check-prefix=REVERSE \
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
 RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
 
 ## Links with `--noinhibit-exec`.
-RUN: not ld.lld --noinhibit-exec -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
-RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning -DDIR="%t" \
+RUN: not ld.lld --noinhibit-exec -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning \
 RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --noinhibit-exec -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
-RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning -DDIR="%t" --check-prefix=REVERSE \
+RUN: not ld.lld --noinhibit-exec -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning --check-prefix=REVERSE \
 RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
 
 ## Links with `--unresolved-symbols=ignore-all`.
-RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
-RUN:   | FileCheck -DERR_OR_WARN=warning -DFILE="%t/main.o" --check-prefix=ERROR \
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DFILE="main.o" --check-prefix=ERROR \
 RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
-RUN:   | FileCheck -DERR_OR_WARN=warning -DFILE="%t/foo.o" --check-prefix=ERROR \
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
+RUN:   | FileCheck -DERR_OR_WARN=warning -DFILE="foo.o" --check-prefix=ERROR \
 RUN:       --implicit-check-not=error: --implicit-check-not=warning: %s
 
 ERROR:      error: undefined hidden symbol: undef_hidden
diff --git a/lld/test/ELF/trace-symbols-dynamic-debug.test b/lld/test/ELF/trace-symbols-dynamic-debug.test
new file mode 100644
index 0000000000000..b0d20bf904adc
--- /dev/null
+++ b/lld/test/ELF/trace-symbols-dynamic-debug.test
@@ -0,0 +1,38 @@
+REQUIRES: x86
+
+## Test dynamic debugging `-trace-symbol=<symbol>`
+
+RUN: rm -rf %t && split-file %s %t
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+
+## Create dynamic debugging style object.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/bar.s -o %t/bar.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/bar-unopt.s -o %t/bar-unopt.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/bar-unopt.o %t/bar.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t/bar.o
+
+# RUN: ld.lld -trace-symbol=foo -shared %t/foo.o %t/bar.o -o /dev/null | FileCheck %s
+# CHECK: foo.o: definition of foo
+# CHECK: bar.o: reference to foo
+# CHECK: bar.o: dynamic debugging reference to foo
+
+#--- foo.s
+.globl foo
+.type foo, at function
+foo:
+  ret
+
+#--- bar.s
+.globl bar
+.type bar, at function
+bar:
+  ret
+
+#--- bar-unopt.s
+.globl __dyndbg.baz
+.hidden __dyndbg.baz
+.type __dyndbg.baz, at function
+__dyndbg.baz:
+  call foo
+  ret
diff --git a/lld/test/ELF/trace-symbols.s b/lld/test/ELF/trace-symbols.s
index ae17899588d31..785414e2bd5b5 100644
--- a/lld/test/ELF/trace-symbols.s
+++ b/lld/test/ELF/trace-symbols.s
@@ -3,23 +3,15 @@
 
 # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t
 # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN:   %p/Inputs/trace-symbols-foo-weak.s -o %t1
+# RUN: %p/Inputs/trace-symbols-foo-weak.s -o %t1
 # RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN:   %p/Inputs/trace-symbols-foo-strong.s -o %t2
+# RUN: %p/Inputs/trace-symbols-foo-strong.s -o %t2
 # RUN: ld.lld -shared %t1 -o %t1.so
 # RUN: ld.lld -shared %t2 -o %t2.so
 # RUN: rm -f %t1.a
 # RUN: llvm-ar rcs %t1.a %t1
 # RUN: rm -f %t2.a
 # RUN: llvm-ar rcs %t2.a %t2
-## Create dynamic debugging style object.
-# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN:   %p/Inputs/trace-symbols-dyndbg-opt.s -o %t3
-# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN:   %p/Inputs/trace-symbols-dyndbg-unopt.s -o %t-unopt
-# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t-unopt %t3
-# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-# RUN:              --set-section-alignment=.debug_llvm_dyndbg=8 %t3
 
 # RUN: ld.lld -y foo -shared %t1 %t1.so -o /dev/null | \
 # RUN:   FileCheck --check-prefix=PREEMPT %s --implicit-check-not=foo
@@ -27,80 +19,75 @@
 # PREEMPT-NEXT: trace-symbols.s.tmp1.so: shared definition of foo
 
 # RUN: ld.lld -y foo -trace-symbol common -trace-symbol=hsymbol \
-# RUN:   %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTRFOO %s
+# RUN:   %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTRFOO %s
 # OBJECTRFOO: trace-symbols.s.tmp: reference to foo
 
 # RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN:   %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTDCOMMON %s
+# RUN:   %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTDCOMMON %s
 # OBJECTDCOMMON: trace-symbols.s.tmp1: common definition of common
 
 # RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN:   %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTD1FOO %s
+# RUN:   %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTD1FOO %s
 # OBJECTD1FOO: trace-symbols.s.tmp: reference to foo
 # OBJECTD1FOO: trace-symbols.s.tmp1: common definition of common
 # OBJECTD1FOO: trace-symbols.s.tmp1: definition of foo
 # OBJECTD1FOO: trace-symbols.s.tmp2: definition of foo
 
-# RUN: ld.lld -y foo %t1 %t2 %t -o /dev/null | FileCheck -check-prefix=REFLAST %s
+# RUN: ld.lld -y foo %t1 %t2 %t -o %t3 | FileCheck -check-prefix=REFLAST %s
 # REFLAST: trace-symbols.s.tmp1: definition of foo
 # REFLAST: trace-symbols.s.tmp2: definition of foo
 # REFLAST: trace-symbols.s.tmp: reference to foo
 
 # RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN:   %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTD2FOO %s
+# RUN:   %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTD2FOO %s
 # RUN: ld.lld -y foo -y common --trace-symbol=hsymbol \
-# RUN:   %t %t2 %t1 -o /dev/null | FileCheck -check-prefix=OBJECTD2FOO %s
-# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o /dev/null | \
+# RUN:   %t %t2 %t1 -o %t3 | FileCheck -check-prefix=OBJECTD2FOO %s
+# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
 # RUN:   FileCheck -check-prefix=OBJECTD2FOO %s
 # OBJECTD2FOO: trace-symbols.s.tmp2: definition of foo
 
-# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o /dev/null | \
+# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o %t3 | \
 # RUN:   FileCheck -check-prefix=FOO_AND_COMMON %s
 # FOO_AND_COMMON: trace-symbols.s.tmp: reference to foo
 # FOO_AND_COMMON: trace-symbols.s.tmp2: definition of foo
 # FOO_AND_COMMON: trace-symbols.s.tmp1.a({{.*}}.tmp1): lazy definition of common
 
-# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o /dev/null | \
+# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
 # RUN:   FileCheck -check-prefix=SHLIBDCOMMON %s
 # SHLIBDCOMMON: trace-symbols.s.tmp1.so: shared definition of common
 
-# RUN: ld.lld -y foo -y common %t %t2.so %t1.so -o /dev/null | \
+# RUN: ld.lld -y foo -y common %t %t2.so %t1.so -o %t3 | \
 # RUN:   FileCheck -check-prefix=SHLIBD2FOO %s
-# RUN: ld.lld -y foo %t %t1.a %t2.so -o /dev/null | \
+# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
 # RUN:   FileCheck -check-prefix=NO-SHLIBD2FOO %s
 # SHLIBD2FOO:        trace-symbols.s.tmp2.so: shared definition of foo
 # NO-SHLIBD2FOO-NOT: trace-symbols.s.tmp2.so: definition of foo
 
-# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o /dev/null | \
+# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o %t3 | \
 # RUN:   FileCheck -check-prefix=ARCHIVEDCOMMON %s
 # ARCHIVEDCOMMON-NOT: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of \
 # common
 
-# RUN: ld.lld -y foo %t %t1.a %t2.so -o /dev/null | \
+# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
 # RUN:   FileCheck -check-prefix=ARCHIVED1FOO %s
 # ARCHIVED1FOO: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of foo
 
-# RUN: ld.lld -y foo %t %t1.a %t2.a -o /dev/null | \
+# RUN: ld.lld -y foo %t %t1.a %t2.a -o %t3 | \
 # RUN:   FileCheck -check-prefix=ARCHIVED2FOO %s
 # ARCHIVED2FOO: trace-symbols.s.tmp2.a(trace-symbols.s.tmp2): definition of foo
 
-# RUN: ld.lld -y bar %t %t1.so %t2.so -o /dev/null | \
+# RUN: ld.lld -y bar %t %t1.so %t2.so -o %t3 | \
 # RUN:   FileCheck -check-prefix=SHLIBDBAR %s
 # SHLIBDBAR: trace-symbols.s.tmp2.so: shared definition of bar
 
-# RUN: ld.lld -y foo -y bar %t %t1.so %t2.so -o /dev/null | \
+# RUN: ld.lld -y foo -y bar %t %t1.so %t2.so -o %t3 | \
 # RUN:   FileCheck -check-prefix=SHLIBRBAR %s
 # SHLIBRBAR: trace-symbols.s.tmp1.so: reference to bar
 
-# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o /dev/null | \
+# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o %t3 | \
 # RUN:   FileCheck -check-prefix=STARTLIB %s
 # STARTLIB: trace-symbols.s.tmp1: reference to bar
 
-# RUN: ld.lld -y foo -shared %t2 %t3 -o /dev/null | FileCheck -check-prefix=DYNDBG %s
-# DYNDBG: trace-symbols.s.tmp2: definition of foo
-# DYNDBG: trace-symbols.s.tmp3: reference to foo
-# DYNDBG: trace-symbols.s.tmp3: dynamic debugging reference to foo
-
 ## Check we do not crash when trying to trace special symbol.
 # RUN: ld.lld -trace-symbol=_end %t %t1 %t2 -o /dev/null
 



More information about the llvm-commits mailing list