[llvm] 1fe126e - [dyndbg][LLD][ELF] Initial LLD support for dynamic debugging (#214188)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Sep 11 06:49:02 PDT 2026
Author: Andrew Ng
Date: 2026-09-11T14:48:56+01:00
New Revision: 1fe126ef2d6330b7459704e28b2cb3f9bb92b63c
URL: https://github.com/llvm/llvm-project/commit/1fe126ef2d6330b7459704e28b2cb3f9bb92b63c
DIFF: https://github.com/llvm/llvm-project/commit/1fe126ef2d6330b7459704e28b2cb3f9bb92b63c.diff
LOG: [dyndbg][LLD][ELF] Initial LLD support for dynamic debugging (#214188)
LLVM's Dynamic Debugging feature (`llvm/docs/DynamicDebugging.md`)
generates optimized relocatable files with an unoptimized relocatable
copy of the functions from its translation unit embedded in a
`SHT_LLVM_DYNDBG_ELF` section named `.debug_llvm_dyndbg`. The
unoptimized functions in the "inner" embedded relocatable reference the
functions and data of the "outer" optimized relocatable.
When LLD encounters dynamic debugging objects, a nested "inner"
relocatable link is run with the embedded "inner" unoptimized objects.
The output is embedded in a `SHT_LLVM_DYNDBG_ELF` section named
`.debug_llvm_dyndbg` and described by an `NT_LLVM_DYNAMIC_DEBUGGING`
note in the "outer" output. The "inner" relocatable link more closely
resembles a final link rather than a regular relocatable `-r` one,
unless the "outer" link is itself a relocatable `-r` link.
The embedded "inner" objects' undefined symbols are added to the "outer"
symbol table so that the "outer" link resolves the dependencies of the
"inner" relocatable link. Symbols referenced by "inner" relocations from
an allocated section are assigned a PLT/GOT entry as appropriate and are
diagnosed when unresolved. In this initial implementation, these symbols
also act as `--gc-sections` roots for the "outer" link and
`--gc-sections` is not supported for the "inner" relocatable link.
ICF is disabled so that individual functions can be patched when
debugging with dynamic debugging without affecting other identical
functions.
RFC: https://discourse.llvm.org/t/90113
Added:
lld/test/ELF/dynamic-debug.test
lld/test/ELF/invalid-dynamic-debug.test
lld/test/ELF/trace-symbols-dynamic-debug.test
Modified:
lld/Common/CommonLinkerContext.cpp
lld/ELF/Config.h
lld/ELF/Driver.cpp
lld/ELF/InputFiles.cpp
lld/ELF/InputFiles.h
lld/ELF/LinkerScript.cpp
lld/ELF/MarkLive.cpp
lld/ELF/Relocations.cpp
lld/ELF/Symbols.h
lld/ELF/SyntheticSections.cpp
lld/ELF/SyntheticSections.h
lld/ELF/Writer.cpp
llvm/include/llvm/BinaryFormat/ELF.h
Removed:
################################################################################
diff --git a/lld/Common/CommonLinkerContext.cpp b/lld/Common/CommonLinkerContext.cpp
index 12f56bc10ec96..f883c4cbd9642 100644
--- a/lld/Common/CommonLinkerContext.cpp
+++ b/lld/Common/CommonLinkerContext.cpp
@@ -23,6 +23,10 @@ using namespace lld;
static CommonLinkerContext *lctx;
CommonLinkerContext::CommonLinkerContext() {
+ // 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.
codegen::RegisterCodeGenFlags CGF;
@@ -34,7 +38,9 @@ CommonLinkerContext::~CommonLinkerContext() {
// new in SpecificAlloc::create().
for (auto &it : instances)
it.second->~SpecificAllocBase();
- lctx = nullptr;
+
+ if (lctx == this)
+ lctx = nullptr;
}
CommonLinkerContext &lld::commonContext() {
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 54e0aa58591ad..e617f0c1895da 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -208,6 +208,7 @@ class LinkerDriver {
LinkerDriver(LinkerDriver &) = delete;
void linkerMain(ArrayRef<const char *> args);
void addFile(StringRef path, bool withLOption);
+ void addFile(std::unique_ptr<ELFFileBase> ef);
void addLibrary(StringRef name);
private:
@@ -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..788e58a9b93d3 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -113,6 +113,18 @@ llvm::raw_fd_ostream Ctx::openAuxiliaryFile(llvm::StringRef filename,
return {filename, ec, flags};
}
+// 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;
+}
+
namespace lld {
namespace elf {
bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
@@ -120,19 +132,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];
+ ctx.e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
+ initContext(ctx, script, args[0]);
ctx.driver.linkerMain(args);
@@ -292,6 +294,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))
@@ -665,7 +672,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
if (args.hasArg(OPT_v) || args.hasArg(OPT_version))
Msg(ctx) << getLLDVersion() << " (compatible with GNU linkers)";
- if (const char *path = getReproduceOption(args)) {
+ if (const char *path = getReproduceOption(args); !ctx.inDynDbgLink && path) {
// Note that --reproduce is a debug option so you can ignore it
// if you are trying to understand the whole picture of the code.
Expected<std::unique_ptr<TarWriter>> errOrWriter =
@@ -3240,6 +3247,46 @@ 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 dctx;
+ LinkerScript script(dctx);
+ 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) {
+ MemoryBufferRef mb(toStringRef(obj->dynDbgSec->contentMaybeDecompress()),
+ obj->mb.getBufferIdentifier());
+ dctx.driver.addFile(createObjFile(dctx, mb));
+ }
+ }
+
+ if (errCount(ctx))
+ return;
+
+ std::vector<const char *> args{
+ 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");
+ dctx.driver.linkerMain(args);
+ if (errCount(dctx) > 0 || !dctx.dynDbgOutput) {
+ Err(ctx) << "failed to create relocatable dynamic debug object";
+ return;
+ }
+
+ ctx.dynDbgOutput = std::move(dctx.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) {
@@ -3259,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;
@@ -3533,6 +3588,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 23649d6200719..f1ae39b534c6b 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -643,6 +643,15 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
continue;
}
+ if (sec.sh_type == SHT_LLVM_DYNDBG_ELF) {
+ if (check(obj.getSectionName(sec, shstrtab)) == dynDbgSecName) {
+ sections[i] = &InputSection::discarded;
+ dynDbgSec = std::make_unique<InputSection>(*this, sec, dynDbgSecName);
+ ctx.hasDynDbg = true;
+ }
+ continue;
+ }
+
switch (ctx.arg.emachine) {
case EM_ARM:
if (sec.sh_type == SHT_ARM_ATTRIBUTES) {
@@ -1249,6 +1258,71 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
sym->isUsedInRegularObj = true;
sym->referenced = true;
}
+
+ if (dynDbgSec)
+ initDynDbgSymbols();
+}
+
+// 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 SHF_ALLOC section with
+// `isDynDbgRef`; the rest are only needed by debug sections.
+template <class ELFT> void ObjFile<ELFT>::initDynDbgSymbols() {
+ MemoryBufferRef dbgMb(toStringRef(dynDbgSec->contentMaybeDecompress()),
+ 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;
+ };
+
+ 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_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 {
+ 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;
+ }
+ }
}
template <class ELFT>
diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h
index 0ded9b2fa38e2..5b6e52cdf5b73 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;
@@ -294,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);
@@ -389,6 +393,8 @@ 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 dcc84b8755db1..c69b0616a2367 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/Relocations.cpp b/lld/ELF/Relocations.cpp
index 7aaae802f4d7c..4864f3410d441 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
diff erent
// .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/Symbols.h b/lld/ELF/Symbols.h
index c489416e1f4da..15032e472505c 100644
--- a/lld/ELF/Symbols.h
+++ b/lld/ELF/Symbols.h
@@ -344,6 +344,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 aa7a859a9da9a..510d13251978d 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4417,6 +4417,38 @@ 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());
+}
+
+constexpr char dynDbgNoteName[] = "LLVM";
+
+DynamicDebugNote::DynamicDebugNote(Ctx &ctx)
+ : SyntheticSection(ctx, ".note.llvm.dyndbg", SHT_NOTE, 0, 4) {}
+
+size_t DynamicDebugNote::getSize() const {
+ return sizeof(llvm::ELF::Elf64_Nhdr) + alignTo(sizeof(dynDbgNoteName), 4) +
+ /*descsz=*/sizeof(uint32_t);
+}
+
+void DynamicDebugNote::writeTo(uint8_t *buf) {
+ 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, dynDbgNoteName, sizeof(dynDbgNoteName));
+ write32(ctx, buf + 12 + alignTo(sizeof(dynDbgNoteName), 4), 0); // Version
+}
+
static OutputSection *findSection(Ctx &ctx, StringRef name) {
for (SectionCommand *cmd : ctx.script->sectionCommands)
if (auto *osd = dyn_cast<OutputDesc>(cmd))
@@ -4641,6 +4673,15 @@ 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);
+ if (!ctx.arg.relocatable) {
+ 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 cbdadcceceaaf..f8b895d9d2a99 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..e2792dc31d031 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 = std::move(buffer);
+ else if (ctx.arg.outputFile == "-") {
ctx.e.outs() << StringRef(
reinterpret_cast<const char *>(buffer->getBufferStart()),
buffer->getBufferSize());
@@ -1914,6 +1918,38 @@ 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. Report references to undefined symbols and ensure that
+ // references to shared symbols have PLT/GOT entries as appropriate.
+ if (ctx.hasDynDbg) {
+ 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()) {
+ // 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;
+ }
+
+ // 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..3f7dd8e96edf3
--- /dev/null
+++ b/lld/test/ELF/dynamic-debug.test
@@ -0,0 +1,393 @@
+# 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 && cd %t
+
+## Create the "outer" optimized objects.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 main.s -o main.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 foo.s -o foo.o
+
+## Link only the "outer" optimized objects using `--gc-sections`.
+# RUN: ld.lld -e main --gc-sections -o out.nodd main.o foo.o
+# RUN: llvm-readelf -s out.nodd | FileCheck --check-prefix=NO-DD %s
+# NO-DD: Symbol table '.symtab'
+# NO-DD-NOT: unused_func
+
+## 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 main-unopt.s -o main-unopt.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 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 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 foo.o
+
+## Create shared object to test handling of "inner" only dependencies.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 shared.s -o shared.o
+# RUN: ld.lld -shared -o shared.so shared.o
+
+## Link simulated dynamic debugging objects.
+# RUN: ld.lld -e main -o out main.o foo.o shared.so
+# RUN: llvm-readelf -Srsn out | 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 out.gc main.o foo.o shared.so
+# RUN: llvm-readelf -Srsn out.gc | FileCheck --check-prefixes=DD,GC %s
+
+## Check dynamic debugging related sections.
+# 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: {{.*}} 0 OBJECT GLOBAL DEFAULT UND shared_data_for_unopt
+# DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT UND shared_func_for_unopt
+
+# DD: Symbol table '.symtab' contains
+# 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 HIDDEN [[#]] static_func.dyndbg.foo
+# DD-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.foo
+# DD-DAG: {{.*}} 0 FUNC WEAK DEFAULT [[#]] comdat_func
+# DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_in_opt
+# DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_for_unopt_and_non_alloc
+# DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] used_by_unused_func
+# NO-GC-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] unused_func
+# 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.
+# DD-REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS main.o
+# DD-REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS foo.o
+
+# DD: LLVM 0x00000004 Unknown note type: (0x00000004)
+# DD-NEXT: description data: 00 00 00 00
+
+# GC-NOT: {{ }}unused_func
+# GC-NOT: undef_used_by_non_alloc
+
+## Extract and check "inner" ELF for the default link.
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.o out /dev/null
+# RUN: llvm-readelf -Ssg in.o | FileCheck --check-prefix=IN %s
+
+## Extract and check "inner" ELF for the `--gc-sections` link.
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.gc.o out.gc /dev/null
+# RUN: llvm-readelf -Ssg in.gc.o | FileCheck --check-prefix=IN %s
+
+# 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-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.main
+# IN-DAG: {{.*}} 0 FUNC WEAK HIDDEN [[#]] __dyndbg.comdat_func
+# IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.unused_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 static_func.dyndbg.foo
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_data.dyndbg.foo
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND comdat_func
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND used_by_unused_func
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND shared_func_for_unopt
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND shared_data_for_unopt
+# 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.
+
+## Compress debug sections of the simulated dynamic debugging objects with zstd compression.
+# RUN: %if zstd %{ llvm-objcopy --compress-debug-sections=zstd main.o main-zstd.o %}
+# RUN: %if zstd %{ llvm-objcopy --compress-debug-sections=zstd foo.o foo-zstd.o %}
+# RUN: %if zstd %{ llvm-readelf -S main-zstd.o | FileCheck --check-prefix=ZSTD %s %}
+# RUN: %if zstd %{ llvm-readelf -S foo-zstd.o | FileCheck --check-prefix=ZSTD %s %}
+
+# ZSTD: .debug_llvm_dyndbg LLVM_DYNDBG_ELF 0000000000000000 {{.*}} {{.*}} 00 C 0 0 8
+
+## Link simulated dynamic debugging objects with zstd compressed debug sections and
+## check the output.
+# RUN: %if zstd %{ ld.lld -e main -o out.zstd main-zstd.o foo-zstd.o shared.so %}
+# RUN: %if zstd %{ llvm-readelf -Srsn out.zstd | FileCheck --check-prefixes=DD,NO-GC %s %}
+
+## Check disabling of incompatible ICF options.
+# RUN: ld.lld -e main --icf=all -o out.icf main.o foo.o shared.so 2>&1 | \
+# RUN: FileCheck --check-prefix=ICF-WARN %s
+# RUN: cmp out.icf out
+# RUN: ld.lld -e main --icf=safe -o out.icf-safe main.o foo.o shared.so 2>&1 | \
+# RUN: FileCheck --check-prefix=ICF-WARN %s
+# RUN: cmp out.icf-safe out
+
+# ICF-WARN: ICF disabled because it is incompatible with dynamic debugging
+
+## Check relocatable link of simulated dynamic debugging objects.
+# RUN: ld.lld -r -o rel.o main.o foo.o
+# RUN: llvm-readelf -Sg rel.o | FileCheck --check-prefix=REL %s
+
+# REL-NOT: .note.llvm.dyndbg
+# REL: COMDAT group section [{{.*}}] `.group' [comdat_func] contains 1 sections:
+# REL-NEXT: Name
+# REL-NEXT: .text.comdat_func
+
+## Extract "inner" ELF from the above relocatable link and compare to a
+## relocatable link of the "inner" unoptimized objects.
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=rel-in.o rel.o /dev/null
+# RUN: ld.lld -r -o rel-unopt.o main-unopt.o foo-unopt.o
+# RUN: cmp rel-in.o rel-unopt.o
+
+## Link the above relocatable link output with and without `--gc-sections`.
+# RUN: ld.lld -e main -o out.rel rel.o shared.so
+# RUN: llvm-readelf -Srsn out.rel | FileCheck --check-prefixes=DD,NO-GC,DD-REL %s
+# RUN: ld.lld -e main --gc-sections -o out.rel-gc rel.o shared.so
+# RUN: llvm-readelf -Srsn out.rel-gc | FileCheck --check-prefixes=DD,GC,DD-REL %s
+
+## Extract and check "inner" ELF for the above links.
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.rel.o out.rel /dev/null
+# RUN: llvm-readelf -Ssg in.rel.o | FileCheck --check-prefix=IN %s
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.rel-gc.o out.rel-gc /dev/null
+# RUN: llvm-readelf -Ssg in.rel-gc.o | FileCheck --check-prefix=IN %s
+
+## Check link with environment variable LLD_REPRODUCE set.
+# RUN: env LLD_REPRODUCE=repro.tar ld.lld -e main -o out.repro main.o foo.o shared.so
+# RUN: tar tf repro.tar | FileCheck --check-prefix=REPRO %s
+# RUN: tar xOf repro.tar repro/response.txt | FileCheck --check-prefix=RSP %s
+
+# REPRO: repro/response.txt
+# REPRO-NEXT: repro/version.txt
+# REPRO-NEXT: repro/{{.*}}/main.o
+# REPRO-NEXT: repro/{{.*}}/foo.o
+# REPRO-NEXT: repro/{{.*}}/shared.so
+
+# RSP: --chroot .
+# RSP-NEXT: --entry main
+# RSP-NEXT: -o out.repro
+# RSP-NEXT: {{.*}}/main.o
+# RSP-NEXT: {{.*}}/foo.o
+# RSP-NEXT: {{.*}}/shared.so
+
+#--- 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"
+
+.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/invalid-dynamic-debug.test b/lld/test/ELF/invalid-dynamic-debug.test
new file mode 100644
index 0000000000000..41039dd516afa
--- /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 && cd %t
+
+## Create the "outer" optimized objects.
+# 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 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 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 foo.o
+
+## Create object without dynamic debugging.
+# 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 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 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 main.o:(.debug_llvm_dyndbg+0x0)
+# CHECK-EMPTY:
+# CHECK-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
+# 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 main.o:(.debug_llvm_dyndbg+0x0)
+# CHECK-EMPTY:
+
+## Default link with reverse input file order.
+# 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 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 foo.o:(.debug_llvm_dyndbg+0x0)
+# REVERSE-EMPTY:
+# REVERSE-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
+# 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 foo.o:(.debug_llvm_dyndbg+0x0)
+# REVERSE-EMPTY:
+
+## Links with `--warn-unresolved-symbols`.
+# 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 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 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 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 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 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
+# 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
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..74e6d2f04ab07
--- /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 && cd %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 foo.s -o foo.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 bar.s -o bar.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 bar-unopt.s -o bar-unopt.o
+
+## Create dynamic debugging style object.
+# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=bar-unopt.o bar.o
+# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+# RUN: --set-section-alignment=.debug_llvm_dyndbg=8 bar.o
+
+# RUN: ld.lld -trace-symbol=foo -shared foo.o bar.o | FileCheck %s
+
+# CHECK: foo.o: definition of foo
+# CHECK-NEXT: bar.o: reference to foo
+# CHECK-NEXT: 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/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
index 46d806e1b9d8a..6bb387799c4a1 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1810,6 +1810,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