older ld converts push with GOTPCRELX into call - is a local guard ok?
Sunil Kumar Dora
sunilkumar.dora@windriver.com
Fri Jul 10 00:49:19 GMT 2026
On 7/10/2026 1:02 AM, Sam James wrote:
> "Dora, Sunil Kumar via Binutils" <binutils@sourceware.org> writes:
>
>> Hi,
>>
>> I was debugging rustc segfaults and traced them to GOTPCRELX handling
>> in older ld.
>>
>> Since commit 11c2852449825 (binutils 2.45), gas emits
>> R_X86_64_GOTPCRELX for "push foo@GOTPCREL(%rip)". Older ld's
>> elf_x86_64_convert_load_reloc() only checks modrm 0x25 (jmp) in the
>> 0xff case and treats everything else as call, so a push (modrm 0x35)
>> gets silently rewritten to "addr32 call foo". In our case foo is a
>> read-only LLVM table statically linked into librustc_driver.so, so
>> rustc calls into .rodata and crashes.
>>
>> Reproducer (two installs on purpose - that mismatch is the bug):
>>
>> $ cat repro.s
>> .text
>> .globl func
>> func:
>> push tbl@GOTPCREL(%rip)
>> add $8, %rsp
>> ret
>> .section .rodata
>> .globl tbl
>> .hidden tbl
>> tbl:
>> .zero 576
>>
>> $ as --version | head -1
>> GNU assembler (GNU Binutils) 2.46.1
>> $ as -o repro.o repro.s
>> $ readelf -rW repro.o | grep tbl
>> 0000000000000002 0000000300000029 R_X86_64_GOTPCRELX 0000000000000000 tbl - 4
>>
>> $ ld --version | head -1
>> GNU ld (GNU Binutils for Ubuntu) 2.38
>> $ ld -shared -o bad.so repro.o
>> $ objdump -d bad.so | grep -A1 '<func>:'
>> 0000000000001000 <func>:
>> 1000: 67 e8 fa 0f 00 00 addr32 call 2000 <tbl>
>>
>> ld 2.46.1 on the same repro.o keeps "ff 35" (push through GOT).
>> gas 2.38 emits plain GOTPCREL for this source, so only new-gas
>> objects with old ld are affected.
>>
>> Old branches are closed, I know - but some of our build hosts must
>> stay on distro binutils for a while, so we plan to carry this locally
>> (skip conversion for anything that isn't call/jmp, matching what newer
>> ld does for push in a shared library):
> Older branches are not closed. Like glibc, we keep older branches alive
> for obvious trivial fixes for as long as people are willing to maintian
> them (within reason).
>
> But in fact, not only are they not closed, H.J. already backported fixes
> for your exact problem a year ago.
>
>> [...]
>>
>> 1. Is this guard safe, or can modrm be something other than
>> 0x15/0x25/0x35 here that needs different handling?
> Use H.J.'s patch?
Thanks Sam, PR32991 is exactly this issue. I should have searched
Bugzilla before posting.
I rebuilt our local ld from the binutils-2_38-branch tip (9bee8d65d32ac),
and it fixes both the reduced test case and the original Rust build. We'll
carry the branch build instead of my local patch. Our distro ld package
was built before this backport, which is why we were still hitting it.
Thanks again,
Sunil
>
>> 2. Worth documenting this incompatibility (PR or release notes)?
>> Every ld before 2.45 silently produces bad code from such objects,
>> and tracing the crash back to the linker was not easy.
> There's already https://sourceware.org/PR32991.
>
>> Thanks,
>> Sunil Dora
> sam
More information about the Binutils
mailing list