[lld] [llvm] [dyndbg][LLD][ELF] Prototype LLD support for dynamic debugging (PR #190939)

Andrew Ng via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 8 02:55:31 PDT 2026


https://github.com/nga888 created https://github.com/llvm/llvm-project/pull/190939

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

>From 9334879a721e9cfa7b3cd0e9a004ecb141be7335 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Wed, 8 Apr 2026 10:45:58 +0100
Subject: [PATCH] [dyndbg][LLD][ELF] Prototype 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                     |  15 ++
 lld/ELF/Driver.cpp                   |  82 +++++--
 lld/ELF/InputFiles.cpp               |  46 +++-
 lld/ELF/InputFiles.h                 |   6 +
 lld/ELF/LinkerScript.cpp             |   4 +-
 lld/ELF/MarkLive.cpp                 |  15 +-
 lld/ELF/Symbols.h                    |   5 +
 lld/ELF/SyntheticSections.cpp        |  38 +++
 lld/ELF/SyntheticSections.h          |  14 ++
 lld/ELF/Writer.cpp                   |  41 +++-
 lld/test/ELF/dynamic-debug.test      | 350 +++++++++++++++++++++++++++
 llvm/include/llvm/BinaryFormat/ELF.h |   1 +
 13 files changed, 609 insertions(+), 22 deletions(-)
 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 cf3cde1cf2696..9a033c400ddc3 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -182,6 +182,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;
@@ -598,6 +599,8 @@ struct InStruct {
   std::unique_ptr<StringTableSection> strTab;
   std::unique_ptr<SymbolTableBaseSection> symTab;
   std::unique_ptr<SymtabShndxSection> symTabShndx;
+  std::unique_ptr<SyntheticSection> dynDbg;
+  std::unique_ptr<SyntheticSection> dynDbgNote;
 };
 
 struct Ctx : CommonLinkerContext {
@@ -735,6 +738,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 dynDbgLink = 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 949314fd70ed2..e2bb28d314635 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -113,6 +113,25 @@ 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.partitions.clear();
+  ctx.partitions.emplace_back(ctx);
+
+  ctx.arg.progName = arg0;
+}
+
 namespace lld {
 namespace elf {
 bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
@@ -120,22 +139,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.partitions.clear();
-  ctx.partitions.emplace_back(ctx);
-
-  ctx.arg.progName = args[0];
+  initContext(ctx, script, args[0], stdoutOS, stderrOS, exitEarly,
+              disableOutput);
 
   ctx.driver.linkerMain(args);
 
@@ -344,6 +350,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
@@ -3208,6 +3219,41 @@ static void postParseObjectFile(ELFFileBase *file) {
   }
 }
 
+template <class ELFT> static void processDynamicDebug(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.dynDbgLink = 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) {
@@ -3517,6 +3563,12 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
     createTaggedSymbols(ctx);
   }
 
+  if (ctx.hasDynDbg) {
+    processDynamicDebug<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 f774391c821c9..1ace7680d9a64 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -591,8 +591,21 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
   for (size_t i = 0; i != size; ++i) {
     const Elf_Shdr &sec = objSections[i];
 
-    if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS))
+    if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS)) {
+      // Check for presence of dynamic debugging section and create the input
+      // section but mark for discard.
+      // TODO: Should the dynamic debugging section have a new section type,
+      // e.g. SHT_LLVM_DYNAMIC_DEBUGGING? However, no other ".debug" section
+      // does.
+      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;
+      }
       continue;
+    }
+
     if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) {
       StringRef signature = getShtGroupSignature(objSections, sec);
       ArrayRef<Elf_Word> entries =
@@ -829,7 +842,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;
       StringRef signature =
           cantFail(this->getELFSyms<ELFT>()[sec.sh_info].getName(stringTable));
@@ -1251,6 +1266,33 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
     sym->isUsedInRegularObj = true;
     sym->referenced = true;
   }
+
+  // Process the undefined symbols of the embedded unoptimized dynamic debugging
+  // object.
+  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) {
+      ArrayRef<Elf_Sym> dbgSyms = dbgObj->template getGlobalELFSyms<ELFT>();
+      for (const Elf_Sym &s : dbgSyms) {
+        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;
+        sym->isDynDbgRef = true;
+      }
+    } 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 24918a067a836..d41ec1fc9b6de 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;
 
