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:
parent
ba83b3239e
commit
6eda706a53
@ -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.
|
||||||
|
|
||||||
|
70
tools/testing/selftests/vDSO/vdso_call.h
Normal file
70
tools/testing/selftests/vDSO/vdso_call.h
Normal 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
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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 };
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user