Currently the vDSO selftests use the time-related types from libc. This works on glibc by chance today but will break with other libc implementations or on distributions which switch to 64-bit times everywhere.
The kernel's UAPI headers provide the proper types to use with the vDSO (and raw syscalls) but are not necessarily compatible with libc types. Introduce a new header which makes the UAPI headers compatible with the libc.
Also contains some related cleanups.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- Thomas Weißschuh (10): Revert "selftests: vDSO: parse_vdso: Use UAPI headers instead of libc headers" selftests: vDSO: Introduce vdso_types.h selftests: vDSO: vdso_test_abi: Use types from vdso_types.h selftests: vDSO: vdso_test_abi: Provide compatibility with 32-bit musl selftests: vDSO: vdso_test_gettimeofday: Remove nolibc checks selftests: vDSO: vdso_test_gettimeofday: Use types from vdso_types.h selftests: vDSO: vdso_test_correctness: Drop SYS_getcpu fallbacks selftests: vDSO: vdso_test_correctness: Use types from vdso_types.h selftests: vDSO: vdso_test_correctness: Provide compatibility with 32-bit musl selftests: vDSO: vdso_test_correctness: Use facilities from parse_vdso.c
tools/testing/selftests/vDSO/Makefile | 6 +- tools/testing/selftests/vDSO/parse_vdso.c | 3 +- tools/testing/selftests/vDSO/vdso_test_abi.c | 35 ++++----- .../testing/selftests/vDSO/vdso_test_correctness.c | 85 +++++++++------------- .../selftests/vDSO/vdso_test_gettimeofday.c | 9 +-- tools/testing/selftests/vDSO/vdso_types.h | 70 ++++++++++++++++++ 6 files changed, 121 insertions(+), 87 deletions(-) --- base-commit: 8c6abf7bda867b82f8a6d60a0d5ce9cb1da6c433 change-id: 20251110-vdso-test-types-68ce0c712b79
Best regards,
This reverts commit c9fbaa879508 ("selftests: vDSO: parse_vdso: Use UAPI headers instead of libc headers")
The kernel headers were used to make parse_vdso.c compatible with nolibc. Unfortunately linux/elf.h is incompatible with glibc's sys/auxv.h. When using glibc it is therefore not possible build parse_vdso.c as part of the same compilation unit as its caller as sys/auxv.h is needed for getauxval().
In the meantime nolibc gained its own elf.h, providing compatibility with the documented libc interfaces.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/Makefile | 2 -- tools/testing/selftests/vDSO/parse_vdso.c | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index e361aca22a74dc5c279ab1aa16f308b7e6e85bf1..2de5cef311c8ab695606cf7f408c04910842e377 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -19,8 +19,6 @@ endif
include ../lib.mk
-CFLAGS += $(TOOLS_INCLUDES) - CFLAGS_NOLIBC := -nostdlib -nostdinc -ffreestanding -fno-asynchronous-unwind-tables \ -fno-stack-protector -include $(top_srcdir)/tools/include/nolibc/nolibc.h \ -I$(top_srcdir)/tools/include/nolibc/ $(KHDR_INCLUDES) diff --git a/tools/testing/selftests/vDSO/parse_vdso.c b/tools/testing/selftests/vDSO/parse_vdso.c index 3ff00fb624a44b964cc54954f1f088cabe11a901..c6ff4413ea367ae57bc6a60073314b29f938c99d 100644 --- a/tools/testing/selftests/vDSO/parse_vdso.c +++ b/tools/testing/selftests/vDSO/parse_vdso.c @@ -19,8 +19,7 @@ #include <stdint.h> #include <string.h> #include <limits.h> -#include <linux/auxvec.h> -#include <linux/elf.h> +#include <elf.h>
#include "parse_vdso.h"
Currently the vDSO selftests use the time-related types from libc. This works on glibc by chance today but will break with other libc implementations or on distributions which switch to 64-bit times everywhere.
The kernel's UAPI headers provide the proper types to use with the vDSO (and raw syscalls) but are not necessarily compatible with libc types. Introduce a new header which makes the UAPI headers compatible with the libc.
Tested with glibc, musl and nolibc.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/vdso_types.h | 70 +++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+)
diff --git a/tools/testing/selftests/vDSO/vdso_types.h b/tools/testing/selftests/vDSO/vdso_types.h new file mode 100644 index 0000000000000000000000000000000000000000..a1f2f5412e813d5ba6e57a87e28b9eacc68cccac --- /dev/null +++ b/tools/testing/selftests/vDSO/vdso_types.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Thomas Weißschuh thomas.weissschuh@linutronix.de, Linutronix GmbH + * + * Types to use with vDSO functions. + * + * The types used by the vDSO functions do not necessarily match the ones used + * by libc. The kernel's UAPI headers do provide definitions for those types + * but are often not compatible with libc which applications need to use. + * As the types differ between platforms listing them manually is a lot of work + * and error prone. Instead hack around the incompatibilities of the libc and + * UAPI headers and use the UAPI types. + */ +#ifndef __VDSO_TYPES_H__ +#define __VDSO_TYPES_H__ + +/* + * Include the headers from libc first to not override any of its types later. + */ +#include <time.h> +#include <sys/time.h> + +/* + * Avoid collisions. + */ +#define timeval kernel_timeval_moved +#define itimerspec kernel_itimerspec_moved +#define itimerval kernel_itimerval_moved +#define timezone kernel_timezone_moved + +/* + * Get the UAPI types. + */ +#include <linux/time.h> + +#undef timeval +#undef itimerspec +#undef itimerval +#undef timezone + +/* + * The UAPI headers do not provide their own 'struct __kernel_timezone'. + * 'struct timezone' is the one from libc. + */ +struct kernel_timezone { + int tz_minuteswest; + int tz_dsttime; +}; + +#include <linux/version.h> + +/* + * UAPI headers from the libc may be older and not provide these. + */ +#if KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE +typedef __kernel_long_t __kernel_old_time_t; + +struct __kernel_old_timespec { + __kernel_old_time_t tv_sec; + long tv_nsec; +}; +#endif + +typedef long (*vdso_gettimeofday_t)(struct __kernel_old_timeval *tv, struct kernel_timezone *tz); +typedef long (*vdso_clock_gettime_t)(__kernel_clockid_t clk_id, struct __kernel_old_timespec *ts); +typedef long (*vdso_clock_gettime64_t)(__kernel_clockid_t clk_id, struct __kernel_timespec *ts); +typedef long (*vdso_clock_getres_t)(__kernel_clockid_t clk_id, struct __kernel_old_timespec *ts); +typedef __kernel_time_t (*vdso_time_t)(__kernel_time_t *t); + +#endif /* __VDSO_TYPES_H__ */
On Tue, Nov 11, 2025, at 11:49, Thomas Weißschuh wrote:
+/*
- UAPI headers from the libc may be older and not provide these.
- */
+#if KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE +typedef __kernel_long_t __kernel_old_time_t;
+struct __kernel_old_timespec {
- __kernel_old_time_t tv_sec;
- long tv_nsec;
+}; +#endif
Doesn't this also need to define __kernel_old_timeval, which you refer to below?
+typedef __kernel_time_t (*vdso_time_t)(__kernel_time_t *t);
This I think needs to be __kernel_old_time_t instead of __kernel_time_t.
Arnd
On Tue, Nov 11, 2025 at 01:45:56PM +0100, Arnd Bergmann wrote:
On Tue, Nov 11, 2025, at 11:49, Thomas Weißschuh wrote:
+/*
- UAPI headers from the libc may be older and not provide these.
- */
+#if KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE +typedef __kernel_long_t __kernel_old_time_t;
+struct __kernel_old_timespec {
- __kernel_old_time_t tv_sec;
- long tv_nsec;
+}; +#endif
Doesn't this also need to define __kernel_old_timeval, which you refer to below?
It was added in 4.17, so it is present in the 4.19.88 headers my musl ships with. I can add it for completeness.
+typedef __kernel_time_t (*vdso_time_t)(__kernel_time_t *t);
This I think needs to be __kernel_old_time_t instead of __kernel_time_t.
Indeed, thanks. The only user assigns the return value to a 'long' and doesn't use the pointer argument. So for now it doesn't matter. I'll add a proper test for it.
Thomas
On Tue, Nov 11, 2025 at 02:47:34PM +0100, Thomas Weißschuh wrote:
On Tue, Nov 11, 2025 at 01:45:56PM +0100, Arnd Bergmann wrote:
On Tue, Nov 11, 2025, at 11:49, Thomas Weißschuh wrote:
+/*
- UAPI headers from the libc may be older and not provide these.
- */
+#if KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE +typedef __kernel_long_t __kernel_old_time_t;
+struct __kernel_old_timespec {
- __kernel_old_time_t tv_sec;
- long tv_nsec;
+}; +#endif
Doesn't this also need to define __kernel_old_timeval, which you refer to below?
It was added in 4.17, so it is present in the 4.19.88 headers my musl ships with. I can add it for completeness.
Given that __kernel_old_timeval has an architecture-specific override on SPARC, I'd rather leave out a custom fallback until we really need one.
(...)
Thomas
The libc types are not necessarily compatible with the vDSO functions.
Use the dedicated types from vdso_types.h instead.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/vdso_test_abi.c | 31 ++++++++-------------------- 1 file changed, 9 insertions(+), 22 deletions(-)
diff --git a/tools/testing/selftests/vDSO/vdso_test_abi.c b/tools/testing/selftests/vDSO/vdso_test_abi.c index 238d609a457a281d802734b40d6a2c35ba7f6d72..bb5a5534ae7e8a46d7e68a561684c29a752b866d 100644 --- a/tools/testing/selftests/vDSO/vdso_test_abi.c +++ b/tools/testing/selftests/vDSO/vdso_test_abi.c @@ -11,9 +11,7 @@ #include <stdint.h> #include <elf.h> #include <stdio.h> -#include <time.h> #include <sys/auxv.h> -#include <sys/time.h> #define _GNU_SOURCE #include <unistd.h> #include <sys/syscall.h> @@ -21,23 +19,12 @@ #include "../kselftest.h" #include "vdso_config.h" #include "vdso_call.h" +#include "vdso_types.h" #include "parse_vdso.h"
static const char *version; static const char **name;
-/* The same as struct __kernel_timespec */ -struct vdso_timespec64 { - uint64_t tv_sec; - uint64_t tv_nsec; -}; - -typedef long (*vdso_gettimeofday_t)(struct timeval *tv, struct timezone *tz); -typedef long (*vdso_clock_gettime_t)(clockid_t clk_id, struct timespec *ts); -typedef long (*vdso_clock_gettime64_t)(clockid_t clk_id, struct vdso_timespec64 *ts); -typedef long (*vdso_clock_getres_t)(clockid_t clk_id, struct timespec *ts); -typedef time_t (*vdso_time_t)(time_t *t); - static const char * const vdso_clock_name[] = { [CLOCK_REALTIME] = "CLOCK_REALTIME", [CLOCK_MONOTONIC] = "CLOCK_MONOTONIC", @@ -65,7 +52,7 @@ static void vdso_test_gettimeofday(void) return; }
- struct timeval tv; + struct __kernel_old_timeval tv; long ret = VDSO_CALL(vdso_gettimeofday, 2, &tv, 0);
if (ret == 0) { @@ -77,7 +64,7 @@ static void vdso_test_gettimeofday(void) } }
-static void vdso_test_clock_gettime64(clockid_t clk_id) +static void vdso_test_clock_gettime64(__kernel_clockid_t clk_id) { /* Find clock_gettime64. */ vdso_clock_gettime64_t vdso_clock_gettime64 = @@ -90,7 +77,7 @@ static void vdso_test_clock_gettime64(clockid_t clk_id) return; }
- struct vdso_timespec64 ts; + struct __kernel_timespec ts; long ret = VDSO_CALL(vdso_clock_gettime64, 2, clk_id, &ts);
if (ret == 0) { @@ -104,7 +91,7 @@ static void vdso_test_clock_gettime64(clockid_t clk_id) } }
-static void vdso_test_clock_gettime(clockid_t clk_id) +static void vdso_test_clock_gettime(__kernel_clockid_t clk_id) { /* Find clock_gettime. */ vdso_clock_gettime_t vdso_clock_gettime = @@ -117,7 +104,7 @@ static void vdso_test_clock_gettime(clockid_t clk_id) return; }
- struct timespec ts; + struct __kernel_old_timespec ts; long ret = VDSO_CALL(vdso_clock_gettime, 2, clk_id, &ts);
if (ret == 0) { @@ -154,7 +141,7 @@ static void vdso_test_time(void) } }
-static void vdso_test_clock_getres(clockid_t clk_id) +static void vdso_test_clock_getres(__kernel_clockid_t clk_id) { int clock_getres_fail = 0;
@@ -169,7 +156,7 @@ static void vdso_test_clock_getres(clockid_t clk_id) return; }
- struct timespec ts, sys_ts; + struct __kernel_old_timespec ts, sys_ts; long ret = VDSO_CALL(vdso_clock_getres, 2, clk_id, &ts);
if (ret == 0) { @@ -200,7 +187,7 @@ static void vdso_test_clock_getres(clockid_t clk_id) * This function calls vdso_test_clock_gettime and vdso_test_clock_getres * with different values for clock_id. */ -static inline void vdso_test_clock(clockid_t clock_id) +static inline void vdso_test_clock(__kernel_clockid_t clock_id) { ksft_print_msg("clock_id: %s\n", vdso_clock_name[clock_id]);
The 32-bit time variants on musl have different names, provide a fallback.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/vdso_test_abi.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/tools/testing/selftests/vDSO/vdso_test_abi.c b/tools/testing/selftests/vDSO/vdso_test_abi.c index bb5a5534ae7e8a46d7e68a561684c29a752b866d..0a6b16a21369642384d43b0efd1bca227a2a4298 100644 --- a/tools/testing/selftests/vDSO/vdso_test_abi.c +++ b/tools/testing/selftests/vDSO/vdso_test_abi.c @@ -166,7 +166,11 @@ static void vdso_test_clock_getres(__kernel_clockid_t clk_id) clock_getres_fail++; }
+#ifdef SYS_clock_getres ret = syscall(SYS_clock_getres, clk_id, &sys_ts); +#else + ret = syscall(SYS_clock_getres_time32, clk_id, &sys_ts); +#endif
ksft_print_msg("The syscall resolution is %lld %lld\n", (long long)sys_ts.tv_sec, (long long)sys_ts.tv_nsec);
On Tue, Nov 11, 2025, at 11:49, Thomas Weißschuh wrote:
The 32-bit time variants on musl have different names, provide a fallback.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de
tools/testing/selftests/vDSO/vdso_test_abi.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/tools/testing/selftests/vDSO/vdso_test_abi.c b/tools/testing/selftests/vDSO/vdso_test_abi.c index bb5a5534ae7e8a46d7e68a561684c29a752b866d..0a6b16a21369642384d43b0efd1bca227a2a4298 100644 --- a/tools/testing/selftests/vDSO/vdso_test_abi.c +++ b/tools/testing/selftests/vDSO/vdso_test_abi.c @@ -166,7 +166,11 @@ static void vdso_test_clock_getres(__kernel_clockid_t clk_id) clock_getres_fail++; }
+#ifdef SYS_clock_getres ret = syscall(SYS_clock_getres, clk_id, &sys_ts); +#else
- ret = syscall(SYS_clock_getres_time32, clk_id, &sys_ts);
+#endif
I think this #ifdef check is not reliable enough and may hide bugs. As with the other syscalls, the best way to call these is to either use __NR_clock_getres_time64 on __kernel_timespec, or to use __NR_clock_getres on __kernel_old_timespec.
If we are trying to validate the interface here, we should probably call both if available. If we just want to know the result and trust that both work correctly, I'd always use __NR_clock_getres_time64 on 32-bit systems and __NR_clock_getres on 64-bit systems.
Arnd
On Tue, Nov 11, 2025 at 01:59:02PM +0100, Arnd Bergmann wrote:
On Tue, Nov 11, 2025, at 11:49, Thomas Weißschuh wrote:
The 32-bit time variants on musl have different names, provide a fallback.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de
tools/testing/selftests/vDSO/vdso_test_abi.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/tools/testing/selftests/vDSO/vdso_test_abi.c b/tools/testing/selftests/vDSO/vdso_test_abi.c index bb5a5534ae7e8a46d7e68a561684c29a752b866d..0a6b16a21369642384d43b0efd1bca227a2a4298 100644 --- a/tools/testing/selftests/vDSO/vdso_test_abi.c +++ b/tools/testing/selftests/vDSO/vdso_test_abi.c @@ -166,7 +166,11 @@ static void vdso_test_clock_getres(__kernel_clockid_t clk_id) clock_getres_fail++; }
+#ifdef SYS_clock_getres ret = syscall(SYS_clock_getres, clk_id, &sys_ts); +#else
- ret = syscall(SYS_clock_getres_time32, clk_id, &sys_ts);
+#endif
I think this #ifdef check is not reliable enough and may hide bugs. As with the other syscalls, the best way to call these is to either use __NR_clock_getres_time64 on __kernel_timespec, or to use __NR_clock_getres on __kernel_old_timespec.
Could you give an example for such a bug?
If we are trying to validate the interface here, we should probably call both if available. If we just want to know the result and trust that both work correctly, I'd always use __NR_clock_getres_time64 on 32-bit systems and __NR_clock_getres on 64-bit systems.
As these are vDSO and not timer selftests I think we trust the syscalls. But how do we detect a native 64-bit time system? The preprocessor builtins won't work as a 32-bit pointer system may use 64-bit time syscalls. I am not aware of the UAPI #define, beyond the absence of __NR_clock_getres_time64.
Thomas
On Tue, Nov 11, 2025, at 14:55, Thomas Weißschuh wrote:
On Tue, Nov 11, 2025 at 01:59:02PM +0100, Arnd Bergmann wrote:
On Tue, Nov 11, 2025, at 11:49, Thomas Weißschuh wrote:
+#ifdef SYS_clock_getres ret = syscall(SYS_clock_getres, clk_id, &sys_ts); +#else
- ret = syscall(SYS_clock_getres_time32, clk_id, &sys_ts);
+#endif
I think this #ifdef check is not reliable enough and may hide bugs. As with the other syscalls, the best way to call these is to either use __NR_clock_getres_time64 on __kernel_timespec, or to use __NR_clock_getres on __kernel_old_timespec.
Could you give an example for such a bug?
If CONFIG_COMPAT_32BIT_TIME is disabled, 32-bit targets only provide clock_getres_time64, so using SYS_clock_getres may -ENOSYS.
Since SYS_clock_getres itself is provided by the libc implementation, I wouldn't trust that this actually means the same as __NR_clock_getres, and it might be set to __NR_clock_getres_time64.
If we are trying to validate the interface here, we should probably call both if available. If we just want to know the result and trust that both work correctly, I'd always use __NR_clock_getres_time64 on 32-bit systems and __NR_clock_getres on 64-bit systems.
As these are vDSO and not timer selftests I think we trust the syscalls. But how do we detect a native 64-bit time system? The preprocessor builtins won't work as a 32-bit pointer system may use 64-bit time syscalls. I am not aware of the UAPI #define, beyond the absence of __NR_clock_getres_time64.
I would just check __BITS_PER_LONG=32 and require a linux-5.6+ kernel at runtime to ensure the 64-bit calls are available.
Arnd
On Tue, Nov 11, 2025 at 03:19:00PM +0100, Arnd Bergmann wrote:
On Tue, Nov 11, 2025, at 14:55, Thomas Weißschuh wrote:
On Tue, Nov 11, 2025 at 01:59:02PM +0100, Arnd Bergmann wrote:
On Tue, Nov 11, 2025, at 11:49, Thomas Weißschuh wrote:
+#ifdef SYS_clock_getres ret = syscall(SYS_clock_getres, clk_id, &sys_ts); +#else
- ret = syscall(SYS_clock_getres_time32, clk_id, &sys_ts);
+#endif
I think this #ifdef check is not reliable enough and may hide bugs. As with the other syscalls, the best way to call these is to either use __NR_clock_getres_time64 on __kernel_timespec, or to use __NR_clock_getres on __kernel_old_timespec.
Could you give an example for such a bug?
If CONFIG_COMPAT_32BIT_TIME is disabled, 32-bit targets only provide clock_getres_time64, so using SYS_clock_getres may -ENOSYS.
Ok. That I am aware of. I expected something more subtle.
Since SYS_clock_getres itself is provided by the libc implementation, I wouldn't trust that this actually means the same as __NR_clock_getres, and it might be set to __NR_clock_getres_time64.
Should that case not work anyways, as libc would also need to convert the parameters transparently? But I'll switch it over to __NR instead.
If we are trying to validate the interface here, we should probably call both if available. If we just want to know the result and trust that both work correctly, I'd always use __NR_clock_getres_time64 on 32-bit systems and __NR_clock_getres on 64-bit systems.
As these are vDSO and not timer selftests I think we trust the syscalls. But how do we detect a native 64-bit time system? The preprocessor builtins won't work as a 32-bit pointer system may use 64-bit time syscalls. I am not aware of the UAPI #define, beyond the absence of __NR_clock_getres_time64.
I would just check __BITS_PER_LONG=32 and require a linux-5.6+ kernel at runtime to ensure the 64-bit calls are available.
That doesn't work for x32. It uses __BITS_PER_LONG but does not have __NR_clock_getres_time64.
Thomas
On Tue, Nov 11, 2025, at 15:46, Thomas Weißschuh wrote:
On Tue, Nov 11, 2025 at 03:19:00PM +0100, Arnd Bergmann wrote:
Since SYS_clock_getres itself is provided by the libc implementation, I wouldn't trust that this actually means the same as __NR_clock_getres, and it might be set to __NR_clock_getres_time64.
Should that case not work anyways, as libc would also need to convert the parameters transparently?
Not sure, I certainly wouldn't expect all libc implementations to do this the same way.
But I'll switch it over to __NR instead.
Ok
If we are trying to validate the interface here, we should probably call both if available. If we just want to know the result and trust that both work correctly, I'd always use __NR_clock_getres_time64 on 32-bit systems and __NR_clock_getres on 64-bit systems.
As these are vDSO and not timer selftests I think we trust the syscalls. But how do we detect a native 64-bit time system? The preprocessor builtins won't work as a 32-bit pointer system may use 64-bit time syscalls. I am not aware of the UAPI #define, beyond the absence of __NR_clock_getres_time64.
I would just check __BITS_PER_LONG=32 and require a linux-5.6+ kernel at runtime to ensure the 64-bit calls are available.
That doesn't work for x32. It uses __BITS_PER_LONG but does not have __NR_clock_getres_time64.
Right. In C code, we can usually check for 'sizeof(time_t) > sizeof(__kernel_long_t)' to catch that case, but that doesn't work as easily with the preprocessor.
A more complex setup using both compile-time and run-time fallbacks would work, e.g.
static int syscall_clock_getres_old(clockid_t clockid, struct timespec *res); #ifdef __NR_clock_getres struct __kernel_old_timespec ts_old; ret = syscall(__NR_clock_getres, clockid, &ts_old); if (ret) return ret; res->tv_sec = ts_old.sec; res->tv_nsec = ts_old.tv_nsec; return 0; #else return -ENOSYS; #endif }
static int syscall_clock_getres_time64(clockid_t clockid, struct timespec *res); #ifdef __NR_clock_getres_time64 struct __kernel_timespec ts_64; ret = syscall(__NR_clock_getres_time64, clockid, &ts_64); if (ret) return ret; res->tv_sec = ts_64.sec; res->tv_nsec = ts_64.tv_nsec; return 0; #else return -ENOSYS; #endif }
static int syscall_clock_getres(clockid_t clockid, struct timespec *res) { ret = syscall_clock_getres_time64(clockid, res); if (ret != -ENOSYS) return ret; return syscall_clock_getres_old(clockid, &ts_old); }
but the simpler check falling back to the 'old' version is probably sufficient.
Arnd
On Tue, Nov 11, 2025 at 04:07:58PM +0100, Arnd Bergmann wrote:
On Tue, Nov 11, 2025, at 15:46, Thomas Weißschuh wrote:
On Tue, Nov 11, 2025 at 03:19:00PM +0100, Arnd Bergmann wrote:
(...)
If we are trying to validate the interface here, we should probably call both if available. If we just want to know the result and trust that both work correctly, I'd always use __NR_clock_getres_time64 on 32-bit systems and __NR_clock_getres on 64-bit systems.
As these are vDSO and not timer selftests I think we trust the syscalls. But how do we detect a native 64-bit time system? The preprocessor builtins won't work as a 32-bit pointer system may use 64-bit time syscalls. I am not aware of the UAPI #define, beyond the absence of __NR_clock_getres_time64.
I would just check __BITS_PER_LONG=32 and require a linux-5.6+ kernel at runtime to ensure the 64-bit calls are available.
That doesn't work for x32. It uses __BITS_PER_LONG but does not have __NR_clock_getres_time64.
Right. In C code, we can usually check for 'sizeof(time_t) > sizeof(__kernel_long_t)' to catch that case, but that doesn't work as easily with the preprocessor.
A more complex setup using both compile-time and run-time fallbacks would work, e.g.
static int syscall_clock_getres_old(clockid_t clockid, struct timespec *res); #ifdef __NR_clock_getres struct __kernel_old_timespec ts_old; ret = syscall(__NR_clock_getres, clockid, &ts_old); if (ret) return ret; res->tv_sec = ts_old.sec; res->tv_nsec = ts_old.tv_nsec; return 0; #else return -ENOSYS; #endif }
static int syscall_clock_getres_time64(clockid_t clockid, struct timespec *res); #ifdef __NR_clock_getres_time64 struct __kernel_timespec ts_64; ret = syscall(__NR_clock_getres_time64, clockid, &ts_64); if (ret) return ret; res->tv_sec = ts_64.sec; res->tv_nsec = ts_64.tv_nsec; return 0; #else return -ENOSYS; #endif }
static int syscall_clock_getres(clockid_t clockid, struct timespec *res) { ret = syscall_clock_getres_time64(clockid, res); if (ret != -ENOSYS) return ret; return syscall_clock_getres_old(clockid, &ts_old); }
This is exactly what I tried to avoid.
but the simpler check falling back to the 'old' version is probably sufficient.
On musl there is no SYS_clock_getres_time64 but instead there is SYS_clock_getres_time32. Switching to __NR gives us back the more natural fallback logic. We are back at the nolibc 64-bit time functions. We can add a static_assert() to gain some compile-time safety.
static int syscall_clock_getres(__kernel_clockid_t clockid, struct __kernel_timespec *res) { #ifdef __NR_clock_getres_time64 return syscall(__NR_clock_getres_time64, clockid, res); #else /* * __NR_clock_getres expects __kernel_old_timespec. * Make sure the actual parameter is compatible. */ _Static_assert(sizeof(struct __kernel_old_timespec) == sizeof(*res)); return syscall(__NR_clock_getres, clockid, res); #endif }
And stick all of those wrappers into another helper header.
Thomas
nolibc now provides these headers, making the check unnecessary.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/vdso_test_gettimeofday.c | 2 -- 1 file changed, 2 deletions(-)
diff --git a/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c b/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c index 9ce795b806f0992b83cef78c7e16fac0e54750da..636a56ccf8e4e7943ca446fe3fad6897598ca77f 100644 --- a/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c +++ b/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c @@ -11,10 +11,8 @@ */
#include <stdio.h> -#ifndef NOLIBC #include <sys/auxv.h> #include <sys/time.h> -#endif
#include "../kselftest.h" #include "parse_vdso.h"
The libc types are not necessarily compatible with the vDSO functions.
Use the dedicated types from vdso_types.h instead.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/vdso_test_gettimeofday.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c b/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c index 636a56ccf8e4e7943ca446fe3fad6897598ca77f..3c14ed654428e0dbe08e9d44671e82737ac61486 100644 --- a/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c +++ b/tools/testing/selftests/vDSO/vdso_test_gettimeofday.c @@ -12,12 +12,12 @@
#include <stdio.h> #include <sys/auxv.h> -#include <sys/time.h>
#include "../kselftest.h" #include "parse_vdso.h" #include "vdso_config.h" #include "vdso_call.h" +#include "vdso_types.h"
int main(int argc, char **argv) { @@ -33,15 +33,14 @@ int main(int argc, char **argv) vdso_init_from_sysinfo_ehdr(getauxval(AT_SYSINFO_EHDR));
/* Find gettimeofday. */ - typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz); - gtod_t gtod = (gtod_t)vdso_sym(version, name[0]); + vdso_gettimeofday_t gtod = (vdso_gettimeofday_t)vdso_sym(version, name[0]);
if (!gtod) { printf("Could not find %s\n", name[0]); return KSFT_SKIP; }
- struct timeval tv; + struct __kernel_old_timeval tv; long ret = VDSO_CALL(gtod, 2, &tv, 0);
if (ret == 0) {
These fallbacks are only valid on x86 and unused in the first place.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/vdso_test_correctness.c | 8 -------- 1 file changed, 8 deletions(-)
diff --git a/tools/testing/selftests/vDSO/vdso_test_correctness.c b/tools/testing/selftests/vDSO/vdso_test_correctness.c index da651cf53c6ca4242085de109c7fc57bd807297c..66cd1d4c19872e78c0e608f5e0fb5215cf902b50 100644 --- a/tools/testing/selftests/vDSO/vdso_test_correctness.c +++ b/tools/testing/selftests/vDSO/vdso_test_correctness.c @@ -25,14 +25,6 @@
static const char **name;
-#ifndef SYS_getcpu -# ifdef __x86_64__ -# define SYS_getcpu 309 -# else -# define SYS_getcpu 318 -# endif -#endif - #ifndef __NR_clock_gettime64 #define __NR_clock_gettime64 403 #endif
The libc types are not necessarily compatible with the vDSO functions.
Use the dedicated types from vdso_types.h instead.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- .../testing/selftests/vDSO/vdso_test_correctness.c | 45 ++++++++-------------- 1 file changed, 15 insertions(+), 30 deletions(-)
diff --git a/tools/testing/selftests/vDSO/vdso_test_correctness.c b/tools/testing/selftests/vDSO/vdso_test_correctness.c index 66cd1d4c19872e78c0e608f5e0fb5215cf902b50..77bd77c32456617fc1ee240aebce57cf5b1cf89d 100644 --- a/tools/testing/selftests/vDSO/vdso_test_correctness.c +++ b/tools/testing/selftests/vDSO/vdso_test_correctness.c @@ -7,7 +7,6 @@ #define _GNU_SOURCE
#include <stdio.h> -#include <sys/time.h> #include <time.h> #include <stdlib.h> #include <unistd.h> @@ -21,6 +20,7 @@
#include "vdso_config.h" #include "vdso_call.h" +#include "vdso_types.h" #include "../kselftest.h"
static const char **name; @@ -29,29 +29,14 @@ static const char **name; #define __NR_clock_gettime64 403 #endif
-#ifndef __kernel_timespec -struct __kernel_timespec { - long long tv_sec; - long long tv_nsec; -}; -#endif - /* max length of lines in /proc/self/maps - anything longer is skipped here */ #define MAPS_LINE_LEN 128
int nerrs = 0;
-typedef int (*vgettime_t)(clockid_t, struct timespec *); - -vgettime_t vdso_clock_gettime; - -typedef int (*vgettime64_t)(clockid_t, struct __kernel_timespec *); - -vgettime64_t vdso_clock_gettime64; - -typedef long (*vgtod_t)(struct timeval *tv, struct timezone *tz); - -vgtod_t vdso_gettimeofday; +vdso_clock_gettime_t vdso_clock_gettime; +vdso_clock_gettime64_t vdso_clock_gettime64; +vdso_gettimeofday_t vdso_gettimeofday;
typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
@@ -124,17 +109,17 @@ static void fill_function_pointers(void)
vgetcpu = (getcpu_t) vsyscall_getcpu();
- vdso_clock_gettime = (vgettime_t)dlsym(vdso, name[1]); + vdso_clock_gettime = (vdso_clock_gettime_t)dlsym(vdso, name[1]); if (!vdso_clock_gettime) printf("Warning: failed to find clock_gettime in vDSO\n");
#if defined(VDSO_32BIT) - vdso_clock_gettime64 = (vgettime64_t)dlsym(vdso, name[5]); + vdso_clock_gettime64 = (vdso_clock_gettime64_t)dlsym(vdso, name[5]); if (!vdso_clock_gettime64) printf("Warning: failed to find clock_gettime64 in vDSO\n"); #endif
- vdso_gettimeofday = (vgtod_t)dlsym(vdso, name[0]); + vdso_gettimeofday = (vdso_gettimeofday_t)dlsym(vdso, name[0]); if (!vdso_gettimeofday) printf("Warning: failed to find gettimeofday in vDSO\n");
@@ -146,17 +131,17 @@ static long sys_getcpu(unsigned * cpu, unsigned * node, return syscall(__NR_getcpu, cpu, node, cache); }
-static inline int sys_clock_gettime(clockid_t id, struct timespec *ts) +static inline int sys_clock_gettime(__kernel_clockid_t id, struct __kernel_old_timespec *ts) { return syscall(__NR_clock_gettime, id, ts); }
-static inline int sys_clock_gettime64(clockid_t id, struct __kernel_timespec *ts) +static inline int sys_clock_gettime64(__kernel_clockid_t id, struct __kernel_timespec *ts) { return syscall(__NR_clock_gettime64, id, ts); }
-static inline int sys_gettimeofday(struct timeval *tv, struct timezone *tz) +static inline int sys_gettimeofday(struct __kernel_old_timeval *tv, struct kernel_timezone *tz) { return syscall(__NR_gettimeofday, tv, tz); } @@ -213,7 +198,7 @@ static void test_getcpu(void) } }
-static bool ts_leq(const struct timespec *a, const struct timespec *b) +static bool ts_leq(const struct __kernel_old_timespec *a, const struct __kernel_old_timespec *b) { if (a->tv_sec != b->tv_sec) return a->tv_sec < b->tv_sec; @@ -230,7 +215,7 @@ static bool ts64_leq(const struct __kernel_timespec *a, return a->tv_nsec <= b->tv_nsec; }
-static bool tv_leq(const struct timeval *a, const struct timeval *b) +static bool tv_leq(const struct __kernel_old_timeval *a, const struct __kernel_old_timeval *b) { if (a->tv_sec != b->tv_sec) return a->tv_sec < b->tv_sec; @@ -255,7 +240,7 @@ static char const * const clocknames[] = {
static void test_one_clock_gettime(int clock, const char *name) { - struct timespec start, vdso, end; + struct __kernel_old_timespec start, vdso, end; int vdso_ret, end_ret;
printf("[RUN]\tTesting clock_gettime for clock %s (%d)...\n", name, clock); @@ -379,8 +364,8 @@ static void test_clock_gettime64(void)
static void test_gettimeofday(void) { - struct timeval start, vdso, end; - struct timezone sys_tz, vdso_tz; + struct __kernel_old_timeval start, vdso, end; + struct kernel_timezone sys_tz, vdso_tz; int vdso_ret, end_ret;
if (!vdso_gettimeofday)
The 32-bit time variants on musl have different names, provide fallbacks.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/vdso_test_correctness.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/tools/testing/selftests/vDSO/vdso_test_correctness.c b/tools/testing/selftests/vDSO/vdso_test_correctness.c index 77bd77c32456617fc1ee240aebce57cf5b1cf89d..b27c28cd00a2b974751dffae850f34517ada094d 100644 --- a/tools/testing/selftests/vDSO/vdso_test_correctness.c +++ b/tools/testing/selftests/vDSO/vdso_test_correctness.c @@ -133,7 +133,11 @@ static long sys_getcpu(unsigned * cpu, unsigned * node,
static inline int sys_clock_gettime(__kernel_clockid_t id, struct __kernel_old_timespec *ts) { +#ifdef __NR_clock_gettime return syscall(__NR_clock_gettime, id, ts); +#else + return syscall(__NR_clock_gettime32, id, ts); +#endif }
static inline int sys_clock_gettime64(__kernel_clockid_t id, struct __kernel_timespec *ts) @@ -143,7 +147,11 @@ static inline int sys_clock_gettime64(__kernel_clockid_t id, struct __kernel_tim
static inline int sys_gettimeofday(struct __kernel_old_timeval *tv, struct kernel_timezone *tz) { +#ifdef __NR_gettimeofday return syscall(__NR_gettimeofday, tv, tz); +#else + return syscall(__NR_gettimeofday_time32, tv, tz); +#endif }
static void test_getcpu(void)
The soname from the vDSO is not a public API. Furthermore it requires libc to implement dlsym() and friends.
Use the facilities from parse_vdso.c instead which uses the official vDSO ABI to find it, aligned with the other vDSO selftests.
Signed-off-by: Thomas Weißschuh thomas.weissschuh@linutronix.de --- tools/testing/selftests/vDSO/Makefile | 4 +-- .../testing/selftests/vDSO/vdso_test_correctness.c | 30 ++++++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-)
diff --git a/tools/testing/selftests/vDSO/Makefile b/tools/testing/selftests/vDSO/Makefile index 2de5cef311c8ab695606cf7f408c04910842e377..a61047bdcd576d47d944f442f9e7140fccfe58e0 100644 --- a/tools/testing/selftests/vDSO/Makefile +++ b/tools/testing/selftests/vDSO/Makefile @@ -26,13 +26,11 @@ CFLAGS_NOLIBC := -nostdlib -nostdinc -ffreestanding -fno-asynchronous-unwind-tab $(OUTPUT)/vdso_test_gettimeofday: parse_vdso.c vdso_test_gettimeofday.c $(OUTPUT)/vdso_test_getcpu: parse_vdso.c vdso_test_getcpu.c $(OUTPUT)/vdso_test_abi: parse_vdso.c vdso_test_abi.c +$(OUTPUT)/vdso_test_correctness: parse_vdso.c vdso_test_correctness.c
$(OUTPUT)/vdso_standalone_test_x86: vdso_standalone_test_x86.c parse_vdso.c | headers $(OUTPUT)/vdso_standalone_test_x86: CFLAGS:=$(CFLAGS_NOLIBC) $(CFLAGS)
-$(OUTPUT)/vdso_test_correctness: vdso_test_correctness.c -$(OUTPUT)/vdso_test_correctness: LDFLAGS += -ldl - $(OUTPUT)/vdso_test_getrandom: parse_vdso.c $(OUTPUT)/vdso_test_getrandom: CFLAGS += -isystem $(top_srcdir)/tools/include \ $(KHDR_INCLUDES) \ diff --git a/tools/testing/selftests/vDSO/vdso_test_correctness.c b/tools/testing/selftests/vDSO/vdso_test_correctness.c index b27c28cd00a2b974751dffae850f34517ada094d..bda090b8f74632325ba0f15898dbc45656a73090 100644 --- a/tools/testing/selftests/vDSO/vdso_test_correctness.c +++ b/tools/testing/selftests/vDSO/vdso_test_correctness.c @@ -10,19 +10,21 @@ #include <time.h> #include <stdlib.h> #include <unistd.h> +#include <sys/auxv.h> #include <sys/syscall.h> -#include <dlfcn.h> #include <string.h> #include <errno.h> #include <sched.h> #include <stdbool.h> #include <limits.h>
+#include "parse_vdso.h" #include "vdso_config.h" #include "vdso_call.h" #include "vdso_types.h" #include "../kselftest.h"
+static const char *version; static const char **name;
#ifndef __NR_clock_gettime64 @@ -87,39 +89,32 @@ static void *vsyscall_getcpu(void)
static void fill_function_pointers(void) { - void *vdso = dlopen("linux-vdso.so.1", - RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); - if (!vdso) - vdso = dlopen("linux-gate.so.1", - RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); - if (!vdso) - vdso = dlopen("linux-vdso32.so.1", - RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); - if (!vdso) - vdso = dlopen("linux-vdso64.so.1", - RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); - if (!vdso) { + unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR); + + if (!sysinfo_ehdr) { printf("[WARN]\tfailed to find vDSO\n"); return; }
- vdso_getcpu = (getcpu_t)dlsym(vdso, name[4]); + vdso_init_from_sysinfo_ehdr(sysinfo_ehdr); + + vdso_getcpu = (getcpu_t)vdso_sym(version, name[4]); if (!vdso_getcpu) printf("Warning: failed to find getcpu in vDSO\n");
vgetcpu = (getcpu_t) vsyscall_getcpu();
- vdso_clock_gettime = (vdso_clock_gettime_t)dlsym(vdso, name[1]); + vdso_clock_gettime = (vdso_clock_gettime_t)vdso_sym(version, name[1]); if (!vdso_clock_gettime) printf("Warning: failed to find clock_gettime in vDSO\n");
#if defined(VDSO_32BIT) - vdso_clock_gettime64 = (vdso_clock_gettime64_t)dlsym(vdso, name[5]); + vdso_clock_gettime64 = (vdso_clock_gettime64_t)vdso_sym(version, name[5]); if (!vdso_clock_gettime64) printf("Warning: failed to find clock_gettime64 in vDSO\n"); #endif
- vdso_gettimeofday = (vdso_gettimeofday_t)dlsym(vdso, name[0]); + vdso_gettimeofday = (vdso_gettimeofday_t)vdso_sym(version, name[0]); if (!vdso_gettimeofday) printf("Warning: failed to find gettimeofday in vDSO\n");
@@ -422,6 +417,7 @@ static void test_gettimeofday(void)
int main(int argc, char **argv) { + version = versions[VDSO_VERSION]; name = (const char **)&names[VDSO_NAMES];
fill_function_pointers();
linux-kselftest-mirror@lists.linaro.org