[v2] ld: Maintain the input file order

Message ID CAMe9rOowYg6PXG6xhXXg_A_zc-maeP6ag7fy5ThpjQ8vo5rCCg@mail.gmail.com
State New
Headers
Series [v2] ld: Maintain the input file order |

Commit Message

H.J. Lu April 21, 2026, 9:12 p.m. UTC
  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.
  

Comments

Jan Beulich April 22, 2026, 6:17 a.m. UTC | #1
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()
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
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.

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?

Jan
  
H.J. Lu April 22, 2026, 6:54 a.m. UTC | #2
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
/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.

> 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.

> 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.
  
Jan Beulich April 22, 2026, 7:42 a.m. UTC | #3
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

? 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;
	   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.

>> 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.)

Jan
  

Patch

From f80db9ae9cedef26da49659c732622725459ca75 Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <hjl.tools@gmail.com>
Date: Wed, 22 Apr 2026 03:53:20 +0800
Subject: [PATCH v2] ld: Maintain the input file order

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 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

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 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                      | 54 +++++++++++++++++++++++++++++---
 ld/testsuite/ld-plugin/lto.exp   | 10 ++++++
 ld/testsuite/ld-plugin/pr34088.c | 10 ++++++
 3 files changed, 69 insertions(+), 5 deletions(-)
 create mode 100644 ld/testsuite/ld-plugin/pr34088.c

diff --git a/ld/ldlang.c b/ld/ldlang.c
index 98adc90e58a..ce79768e123 100644
--- a/ld/ldlang.c
+++ b/ld/ldlang.c
@@ -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,31 @@  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.  */
+      prev = NULL;
+      for (f = (void *) input_file_chain.head;
+	   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 +1343,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 +3252,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 +7921,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 = (void *) input_file_chain.head;
+       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 +8818,10 @@  lang_process (void)
   lang_common ();
 
   if (0)
-    debug_prefix_tree ();
+    {
+      debug_prefix_tree ();
+      debug_input_files ();
+    }
 
   resolve_wilds ();
 
diff --git a/ld/testsuite/ld-plugin/lto.exp b/ld/testsuite/ld-plugin/lto.exp
index 139bc0b9f98..62cca45f42b 100644
--- a/ld/testsuite/ld-plugin/lto.exp
+++ b/ld/testsuite/ld-plugin/lto.exp
@@ -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
index 00000000000..20544951e03
--- /dev/null
+++ b/ld/testsuite/ld-plugin/pr34088.c
@@ -0,0 +1,10 @@ 
+#include <stdio.h>
+#include <fenv.h>
+
+int
+main (void)
+{
+  feclearexcept(FE_ALL_EXCEPT);
+  printf ("PASS\n");
+  return 0;
+}
-- 
2.53.0