From c7c09dac15305bb03165e8052fa6617bad716731 Mon Sep 17 00:00:00 2001 From: Parth Arora Date: Mon, 21 Sep 2026 07:04:09 -0700 Subject: [PATCH] Add support for version node dependencies in version scripts This commit adds real support for GNU version-script node dependencies instead of warning and discarding them. A dependent node such as V1 { ... }; V2 { ... } V1; previously triggered `Dependent nodes are not supported` and the dependency was silently dropped. Now, a dependency is validated against already-registered named version nodes when symbol versioning is enabled, rejecting unknown, forward, and self references the same way GNU ld does. Symbol scope, version ID assignment, and `.gnu.version` continue to be driven purely by node order and symbol rules, so this is metadata-only. The `.gnu.version_d` writer now emits a dependency as an additional `Verdaux` entry on the dependent version definition. Resolves #1581 Signed-off-by: Parth Arora --- .../eld/Diagnostics/DiagSymbolVersioning.inc | 2 + include/eld/Fragment/GNUVerDefFragment.h | 1 + include/eld/Script/VersionScript.h | 5 ++ lib/Fragment/GNUVerDefFragment.cpp | 47 +++++++++++++------ lib/Object/ObjectLinker.cpp | 22 ++++++++- lib/Script/VersionScript.cpp | 5 ++ .../VersionNodeDependency/Inputs/1.c | 2 + .../VersionNodeDependency/Inputs/unknown.t | 6 +++ .../VersionNodeDependency/Inputs/vs.t | 13 +++++ .../VersionNodeDependency.test | 33 +++++++++++++ .../VersionNodeDependencyErrors.test | 11 +++++ 11 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/1.c create mode 100644 test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/unknown.t create mode 100644 test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/vs.t create mode 100644 test/Common/standalone/SymbolVersioning/VersionNodeDependency/VersionNodeDependency.test create mode 100644 test/Common/standalone/SymbolVersioning/VersionNodeDependency/VersionNodeDependencyErrors.test diff --git a/include/eld/Diagnostics/DiagSymbolVersioning.inc b/include/eld/Diagnostics/DiagSymbolVersioning.inc index f10f8f934d..faf9ea1ea3 100644 --- a/include/eld/Diagnostics/DiagSymbolVersioning.inc +++ b/include/eld/Diagnostics/DiagSymbolVersioning.inc @@ -23,6 +23,8 @@ DIAG(error_missing_version_node, DiagnosticEngine::Error, DIAG(error_undefined_version, DiagnosticEngine::Error, "version script assignment of '%0' to symbol '%1' failed: symbol not " "defined") +DIAG(error_unknown_version_node_dependency, DiagnosticEngine::Error, + "%2: version node '%0' depends on unknown version node '%1'") DIAG(trace_assign_output_version_ids, DiagnosticEngine::Trace, "Assigning version IDs to output symbols") DIAG(trace_adding_verneed_entry, DiagnosticEngine::Trace, diff --git a/include/eld/Fragment/GNUVerDefFragment.h b/include/eld/Fragment/GNUVerDefFragment.h index fc1f87dc78..1c4c8005d9 100644 --- a/include/eld/Fragment/GNUVerDefFragment.h +++ b/include/eld/Fragment/GNUVerDefFragment.h @@ -48,6 +48,7 @@ class GNUVerDefFragment : public Fragment { uint16_t VersionID = 0; uint32_t VersionNameOffset = 0; uint32_t VersionNameHash = 0; + std::vector DependencyNameOffsets; }; protected: diff --git a/include/eld/Script/VersionScript.h b/include/eld/Script/VersionScript.h index 8f3e1148e1..012b8131de 100644 --- a/include/eld/Script/VersionScript.h +++ b/include/eld/Script/VersionScript.h @@ -8,6 +8,7 @@ #define ELD_SCRIPT_VERSIONSCRIPT_H #include "eld/Script/ScriptSymbol.h" +#include "llvm/ADT/StringRef.h" #include namespace eld { class StrToken; @@ -166,6 +167,10 @@ class VersionScriptNode { void setDependency(eld::StrToken *Dependency) { MDependency = Dependency; } + llvm::StringRef getDependency() const { + return MDependency ? MDependency->name() : llvm::StringRef(); + } + VersionScriptBlock *getLocalBlock() const { return MLocal; } VersionScriptBlock *getGlobalBlock() const { return MGlobal; } diff --git a/lib/Fragment/GNUVerDefFragment.cpp b/lib/Fragment/GNUVerDefFragment.cpp index ac30c60033..f468c4a55a 100644 --- a/lib/Fragment/GNUVerDefFragment.cpp +++ b/lib/Fragment/GNUVerDefFragment.cpp @@ -21,6 +21,7 @@ #include "llvm/Object/ELF.h" #include "llvm/Support/Path.h" #include +#include using namespace eld; @@ -48,8 +49,10 @@ GNUVerDefFragment::computeVersionDefs(Module &M, DynStrFragment *DynStr, baseVersion = std::string(llvm::sys::path::filename(outputFileName)); } std::size_t baseVersionOffset = DynStr->addString(baseVersion); - VersionDefs.push_back(VerDefInfo{1, static_cast(baseVersionOffset), - llvm::object::hashSysV(baseVersion)}); + VersionDefs.push_back(VerDefInfo{1, + static_cast(baseVersionOffset), + llvm::object::hashSysV(baseVersion), + {}}); uint16_t VerID = 2; // 0 and 1 are reserved for (const auto *Node : VSNodes) { @@ -58,8 +61,16 @@ GNUVerDefFragment::computeVersionDefs(Module &M, DynStrFragment *DynStr, continue; llvm::StringRef VerName = Node->getName(); std::size_t NameOffset = DynStr->addString(VerName.str()); - VersionDefs.push_back(VerDefInfo{VerID, static_cast(NameOffset), - llvm::object::hashSysV(VerName)}); + std::vector DependencyNameOffsets; + if (Node->hasDependency()) { + std::size_t DependencyOffset = + DynStr->addString(Node->getDependency().str()); + DependencyNameOffsets.push_back(static_cast(DependencyOffset)); + } + VerDefInfo Def{VerID, static_cast(NameOffset), + llvm::object::hashSysV(VerName), + std::move(DependencyNameOffsets)}; + VersionDefs.push_back(std::move(Def)); ++VerID; } return {}; @@ -67,7 +78,9 @@ GNUVerDefFragment::computeVersionDefs(Module &M, DynStrFragment *DynStr, size_t GNUVerDefFragment::size() const { size_t VerDefSize = VersionDefs.size() * VerDefEntrySize; - size_t VerdAuxSize = VersionDefs.size() * VerdAuxEntrySize; // one aux/name + size_t VerdAuxSize = 0; + for (const auto &VD : VersionDefs) + VerdAuxSize += (1 + VD.DependencyNameOffsets.size()) * VerdAuxEntrySize; return VerDefSize + VerdAuxSize; } @@ -85,26 +98,32 @@ eld::Expected GNUVerDefFragment::emitImpl(uint8_t *Buf, Module &M) { auto *VerDefBuf = reinterpret_cast(Buf); auto *VerdAuxBuf = reinterpret_cast( VerDefBuf + VersionDefs.size()); - for (const auto &VD : VersionDefs) { + for (size_t I = 0; I < VersionDefs.size(); ++I) { + const auto &VD = VersionDefs[I]; + const size_t AuxCount = 1 + VD.DependencyNameOffsets.size(); uint16_t flags = (VD.VersionID == 1 ? llvm::ELF::VER_FLG_BASE : 0); VerDefBuf->vd_version = 1; VerDefBuf->vd_flags = flags; VerDefBuf->vd_ndx = VD.VersionID; - VerDefBuf->vd_cnt = 1; // one aux: name + VerDefBuf->vd_cnt = AuxCount; VerDefBuf->vd_hash = VD.VersionNameHash; VerDefBuf->vd_aux = reinterpret_cast(VerdAuxBuf) - reinterpret_cast(VerDefBuf); - VerDefBuf->vd_next = sizeof(typename ELFT::Verdef); + VerDefBuf->vd_next = + (I + 1 == VersionDefs.size()) ? 0 : sizeof(typename ELFT::Verdef); ++VerDefBuf; VerdAuxBuf->vda_name = VD.VersionNameOffset; - VerdAuxBuf->vda_next = sizeof(typename ELFT::Verdaux); + VerdAuxBuf->vda_next = AuxCount == 1 ? 0 : sizeof(typename ELFT::Verdaux); ++VerdAuxBuf; - } - // Terminate aux and def chains - if (!VersionDefs.empty()) { - VerdAuxBuf[-1].vda_next = 0; - VerDefBuf[-1].vd_next = 0; + + for (size_t J = 0; J < VD.DependencyNameOffsets.size(); ++J) { + VerdAuxBuf->vda_name = VD.DependencyNameOffsets[J]; + VerdAuxBuf->vda_next = (J + 1 == VD.DependencyNameOffsets.size()) + ? 0 + : sizeof(typename ELFT::Verdaux); + ++VerdAuxBuf; + } } return {}; } diff --git a/lib/Object/ObjectLinker.cpp b/lib/Object/ObjectLinker.cpp index 92b6338f5e..418be65d30 100644 --- a/lib/Object/ObjectLinker.cpp +++ b/lib/Object/ObjectLinker.cpp @@ -74,6 +74,7 @@ #include "eld/Target/Relocator.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringSet.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/Bitcode/BitcodeWriter.h" @@ -443,6 +444,13 @@ void ObjectLinker::createDefaultSymverNode() { bool ObjectLinker::registerVersionScriptNodes(const VersionScript *VS, llvm::StringRef DecoratedPath) { +#ifdef ELD_ENABLE_SYMBOL_VERSIONING + llvm::StringSet<> SeenVersionNames; + for (const VersionScriptNode *N : ThisModule->getVersionScriptNodes()) + if (N && !N->isAnonymous()) + SeenVersionNames.insert(N->getName()); +#endif + for (auto &VersionScriptNode : VS->getNodes()) { if (!VersionScriptNode->isAnonymous()) { #ifdef ELD_ENABLE_SYMBOL_VERSIONING @@ -453,10 +461,16 @@ bool ObjectLinker::registerVersionScriptNodes(const VersionScript *VS, #endif } if (VersionScriptNode->hasDependency()) { - ThisConfig.raise(Diag::unsupported_dependent_node) - << VersionScriptNode->getName() << DecoratedPath; #ifndef ELD_ENABLE_SYMBOL_VERSIONING continue; +#else + llvm::StringRef Dependency = VersionScriptNode->getDependency(); + if (Dependency == VersionScriptNode->getName() || + !SeenVersionNames.contains(Dependency)) { + ThisConfig.raise(Diag::error_unknown_version_node_dependency) + << VersionScriptNode->getName() << Dependency << DecoratedPath; + return false; + } #endif } // FIXME: Why did we reach here at all if the version script parsing @@ -466,6 +480,10 @@ bool ObjectLinker::registerVersionScriptNodes(const VersionScript *VS, return false; } ThisModule->addVersionScriptNode(VersionScriptNode); +#ifdef ELD_ENABLE_SYMBOL_VERSIONING + if (!VersionScriptNode->isAnonymous()) + SeenVersionNames.insert(VersionScriptNode->getName()); +#endif } return true; } diff --git a/lib/Script/VersionScript.cpp b/lib/Script/VersionScript.cpp index be975f24f9..f0319d11b2 100644 --- a/lib/Script/VersionScript.cpp +++ b/lib/Script/VersionScript.cpp @@ -67,6 +67,11 @@ VersionScriptBlock *VersionScriptNode::switchToGlobal() { void VersionScriptNode::dump( llvm::raw_ostream &Ostream, std::function GetDecoratedPath) const { + if (!isAnonymous()) { + Ostream << "\nVersion node: " << getName(); + if (hasDependency()) + Ostream << "\nDependency: " << getDependency(); + } if (MGlobal) MGlobal->dump(Ostream, GetDecoratedPath); if (MLocal) diff --git a/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/1.c b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/1.c new file mode 100644 index 0000000000..5967a4f88d --- /dev/null +++ b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/1.c @@ -0,0 +1,2 @@ +int foo() { return 0; } +int bar() { return 0; } diff --git a/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/unknown.t b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/unknown.t new file mode 100644 index 0000000000..6b9cdae13b --- /dev/null +++ b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/unknown.t @@ -0,0 +1,6 @@ +V2 { + global: + bar; + local: + *; +} V_UNKNOWN; diff --git a/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/vs.t b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/vs.t new file mode 100644 index 0000000000..b4e956bab0 --- /dev/null +++ b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/Inputs/vs.t @@ -0,0 +1,13 @@ +V1 { + global: + foo; + local: + *; +}; + +V2 { + global: + bar; + local: + *; +} V1; diff --git a/test/Common/standalone/SymbolVersioning/VersionNodeDependency/VersionNodeDependency.test b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/VersionNodeDependency.test new file mode 100644 index 0000000000..ad15996e4e --- /dev/null +++ b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/VersionNodeDependency.test @@ -0,0 +1,33 @@ +REQUIRES: symbol_versioning +#---VersionNodeDependency.test---------------- SymbolVersioning ---# +#BEGIN_COMMENT +# Verify that a version node dependency is emitted in .gnu.version_d without +# changing symbol version assignment. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -o %t.o %p/Inputs/1.c -c -fPIC +RUN: %link %linkopts -o %t.so %t.o -shared --version-script %p/Inputs/vs.t -Map %t.map 2>&1 | %filecheck %s --check-prefix=NO-WARN --allow-empty +RUN: %readelf --dyn-syms --version-info %t.so | %filecheck %s --check-prefix=VERINFO +RUN: %filecheck %s --check-prefix=MAP < %t.map +#END_TEST + +NO-WARN-NOT: Dependent nodes are not supported + +VERINFO-DAG: foo@@V1 +VERINFO-DAG: bar@@V2 +VERINFO: '.gnu.version_d' contains 3 entries: +VERINFO: Name: {{.*}} +VERINFO: Name: V1 +VERINFO-NOT: Parent +VERINFO: Name: V2 +VERINFO: Parent 1: V1 + +MAP: Version Script Information +MAP: Version Script file +MAP: {{.*}}vs.t +MAP: Version node: V1 +MAP-NOT: Dependency: +MAP: Global: +MAP: Version node: V2 +MAP: Dependency: V1 +MAP: Global: diff --git a/test/Common/standalone/SymbolVersioning/VersionNodeDependency/VersionNodeDependencyErrors.test b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/VersionNodeDependencyErrors.test new file mode 100644 index 0000000000..ffb8e0325c --- /dev/null +++ b/test/Common/standalone/SymbolVersioning/VersionNodeDependency/VersionNodeDependencyErrors.test @@ -0,0 +1,11 @@ +REQUIRES: symbol_versioning +#---VersionNodeDependencyErrors.test------------ SymbolVersioning ---# +#BEGIN_COMMENT +# Verify that a dependency must refer to an earlier named version node. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -o %t.o %p/Inputs/1.c -c -fPIC +RUN: %not %link %linkopts -o %t.so %t.o -shared --version-script %p/Inputs/unknown.t 2>&1 | %filecheck %s +#END_TEST + +CHECK: Error: {{.*}}unknown.t: version node 'V2' depends on unknown version node 'V_UNKNOWN'