[PATCH v2 03/14] selftests: vDSO: Introduce vdso_syscalls.h

Thomas Weißschuh posted 14 patches 2 months, 3 weeks ago
[PATCH v2 03/14] selftests: vDSO: Introduce vdso_syscalls.h
Posted by Thomas Weißschuh 2 months, 3 weeks ago
The vDSO selftests use raw system call wrapper to validate the
correctness of the vDSO implementation. The exactly available system
calls differ between architectures and kernel configurations.
Raw system calls should not use libc types as these are not necessarily
compatible.

Introduce a helper header which uses the correct types and fallbacks.

Link: https://lore.kernel.org/lkml/29dd9e11-9ae8-415a-acb3-b96af56550b0@app.fastmail.com/
Signed-off-by: Thomas Weißschuh <thomas.weissschuh@linutronix.de>
---
 tools/testing/selftests/vDSO/vdso_syscalls.h | 93 ++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/tools/testing/selftests/vDSO/vdso_syscalls.h b/tools/testing/selftests/vDSO/vdso_syscalls.h
new file mode 100644
index 0000000000000000000000000000000000000000..1419f8dd3ea831beaf582c47f6acf2ce5d5d12f8
--- /dev/null
+++ b/tools/testing/selftests/vDSO/vdso_syscalls.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Thomas Weißschuh <thomas.weissschuh@linutronix.de>, Linutronix GmbH
+ *
+ * System call wrappers to use for vDSO testing.
+ *
+ * vDSO calls are expected to return the same data as the equivalent system call.
+ * To ensure this the tests need to trigger system calls. Calling into libc may
+ * silently use the vDSO, so explicit system calls are necessary.
+ * Not all system calls are available on all platforms, so some fallback logic
+ * is needed. Use __NR_ constants from the kernel's UAPI headers over SYS_ from
+ * ones from libc to avoid any potential interference from libc.
+ * Always prefer the 64-bit time variants of the system calls as 32-bit ones
+ * may not be present on the platform or disabled in the kernel configuration.
+ */
+#ifndef __VDSO_SYSCALLS_H__
+#define __VDSO_SYSCALLS_H__
+
+#include "vdso_types.h"
+
+#include <stddef.h>
+#include <sys/syscall.h>
+#include <linux/unistd.h>
+
+#define typeof_member(T, m)	typeof(((T*)0)->m)
+#define sizeof_member(T, m)	sizeof(typeof_member(T, m))
+
+/*
+ * To keep the fallback logic simple we assume that although the types between
+ * the wrapper and the system call are different they are compatible.
+ * Validate that assumption.
+ * On x32 tv_nsec of __kernel_old_timespec is smaller than the one from
+ * __kernel_timespec. This is fine, as only the lower 4 bytes are relevant and
+ * it is a little-endian architecture.
+ */
+#define ASSERT_TIMESPEC_COMPATIBLE(T1, T2)							\
+	do {											\
+		_Static_assert(sizeof(T2) == sizeof(T2));					\
+		_Static_assert(offsetof(T1, tv_sec) == offsetof(T2, tv_sec));			\
+		_Static_assert(sizeof_member(T1, tv_sec) == sizeof_member(T2, tv_sec));		\
+		_Static_assert(offsetof(T1, tv_nsec) == offsetof(T2, tv_nsec));			\
+		_Static_assert(sizeof_member(T1, tv_nsec) == sizeof_member(T2, tv_nsec) ||	\
+			       (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && 			\
+			       sizeof_member(T1, tv_nsec) > sizeof_member(T2, tv_nsec)));	\
+	} while(0)
+
+static inline
+int sys_clock_getres(__kernel_clockid_t clock, struct __kernel_timespec *ts)
+{
+#ifdef __NR_clock_getres_time64
+	return syscall(__NR_clock_getres_time64, clock, ts);
+#else
+	ASSERT_TIMESPEC_COMPATIBLE(typeof(*ts), struct __kernel_old_timespec);
+	return syscall(__NR_clock_getres, clock, ts);
+#endif
+}
+
+static inline
+int sys_clock_gettime(__kernel_clockid_t clock, struct __kernel_timespec *ts)
+{
+#ifdef __NR_clock_gettime64
+	return syscall(__NR_clock_gettime64, clock, ts);
+#else
+	ASSERT_TIMESPEC_COMPATIBLE(typeof(*ts), struct __kernel_old_timespec);
+	return syscall(__NR_clock_gettime, clock, ts);
+#endif
+}
+
+static inline
+int sys_gettimeofday(struct __kernel_old_timeval *tv, struct kernel_timezone *tz)
+{
+#ifdef __NR_gettimeofday
+	return syscall(__NR_gettimeofday, tv, tz);
+#else
+	/* Architectures with vdso_gettimeofday() also have __NR_gettimeofday */
+	errno = ENOSYS;
+	return -1;
+#endif
+}
+
+static inline
+__kernel_old_time_t sys_time(__kernel_old_time_t *tloc)
+{
+#ifdef __NR_time
+	return syscall(__NR_time, tloc);
+#else
+	/* Architectures with vdso_time() also have __NR_time */
+	errno = ENOSYS;
+	return -1;
+#endif
+}
+
+#endif /* __VDSO_SYSCALLS_H__ */

-- 
2.51.0

