[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