[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 Jun 3 09:50:15 PDT 2026
https://github.com/nga888 updated https://github.com/llvm/llvm-project/pull/190939
>From 7238c4e4520e249e94b3525af0a05c93329bd90e Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Wed, 20 May 2026 11:43:42 +0100
Subject: [PATCH 1/2] [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 | 19 +-
lld/ELF/Driver.cpp | 85 ++++--
lld/ELF/InputFiles.cpp | 86 +++++-
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 | 381 +++++++++++++++++++++++++++
llvm/include/llvm/BinaryFormat/ELF.h | 1 +
13 files changed, 683 insertions(+), 26 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 485f5bf657d3e..042d160b3281d 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -198,6 +198,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;
@@ -221,8 +222,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;
};
@@ -617,6 +618,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 {
@@ -754,6 +757,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 214cb5678f4d8..baa1028a05e33 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);
@@ -304,6 +310,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
@@ -2226,8 +2237,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()) {
@@ -3291,6 +3300,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.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) {
@@ -3600,6 +3644,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 87ea8e890626e..cb1265ecb94f7 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -586,8 +586,29 @@ 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;
+
+ // 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;
+ }
+
if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) {
StringRef signature = getShtGroupSignature(objSections, sec);
ArrayRef<Elf_Word> entries =
@@ -824,7 +845,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));
@@ -1246,6 +1269,65 @@ 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) {
+ 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;
+ sym->isDynDbgRef = globalUsed[i];
+ }
+ } 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 aef599102ecfc..d46a449786545 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 b9ed964b8c720..dfddd4a840626 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 527a2d8a871d9..f21ba71ad9428 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");
+ }
// 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 c384f4a5b7e7f..111320586bb61 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 c4807e1e150b5..11780031d9e56 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4451,6 +4451,37 @@ size_t MemtagGlobalDescriptors::getSize() const {
return createMemtagGlobalDescriptors(ctx, symbols);
}
+DynamicDebugSection::DynamicDebugSection(Ctx &ctx)
+ : SyntheticSection(ctx, dynDbgSecName, SHT_PROGBITS, 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
+ // 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))
@@ -4720,6 +4751,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..18ffc9cecd708
--- /dev/null
+++ b/lld/test/ELF/dynamic-debug.test
@@ -0,0 +1,381 @@
+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-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-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 --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 --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 PROGBITS 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
+
+NO-GC: Symbol table '.symtab' contains 20 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
+NO-GC-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
+
+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 24 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 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
+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 --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 -Srsn %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 /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
+
+.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
+
+.global undef_used_by_non_alloc
+
+.section .debug_xyz,"", at progbits
+.quad undef_used_by_non_alloc
+
+#--- 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 72cded68463a6..e04cbac3d0e4c 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1802,6 +1802,7 @@ enum : unsigned {
// LLVM-specific notes.
enum {
NT_LLVM_HWASAN_GLOBALS = 3,
+ NT_LLVM_DYNAMIC_DEBUGGING = 4,
};
// GNU note types.
>From 8f2d6df06b9c2dae4c17b95dba13fe0730d96430 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Wed, 3 Jun 2026 17:30:33 +0100
Subject: [PATCH 2/2] Fix dynamic debugging missing "inner" dependency
When `--gc-sections` is enabled and an "outer" symbol is referenced by
an "inner" symbol but also only referenced by a non-alloc section,
i.e. debug info, in another object's "inner", this "outer" symbol
dependency can be lost. Fix the broken logic that causes this issue to
occur.
---
lld/ELF/InputFiles.cpp | 6 ++-
.../ELF/Inputs/trace-symbols-dyndbg-opt.s | 4 ++
.../ELF/Inputs/trace-symbols-dyndbg-unopt.s | 5 ++
lld/test/ELF/dynamic-debug.test | 28 ++++++++---
lld/test/ELF/trace-symbols.s | 46 ++++++++++++-------
5 files changed, 65 insertions(+), 24 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
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index cb1265ecb94f7..6300349acd2a4 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1321,7 +1321,11 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
Undefined{this, name, s.getBinding(), s.st_other, s.getType()});
sym->isUsedInRegularObj = true;
sym->referenced = true;
- sym->isDynDbgRef = globalUsed[i];
+ if (globalUsed[i]) {
+ sym->isDynDbgRef = true;
+ if (sym->traced)
+ Msg(ctx) << this << ": dynamic debugging reference to " << name;
+ }
}
} else {
Err(ctx) << this << ": " << dynDbgSecName
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
index 18ffc9cecd708..da004434f6246 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -69,8 +69,8 @@ DD-DAG: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND shared_data_for_u
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
-NO-GC: Symbol table '.symtab' contains 20 entries:
-GC: Symbol table '.symtab' contains 18 entries:
+NO-GC: Symbol table '.symtab' contains 21 entries:
+GC: Symbol table '.symtab' contains 19 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
@@ -87,6 +87,7 @@ 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
@@ -113,7 +114,7 @@ IN-NEXT: .symtab SYMTAB 0000000000000000 {{.*}} {{.*}} 18 [[#]] [[
IN-NEXT: .shstrtab STRTAB 0000000000000000 {{.*}} {{.*}} 00 0 0 1
IN-NEXT: .strtab STRTAB 0000000000000000 {{.*}} {{.*}} 00 0 0 1
-IN: Symbol table '.symtab' contains 24 entries:
+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
@@ -123,6 +124,7 @@ 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
@@ -130,6 +132,7 @@ 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
@@ -216,6 +219,12 @@ 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
@@ -254,6 +263,7 @@ __dyndbg.func:
# 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
@@ -281,6 +291,13 @@ __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"
@@ -362,10 +379,9 @@ __dyndbg.foo:
__dyndbg.used_by_unused_func:
ret
-.global undef_used_by_non_alloc
-
.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
@@ -375,7 +391,7 @@ shared_func_for_unopt:
.data
-.global shared_func_for_unopt,shared_data_for_unopt
+.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..cd20f97a36d94 100644
--- a/lld/test/ELF/trace-symbols.s
+++ b/lld/test/ELF/trace-symbols.s
@@ -12,6 +12,13 @@
# 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-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 +26,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
More information about the llvm-commits
mailing list