From 1b579617a6714c99708d2251d48ab965828fea7b Mon Sep 17 00:00:00 2001 From: Parth Arora Date: Wed, 11 Feb 2026 05:53:52 -0800 Subject: [PATCH] Correct sysroot-prepend conditions for script inputs This commit fixes the logic that determines when the script input path should be prepended with sysroot. The summary of the new behavior: - sysroot is only prepended to the script inputs when all the below constraints are true: - the input is specified in INPUT/GROUP linker script command. - The input path begins with '/'. - The linker script that contains this INPUT/GROUP command is within the sysroot directory. - In all other cases, sysroot is not prepended to the script inputs. Resolves #786 Resolves #808 Signed-off-by: Parth Arora --- include/eld/Driver/GnuLinkerOptions.td | 2 +- include/eld/Input/Input.h | 13 ++++++ lib/Input/Input.cpp | 27 +++++++++++- lib/Script/GroupCmd.cpp | 9 +++- lib/Script/InputCmd.cpp | 9 +++- .../linkerscript/SysrootInputGroup/Inputs/1.c | 1 + .../linkerscript/SysrootInputGroup/Inputs/2.c | 1 + .../SysrootInputGroup/Inputs/main.c | 1 + .../SysrootInputGroup/Inputs/script1.t | 1 + .../SysrootInputGroup/Inputs/script2.t | 1 + .../SysrootInputGroup/SysrootInputGroup.test | 32 ++++++++++++++ .../VersionAndDynamicScripts/Inputs/1.c | 4 ++ .../Inputs/dynamic.list | 4 ++ .../Inputs/version.script | 6 +++ .../VersionAndDynamicScriptAbsolutePath.test | 44 +++++++++++++++++++ 15 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/1.c create mode 100644 test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/2.c create mode 100644 test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/main.c create mode 100644 test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/script1.t create mode 100644 test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/script2.t create mode 100644 test/Common/standalone/linkerscript/SysrootInputGroup/SysrootInputGroup.test create mode 100644 test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/1.c create mode 100644 test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/dynamic.list create mode 100644 test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/version.script create mode 100644 test/Common/standalone/sysRoot/VersionAndDynamicScripts/VersionAndDynamicScriptAbsolutePath.test diff --git a/include/eld/Driver/GnuLinkerOptions.td b/include/eld/Driver/GnuLinkerOptions.td index 5e4fe5fde..12caa5fa3 100644 --- a/include/eld/Driver/GnuLinkerOptions.td +++ b/include/eld/Driver/GnuLinkerOptions.td @@ -136,7 +136,7 @@ defm output_file : smDashTwoWithOpt<"o", "output", "output_file", MetaVarName<"">, Group; defm sysroot : smDash<"sysroot", "sysroot", "Set the system root">, - MetaVarName<"">, + MetaVarName<"">, Group; def version : Flag<["--"], "version">, HelpText<"Print the Linker version">, diff --git a/include/eld/Input/Input.h b/include/eld/Input/Input.h index 26fa72292..47eb9f6dc 100644 --- a/include/eld/Input/Input.h +++ b/include/eld/Input/Input.h @@ -76,6 +76,12 @@ class Input { void setMemberNameHash(uint64_t Hash) { MemberNameHash = Hash; } + /// Set the parent script file for inputs from INPUT/GROUP commands. + void setParentScriptFile(InputFile *File) { ParentScriptFile = File; } + + /// Get the parent script file. + InputFile *getParentScriptFile() const { return ParentScriptFile; } + uint32_t getInputOrdinal() { return InputOrdinal; } Attribute &getAttribute() { return Attr; } @@ -163,6 +169,12 @@ class Input { // Check if a path is valid and emit any errors bool isPathValid(const std::string &Path) const; + // Return true if sysroot should be prepended for a Script-typed input whose + // filename starts with '/'. This is used for INPUT/GROUP entries coming from + // a linker script: sysroot is applied only when the parent script is within + // the sysroot directory. + bool shouldPrependSysrootToScriptInput(const LinkerConfig &Config) const; + protected: InputFile *IF = nullptr; MemoryArea *MemArea = nullptr; @@ -178,6 +190,7 @@ class Input { InputType Type = Default; // The type of input file. bool TraceMe = false; DiagnosticEngine *DiagEngine = nullptr; + InputFile *ParentScriptFile = nullptr; // Parent script for INPUT/GROUP inputs. /// Keeps track of already created MemoryAreas. /// diff --git a/lib/Input/Input.cpp b/lib/Input/Input.cpp index bd643f67a..bde18b2a7 100644 --- a/lib/Input/Input.cpp +++ b/lib/Input/Input.cpp @@ -102,8 +102,7 @@ bool Input::resolvePath(const LinkerConfig &PConfig) { } if (Type == Input::Script) { - if (PSearchDirs.hasSysRoot() && - (FileName.size() > 0 && FileName[0] == '/')) { + if (shouldPrependSysrootToScriptInput(PConfig)) { ResolvedPath = PSearchDirs.sysroot(); ResolvedPath->append(FileName); } @@ -151,6 +150,30 @@ bool Input::resolvePath(const LinkerConfig &PConfig) { return true; } +bool Input::shouldPrependSysrootToScriptInput( + const LinkerConfig &Config) const { + auto &searchDirs = Config.directories(); + if (!searchDirs.hasSysRoot()) + return false; + if (Type != Input::Script) + return false; + if (FileName.empty() || FileName[0] != '/') + return false; + + // Only apply sysroot for INPUT/GROUP entries when we know which script they + // came from and that script is inside sysroot. + if (!ParentScriptFile) + return false; + + Input *scriptInput = ParentScriptFile->getInput(); + + std::string scriptPath = scriptInput->getResolvedPath().getFullPath(); + std::string sysrootPath = searchDirs.sysroot().getFullPath(); + + return scriptPath.size() >= sysrootPath.size() && + scriptPath.compare(0, sysrootPath.size(), sysrootPath) == 0; +} + void Input::setInputFile(InputFile *Inp) { IF = Inp; } void Input::overrideInputFile(InputFile *Inp) { IF = Inp; } diff --git a/lib/Script/GroupCmd.cpp b/lib/Script/GroupCmd.cpp index f42dcb21a..d59702768 100644 --- a/lib/Script/GroupCmd.cpp +++ b/lib/Script/GroupCmd.cpp @@ -59,6 +59,9 @@ eld::Expected GroupCmd::activate(Module &CurModule) { // --start-group ThisBuilder.enterGroup(); + // Prefer ScriptCommand context (handles included scripts). + InputFile *parentScriptFile = &ThisScriptFile.getLinkerScriptFile(); + for (auto &S : ThisStringList) { InputToken *Token = llvm::cast(S); Token = ThisScriptFile.findResolvedFilename(Token); @@ -67,9 +70,11 @@ eld::Expected GroupCmd::activate(Module &CurModule) { else ThisBuilder.getAttributes().unsetAsNeeded(); switch (Token->type()) { - case InputToken::File: - ThisBuilder.createDeferredInput(Token->name(), Input::Script); + case InputToken::File: { + Input *I = ThisBuilder.createDeferredInput(Token->name(), Input::Script); + I->setParentScriptFile(parentScriptFile); break; + } case InputToken::NameSpec: { ThisBuilder.createDeferredInput(Token->name(), Input::Namespec); break; diff --git a/lib/Script/InputCmd.cpp b/lib/Script/InputCmd.cpp index 346c1e4e3..6bc1cf914 100644 --- a/lib/Script/InputCmd.cpp +++ b/lib/Script/InputCmd.cpp @@ -56,6 +56,9 @@ void InputCmd::dump(llvm::raw_ostream &Outs) const { } eld::Expected InputCmd::activate(Module &CurModule) { + // Prefer ScriptCommand context (handles included scripts). + InputFile *parentScriptFile = &ThisScriptFile.getLinkerScriptFile(); + for (auto &S : ThisStringList) { InputToken *Token = llvm::cast(S); Token = ThisScriptFile.findResolvedFilename(Token); @@ -64,9 +67,11 @@ eld::Expected InputCmd::activate(Module &CurModule) { else ThisBuilder.getAttributes().unsetAsNeeded(); switch (Token->type()) { - case InputToken::File: - ThisBuilder.createDeferredInput(Token->name(), Input::Script); + case InputToken::File: { + Input *I = ThisBuilder.createDeferredInput(Token->name(), Input::Script); + I->setParentScriptFile(parentScriptFile); break; + } case InputToken::NameSpec: { ThisBuilder.createDeferredInput(Token->name(), Input::Namespec); break; diff --git a/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/1.c b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/1.c new file mode 100644 index 000000000..a60f28c10 --- /dev/null +++ b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/1.c @@ -0,0 +1 @@ +int foo() { return 1; } diff --git a/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/2.c b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/2.c new file mode 100644 index 000000000..75dae47b6 --- /dev/null +++ b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/2.c @@ -0,0 +1 @@ +int bar() { return 2; } diff --git a/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/main.c b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/main.c new file mode 100644 index 000000000..76e819701 --- /dev/null +++ b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/main.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/script1.t b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/script1.t new file mode 100644 index 000000000..2ac431a63 --- /dev/null +++ b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/script1.t @@ -0,0 +1 @@ +GROUP(/lib64/lib1.so) diff --git a/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/script2.t b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/script2.t new file mode 100644 index 000000000..6cbd52c23 --- /dev/null +++ b/test/Common/standalone/linkerscript/SysrootInputGroup/Inputs/script2.t @@ -0,0 +1 @@ +INPUT(/lib64/lib2.so) diff --git a/test/Common/standalone/linkerscript/SysrootInputGroup/SysrootInputGroup.test b/test/Common/standalone/linkerscript/SysrootInputGroup/SysrootInputGroup.test new file mode 100644 index 000000000..c94b29d48 --- /dev/null +++ b/test/Common/standalone/linkerscript/SysrootInputGroup/SysrootInputGroup.test @@ -0,0 +1,32 @@ +#---SysrootInputGroup.test---------------- Linker Script ----------------# +#BEGIN_COMMENT +# Validate sysroot handling for INPUT/GROUP script commands (issue #808). +# Sysroot is prepended for "/..." only when the linker script itself is within +# the sysroot directory. +#END_COMMENT +#START_TEST + +RUN: rm -rf %t.dir && mkdir -p %t.dir/lib64 %t.dir/scripts +RUN: %clang %clangopts -o %t.1.o %p/Inputs/1.c -c -fPIC +RUN: %clang %clangopts -o %t.2.o %p/Inputs/2.c -c -fPIC +RUN: %clang %clangopts -o %t.main.o %p/Inputs/main.c -c +RUN: %link %linkopts -o %t.dir/lib64/lib1.so -shared %t.1.o +RUN: %link %linkopts -o %t.dir/lib64/lib2.so -shared %t.2.o + +# Script outside sysroot: do not prepend sysroot for "/...". +RUN: %not %link %linkopts -o %t.out1 --sysroot=%t.dir -T %p/Inputs/script1.t %t.main.o 2>&1 | %filecheck %s --check-prefix=ERR1 +RUN: %not %link %linkopts -o %t.out2 --sysroot=%t.dir -T %p/Inputs/script2.t %t.main.o 2>&1 | %filecheck %s --check-prefix=ERR2 + +# Script in sysroot: prepend sysroot for "/...". +RUN: %cp %p/Inputs/script1.t %t.dir/scripts/script1.t +RUN: %cp %p/Inputs/script2.t %t.dir/scripts/script2.t +RUN: %link %linkopts -o %t.out3 --sysroot=%t.dir -T %t.dir/scripts/script1.t %t.main.o --verbose 2>&1 | %filecheck %s --check-prefix=SYSROOT1 +RUN: %link %linkopts -o %t.out4 --sysroot=%t.dir -T %t.dir/scripts/script2.t %t.main.o --verbose 2>&1 | %filecheck %s --check-prefix=SYSROOT2 +#END_TEST + +ERR1: Fatal: cannot read file /lib64/lib1.so +ERR2: Fatal: cannot read file /lib64/lib2.so + +SYSROOT1: Verbose: Mapping input file '{{.*}}dir/lib64/lib1.so' into memory +SYSROOT2: Verbose: Mapping input file '{{.*}}dir/lib64/lib2.so' into memory + diff --git a/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/1.c b/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/1.c new file mode 100644 index 000000000..8beef3e26 --- /dev/null +++ b/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/1.c @@ -0,0 +1,4 @@ +// Simple library with exported functions +int exported_func_v1() { return 1; } +int exported_func_v2() { return 2; } +int hidden_func() { return 3; } diff --git a/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/dynamic.list b/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/dynamic.list new file mode 100644 index 000000000..71ffea02b --- /dev/null +++ b/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/dynamic.list @@ -0,0 +1,4 @@ +{ + exported_func_v1; + exported_func_v2; +}; diff --git a/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/version.script b/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/version.script new file mode 100644 index 000000000..a19e54f43 --- /dev/null +++ b/test/Common/standalone/sysRoot/VersionAndDynamicScripts/Inputs/version.script @@ -0,0 +1,6 @@ +{ + global: + exported_func_v1; + local: + *; +}; diff --git a/test/Common/standalone/sysRoot/VersionAndDynamicScripts/VersionAndDynamicScriptAbsolutePath.test b/test/Common/standalone/sysRoot/VersionAndDynamicScripts/VersionAndDynamicScriptAbsolutePath.test new file mode 100644 index 000000000..3765824ca --- /dev/null +++ b/test/Common/standalone/sysRoot/VersionAndDynamicScripts/VersionAndDynamicScriptAbsolutePath.test @@ -0,0 +1,44 @@ +#---VersionAndDynamicScripts.test---- sysRoot ----------------# + +#BEGIN_COMMENT +# Test that the version scripts and dynamic lists paths are properly handled +# when sysroot is used. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -o %t1.1.o %p/Inputs/1.c -c +RUN: %rm -rf %t1.sysroot %t1.dir +RUN: %mkdir %t.sysroot +RUN: %link %linkopts -o %t1.lib1.so %t1.1.o -shared --sysroot=%t.sysroot -L=/ \ +RUN: --version-script=%p/Inputs/version.script --verbose 2>&1 \ +RUN: | %filecheck %s --check-prefix VersionScriptAbs +RUN: %link %linkopts -o %t1.lib2.so %t1.1.o -shared --sysroot=%t.sysroot -L=/ \ +RUN: --dynamic-list=%p/Inputs/dynamic.list --verbose 2>&1 \ +RUN: | %filecheck %s --check-prefix DynScriptAbs +RUN: %mkdir %t1.dir +RUN: cd %t1.dir +RUN: %link %linkopts -o %t1.lib1.so %t1.1.o -shared --sysroot=%p/Inputs -L=/ \ +RUN: --version-script=version.script --verbose 2>&1 \ +RUN: | %filecheck %s --check-prefix VersionScriptSysRoot +RUN: %link %linkopts -o %t1.lib2.so %t1.1.o -shared --sysroot=%p/Inputs -L=/ \ +RUN: --dynamic-list=dynamic.list --verbose 2>&1 \ +RUN: | %filecheck %s --check-prefix DynScriptSysRoot +RUN: %cp %p/Inputs/version.script version.script +RUN: %cp %p/Inputs/dynamic.list dynamic.list +RUN: %link %linkopts -o %t1.lib1.so %t1.1.o -shared --sysroot=%p/Inputs -L=/ \ +RUN: --version-script=version.script --verbose 2>&1 \ +RUN: | %filecheck %s --check-prefix VersionScriptRel +RUN: %link %linkopts -o %t1.lib2.so %t1.1.o -shared --sysroot=%p/Inputs -L=/ \ +RUN: --dynamic-list=dynamic.list --verbose 2>&1 \ +RUN: | %filecheck %s --check-prefix DynScriptSysRel +#END_TEST + +VersionScriptAbs: Verbose: Parsing version script + +DynScriptAbs: Verbose: Dynamic List[{{.*}}] : exported_func_v1 +DynScriptAbs: Verbose: Dynamic List[{{.*}}] : exported_func_v2 + +VersionScriptSysRoot: Verbose: Trying to open input `{{.*}}/Inputs//version.script' of type `linker script' for namespec `version.script': found +DynScriptSysRoot: Verbose: Trying to open input `{{.*}}/Inputs//dynamic.list' of type `linker script' for namespec `dynamic.list': found + +VersionScriptRel: Verbose: Mapping input file 'version.script' into memory +DynScriptSysRel: Verbose: Mapping input file 'dynamic.list' into memory