]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
ld: Maintain the input file order
authorH.J. Lu <hjl.tools@gmail.com>
Tue, 21 Apr 2026 19:53:20 +0000 (03:53 +0800)
committerH.J. Lu <hjl.tools@gmail.com>
Wed, 22 Apr 2026 22:02:13 +0000 (06:02 +0800)
When adding a new input archive, which comes from a linker script file
and isn't referenced by any inputs, ld appends it to the input file list.
On Linux, when -lm is used with /usr/lib64/libm.a:

GROUP ( /usr/lib64/libm-2.42.a /usr/lib64/libmvec.a )

the input file order looks like

/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crt1.o
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crti.o
/usr/lib/gcc/x86_64-redhat-linux/15/crtbeginT.o
x.o (symbol from plugin)
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libm.a
/usr/lib/gcc/x86_64-redhat-linux/15/libgcc.a
/usr/lib/gcc/x86_64-redhat-linux/15/libgcc_eh.a
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libc.a
/usr/lib/gcc/x86_64-redhat-linux/15/crtend.o
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crtn.o
/usr/lib64/libm-2.42.a
/usr/lib64/libmvec.a

since the compiler may not add compiler builtin functions to the LTO
symbol table as it doesn't really know if builtin functions will have
real symbols.  When ld extracts an element from the archive later during
LTO rescan, the final input file order is

/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crt1.o
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crti.o
/usr/lib/gcc/x86_64-redhat-linux/15/crtbeginT.o
x.o (symbol from plugin)
/tmp/ccHN6O4n.ltrans0.ltrans.o
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libm.a
/usr/lib/gcc/x86_64-redhat-linux/15/libgcc.a
/usr/lib/gcc/x86_64-redhat-linux/15/libgcc_eh.a
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libc.a
/usr/lib/gcc/x86_64-redhat-linux/15/crtend.o
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crtn.o
fclrexcpt.o

where x.o references the builtin function, feclearexcept which is defined
in fclrexcpt.o from /usr/lib64/libm-2.42.a.

As the result, the .eh_frame section terminator in crtn.o is placed before
fclrexcpt.o and the .eh_frame section in the output isn't terminated.  The
output crashes when it runs over the .eh_frame section during EH frame
registration.  Insert the new input file before the current input file to
maintain the same input file order:

/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crt1.o
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crti.o
/usr/lib/gcc/x86_64-redhat-linux/15/crtbeginT.o
x.o (symbol from plugin)
/usr/lib64/libm-2.42.a
/usr/lib64/libmvec.a
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libm.a
/usr/lib/gcc/x86_64-redhat-linux/15/libgcc.a
/usr/lib/gcc/x86_64-redhat-linux/15/libgcc_eh.a
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libc.a
/usr/lib/gcc/x86_64-redhat-linux/15/crtend.o
/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crtn.o

as the non-LTO input to properly terminate the .eh_frame section.

Add a static LTO test to reference feclearexcept which is a compiler
builtin function and isn't in the LTO symbol table when GCC is used.
It triggers the run-time crash on glibc targets of a linker script
libm.a without this fix when GCC 13 or above is used:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124869

Also add a debug function, debug_input_files, to display the input file
chain.  It is optimized out when compiler optimization is turned on.

PR ld/34088
* ldlang.c (current_input_file): Changed to the pointer to
lang_input_statement_type.
(new_afile): Insert the new input file before the current input
file to maintain the input file order.
(lang_add_input_file): Updated.
(load_symbols): Likewise.
(debug_input_files): New function.
(lang_process): Reference it.
* testsuite/ld-plugin/lto.exp: Run PR ld/34088 test.
* testsuite/ld-plugin/pr34088.c: New file.

Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
ld/ldlang.c
ld/testsuite/ld-plugin/lto.exp
ld/testsuite/ld-plugin/pr34088.c [new file with mode: 0644]

index 98adc90e58a2e1ef8d78986ddb749db43e2ef6e4..d75f9df4d434f03ecbc28f6b4633e457fad6bd98 100644 (file)
@@ -135,7 +135,7 @@ lang_statement_list_type file_chain = { NULL, NULL };
    lang_input_statement_type statement (reached via input_statement field in a
    lang_statement_union).  */
 lang_statement_list_type input_file_chain;