@@ -385,6 +388,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 23b59349ab0be..9fe9979e4afc7 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 3ed1a51ef13bd..5c218a92b4025 100644
--- a/lld/ELF/MarkLive.cpp
+++ b/lld/ELF/MarkLive.cpp
@@ -352,11 +352,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: support `--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 && sym->partition == partition)
       markSymbol(sym, "externally visible symbol; may interpose");
+  }
 
   // If this isn't the main partition, that's all that we need to preserve.
   if (partition != 1) {
diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h
index b0ed3e2b63d40..787e2ede33c23 100644
--- a/lld/ELF/Symbols.h
+++ b/lld/ELF/Symbols.h
@@ -145,6 +145,11 @@ class Symbol {
   LLVM_PREFERRED_TYPE(bool)
   uint8_t hasVersionSuffix : 1;
 
+  // True if this symbol is referenced by the embedded unoptimized part of
+  // dynamic debugging.
+  LLVM_PREFERRED_TYPE(bool)
+  uint8_t isDynDbgRef : 1;
+
   // Symbol visibility. This is the computed minimum visibility of all
   // observed non-DSO symbols.
   uint8_t visibility() const { return stOther & 3; }
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 2da3f1afeb821..faaf39624cd77 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4449,6 +4449,37 @@ size_t MemtagGlobalDescriptors::getSize() const {
   return createMemtagGlobalDescriptors(ctx, symbols);
 }
 
+DynamicDebugSection::DynamicDebugSection(Ctx &ctx)
+    : SyntheticSection(ctx, dynDbgSecName, SHT_PROGBITS, 0, 1) {
+  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
+  // Version. TODO: decide on version "format" and any other content.
+  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))
@@ -4718,6 +4749,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 92dcbb922aa58..fc0b3b9f8b030 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -1338,6 +1338,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 ad5cf39c72163..5b7f1be042956 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -384,7 +384,11 @@ template <class ELFT> void Writer<ELFT>::run() {
     if (errCount(ctx))
       return;
 
-    if (!ctx.e.disableOutput) {
+    // Capture output for the embedded unoptimized dynamic debugging relocatable
+    // link.
+    if (ctx.dynDbgLink)
+      ctx.dynDbgOutput.swap(buffer);
+    else if (!ctx.e.disableOutput) {
       if (auto e = buffer->commit())
         Err(ctx) << "failed to write output '" << buffer->getPath()
                  << "': " << std::move(e);
@@ -1921,6 +1925,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/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
new file mode 100644
index 0000000000000..a4b8310187d45
--- /dev/null
+++ b/lld/test/ELF/dynamic-debug.test
@@ -0,0 +1,350 @@
+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.
+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 --add-section=.debug_llvm_dyndbg=%t/foo-unopt.o %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 -rsn %t/out-dd | FileCheck --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 -rsn %t/out-dd-gc | FileCheck --check-prefixes=DD,GC %s
+
+## 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
+
+DD: 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: Symbol table '.symtab' contains 19 entries:
+GC:    Symbol table '.symtab' contains 18 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 OBJECT  GLOBAL DEFAULT   UND shared_data_for_unopt
+DD-DAG:    {{.*}} 0 FUNC    GLOBAL DEFAULT   UND shared_func_for_unopt
+
+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
+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
+RUN: llvm-readelf -Ssg %t/in-dd-gc.o | FileCheck --check-prefix=IN %s
+
+IN: There are 7 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: .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 22 entries:
+IN-DAG: {{.*}} 0 SECTION LOCAL  DEFAULT [[#]] .text
+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 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.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
+## Groups have been resolved.
+IN: There are no section groups in this file.
+
+## 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
+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 -rsn %t/out-rel-dd | FileCheck --check-prefixes=DD,NO-GC %s
+RUN: ld.lld -e main --gc-sections -o %t/out-rel-dd-gc %t/rel.o %t/shared.so
+RUN: llvm-readelf -rsn %t/out-rel-dd-gc | FileCheck --check-prefixes=DD,GC %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
+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
+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
+
+.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
+  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
+
+#--- 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
+
+#--- shared.s
+.global shared_func_for_unopt
+.type shared_func_for_unopt, at function
+shared_func_for_unopt:
+  ret
+
+.data
+
+.global shared_func_for_unopt,shared_data_for_unopt
+.type shared_data_for_unopt, at object
+shared_data_for_unopt:
+.word 0xcafe
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
index 3fb5b51fb0a94..ebcb1adfa159e 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1800,6 +1800,7 @@ enum : unsigned {
 // LLVM-specific notes.
 enum {
   NT_LLVM_HWASAN_GLOBALS = 3,
+  NT_LLVM_DYNAMIC_DEBUGGING = 4,
 };
 
 // GNU note types.



More information about the llvm-commits mailing list