Re: [PATCH v2 03/14] selftests: vDSO: Introduce vdso_syscalls.h
Posted by Arnd Bergmann 2 months, 3 weeks ago
On Thu, Nov 13, 2025, at 16:30, Thomas Weißschuh wrote:
> The vDSO selftests use raw system call wrapper to validate the
> correctness of the vDSO implementation. The exactly available system
> calls differ between architectures and kernel configurations.
> Raw system calls should not use libc types as these are not necessarily
> compatible.
>
> Introduce a helper header which uses the correct types and fallbacks.

After I looked at how these are used in patch 8, I think it's
much easier to just always use the same types as the kernel interfaces
here and skip the type mangling entirely:

> +static inline
> +int sys_clock_getres(__kernel_clockid_t clock, struct 
> __kernel_timespec *ts)
> +{
> +#ifdef __NR_clock_getres_time64
> +	return syscall(__NR_clock_getres_time64, clock, ts);
> +#else
> +	ASSERT_TIMESPEC_COMPATIBLE(typeof(*ts), struct __kernel_old_timespec);
> +	return syscall(__NR_clock_getres, clock, ts);
> +#endif
> +}

__NR_clock_getres and vdso_clock_getres() both always return a
__kernel_old_timespec, so I now think it's best to return that from
sys_clock_getres() without the __NR_clock_getres_time64 alternative
here and not worry about whether that is a 32-bit or 64-bit type,

I should have thought this through better in my comments to the
previous version.

In kernels without CONFIG_COMPAT_32BIT_TIME, we currently leave
out the clock_getres/clock_gettime/gettimeofday/time syscalls,
but still provide the vdso interfaces. For consistency we should
probably leave out both syscall and vdso in that configuration,
and then we also don't need to compare the vdso_getres result
against sys_getres_time64.

> +static inline
> +int sys_clock_gettime(__kernel_clockid_t clock, struct 
> __kernel_timespec *ts)
> +{
> +#ifdef __NR_clock_gettime64
> +	return syscall(__NR_clock_gettime64, clock, ts);
> +#else
> +	ASSERT_TIMESPEC_COMPATIBLE(typeof(*ts), struct __kernel_old_timespec);
> +	return syscall(__NR_clock_gettime, clock, ts);
> +#endif
> +}

Same here.

> +static inline
> +int sys_gettimeofday(struct __kernel_old_timeval *tv, struct 
> kernel_timezone *tz)
> +{
> +#ifdef __NR_gettimeofday
> +	return syscall(__NR_gettimeofday, tv, tz);
> +#else
> +	/* Architectures with vdso_gettimeofday() also have __NR_gettimeofday 
> */
> +	errno = ENOSYS;
> +	return -1;
> +#endif
> +}
> +
> +static inline
> +__kernel_old_time_t sys_time(__kernel_old_time_t *tloc)
> +{
> +#ifdef __NR_time
> +	return syscall(__NR_time, tloc);
> +#else
> +	/* Architectures with vdso_time() also have __NR_time */
> +	errno = ENOSYS;
> +	return -1;
> +#endif
> +}

These both look good to me.

     Arnd
Re: [PATCH v2 03/14] selftests: vDSO: Introduce vdso_syscalls.h
Posted by Thomas Weißschuh 2 months, 3 weeks ago
On Fri, Nov 14, 2025 at 09:13:02AM +0100, Arnd Bergmann wrote:
> On Thu, Nov 13, 2025, at 16:30, Thomas Weißschuh wrote:
> > The vDSO selftests use raw system call wrapper to validate the
> > correctness of the vDSO implementation. The exactly available system
> > calls differ between architectures and kernel configurations.
> > Raw system calls should not use libc types as these are not necessarily
> > compatible.
> >
> > Introduce a helper header which uses the correct types and fallbacks.
> 
> After I looked at how these are used in patch 8, I think it's
> much easier to just always use the same types as the kernel interfaces
> here and skip the type mangling entirely:

Please see below.

> > +static inline
> > +int sys_clock_getres(__kernel_clockid_t clock, struct 
> > __kernel_timespec *ts)
> > +{
> > +#ifdef __NR_clock_getres_time64
> > +	return syscall(__NR_clock_getres_time64, clock, ts);
> > +#else
> > +	ASSERT_TIMESPEC_COMPATIBLE(typeof(*ts), struct __kernel_old_timespec);
> > +	return syscall(__NR_clock_getres, clock, ts);
> > +#endif
> > +}
> 
> __NR_clock_getres and vdso_clock_getres() both always return a
> __kernel_old_timespec, so I now think it's best to return that from
> sys_clock_getres() without the __NR_clock_getres_time64 alternative
> here and not worry about whether that is a 32-bit or 64-bit type,
>
> I should have thought this through better in my comments to the
> previous version.
> 
> In kernels without CONFIG_COMPAT_32BIT_TIME, we currently leave
> out the clock_getres/clock_gettime/gettimeofday/time syscalls,
> but still provide the vdso interfaces. For consistency we should
> probably leave out both syscall and vdso in that configuration,
> and then we also don't need to compare the vdso_getres result
> against sys_getres_time64.

That sounds good. But today no vDSO provides clock_getres_time64,
so removing clock_getres from the vDSO will affect users.
So we will end up with some sort of inconsistency in any case.
While I agree that it would be nice if the type mangling was unnecessary,
I prefer to correctly test what we have today. If we decide to simplify
the vDSO itself then we have working tests.

sys_clock_gettime() should probably be called sys_clock_gettime64(),
as that is what it actually is.

FYI: gettimeoday() seems to be available even in kernels without
CONFIG_COMPAT_32BIT_TIME.

Thomas