-static const char *current_input_file;
+static lang_input_statement_type *current_input_file;
 struct bfd_elf_dynamic_list **current_dynamic_list_p;
 struct bfd_sym_chain entry_symbol = { NULL, NULL };
 const char *entry_section = ".text";
@@ -1287,7 +1287,32 @@ new_afile (const char *name,
       FAIL ();
     }
 
-  lang_statement_append (&input_file_chain, p, &p->next_real_file);
+  if (current_input_file != NULL)
+    {
+      lang_input_statement_type *f, *prev;
+
+      /* Insert the new input file before the current input file to
+        maintain the input file order.  NB: The first item on the
+        input file chain is a null one.  */
+      prev = &input_file_chain.head->input_statement;
+      for (f = prev->next_real_file;
+          f != NULL;
+          f = f->next_real_file)
+       {
+         if (f == current_input_file)
+           {
+             p->next_real_file = prev->next_real_file;
+             prev->next_real_file = p;
+             break;
+           }
+         prev = f;
+       }
+
+      if (f == NULL)
+       abort ();
+    }
+  else
+    lang_statement_append (&input_file_chain, p, &p->next_real_file);
   return p;
 }
 
@@ -1319,7 +1344,10 @@ lang_add_input_file (const char *name,
       return ret;
     }
 
-  return new_afile (name, file_type, target, current_input_file);
+  return new_afile (name, file_type, target,
+                   (current_input_file
+                    ? current_input_file->filename
+                    : NULL));
 }
 
 struct out_section_hash_entry
@@ -3225,7 +3253,7 @@ load_symbols (lang_input_statement_type *entry,
 
       ldfile_assumed_script = true;
       parser_input = input_script;
-      current_input_file = entry->filename;
+      current_input_file = entry;
       yyparse ();
       current_input_file = NULL;
       ldfile_assumed_script = false;
@@ -7894,6 +7922,20 @@ lang_set_flags (lang_memory_region_type *ptr, const char *flags, int invert)
     }
 }
 
+static void
+debug_input_files (void)
+{
+  lang_input_statement_type *f;
+
+  for (f = &input_file_chain.head->input_statement;
+       f != NULL;
+       f = f->next_real_file)
+    if (f->the_bfd)
+      fprintf (stderr, "file: %s\n", f->the_bfd->filename);
+    else
+      fprintf (stderr, "input: %s\n", f->filename);
+}
+
 /* Call a function on each real input file.  This function will be
    called on an archive, but not on the elements.  */
 
@@ -8777,7 +8819,10 @@ lang_process (void)
   lang_common ();
 
   if (0)
-    debug_prefix_tree ();
+    {
+      debug_prefix_tree ();
+      debug_input_files ();
+    }
 
   resolve_wilds ();
 
index 139bc0b9f98329b1b0e4d5cec92965c81b521589..62cca45f42b915f1c53480ba92ad8026f69866db 100644 (file)
@@ -929,6 +929,16 @@ set lto_run_elf_shared_tests [list \
    {-Wl,-R,tmpdir} {} \
    {pr31644a.c} {pr31644b.exe} {pass.out} {-flto} {c} {} \
    {-Wl,--as-needed tmpdir/pr31644b.a tmpdir/pr31644c.so}] \
+  [list {PR ld/34088} \
+   {-static -flto -fuse-linker-plugin} \
+   {} \
+   {pr34088.c} \
+   {pr34088.exe} \
+   {pass.out} \
+   {-flto -O0} \
+   {c} \
+   {} \
+   {-lm}] \
 ]
 
 # LTO run-time tests for ELF
diff --git a/ld/testsuite/ld-plugin/pr34088.c b/ld/testsuite/ld-plugin/pr34088.c
new file mode 100644 (file)
index 0000000..dfa2ca6
--- /dev/null
@@ -0,0 +1,19 @@
+/* A static LTO test to reference feclearexcept which is a compiler
+   builtin function and isn't in the LTO symbol table when GCC is used.
+   It triggers the run-time crash on glibc targets of a linker script
+   libm.a without the fix for PR ld/34088 when GCC 13 or above is used:
+
+   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124869
+
+ */
+
+#include <stdio.h>
+#include <fenv.h>
+
+int
+main (void)
+{
+  feclearexcept (FE_ALL_EXCEPT);
+  printf ("PASS\n");
+  return 0;
+}