1

selftests: vDSO: fix the way vDSO functions are called for powerpc

vdso_test_correctness test fails on powerpc:

~ # ./vdso_test_correctness
...
[RUN]	Testing clock_gettime for clock CLOCK_REALTIME_ALARM (8)...
[FAIL]	No such clock, but __vdso_clock_gettime returned 22
[RUN]	Testing clock_gettime for clock CLOCK_BOOTTIME_ALARM (9)...
[FAIL]	No such clock, but __vdso_clock_gettime returned 22
[RUN]	Testing clock_gettime for clock CLOCK_SGI_CYCLE (10)...
[FAIL]	No such clock, but __vdso_clock_gettime returned 22
...
[RUN]	Testing clock_gettime for clock invalid (-1)...
[FAIL]	No such clock, but __vdso_clock_gettime returned 22
[RUN]	Testing clock_gettime for clock invalid (-2147483648)...
[FAIL]	No such clock, but __vdso_clock_gettime returned 22
[RUN]	Testing clock_gettime for clock invalid (2147483647)...
[FAIL]	No such clock, but __vdso_clock_gettime returned 22

On powerpc, a call to a VDSO function is not an ordinary C function
call. Unlike several architectures which returns a negative error code
in case of an error, powerpc sets CR[SO] and returns the error code
as a positive value.

Define and use a macro called VDSO_CALL() which takes a pointer
to the function to call, the number of arguments and the arguments.

Also update ABI vdso documentation to reflect this subtlety.

Provide a specific version of VDSO_CALL() for powerpc that negates
the error code on return when CR[SO] is set.

Fixes: c7e5789b24 ("kselftest: Move test_vdso to the vDSO test suite")
Fixes: 2e9a972566 ("selftests: vdso: Add a selftest for vDSO getcpu()")
Fixes: 693f5ca08c ("kselftest: Extend vDSO selftest")
Fixes: b2f1c3db28 ("kselftest: Extend vdso correctness test to clock_gettime64")
Fixes: 4920a2590e ("selftests/vDSO: add tests for vgetrandom")
Signed-off-by: Christophe Leroy <christophe.leroy@csgroup.eu>
Acked-by: Shuah Khan <skhan@linuxfoundation.org>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Christophe Leroy 2024-08-30 14:28:38 +02:00 committed by Jason A. Donenfeld
parent ba83b3239e
commit 6eda706a53
7 changed files with 95 additions and 18 deletions

View File

@ -9,9 +9,11 @@ maps an ELF DSO into that program's address space. This DSO is called
the vDSO and it often contains useful and highly-optimized alternatives the vDSO and it often contains useful and highly-optimized alternatives
to real syscalls. to real syscalls.
These functions are called just like ordinary C function according to These functions are called according to your platform's ABI. On many
your platform's ABI. Call them from a sensible context. (For example, platforms they are called just like ordinary C function. On other platforms
if you set CS on x86 to something strange, the vDSO functions are (ex: powerpc) they are called with the same convention as system calls which
is different from ordinary C functions. Call them from a sensible context.
(For example, if you set CS on x86 to something strange, the vDSO functions are
within their rights to crash.) In addition, if you pass a bad within their rights to crash.) In addition, if you pass a bad
pointer to a vDSO function, you might get SIGSEGV instead of -EFAULT. pointer to a vDSO function, you might get SIGSEGV instead of -EFAULT.

View File

@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Macro to call vDSO functions
*
* Copyright (C) 2024 Christophe Leroy <christophe.leroy@csgroup.eu>, CS GROUP France
*/
#ifndef __VDSO_CALL_H__
#define __VDSO_CALL_H__
#ifdef __powerpc__
#define LOADARGS_1(fn, __arg1) do { \
_r0 = fn; \
_r3 = (long)__arg1; \
} while (0)
#define LOADARGS_2(fn, __arg1, __arg2) do { \
_r0 = fn; \
_r3 = (long)__arg1; \
_r4 = (long)__arg2; \
} while (0)
#define LOADARGS_3(fn, __arg1, __arg2, __arg3) do { \
_r0 = fn; \
_r3 = (long)__arg1; \
_r4 = (long)__arg2; \
_r5 = (long)__arg3; \
} while (0)
#define LOADARGS_5(fn, __arg1, __arg2, __arg3, __arg4, __arg5) do { \
_r0 = fn; \
_r3 = (long)__arg1; \
_r4 = (long)__arg2; \
_r5 = (long)__arg3; \
_r6 = (long)__arg4; \
_r7 = (long)__arg5; \
} while (0)
#define VDSO_CALL(fn, nr, args...) ({ \
register void *_r0 asm ("r0"); \
register long _r3 asm ("r3"); \
register long _r4 asm ("r4"); \
register long _r5 asm ("r5"); \
register long _r6 asm ("r6"); \
register long _r7 asm ("r7"); \
register long _r8 asm ("r8"); \
register long _rval asm ("r3"); \
\
LOADARGS_##nr(fn, args); \
\
asm volatile( \
" mtctr %0\n" \
" bctrl\n" \
" bns+ 1f\n" \
" neg 3, 3\n" \
"1:" \
: "+r" (_r0), "=r" (_r3), "+r" (_r4), "+r" (_r5), \
"+r" (_r6), "+r" (_r7), "+r" (_r8) \
: "r" (_rval) \
: "r9", "r10", "r11", "r12", "cr0", "cr1", "cr5", \
"cr6", "cr7", "xer", "lr", "ctr", "memory" \
); \
_rval; \
})
#else
#define VDSO_CALL(fn, nr, args...) fn(args)
#endif
#endif

