Skip to content

[SymbolVersioning] fix version-script input ordering - #1672

Open
Rachit Mehta (rachitmeht) wants to merge 1 commit into
qualcomm:mainfrom
rachitmeht:fix/version-script-input-order
Open

[SymbolVersioning] fix version-script input ordering#1672
Rachit Mehta (rachitmeht) wants to merge 1 commit into
qualcomm:mainfrom
rachitmeht:fix/version-script-input-order

Conversation

@rachitmeht

@rachitmeht Rachit Mehta (rachitmeht) commented Aug 10, 2026

Copy link
Copy Markdown

ELD's version-script node order did not follow command-line order; GNU ld processes both -T and --version-script inline in a single option loop, so node order always equals CLI order.

Move --version-script into createInputActions() as a ScriptAction (VersionScript) at its exact CLI position. Add readVersionScriptFile() to parse it during activateInputs(), mark it setToSkip() to prevent reprocessing in normalize(), and append to Module::VersionScripts.

readLinkerScript() appends embedded VERSION{} blocks to the same list via addVersionScript(). Module::LinkerScriptVersionScripts, GeneralOptions::VersionScripts, and their accessors are removed. Module::VersionScripts is now the single ordered list for both sources.

parseVersionScript() collapses to one loop over getVersionScripts() calling registerVersionScriptNodes(). Registration stays deferred as getTargetBackend() is unsafe during activateInputs().

applyVersionScriptScopes() guard changed from hasVersionScript() to getVersionScriptNodes().empty(). readVersionScriptFile() sets setParsed()/setToSkip() only after successful parse and null-guards getVersionScript() before addVersionScript().

Add test VersionScriptInputOrder covering --version-script before/after -T, two --version-script files in both orders, and embedded-only VERSION{} local scope.

Resolves #1636

@quic-areg quic-areg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a bit of a contrived case, but evaluating --version-script positionally made it become a --start-lib --end-lib member:

ld.eld -shared --whole-archive --start-lib a.o \
       --version-script=vs --end-lib --no-whole-archive -o out.so
# Error: vs:1:1: unknown directive: V
# Fatal: Linker script <start-lib:1> has errors

for (llvm::opt::Arg *arg : Args) {
switch (arg->getOption().getID()) {
// -T script, --default-script
case T::default_script: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should --default-script be evaluated positionally too?

@rachitmeht

Copy link
Copy Markdown
Author

it's a bit of a contrived case, but evaluating --version-script positionally made it become a --start-lib --end-lib member:

ld.eld -shared --whole-archive --start-lib a.o \
       --version-script=vs --end-lib --no-whole-archive -o out.so
# Error: vs:1:1: unknown directive: V
# Fatal: Linker script <start-lib:1> has errors

Scripts should not be part of input tree (--start-lib --end-lib member). is it correct? this will fix the error.

@rachitmeht
Rachit Mehta (rachitmeht) force-pushed the fix/version-script-input-order branch from 31ac472 to 475e6e7 Compare August 14, 2026 10:03
@parth-07

Copy link
Copy Markdown
Contributor

Rachit Mehta (@rachitmeht) Can you please rebase this PR on top of origin/main?

Comment thread lib/Core/Linker.cpp
ThisModule->setFailure(true);
return false;
auto *SA = llvm::dyn_cast<eld::ScriptAction>(Action);
if (SA->kind() == eld::ScriptFile::VersionScript) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please read version script along with the other scripts in ObjectLinker::readInputs? We should only read -T <script> scripts early, and too because some nodes in -T <script> scripts has to be processed early.

One of the previous patch added support for ScriptFile::ScriptActivationKind::Early and ScriptFile::ScriptActivationKind::Full which we can use in ObjectLinker::readLinkerScript while adding the version script to the Module object.

@parth-07

Copy link
Copy Markdown
Contributor

Rachit Mehta (@rachitmeht) Can you please check and report what is the behaviour of GNU ld and lld when a version script is present in between --start-lib and --end-lib?

…behavior

ELD's version-script node order did not follow command-line order;
GNU ld processes both -T and --version-script inline in a single
option loop, so node order always equals CLI order.

Move --version-script into createInputActions() as a ScriptAction
(VersionScript) at its exact CLI position. Add readVersionScriptFile()
to parse it during activateInputs(), mark it setToSkip() to prevent
reprocessing in normalize(), and append to Module::VersionScripts.

Additionally, fix scripts being incorrectly added to the input tree.
ScriptAction::activate() now calls createInput() directly instead of
InputFileAction::activate(), avoiding createInputNode() which adds to
the tree. This prevents scripts from becoming lib members when inside
--start-lib, matching GNU ld behavior.

readLinkerScript() appends embedded VERSION{} blocks to the same
list via addVersionScript(). Module::LinkerScriptVersionScripts,
GeneralOptions::VersionScripts, and their accessors are removed.
Module::VersionScripts is now the single ordered list for both sources.

parseVersionScript() collapses to one loop over getVersionScripts()
calling registerVersionScriptNodes(). Registration stays deferred
as getTargetBackend() is unsafe during activateInputs().

applyVersionScriptScopes() guard changed from hasVersionScript()
to getVersionScriptNodes().empty(). readVersionScriptFile() sets
setParsed()/setToSkip() only after successful parse and null-guards
getVersionScript() before addVersionScript().

Add test VersionScriptInputOrder covering --version-script before/after
-T, two --version-script files in both orders, and embedded-only
VERSION{} local scope.

Signed-off-by: Rachit Mehta <rachmeht@qti.qualcomm.com>
@rachitmeht
Rachit Mehta (rachitmeht) force-pushed the fix/version-script-input-order branch from 475e6e7 to b9a511e Compare August 20, 2026 05:40
@rachitmeht

Copy link
Copy Markdown
Author

GNU gold (bfd doesn't have --start-lib functionality)

--start-lib creates an Input_file_lib and sets in_lib = true.
Object files encountered afterwards are added to that library.

--version-script is still treated as a linker option while inside the
--start-lib/--end-lib range. Gold calls read_version_script() immediately
during command-line processing. The version script is not added to the
Input_file_lib and therefore cannot become a lazy archive member.

--end-lib closes the library range. Later, gold uses the parsed version
information while adding and resolving symbols from the selected lazy
members.

Gold processes -T scripts and --version-script options in the same
left-to-right command-line loop. Consequently, their relative command-line
order is preserved, including the order of embedded VERSION{} blocks in
-T scripts.

lld

lld first calls readConfigs(), before createFiles(). In readConfigs(), it
processes all standalone --version-script options:

for (auto *arg : args.filtered(OPT_version_script))
readVersionScript(...);

These scripts are parsed before lld processes --start-lib, --end-lib,
objects, or -T scripts. They are not added to the lazy library.

createFiles() later processes --start-lib and marks object inputs as lazy.
The --version-script options are not handled there because they were already
consumed by readConfigs().

Thus lld does not preserves the order between standalone version scripts and embedded VERSION{}
blocks in -T scripts.

For example:

-T script-with-VERSION-B --version-script=A.map

Gold processes B then A.

Current lld processes A first in readConfigs(), then B while processing -T.

@parth-07

Copy link
Copy Markdown
Contributor

Rachit Mehta (@rachitmeht) Thanks for reporting the behavior here across linkers. Can you update the patch such that the --version-script <file> is not added as an archive member when used with --start-lib/--end-lib?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SymbolVersioning] Investigate input order of the version scripts

3 participants