[PATCH] arm: Save/restore VFP argument registers in PLT trampolines (BZ 34144, BZ 15792)

Adhemerval Zanella adhemerval.zanella@linaro.org
Tue May 12 20:35:55 GMT 2026


_dl_runtime_resolve and _dl_runtime_profile only preserved r0-r3 across
the inner fixup call.  Recent GCC emits VFP instructions inside _dl_fixup
and _dl_profile_fixup, clobbering d0-d7 -- which under AAPCS-VFP hold the
caller's double arguments to the function being resolved.

Save d0-d7 around the call.  d8-d15 are callee-saved by the C fixup
routines themselves, and d16-d31 are caller-saved scratch never used for
argument passing, so neither needs preserving here.  Gate on
HAVE_ARM_PCS_VFP at build time: a hardfp ld.so cannot run without VFP,
and a softfp ld.so emits no VFP instruction.

Two new tests in sysdeps/arm/: tst-bz34144 exercises _dl_runtime_resolve;
tst-bz34144-audit installs a minimal audit module that defines
la_arm_gnu_pltenter to route lazy resolution through _dl_runtime_profile.
Both pass a function eight doubles via PLT lazy binding and verify they
arrive intact.

Checked on arm-linux-gnueabihf.
---
 sysdeps/arm/Makefile               | 19 ++++++++++++
 sysdeps/arm/dl-trampoline.S        | 50 ++++++++++++++++++++++++++++--
 sysdeps/arm/tst-bz34144-audit.c    | 32 +++++++++++++++++++
 sysdeps/arm/tst-bz34144-auditmod.c | 50 ++++++++++++++++++++++++++++++
 sysdeps/arm/tst-bz34144-mod.c      | 28 +++++++++++++++++
 sysdeps/arm/tst-bz34144.c          | 32 +++++++++++++++++++
 6 files changed, 209 insertions(+), 2 deletions(-)
 create mode 100644 sysdeps/arm/tst-bz34144-audit.c
 create mode 100644 sysdeps/arm/tst-bz34144-auditmod.c
 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..0bb1b6e05be 100644
