diff --git a/docs/userguide/documentation/diagnostic_reports.rst b/docs/userguide/documentation/diagnostic_reports.rst index 65b35e392d..30d5fcbbe1 100644 --- a/docs/userguide/documentation/diagnostic_reports.rst +++ b/docs/userguide/documentation/diagnostic_reports.rst @@ -85,7 +85,7 @@ General Warning Flags * - ``-Werror`` - Treats all warnings as errors, halting the link process. * - ``-Wlinker-script`` - - Enables warnings specific to linker script issues, such as malformed directives, deprecated syntax, or unsupported constructs. + - Enables warnings specific to linker script issues, such as malformed directives, deprecated syntax, unsupported constructs or specifying multiple entry points. * - ``-Wlinker-script-memory`` - Focuses on memory region definitions in linker scripts, such as overlaps or undefined regions. * - ``-Wwhole-archive`` diff --git a/docs/userguide/documentation/linker_faq.rst b/docs/userguide/documentation/linker_faq.rst index dcfc533320..eaf7e053e4 100644 --- a/docs/userguide/documentation/linker_faq.rst +++ b/docs/userguide/documentation/linker_faq.rst @@ -344,6 +344,7 @@ Multiple ways to provide entry point to linker * Initialising the value of linker symbol "start" * Specifying the start address for the first input section in linker script (eg: .text : AT(0)) +* When multiple -e options are given, the last one takes effect. How to obtain a non-executable stack -------------------------------------- diff --git a/docs/userguide/documentation/linker_script.rst b/docs/userguide/documentation/linker_script.rst index 4c9ed971f3..7987005e85 100644 --- a/docs/userguide/documentation/linker_script.rst +++ b/docs/userguide/documentation/linker_script.rst @@ -474,6 +474,8 @@ Syntax :- ``ENTRY(symbol)`` point. - The entry point is the first instruction that is executed after a program is loaded. +- When multiple ENTRY() commands appear (in one script or across + several -T scripts), the last one takes effect. - This command is equivalent to the linker command-line option :option:`-e`. diff --git a/include/eld/Config/GeneralOptions.h b/include/eld/Config/GeneralOptions.h index a3a8c1d0aa..79d522be71 100644 --- a/include/eld/Config/GeneralOptions.h +++ b/include/eld/Config/GeneralOptions.h @@ -759,6 +759,10 @@ class GeneralOptions { bool hasEntry() const; + void setEntryFromCmdLine(); + + bool isEntryFromCmdLine() const; + llvm::ArrayRef mapStyle() const { return MapStyles; } bool setMapStyle(llvm::StringRef MapStyle); @@ -1395,6 +1399,7 @@ class GeneralOptions { std::vector LTOOutputFile; std::optional ImageBase; // --image-base=value std::string Entry; + bool EntryFromCmdLine = false; SymbolRenameMap SymbolRenames; AddressMapType AddressMap; std::vector CommandLineArgs; diff --git a/include/eld/Diagnostics/DiagLDScript.inc b/include/eld/Diagnostics/DiagLDScript.inc index 410c3b53ac..a475a19531 100644 --- a/include/eld/Diagnostics/DiagLDScript.inc +++ b/include/eld/Diagnostics/DiagLDScript.inc @@ -39,6 +39,8 @@ DIAG(assert_failed, DiagnosticEngine::Error, "Assertion failed %0") DIAG(error_printcmd, DiagnosticEngine::Error, "%0: PRINT: %1") DIAG(linker_script_uses_phdrs_no_sections, DiagnosticEngine::Error, "Linker Script is using PHDR's but not using SECTIONS command") +DIAG(warn_multiple_entry, DiagnosticEngine::Warning, + "multiple entry points specified; the last one takes effect") DIAG(cannot_set_at_address, DiagnosticEngine::Error, "Address for section %0 specified with AT cannot be set, as there is a " "dependency issue, check the map file for more analysis") diff --git a/lib/Config/GeneralOptions.cpp b/lib/Config/GeneralOptions.cpp index a0cd9decdc..a3611e37cf 100644 --- a/lib/Config/GeneralOptions.cpp +++ b/lib/Config/GeneralOptions.cpp @@ -127,6 +127,10 @@ void GeneralOptions::setEntry(const std::string &PEntry) { Entry = PEntry; } bool GeneralOptions::hasEntry() const { return !Entry.empty(); } +bool GeneralOptions::isEntryFromCmdLine() const { return EntryFromCmdLine; } + +void GeneralOptions::setEntryFromCmdLine() { EntryFromCmdLine = true; } + void GeneralOptions::setTrace(bool EnableTrace) { DiagEngine->getPrinter()->setTrace(DiagEngine->getPrinter()->TraceFiles); } diff --git a/lib/LinkerWrapper/GnuLdDriver.cpp b/lib/LinkerWrapper/GnuLdDriver.cpp index 192a9b1c1a..affb638168 100644 --- a/lib/LinkerWrapper/GnuLdDriver.cpp +++ b/lib/LinkerWrapper/GnuLdDriver.cpp @@ -719,9 +719,10 @@ bool GnuLdDriver::processOptions(llvm::opt::InputArgList &Args) { T::fatal_internal_errors, T::no_fatal_internal_errors, /*default=*/false); Config.options().setFatalInternalErrors(enableFatalInternalErrors); - // set up entry point from -e + // set up entry point from -e; if (llvm::opt::Arg *arg = Args.getLastArg(T::entrypoint)) { Config.options().setEntry(arg->getValue()); + Config.options().setEntryFromCmdLine(); Config.addCommandLine(Table->getOptionName(T::entrypoint), arg->getValue()); } diff --git a/lib/Script/EntryCmd.cpp b/lib/Script/EntryCmd.cpp index 234bd753ce..3240dfd22f 100644 --- a/lib/Script/EntryCmd.cpp +++ b/lib/Script/EntryCmd.cpp @@ -32,7 +32,12 @@ void EntryCmd::dump(llvm::raw_ostream &Outs) const { } eld::Expected EntryCmd::activate(Module &CurModule) { - if ((!EntrySymbol.empty()) && (!(CurModule.getConfig().options().hasEntry()))) - CurModule.getConfig().options().setEntry(EntrySymbol); + GeneralOptions &Options = CurModule.getConfig().options(); + if (!EntrySymbol.empty()) { + if (Options.hasEntry() && CurModule.getConfig().showLinkerScriptWarnings()) + CurModule.getConfig().raise(Diag::warn_multiple_entry); + if (!Options.isEntryFromCmdLine()) + Options.setEntry(EntrySymbol); + } return eld::Expected(); } diff --git a/test/Common/standalone/linkerscript/EntryCmd/EntryCmdPrecedence.test b/test/Common/standalone/linkerscript/EntryCmd/EntryCmdPrecedence.test new file mode 100644 index 0000000000..b00f46a7cb --- /dev/null +++ b/test/Common/standalone/linkerscript/EntryCmd/EntryCmdPrecedence.test @@ -0,0 +1,50 @@ +#---EntryCmdPrecedence.test-------------- Executable,LS ----------------------# +#BEGIN_COMMENT +# Tests GNU ld entry point precedence rules: +# Case 1: Last ENTRY() in a single script wins over earlier ENTRY() commands. +# Case 2: Command-line -e always overrides any script ENTRY(). +# Case 3: Last -e on command line wins over earlier -e. +# Case 4: -e with a raw address overrides script ENTRY(). +# Case 5: Across multiple -T scripts, the last ENTRY() activated wins. +# Case 6: -e overrides script ENTRY() regardless of command-line order +# (i.e. -e placed before -T still wins). +# +# Input scripts fix symbol addresses so entry points can be verified with +# literal address checks (same style as EntryCmd.test): +# two_entries.t : bar=0x1000, foo=0x2000 +# script.t : foo=0x1000, bar=0x2000 +# entry_foo.t : bar=0x1000, foo=0x2000 (SECTIONS; entry_bar.t is ENTRY only) +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/1.c -o %t1.o -ffunction-sections + +# Case 1: ENTRY(bar) then ENTRY(foo) in one script -- foo must win (last wins). +RUN: %link %linkopts %t1.o -T %p/Inputs/two_entries.t -o %t.lastwins.out +RUN: %readelf -h %t.lastwins.out | %filecheck %s --check-prefix=LASTWINS +#LASTWINS: Entry point address: 0x2000 + +# Case 2: -e bar overrides the script ENTRY(foo) -- command line always wins. +RUN: %link %linkopts %t1.o -T %p/Inputs/two_entries.t -e bar -o %t.cmdline.out +RUN: %readelf -h %t.cmdline.out | %filecheck %s --check-prefix=CMDLINE +#CMDLINE: Entry point address: 0x1000 + +# Case 3: -e foo -e bar -- bar must win (last -e wins). +RUN: %link %linkopts %t1.o -T %p/Inputs/script.t -e foo -e bar -o %t.lastcmd.out +RUN: %readelf -h %t.lastcmd.out | %filecheck %s --check-prefix=LASTCMD +#LASTCMD: Entry point address: 0x2000 + +# Case 4: -e 0 overrides script ENTRY(foo) -- entry must be 0x0. +RUN: %link %linkopts %t1.o -T %p/Inputs/script.t -e 0 -o %t.rawaddr.out +RUN: %readelf -h %t.rawaddr.out | %filecheck %s --check-prefix=RAWADDR +#RAWADDR: Entry point address: 0x0 + +# Case 5: Two separate scripts, ENTRY(bar) then ENTRY(foo) -- last script wins. +RUN: %link %linkopts %t1.o -T %p/Inputs/entry_bar.t -T %p/Inputs/entry_foo.t -o %t.crossscript.out +RUN: %readelf -h %t.crossscript.out | %filecheck %s --check-prefix=CROSSSCRIPT +#CROSSSCRIPT: Entry point address: 0x2000 + +# Case 6: -e bar placed BEFORE -T -- command line still wins over script ENTRY(foo). +RUN: %link %linkopts %t1.o -e bar -T %p/Inputs/two_entries.t -o %t.eorder.out +RUN: %readelf -h %t.eorder.out | %filecheck %s --check-prefix=EORDER +#EORDER: Entry point address: 0x1000 +#END_TEST diff --git a/test/Common/standalone/linkerscript/EntryCmd/EntryCmdWarn.test b/test/Common/standalone/linkerscript/EntryCmd/EntryCmdWarn.test new file mode 100644 index 0000000000..dd3903b83b --- /dev/null +++ b/test/Common/standalone/linkerscript/EntryCmd/EntryCmdWarn.test @@ -0,0 +1,53 @@ +#---EntryCmdWarn.test-------------------- Executable,LS ----------------------# +#BEGIN_COMMENT +# Tests that -Wlinker-script emits a warning when multiple entry points are +# specified, and that the warning is suppressed without -Wlinker-script. +# +# Warning cases: +# Case 1: Two ENTRY() in one script -- warns once with -Wlinker-script. +# Case 2: -e combined with a single script ENTRY() -- warns with -Wlinker-script. +# Case 3: Multiple -e on command line -- warns once with -Wlinker-script. +# Case 4: Cross-script two ENTRY() -- warns with -Wlinker-script. +# Case 5: Single ENTRY(), no -e -- no warning even with -Wlinker-script. +# Case 6: Two ENTRY() without -Wlinker-script -- no warning. +# Case 7: Three ENTRY() in one script -- warns once per overriding ENTRY(). +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/1.c -o %t1.o -ffunction-sections + +# Case 1: Two ENTRY() in one script -- exactly one warning. +RUN: %link %linkopts %t1.o -T %p/Inputs/two_entries.t -Wlinker-script -o %t.warn1.out 2>&1 | \ +RUN: %filecheck %s --check-prefix=WARN1 +#WARN1-COUNT-1: Warning: multiple entry points specified; the last one takes effect +#WARN1-NOT: Warning: multiple entry points specified; the last one takes effect + +# Case 2: -e combined with a single script ENTRY() -- warn. +RUN: %link %linkopts %t1.o -T %p/Inputs/script.t -e bar -Wlinker-script -o %t.warn2.out 2>&1 | \ +RUN: %filecheck %s --check-prefix=WARN +#WARN: Warning: multiple entry points specified; the last one takes effect + +# Case 3: Multiple -e on command line -- exactly one warning. +RUN: %link %linkopts %t1.o -T %p/Inputs/script.t -e foo -e bar -Wlinker-script -o %t.warn3.out 2>&1 | \ +RUN: %filecheck %s --check-prefix=WARN3 +#WARN3-COUNT-1: Warning: multiple entry points specified; the last one takes effect +#WARN3-NOT: Warning: multiple entry points specified; the last one takes effect + +# Case 4: Two ENTRY() across two scripts -- warn. +RUN: %link %linkopts %t1.o -T %p/Inputs/entry_bar.t -T %p/Inputs/entry_foo.t -Wlinker-script -o %t.warn4.out 2>&1 | \ +RUN: %filecheck %s --check-prefix=WARN + +# Case 5: Single ENTRY(), no -e -- no warning even with -Wlinker-script. +RUN: %link %linkopts %t1.o -T %p/Inputs/script.t -Wlinker-script -o %t.nowarn1.out 2>&1 | \ +RUN: %filecheck %s --allow-empty --check-prefix=NOWARN +#NOWARN-NOT: multiple entry points + +# Case 6: Two ENTRY() without -Wlinker-script -- no warning. +RUN: %link %linkopts %t1.o -T %p/Inputs/two_entries.t -o %t.nowarn2.out 2>&1 | \ +RUN: %filecheck %s --allow-empty --check-prefix=NOWARN + +# Case 7: Three ENTRY() in one script -- one warning per overriding ENTRY() (two total). +RUN: %link %linkopts %t1.o -T %p/Inputs/three_entries.t -Wlinker-script -o %t.warn7.out 2>&1 | \ +RUN: %filecheck %s --check-prefix=WARN7 +#WARN7-COUNT-2: Warning: multiple entry points specified; the last one takes effect +#WARN7-NOT: Warning: multiple entry points specified; the last one takes effect +#END_TEST diff --git a/test/Common/standalone/linkerscript/EntryCmd/Inputs/entry_bar.t b/test/Common/standalone/linkerscript/EntryCmd/Inputs/entry_bar.t new file mode 100644 index 0000000000..3d6ac6718f --- /dev/null +++ b/test/Common/standalone/linkerscript/EntryCmd/Inputs/entry_bar.t @@ -0,0 +1 @@ +ENTRY(bar) diff --git a/test/Common/standalone/linkerscript/EntryCmd/Inputs/entry_foo.t b/test/Common/standalone/linkerscript/EntryCmd/Inputs/entry_foo.t new file mode 100644 index 0000000000..8df658f989 --- /dev/null +++ b/test/Common/standalone/linkerscript/EntryCmd/Inputs/entry_foo.t @@ -0,0 +1,7 @@ +ENTRY(foo) +SECTIONS { + . = 0x1000; + .bar : { *(.text.bar) } + . = 0x2000; + .foo : { *(.text.foo) } +} diff --git a/test/Common/standalone/linkerscript/EntryCmd/Inputs/script.t b/test/Common/standalone/linkerscript/EntryCmd/Inputs/script.t index 284f2629ca..ec51bb186b 100644 --- a/test/Common/standalone/linkerscript/EntryCmd/Inputs/script.t +++ b/test/Common/standalone/linkerscript/EntryCmd/Inputs/script.t @@ -2,4 +2,6 @@ ENTRY(foo) SECTIONS { . = 0x1000; .foo : { *(.text.foo) } + . = 0x2000; + .bar : { *(.text.bar) } } diff --git a/test/Common/standalone/linkerscript/EntryCmd/Inputs/three_entries.t b/test/Common/standalone/linkerscript/EntryCmd/Inputs/three_entries.t new file mode 100644 index 0000000000..3e60cabe86 --- /dev/null +++ b/test/Common/standalone/linkerscript/EntryCmd/Inputs/three_entries.t @@ -0,0 +1,9 @@ +ENTRY(bar) +ENTRY(foo) +ENTRY(bar) +SECTIONS { + . = 0x1000; + .bar : { *(.text.bar) } + . = 0x2000; + .foo : { *(.text.foo) } +} diff --git a/test/Common/standalone/linkerscript/EntryCmd/Inputs/two_entries.t b/test/Common/standalone/linkerscript/EntryCmd/Inputs/two_entries.t new file mode 100644 index 0000000000..3b4f3aaf93 --- /dev/null +++ b/test/Common/standalone/linkerscript/EntryCmd/Inputs/two_entries.t @@ -0,0 +1,8 @@ +ENTRY(bar) +ENTRY(foo) +SECTIONS { + . = 0x1000; + .bar : { *(.text.bar) } + . = 0x2000; + .foo : { *(.text.foo) } +}