[PATCH v3] ld: Maintain the input file order
H.J. Lu
hjl.tools@gmail.com
Wed Apr 22 08:48:36 GMT 2026
On Wed, Apr 22, 2026 at 3:42 PM Jan Beulich <jbeulich@suse.com> wrote:
>
> On 22.04.2026 08:54, H.J. Lu wrote:
> > On Wed, Apr 22, 2026 at 2:17 PM Jan Beulich <jbeulich@suse.com> wrote:
> >> On 21.04.2026 23:12, H.J. Lu wrote:
> >>> On Wed, Apr 22, 2026 at 4:22 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> >>>>
> >>>> 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, 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/libmvec.a
> >>>> /usr/lib64/libm-2.42.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)
> >>>> /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 after the current input file to
> >>>> maintain the 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/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libm.a
> >>>> /usr/lib64/libmvec.a
> >>>> /usr/lib64/libm-2.42.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
> >>>>
> >>>> to properly terminate the .eh_frame section.
> >>>>
> >>>> PR ld/34088
> >>>> * ldlang.c (current_input_file): Changed to the pointer to
> >>>> lang_input_statement_type.
> >>>> (new_afile): Insert the new input file after the current input
> >>>> file to maintain the input file order.
> >>>> (lang_add_input_file): Updated.
> >>>> (load_symbols): Likewise.
> >>>> * testsuite/ld-plugin/lto.exp: Run PR ld/34088 test.
> >>>> * testsuite/ld-plugin/pr34088.c: New file.
> >>>>
> >>>> --
> >>>> H.J.
> >>>
> >>> Changes from v1:
> >>>
> >>> 1. Add debug_input_files.
> >>> 2. Insert the new input file before the current input file to keep
> >>> the order within the linker script file.
> >>
> >> Why would "before" vs "after" matter? Plus in the new code in new_afile()
> >
> > /usr/lib64/libm.a has
> >
> > GROUP ( /usr/lib64/libm-2.42.a /usr/lib64/libmvec.a )
> >
> > With before -lm, the 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)
> > /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
> >
> > which is the same as x.o isn't an LTO input. With after -lm,
> > the 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)
> > /usr/lib64/libmvec.a
> > /usr/lib64/libm-2.42.a
> > /usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libm.a
>
> That's still "before", just with the order of the two libs reversed,
> isn't it? I.e. don't you mean
>
> /usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/libm.a
> /usr/lib64/libmvec.a
> /usr/lib64/libm-2.42.a
>
Correct.
> ? But yes, I see how inserting before makes it easier to maintain the
> order as specified by the script.
>
> > /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
> >
> > "before" makes the input file order the same with and without
> > LTO input.
> >
> >> it very much looks as if prev still being NULL could be de-referenced.
> >> input_file_chain.head starts out as NULL, after all, and hence it could
> >
> > input_file_chain.head starts with a "null" item:
> >
> > (gdb) r `cat doit`
> > The program being debugged has been started already.
> > Start it from the beginning? (y or n) y
> > Starting program:
> > /export/build/gnu/tools-build/binutils-gitlab/build-x86_64-linux/ld/ld-new
> > `cat doit`
> > [Thread debugging using libthread_db enabled]
> > Using host libthread_db library "/lib64/libthread_db.so.1".
> >
> > Breakpoint 1, lang_add_input_file (name=0x0,
> > file_type=lang_input_file_is_marker_enum, target=0x0)
> > at /export/gnu/import/git/gitlab/x86-binutils/ld/ldlang.c:1323
> > 1323 if (name != NULL
> > (gdb) call debug_input_files ()
> > (gdb) c
> > Continuing.
> >
> > Breakpoint 1, lang_add_input_file (
> > name=0x7fffffffddba
> > "/usr/lib/gcc/x86_64-redhat-linux/15/../../../../lib64/crt1.o",
> > file_type=lang_input_file_is_file_enum, target=0x0)
> > at /export/gnu/import/git/gitlab/x86-binutils/ld/ldlang.c:1323
> > 1323 if (name != NULL
> > (gdb) call debug_input_files ()
> > input: (null)
> > (gdb)
> >
> > "prev" will never be NULL.
>
> Alright, but see how non-obvious this is? If the first item in the list
> is a null one, why not (keeping the bogus cast; see below) e.g.
>
> for (prev = (void *) input_file_chain.head, f = prev->next_real_file;
Fixed with
prev = &input_file_chain.head->input_statement;
for (f = prev->next_real_file;
f != NULL;
f = f->next_real_file)
> f != NULL;
> f = f->next_real_file)
>
> making entirely obvious that with a non-empty list (as guaranteed by
> current_input_file being non-NULL and needing to be on the list) prev
> won't be NULL? Plus this is reducing the loop by one iteration, albeit
> at the expense of marginally higher loop setup cost.
>
> >> very well point at current_input_file.
> >>
> >> The casting to void * there also isn't nice. Can't this be
> >> &input_file_chain.head->input_statement? Same in debug_input_files().
> >> There f also wants to be pointer-to-const.
> >
> > This is how the current input_file_chain.head is referenced in all other places.
> > I'd like to keep it this way for consistency.
>
> Well, yes, that's the one side of it. The other is that some time ago
> we started to try to limit the number of casts, because quite a few
> actually are bogus, risky, and/or UB. Imo in new code we ought to do
> better. And ideally we'd switch over existing code.
Fixed with
f = &input_file_chain.head->input_statement;
> >> As to the new testcase: Doesn't that make assumptions on how runtime
> >> libraries are structured on the target? IOW is the test passing really
> >> an indication of the issue at hand not occurring?
> >
> > It is true that such a test won't fail without libm.a being a linker script
> > if the bug isn't fixed. But it will fail on glibc targets. Its coverage
> > should be sufficient.
>
> Can this restriction at least be made explicit in the description,
> please? (Also even if libm.a is a linker script, the issue may still
> not be covered. So the wording in the description will want to be yet
> more general.)
>
I added
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
in the commit message.
Here is the v3 patch.
--
H.J.
---
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.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: v3-0001-ld-Maintain-the-input-file-order.patch
Type: text/x-patch
Size: 8301 bytes
Desc: not available
URL: <https://sourceware.org/pipermail/binutils/attachments/20260422/c536118b/attachment-0001.bin>
More information about the Binutils
mailing list