--- a/sysdeps/arm/Makefile
+++ b/sysdeps/arm/Makefile
@@ -30,6 +30,25 @@ $(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 \
+  tst-bz34144-audit \
+  # tests
+modules-names += \
+  tst-bz34144-auditmod \
+  tst-bz34144-mod \
+  # modules-names
+$(objpfx)tst-bz34144: $(objpfx)tst-bz34144-mod.so
+$(objpfx)tst-bz34144-audit: $(objpfx)tst-bz34144-mod.so
+$(objpfx)tst-bz34144-audit.out: $(objpfx)tst-bz34144-auditmod.so
+# Use lazy binding to check if _dl_runtime_resolve correctly save/restore
+# the VFP state.
+LDFLAGS-tst-bz34144 = -Wl,-z,lazy
+# With LD_AUDIT, lazy resolution goes through _dl_runtime_profile, which
+# must also save/restore VFP state (BZ 34144).
+LDFLAGS-tst-bz34144-audit = -Wl,-z,lazy
+tst-bz34144-audit-ENV = LD_AUDIT=$(objpfx)tst-bz34144-auditmod.so
 endif
 endif
 
diff --git a/sysdeps/arm/dl-trampoline.S b/sysdeps/arm/dl-trampoline.S
index eb6d464384d..7433648c1ef 100644
--- a/sysdeps/arm/dl-trampoline.S
+++ b/sysdeps/arm/dl-trampoline.S
@@ -44,6 +44,16 @@ _dl_runtime_resolve:
 	cfi_rel_offset (r2, 8)
 	cfi_rel_offset (r3, 12)
 
+#if defined SHARED && defined HAVE_ARM_PCS_VFP
+	@ Save the VFP argument registers d0-d7 around the call to
+	@ _dl_fixup.  Under AAPCS-VFP these hold caller-supplied
+	@ floating-point arguments which must be forwarded unchanged
+	@ to the resolved function.
+	sub	sp, sp, #64
+	cfi_adjust_cfa_offset (64)
+	.inst	0xec8d0b10	@ vstmia sp, {d0-d7}
+#endif
+
 	@ get pointer to linker struct
 	ldr	r0, [lr, #-4]
 
@@ -59,8 +69,14 @@ _dl_runtime_resolve:
 	@ save the return
 	mov	ip, r0
 
-	@ get arguments and return address back.  We restore r4
-	@ only to realign the stack.
+#if defined SHARED && defined HAVE_ARM_PCS_VFP
+	.inst	0xec9d0b10	@ vldmia sp, {d0-d7}
+	add	sp, sp, #64
+	cfi_adjust_cfa_offset (-64)
+#endif
+
+	@ get arguments and return address back.  We restore r4 to
+	@ realign the stack.
 	pop	{r0-r4,lr}
 	cfi_adjust_cfa_offset (-24)
 
@@ -124,14 +140,44 @@ _dl_runtime_profile:
 	add	r3, sp, #8
 	stmia	r3!, {r0,r1}
 
+#ifdef HAVE_ARM_PCS_VFP
+	@ Save the VFP argument registers d0-d7 around the call to
+	@ _dl_profile_fixup.  Under AAPCS-VFP these hold caller-supplied
+	@ floating-point arguments which must be forwarded unchanged to
+	@ the resolved function.
+	@
+	@ Layout below new sp:
+	@   sp + 0 ..  3: outgoing arg (framesizep) for _dl_profile_fixup
+	@   sp + 4 ..  7: padding (for 8-byte alignment of the VFP area)
+	@   sp + 8 .. 71: d0-d7
+	sub	sp, sp, #72
+	cfi_adjust_cfa_offset (72)
+	add	ip, sp, #8
+	.inst	0xec8c0b10	@ vstmia ip, {d0-d7}
+
+	@ Set up extra args for _dl_profile_fixup.
+	@ The framesize slot is at the old sp+208, which is the new
+	@ sp + 72 + 208 = sp + 280.
+	add	ip, sp, #280
+	str	ip, [sp, #0]
+#else
 	@ Set up extra args for _dl_profile_fixup.
 	@ r2 and r3 are already loaded.
 	add	ip, sp, #208
 	str	ip, [sp, #0]
+#endif
 
 	@ call profiling fixup routine
 	bl	_dl_profile_fixup
 
+#ifdef HAVE_ARM_PCS_VFP
+	@ Restore d0-d7.  r0 holds the resolved function address.
+	add	ip, sp, #8
+	.inst	0xec9c0b10	@ vldmia ip, {d0-d7}
+	add	sp, sp, #72
+	cfi_adjust_cfa_offset (-72)
+#endif
+
 	@ The address to call is now in r0.
 
 	@ Check whether we're wrapping this function.
diff --git a/sysdeps/arm/tst-bz34144-audit.c b/sysdeps/arm/tst-bz34144-audit.c
new file mode 100644
index 00000000000..8f1084fa0af
--- /dev/null
+++ b/sysdeps/arm/tst-bz34144-audit.c
@@ -0,0 +1,32 @@
+/* Test that lazy PLT resolution via _dl_runtime_profile 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>
diff --git a/sysdeps/arm/tst-bz34144-auditmod.c b/sysdeps/arm/tst-bz34144-auditmod.c
new file mode 100644
index 00000000000..ada9f126c28
--- /dev/null
+++ b/sysdeps/arm/tst-bz34144-auditmod.c
@@ -0,0 +1,50 @@
+/* Minimal audit module used by tst-bz34144-audit to force PLT calls
+   to go through _dl_runtime_profile instead of _dl_runtime_resolve.
+   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 <link.h>
+#include <stddef.h>
+#include <stdint.h>
+
+unsigned int
+la_version (unsigned int v)
+{
+  return v;
+}
+
+unsigned int
+la_objopen (struct link_map *l, Lmid_t lmid, uintptr_t *cookie)
+{
+  return LA_FLG_BINDFROM | LA_FLG_BINDTO;
+}
+
+uintptr_t
+la_symbind32 (Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook,
+	      uintptr_t *defcook, unsigned int *flags, const char *symname)
+{
+  return sym->st_value;
+}
+
+Elf32_Addr
+la_arm_gnu_pltenter (Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook,
+		     uintptr_t *defcook, La_arm_regs *regs,
+		     unsigned int *flags, const char *symname,
+		     long int *framesizep)
+{
+  return sym->st_value;
+}
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