[PATCH] arm: Prevent GCC from using VFP registers in code used during dynamic linking
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Tue May 12 13:33:07 GMT 2026
On 11/05/26 18:50, Aurelien Jarno wrote:
> On 2026-05-11 15:26, Adhemerval Zanella Netto wrote:
>>
>>
>> On 11/05/26 13:51, Aurelien Jarno wrote:
>>> On 2026-05-11 10:54, Adhemerval Zanella Netto wrote:
>>>>
>>>>
>>>> On 10/05/26 19:00, Aurelien Jarno wrote:
>>>>> Starting with GCC 15, VFP registers are used more aggressively on ARM,
>>>>> including in ld.so code involved in runtime linking. For instance d7 is
>>>>> used in _dl_lookup_symbol_, which clobbers the 8th floating point
>>>>> argument of a function on its first call.
>>>>
>>>> We added the --with-rtld-early-cflags configure option for avoid such issue,
>>>> couldn't this be used instead of specific flags to specific objects?
>>>
>>> This is a configure option, so do you mean changing the default value
>>> for arm?
>>
>> I think it should a decision on which ISA do you want to deploy glibc.
>> If you targetting armv7a system, runtime should be free to use any register
>> on dynamic loader.
>
> Yes, I am targetting a armv7a system, this is where the bug exists. I
> have opened bug libc/34144 with a small reproducer.
>
>>> Anyway is doesn't work as it misses dl-lookup.os and dl-runtime.os, but
>>> works for dl-misc.os.
>>>
>>
>> I think it would be better to add $(rtld-early-cflags) for such objects
>> as well.
>>
>> What I want to avoid is having multiple configurations on how to build
>> startup code. We already have the stack-protector, stack usage, ifunc
>> redirections to be aware.
>
> Ok, I confirm that this way works. Now I just need a way to
> automatically add -mgeneral-regs-only to rtld-early-cflags on armv7
> systems using the hard float ABI. It looks like other architectures are
> not using this mechanism to achieve that.
>
Thanks for the testcase, I missed that ARM does not have multiple
_dl_runtime_resolve. Other ABIs install different ISA-specific lazy
resolvers to handle such cases, so I think it would be better to do something
similar instead of messing with compiler flags (it might become a rabbit hole,
since we might eventually need to build a lot of objects with specific flags
and adds extra maintainability in cases of refactor).
I think it would be better to do something like 64c7e344289e ("arm: Update
_dl_tlsdesc_dynamic to preserve caller-saved registers (BZ 31372)") and
save/restore the FP registers if chip and kernel supports VFP. It adds
a small extra cost for lazy-binding, but I think bindnow is the standard
now.
Does the following patch fix the issue?
>From b1442daacd0ba33166a05cdcccdd54d93376bea0 Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue, 12 May 2026 10:19:03 -0300
Subject: [PATCH] arm: Save/restore VFP registers in _dl_runtime_resolve (BZ
34144)
The ARM _dl_runtime_resolve PLT trampoline only preserves the integer
argument registers (r0-r3) around the call to _dl_fixup. Recent GCC
emits VFP instructions inside _dl_fixup and the resolvers it invokes,
which clobbers the caller-saved VFP registers. Under AAPCS-VFP
(hardfp), d0-d7 are used to pass double arguments, so the first PLT
call to a function taking floating-point arguments sees corrupted
values.
Save and restore the VFP registers around the _dl_fixup call, gated on
hwcap (HWCAP_ARM_VFP for softfp builds, HWCAP_ARM_VFPD32 for the upper
bank d16-d31), mirroring the approach used by _dl_tlsdesc_dynamic in
commit 64c7e344289e ("arm: Update _dl_tlsdesc_dynamic to preserve
caller-saved registers (BZ 31372)"). Use .inst encodings for the VFP
load/store multiple instructions so the file still assembles in
non-VFP multilibs. The fix is wrapped in #ifdef SHARED because
_dl_runtime_resolve is only ever invoked from ld.so; the static libc
copy is dead code.
A new test, tst-bz34144, follows the reproducer from the bug
report: a shared-library function takes eight double arguments and
verifies their values, while the main program issues the first
(lazy-resolved) call.
Checked on arm-linux-gnueabihf.
---
sysdeps/arm/Makefile | 7 +++++
sysdeps/arm/dl-trampoline.S | 52 +++++++++++++++++++++++++++++++++--
sysdeps/arm/tst-bz34144-mod.c | 28 +++++++++++++++++++
sysdeps/arm/tst-bz34144.c | 32 +++++++++++++++++++++
4 files changed, 116 insertions(+), 3 deletions(-)
create mode 100644 sysdeps/arm/tst-bz34144-mod.c
create mode 100644 sysdeps/arm/tst-bz34144.c
diff --git a/sysdeps/arm/Makefile b/sysdeps/arm/Makefile
index d08dade3c5b..fa196656218 100644
--- a/sysdeps/arm/Makefile
+++ b/sysdeps/arm/Makefile
@@ -30,6 +30,13 @@ $(objpfx)tst-armtlsdescloc: $(objpfx)tst-armtlsdesclocmod.so
$(objpfx)tst-armtlsdescextnow: $(objpfx)tst-armtlsdescextnowmod.so
$(objpfx)tst-armtlsdescextlazy: $(objpfx)tst-armtlsdescextlazymod.so
endif
+
+tests += tst-bz34144
+modules-names += tst-bz34144-mod
+$(objpfx)tst-bz34144: $(objpfx)tst-bz34144-mod.so
+# Use lazy binding to check if _dl_runtime_resolve correctly save/restore
+# the VFP state.
+LDFLAGS-tst-bz34144 = -Wl,-z,lazy
endif
endif
diff --git a/sysdeps/arm/dl-trampoline.S b/sysdeps/arm/dl-trampoline.S
index eb6d464384d..8f26ab924d9 100644
--- a/sysdeps/arm/dl-trampoline.S
+++ b/sysdeps/arm/dl-trampoline.S
@@ -20,6 +20,7 @@
#define NO_THUMB
#include <sysdep.h>
#include <libc-symbols.h>
+#include <rtld-global-offsets.h>
.text
.globl _dl_runtime_resolve
@@ -36,13 +37,41 @@ _dl_runtime_resolve:
@ ip contains &GOT[n+3] (pointer to function)
@ lr points to &GOT[2]
- @ Save arguments. We save r4 to realign the stack.
+ @ Save arguments. We save r4 to realign the stack and to hold
+ @ the hwcap value used to decide whether to save VFP registers.
push {r0-r4}
cfi_adjust_cfa_offset (20)
cfi_rel_offset (r0, 0)
cfi_rel_offset (r1, 4)
cfi_rel_offset (r2, 8)
cfi_rel_offset (r3, 12)
+ cfi_rel_offset (r4, 16)
+
+#ifdef SHARED
+ @ Load the hwcap to check for vector support. Recent GCC may
+ @ emit VFP instructions inside _dl_fixup and the resolvers it
+ @ calls, so the caller-saved VFP registers must be preserved
+ @ around the call.
+ LDR_GLOBAL (r4, r3, C_SYMBOL_NAME(_rtld_global_ro), \
+ RTLD_GLOBAL_RO_DL_HWCAP_OFFSET)
+
+# ifdef __SOFTFP__
+ tst r4, #HWCAP_ARM_VFP
+ beq .Lno_vfp_save
+# endif
+
+# define VFP_STACK_REQ (32*8 + 8)
+ sub sp, sp, VFP_STACK_REQ
+ cfi_adjust_cfa_offset (VFP_STACK_REQ)
+ mov r3, sp
+ .inst 0xeca30b20 @ vstmia r3!, {d0-d15}
+ tst r4, #HWCAP_ARM_VFPD32
+ beq 1f
+ .inst 0xece30b20 @ vstmia r3!, {d16-d31}
+1: .inst 0xeef12a10 @ vmrs r2, fpscr
+ str r2, [r3]
+.Lno_vfp_save:
+#endif /* SHARED */
@ get pointer to linker struct
ldr r0, [lr, #-4]
@@ -59,8 +88,25 @@ _dl_runtime_resolve:
@ save the return
mov ip, r0
- @ get arguments and return address back. We restore r4
- @ only to realign the stack.
+#ifdef SHARED
+# ifdef __SOFTFP__
+ tst r4, #HWCAP_ARM_VFP
+ beq .Lno_vfp_restore
+# endif
+ mov r3, sp
+ .inst 0xecb30b20 @ vldmia r3!, {d0-d15}
+ tst r4, #HWCAP_ARM_VFPD32
+ beq 2f
+ .inst 0xecf30b20 @ vldmia r3!, {d16-d31}
+2: ldr r2, [r3]
+ .inst 0xeee12a10 @ vmsr fpscr, r2
+ add sp, sp, VFP_STACK_REQ
+ cfi_adjust_cfa_offset (-VFP_STACK_REQ)
+.Lno_vfp_restore:
+#endif /* SHARED */
+
+ @ get arguments and return address back. We restore r4 to
+ @ its original value as well.
pop {r0-r4,lr}
cfi_adjust_cfa_offset (-24)
diff --git a/sysdeps/arm/tst-bz34144-mod.c b/sysdeps/arm/tst-bz34144-mod.c
new file mode 100644
index 00000000000..be6b54bf91d
--- /dev/null
+++ b/sysdeps/arm/tst-bz34144-mod.c
@@ -0,0 +1,28 @@
+/* DSO used by tst-bz34144.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+
+void
+test_float_args (double a, double b, double c, double d,
+ double e, double f, double g, double h)
+{
+ if (a != 2.0 || b != 3.0 || c != 4.0 || d != 5.0
+ || e != 6.0 || f != 7.0 || g != 8.0 || h != 9.0)
+ abort ();
+}
diff --git a/sysdeps/arm/tst-bz34144.c b/sysdeps/arm/tst-bz34144.c
new file mode 100644
index 00000000000..61e41b3945c
--- /dev/null
+++ b/sysdeps/arm/tst-bz34144.c
@@ -0,0 +1,32 @@
+/* Test that lazy PLT resolution preserves caller-saved VFP registers
+ used to pass double arguments (BZ 34144).
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <support/test-driver.h>
+
+extern void test_float_args (double a, double b, double c, double d,
+ double e, double f, double g, double h);
+
+static int
+do_test (void)
+{
+ test_float_args (2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.43.0
More information about the Libc-alpha
mailing list