View File

@ -20,6 +20,7 @@
#include "../kselftest.h" #include "../kselftest.h"
#include "vdso_config.h" #include "vdso_config.h"
#include "vdso_call.h"
extern void *vdso_sym(const char *version, const char *name); extern void *vdso_sym(const char *version, const char *name);
extern void vdso_init_from_sysinfo_ehdr(uintptr_t base); extern void vdso_init_from_sysinfo_ehdr(uintptr_t base);
@ -61,7 +62,7 @@ static void vdso_test_gettimeofday(void)
} }
struct timeval tv; struct timeval tv;
long ret = vdso_gettimeofday(&tv, 0); long ret = VDSO_CALL(vdso_gettimeofday, 2, &tv, 0);
if (ret == 0) { if (ret == 0) {
ksft_print_msg("The time is %lld.%06lld\n", ksft_print_msg("The time is %lld.%06lld\n",
@ -86,7 +87,7 @@ static void vdso_test_clock_gettime(clockid_t clk_id)
} }
struct timespec ts; struct timespec ts;
long ret = vdso_clock_gettime(clk_id, &ts); long ret = VDSO_CALL(vdso_clock_gettime, 2, clk_id, &ts);
if (ret == 0) { if (ret == 0) {
ksft_print_msg("The time is %lld.%06lld\n", ksft_print_msg("The time is %lld.%06lld\n",
@ -111,7 +112,7 @@ static void vdso_test_time(void)
return; return;
} }
long ret = vdso_time(NULL); long ret = VDSO_CALL(vdso_time, 1, NULL);
if (ret > 0) { if (ret > 0) {
ksft_print_msg("The time in hours since January 1, 1970 is %lld\n", ksft_print_msg("The time in hours since January 1, 1970 is %lld\n",
@ -138,7 +139,7 @@ static void vdso_test_clock_getres(clockid_t clk_id)
} }
struct timespec ts, sys_ts; struct timespec ts, sys_ts;
long ret = vdso_clock_getres(clk_id, &ts); long ret = VDSO_CALL(vdso_clock_getres, 2, clk_id, &ts);
if (ret == 0) { if (ret == 0) {
ksft_print_msg("The vdso resolution is %lld %lld\n", ksft_print_msg("The vdso resolution is %lld %lld\n",

View File

@ -20,6 +20,7 @@
#include <limits.h> #include <limits.h>
#include "vdso_config.h" #include "vdso_config.h"
#include "vdso_call.h"
#include "../kselftest.h" #include "../kselftest.h"
static const char **name; static const char **name;
@ -186,7 +187,7 @@ static void test_getcpu(void)
ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0); ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
if (vdso_getcpu) if (vdso_getcpu)
ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0); ret_vdso = VDSO_CALL(vdso_getcpu, 3, &cpu_vdso, &node_vdso, 0);
if (vgetcpu) if (vgetcpu)
ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0); ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
@ -269,7 +270,7 @@ static void test_one_clock_gettime(int clock, const char *name)
if (sys_clock_gettime(clock, &start) < 0) { if (sys_clock_gettime(clock, &start) < 0) {
if (errno == EINVAL) { if (errno == EINVAL) {
vdso_ret = vdso_clock_gettime(clock, &vdso); vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso);
if (vdso_ret == -EINVAL) { if (vdso_ret == -EINVAL) {
printf("[OK]\tNo such clock.\n"); printf("[OK]\tNo such clock.\n");
} else { } else {
@ -282,7 +283,7 @@ static void test_one_clock_gettime(int clock, const char *name)
return; return;
} }
vdso_ret = vdso_clock_gettime(clock, &vdso); vdso_ret = VDSO_CALL(vdso_clock_gettime, 2, clock, &vdso);
end_ret = sys_clock_gettime(clock, &end); end_ret = sys_clock_gettime(clock, &end);
if (vdso_ret != 0 || end_ret != 0) { if (vdso_ret != 0 || end_ret != 0) {
@ -331,7 +332,7 @@ static void test_one_clock_gettime64(int clock, const char *name)
if (sys_clock_gettime64(clock, &start) < 0) { if (sys_clock_gettime64(clock, &start) < 0) {
if (errno == EINVAL) { if (errno == EINVAL) {
vdso_ret = vdso_clock_gettime64(clock, &vdso); vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso);
if (vdso_ret == -EINVAL) { if (vdso_ret == -EINVAL) {
printf("[OK]\tNo such clock.\n"); printf("[OK]\tNo such clock.\n");
} else { } else {
@ -344,7 +345,7 @@ static void test_one_clock_gettime64(int clock, const char *name)
return; return;
} }
vdso_ret = vdso_clock_gettime64(clock, &vdso); vdso_ret = VDSO_CALL(vdso_clock_gettime64, 2, clock, &vdso);
end_ret = sys_clock_gettime64(clock, &end); end_ret = sys_clock_gettime64(clock, &end);
if (vdso_ret != 0 || end_ret != 0) { if (vdso_ret != 0 || end_ret != 0) {
@ -401,7 +402,7 @@ static void test_gettimeofday(void)
return; return;
} }
vdso_ret = vdso_gettimeofday(&vdso, &vdso_tz); vdso_ret = VDSO_CALL(vdso_gettimeofday, 2, &vdso, &vdso_tz);
end_ret = sys_gettimeofday(&end, NULL); end_ret = sys_gettimeofday(&end, NULL);
if (vdso_ret != 0 || end_ret != 0) { if (vdso_ret != 0 || end_ret != 0) {
@ -431,7 +432,7 @@ static void test_gettimeofday(void)
} }
/* And make sure that passing NULL for tz doesn't crash. */ /* And make sure that passing NULL for tz doesn't crash. */
vdso_gettimeofday(&vdso, NULL); VDSO_CALL(vdso_gettimeofday, 2, &vdso, NULL);
} }
int main(int argc, char **argv) int main(int argc, char **argv)

View File

@ -14,6 +14,7 @@
#include "../kselftest.h" #include "../kselftest.h"
#include "parse_vdso.h" #include "parse_vdso.h"
#include "vdso_config.h" #include "vdso_config.h"
#include "vdso_call.h"
struct getcpu_cache; struct getcpu_cache;
typedef long (*getcpu_t)(unsigned int *, unsigned int *, typedef long (*getcpu_t)(unsigned int *, unsigned int *,
@ -42,7 +43,7 @@ int main(int argc, char **argv)
return KSFT_SKIP; return KSFT_SKIP;
} }
ret = get_cpu(&cpu, &node, 0); ret = VDSO_CALL(get_cpu, 3, &cpu, &node, 0);
if (ret == 0) { if (ret == 0) {
printf("Running on CPU %u node %u\n", cpu, node); printf("Running on CPU %u node %u\n", cpu, node);
} else { } else {

View File

@ -22,6 +22,7 @@
#include "../kselftest.h" #include "../kselftest.h"
#include "parse_vdso.h" #include "parse_vdso.h"
#include "vdso_config.h" #include "vdso_config.h"
#include "vdso_call.h"
#ifndef timespecsub #ifndef timespecsub
#define timespecsub(tsp, usp, vsp) \ #define timespecsub(tsp, usp, vsp) \
@ -116,7 +117,7 @@ static void vgetrandom_init(void)
printf("%s is missing!\n", name); printf("%s is missing!\n", name);
exit(KSFT_FAIL); exit(KSFT_FAIL);
} }
ret = vgrnd.fn(NULL, 0, 0, &vgrnd.params, ~0UL); ret = VDSO_CALL(vgrnd.fn, 5, NULL, 0, 0, &vgrnd.params, ~0UL);
if (ret == -ENOSYS) { if (ret == -ENOSYS) {
printf("unsupported architecture\n"); printf("unsupported architecture\n");
exit(KSFT_SKIP); exit(KSFT_SKIP);
@ -137,7 +138,7 @@ static ssize_t vgetrandom(void *buf, size_t len, unsigned long flags)
exit(KSFT_FAIL); exit(KSFT_FAIL);
} }
} }
return vgrnd.fn(buf, len, flags, state, vgrnd.params.size_of_opaque_state); return VDSO_CALL(vgrnd.fn, 5, buf, len, flags, state, vgrnd.params.size_of_opaque_state);
} }
enum { TRIALS = 25000000, THREADS = 256 }; enum { TRIALS = 25000000, THREADS = 256 };

View File

@ -19,6 +19,7 @@
#include "../kselftest.h" #include "../kselftest.h"
#include "parse_vdso.h" #include "parse_vdso.h"
#include "vdso_config.h" #include "vdso_config.h"
#include "vdso_call.h"
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
@ -43,7 +44,7 @@ int main(int argc, char **argv)
} }
struct timeval tv; struct timeval tv;
long ret = gtod(&tv, 0); long ret = VDSO_CALL(gtod, 2, &tv, 0);
if (ret == 0) { if (ret == 0) {
printf("The time is %lld.%06lld\n", printf("The time is %lld.%06lld\n",