Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,4 @@
abi_test
dyn_test
ftrace_test
perf_test
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS += -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
LDLIBS += -lrt -lpthread -lm
TEST_GEN_PROGS = ftrace_test dyn_test perf_test abi_test
TEST_FILES := settings
include ../lib.mk
@@ -0,0 +1,450 @@
// SPDX-License-Identifier: GPL-2.0
/*
* User Events ABI Test Program
*
* Copyright (c) 2022 Beau Belgrave <beaub@linux.microsoft.com>
*/
#define _GNU_SOURCE
#include <sched.h>
#include <errno.h>
#include <linux/user_events.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glob.h>
#include <string.h>
#include <asm/unistd.h>
#include "kselftest_harness.h"
#include "user_events_selftests.h"
const char *data_file = "/sys/kernel/tracing/user_events_data";
const char *enable_file = "/sys/kernel/tracing/events/user_events/__abi_event/enable";
const char *multi_dir_glob = "/sys/kernel/tracing/events/user_events_multi/__abi_event.*";
static int wait_for_delete(char *dir)
{
struct stat buf;
int i;
for (i = 0; i < 10000; ++i) {
if (stat(dir, &buf) == -1 && errno == ENOENT)
return 0;
usleep(1000);
}
return -1;
}
static int find_multi_event_dir(char *unique_field, char *out_dir, int dir_len)
{
char path[256];
glob_t buf;
int i, ret;
ret = glob(multi_dir_glob, GLOB_ONLYDIR, NULL, &buf);
if (ret)
return -1;
ret = -1;
for (i = 0; i < buf.gl_pathc; ++i) {
FILE *fp;
snprintf(path, sizeof(path), "%s/format", buf.gl_pathv[i]);
fp = fopen(path, "r");
if (!fp)
continue;
while (fgets(path, sizeof(path), fp) != NULL) {
if (strstr(path, unique_field)) {
fclose(fp);
/* strscpy is not available, use snprintf */
snprintf(out_dir, dir_len, "%s", buf.gl_pathv[i]);
ret = 0;
goto out;
}
}
fclose(fp);
}
out:
globfree(&buf);
return ret;
}
static bool event_exists(void)
{
int fd = open(enable_file, O_RDWR);
if (fd < 0)
return false;
close(fd);
return true;
}
static int change_event(bool enable)
{
int fd = open(enable_file, O_RDWR);
int ret;
if (fd < 0)
return -1;
if (enable)
ret = write(fd, "1", 1);
else
ret = write(fd, "0", 1);
close(fd);
if (ret == 1)
ret = 0;
else
ret = -1;
return ret;
}
static int event_delete(void)
{
int fd = open(data_file, O_RDWR);
int ret;
if (fd < 0)
return -1;
ret = ioctl(fd, DIAG_IOCSDEL, "__abi_event");
close(fd);
return ret;
}
/*
* Deleting an event drops its last reference, but an unregister may defer
* that put (and the freeing of the associated enabler) past an RCU grace
* period. The delete can therefore transiently fail with -EBUSY while the
* previous reference is still being dropped. Retry only on that transient
* failure; treat an already-deleted event (-ENOENT) as success and return
* any other error immediately rather than spinning for the full timeout.
*/
static int wait_for_event_delete(void)
{
int i, ret;
for (i = 0; i < 10000; ++i) {
ret = event_delete();
if (ret == 0 || errno == ENOENT)
return 0;
if (errno != EBUSY)
return ret;
usleep(1000);
}
return ret;
}
static int reg_enable_multi(void *enable, int size, int bit, int flags,
char *args)
{
struct user_reg reg = {0};
char full_args[512] = {0};
int fd = open(data_file, O_RDWR);
int len;
int ret;
if (fd < 0)
return -1;
len = snprintf(full_args, sizeof(full_args), "__abi_event %s", args);
if (len > sizeof(full_args)) {
ret = -E2BIG;
goto out;
}
reg.size = sizeof(reg);
reg.name_args = (__u64)full_args;
reg.flags = USER_EVENT_REG_MULTI_FORMAT | flags;
reg.enable_bit = bit;
reg.enable_addr = (__u64)enable;
reg.enable_size = size;
ret = ioctl(fd, DIAG_IOCSREG, &reg);
out:
close(fd);
return ret;
}
static int reg_enable_flags(void *enable, int size, int bit, int flags)
{
struct user_reg reg = {0};
int fd = open(data_file, O_RDWR);
int ret;
if (fd < 0)
return -1;
reg.size = sizeof(reg);
reg.name_args = (__u64)"__abi_event";
reg.flags = flags;
reg.enable_bit = bit;
reg.enable_addr = (__u64)enable;
reg.enable_size = size;
ret = ioctl(fd, DIAG_IOCSREG, &reg);
close(fd);
return ret;
}
static int reg_enable(void *enable, int size, int bit)
{
return reg_enable_flags(enable, size, bit, 0);
}
static int reg_disable(void *enable, int bit)
{
struct user_unreg reg = {0};
int fd = open(data_file, O_RDWR);
int ret;
if (fd < 0)
return -1;
reg.size = sizeof(reg);
reg.disable_bit = bit;
reg.disable_addr = (__u64)enable;
ret = ioctl(fd, DIAG_IOCSUNREG, &reg);
close(fd);
return ret;
}
FIXTURE(user) {
int check;
long check_long;
bool umount;
};
FIXTURE_SETUP(user) {
USER_EVENT_FIXTURE_SETUP(return, self->umount);
change_event(false);
self->check = 0;
self->check_long = 0;
}
FIXTURE_TEARDOWN(user) {
USER_EVENT_FIXTURE_TEARDOWN(self->umount);
}
TEST_F(user, enablement) {
/* Changes should be reflected immediately */
ASSERT_EQ(0, self->check);
ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0));
ASSERT_EQ(0, change_event(true));
ASSERT_EQ(1, self->check);
ASSERT_EQ(0, change_event(false));
ASSERT_EQ(0, self->check);
/* Ensure kernel clears bit after disable */
ASSERT_EQ(0, change_event(true));
ASSERT_EQ(1, self->check);
ASSERT_EQ(0, reg_disable(&self->check, 0));
ASSERT_EQ(0, self->check);
/* Ensure doesn't change after unreg */
ASSERT_EQ(0, change_event(true));
ASSERT_EQ(0, self->check);
ASSERT_EQ(0, change_event(false));
}
TEST_F(user, flags) {
/* USER_EVENT_REG_PERSIST is allowed */
ASSERT_EQ(0, reg_enable_flags(&self->check, sizeof(int), 0,
USER_EVENT_REG_PERSIST));
ASSERT_EQ(0, reg_disable(&self->check, 0));
/* Ensure it exists after close and disable */
ASSERT_TRUE(event_exists());
/* Ensure we can delete it */
ASSERT_EQ(0, wait_for_event_delete());
/* USER_EVENT_REG_MAX or above is not allowed */
ASSERT_EQ(-1, reg_enable_flags(&self->check, sizeof(int), 0,
USER_EVENT_REG_MAX));
/* Ensure it does not exist after invalid flags */
ASSERT_FALSE(event_exists());
}
TEST_F(user, bit_sizes) {
/* Allow 0-31 bits for 32-bit */
ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0));
ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 31));
ASSERT_NE(0, reg_enable(&self->check, sizeof(int), 32));
ASSERT_EQ(0, reg_disable(&self->check, 0));
ASSERT_EQ(0, reg_disable(&self->check, 31));
#if BITS_PER_LONG == 8
/* Allow 0-64 bits for 64-bit */
ASSERT_EQ(0, reg_enable(&self->check_long, sizeof(long), 63));
ASSERT_NE(0, reg_enable(&self->check_long, sizeof(long), 64));
ASSERT_EQ(0, reg_disable(&self->check_long, 63));
#endif
/* Disallowed sizes (everything beside 4 and 8) */
ASSERT_NE(0, reg_enable(&self->check, 1, 0));
ASSERT_NE(0, reg_enable(&self->check, 2, 0));
ASSERT_NE(0, reg_enable(&self->check, 3, 0));
ASSERT_NE(0, reg_enable(&self->check, 5, 0));
ASSERT_NE(0, reg_enable(&self->check, 6, 0));
ASSERT_NE(0, reg_enable(&self->check, 7, 0));
ASSERT_NE(0, reg_enable(&self->check, 9, 0));
ASSERT_NE(0, reg_enable(&self->check, 128, 0));
}
TEST_F(user, multi_format) {
char first_dir[256];
char second_dir[256];
struct stat buf;
/* Multiple formats for the same name should work */
ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 0,
0, "u32 multi_first"));
ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 1,
0, "u64 multi_second"));
/* Same name with same format should also work */
ASSERT_EQ(0, reg_enable_multi(&self->check, sizeof(int), 2,
0, "u64 multi_second"));
ASSERT_EQ(0, find_multi_event_dir("multi_first",
first_dir, sizeof(first_dir)));
ASSERT_EQ(0, find_multi_event_dir("multi_second",
second_dir, sizeof(second_dir)));
/* Should not be found in the same dir */
ASSERT_NE(0, strcmp(first_dir, second_dir));
/* First dir should still exist */
ASSERT_EQ(0, stat(first_dir, &buf));
/* Disabling first register should remove first dir */
ASSERT_EQ(0, reg_disable(&self->check, 0));
ASSERT_EQ(0, wait_for_delete(first_dir));
/* Second dir should still exist */
ASSERT_EQ(0, stat(second_dir, &buf));
/* Disabling second register should remove second dir */
ASSERT_EQ(0, reg_disable(&self->check, 1));
/* Ensure bit 1 and 2 are tied together, should not delete yet */
ASSERT_EQ(0, stat(second_dir, &buf));
ASSERT_EQ(0, reg_disable(&self->check, 2));
ASSERT_EQ(0, wait_for_delete(second_dir));
}
TEST_F(user, forks) {
int i;
/* Ensure COW pages get updated after fork */
ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0));
ASSERT_EQ(0, self->check);
if (fork() == 0) {
/* Force COW */
self->check = 0;
/* Up to 1 sec for enablement */
for (i = 0; i < 10; ++i) {
usleep(100000);
if (self->check)
exit(0);
}
exit(1);
}
/* Allow generous time for COW, then enable */
usleep(100000);
ASSERT_EQ(0, change_event(true));
ASSERT_NE(-1, wait(&i));
ASSERT_EQ(0, WEXITSTATUS(i));
/* Ensure child doesn't disable parent */
if (fork() == 0)
exit(reg_disable(&self->check, 0));
ASSERT_NE(-1, wait(&i));
ASSERT_EQ(0, WEXITSTATUS(i));
ASSERT_EQ(1, self->check);
ASSERT_EQ(0, change_event(false));
ASSERT_EQ(0, self->check);
}
/* Waits up to 1 sec for enablement */
static int clone_check(void *check)
{
int i;
for (i = 0; i < 10; ++i) {
usleep(100000);
if (*(int *)check)
return 0;
}
return 1;
}
TEST_F(user, clones) {
int i, stack_size = 4096;
void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK,
-1, 0);
ASSERT_NE(MAP_FAILED, stack);
ASSERT_EQ(0, reg_enable(&self->check, sizeof(int), 0));
ASSERT_EQ(0, self->check);
/* Shared VM should see enablements */
ASSERT_NE(-1, clone(&clone_check, stack + stack_size,
CLONE_VM | SIGCHLD, &self->check));
ASSERT_EQ(0, change_event(true));
ASSERT_NE(-1, wait(&i));
ASSERT_EQ(0, WEXITSTATUS(i));
munmap(stack, stack_size);
ASSERT_EQ(0, change_event(false));
}
int main(int argc, char **argv)
{
return test_harness_run(argc, argv);
}
@@ -0,0 +1 @@
CONFIG_USER_EVENTS=y
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0
/*
* User Events Dyn Events Test Program
*
* Copyright (c) 2021 Beau Belgrave <beaub@linux.microsoft.com>
*/
#include <errno.h>
#include <linux/user_events.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "kselftest_harness.h"
#include "user_events_selftests.h"
const char *dyn_file = "/sys/kernel/tracing/dynamic_events";
const char *abi_file = "/sys/kernel/tracing/user_events_data";
const char *enable_file = "/sys/kernel/tracing/events/user_events/__test_event/enable";
static int event_delete(void)
{
int fd = open(abi_file, O_RDWR);
int ret;
if (fd < 0)
return -1;
ret = ioctl(fd, DIAG_IOCSDEL, "__test_event");
close(fd);
return ret;
}
static bool wait_for_delete(void)
{
int i;
for (i = 0; i < 1000; ++i) {
int fd = open(enable_file, O_RDONLY);
if (fd == -1)
return true;
close(fd);
usleep(1000);
}
return false;
}
static int reg_event(int fd, int *check, int bit, const char *value)
{
struct user_reg reg = {0};
reg.size = sizeof(reg);
reg.name_args = (__u64)value;
reg.enable_bit = bit;
reg.enable_addr = (__u64)check;
reg.enable_size = sizeof(*check);
if (ioctl(fd, DIAG_IOCSREG, &reg) == -1)
return -1;
return 0;
}
static int unreg_event(int fd, int *check, int bit)
{
struct user_unreg unreg = {0};
unreg.size = sizeof(unreg);
unreg.disable_bit = bit;
unreg.disable_addr = (__u64)check;
return ioctl(fd, DIAG_IOCSUNREG, &unreg);
}
static int parse_dyn(const char *value)
{
int fd = open(dyn_file, O_RDWR | O_APPEND);
int len = strlen(value);
int ret;
if (fd == -1)
return -1;
ret = write(fd, value, len);
if (ret == len)
ret = 0;
else
ret = -1;
close(fd);
if (ret == 0)
event_delete();
return ret;
}
static int parse_abi(int *check, const char *value)
{
int fd = open(abi_file, O_RDWR);
int ret;
if (fd == -1)
return -1;
/* Until we have persist flags via dynamic events, use the base name */
if (value[0] != 'u' || value[1] != ':') {
close(fd);
return -1;
}
ret = reg_event(fd, check, 31, value + 2);
if (ret != -1) {
if (unreg_event(fd, check, 31) == -1)
printf("WARN: Couldn't unreg event\n");
}
close(fd);
wait_for_delete();
return ret;
}
static int parse(int *check, const char *value)
{
int abi_ret = parse_abi(check, value);
int dyn_ret = parse_dyn(value);
/* Ensure both ABI and DYN parse the same way */
if (dyn_ret != abi_ret)
return -1;
return dyn_ret;
}
static int check_match(int *check, const char *first, const char *second, bool *match)
{
int fd = open(abi_file, O_RDWR);
int ret = -1;
if (fd == -1)
return -1;
if (reg_event(fd, check, 31, first) == -1)
goto cleanup;
if (reg_event(fd, check, 30, second) == -1) {
if (errno == EADDRINUSE) {
/* Name is in use, with different fields */
*match = false;
ret = 0;
}
goto cleanup;
}
*match = true;
ret = 0;
cleanup:
unreg_event(fd, check, 31);
unreg_event(fd, check, 30);
close(fd);
wait_for_delete();
return ret;
}
#define TEST_MATCH(x, y) \
do { \
bool match; \
ASSERT_NE(-1, check_match(&self->check, x, y, &match)); \
ASSERT_EQ(true, match); \
} while (0)
#define TEST_NMATCH(x, y) \
do { \
bool match; \
ASSERT_NE(-1, check_match(&self->check, x, y, &match)); \
ASSERT_EQ(false, match); \
} while (0)
#define TEST_PARSE(x) ASSERT_NE(-1, parse(&self->check, x))
#define TEST_NPARSE(x) ASSERT_EQ(-1, parse(&self->check, x))
FIXTURE(user) {
int check;
bool umount;
};
FIXTURE_SETUP(user) {
USER_EVENT_FIXTURE_SETUP(return, self->umount);
}
FIXTURE_TEARDOWN(user) {
USER_EVENT_FIXTURE_TEARDOWN(self->umount);
wait_for_delete();
}
TEST_F(user, basic_types) {
/* All should work */
TEST_PARSE("u:__test_event u64 a");
TEST_PARSE("u:__test_event u32 a");
TEST_PARSE("u:__test_event u16 a");
TEST_PARSE("u:__test_event u8 a");
TEST_PARSE("u:__test_event char a");
TEST_PARSE("u:__test_event unsigned char a");
TEST_PARSE("u:__test_event int a");
TEST_PARSE("u:__test_event unsigned int a");
TEST_PARSE("u:__test_event short a");
TEST_PARSE("u:__test_event unsigned short a");
TEST_PARSE("u:__test_event char[20] a");
TEST_PARSE("u:__test_event unsigned char[20] a");
TEST_PARSE("u:__test_event char[0x14] a");
TEST_PARSE("u:__test_event unsigned char[0x14] a");
/* Bad size format should fail */
TEST_NPARSE("u:__test_event char[aa] a");
/* Large size should fail */
TEST_NPARSE("u:__test_event char[9999] a");
/* Long size string should fail */
TEST_NPARSE("u:__test_event char[0x0000000000001] a");
}
TEST_F(user, loc_types) {
/* All should work */
TEST_PARSE("u:__test_event __data_loc char[] a");
TEST_PARSE("u:__test_event __data_loc unsigned char[] a");
TEST_PARSE("u:__test_event __rel_loc char[] a");
TEST_PARSE("u:__test_event __rel_loc unsigned char[] a");
}
TEST_F(user, size_types) {
/* Should work */
TEST_PARSE("u:__test_event struct custom a 20");
/* Size not specified on struct should fail */
TEST_NPARSE("u:__test_event struct custom a");
/* Size specified on non-struct should fail */
TEST_NPARSE("u:__test_event char a 20");
}
TEST_F(user, matching) {
/* Single name matches */
TEST_MATCH("__test_event u32 a",
"__test_event u32 a");
/* Multiple names match */
TEST_MATCH("__test_event u32 a; u32 b",
"__test_event u32 a; u32 b");
/* Multiple names match with dangling ; */
TEST_MATCH("__test_event u32 a; u32 b",
"__test_event u32 a; u32 b;");
/* Single name doesn't match */
TEST_NMATCH("__test_event u32 a",
"__test_event u32 b");
/* Multiple names don't match */
TEST_NMATCH("__test_event u32 a; u32 b",
"__test_event u32 b; u32 a");
/* Types don't match */
TEST_NMATCH("__test_event u64 a; u64 b",
"__test_event u32 a; u32 b");
/* Struct name and size matches */
TEST_MATCH("__test_event struct my_struct a 20",
"__test_event struct my_struct a 20");
/* Struct name don't match */
TEST_NMATCH("__test_event struct my_struct a 20",
"__test_event struct my_struct b 20");
/* Struct size don't match */
TEST_NMATCH("__test_event struct my_struct a 20",
"__test_event struct my_struct a 21");
}
int main(int argc, char **argv)
{
return test_harness_run(argc, argv);
}
@@ -0,0 +1,597 @@
// SPDX-License-Identifier: GPL-2.0
/*
* User Events FTrace Test Program
*
* Copyright (c) 2021 Beau Belgrave <beaub@linux.microsoft.com>
*/
#include <errno.h>
#include <linux/user_events.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>
#include "kselftest_harness.h"
#include "user_events_selftests.h"
const char *data_file = "/sys/kernel/tracing/user_events_data";
const char *status_file = "/sys/kernel/tracing/user_events_status";
const char *enable_file = "/sys/kernel/tracing/events/user_events/__test_event/enable";
const char *trace_file = "/sys/kernel/tracing/trace";
const char *fmt_file = "/sys/kernel/tracing/events/user_events/__test_event/format";
static int trace_bytes(void)
{
int fd = open(trace_file, O_RDONLY);
char buf[256];
int bytes = 0, got;
if (fd == -1)
return -1;
while (true) {
got = read(fd, buf, sizeof(buf));
if (got == -1)
return -1;
if (got == 0)
break;
bytes += got;
}
close(fd);
return bytes;
}
static int skip_until_empty_line(FILE *fp)
{
int c, last = 0;
while (true) {
c = getc(fp);
if (c == EOF)
break;
if (last == '\n' && c == '\n')
return 0;
last = c;
}
return -1;
}
static int get_print_fmt(char *buffer, int len)
{
FILE *fp = fopen(fmt_file, "r");
char *newline;
if (!fp)
return -1;
/* Read until empty line (Skip Common) */
if (skip_until_empty_line(fp) < 0)
goto err;
/* Read until empty line (Skip Properties) */
if (skip_until_empty_line(fp) < 0)
goto err;
/* Read in print_fmt: */
if (fgets(buffer, len, fp) == NULL)
goto err;
newline = strchr(buffer, '\n');
if (newline)
*newline = '\0';
fclose(fp);
return 0;
err:
fclose(fp);
return -1;
}
static bool wait_for_delete(void)
{
int i;
for (i = 0; i < 1000; ++i) {
int fd = open(enable_file, O_RDONLY);
if (fd == -1)
return true;
close(fd);
usleep(1000);
}
return false;
}
static int clear(int *check)
{
struct user_unreg unreg = {0};
int fd;
unreg.size = sizeof(unreg);
unreg.disable_bit = 31;
unreg.disable_addr = (__u64)check;
fd = open(data_file, O_RDWR);
if (fd == -1)
return -1;
if (ioctl(fd, DIAG_IOCSUNREG, &unreg) == -1)
if (errno != ENOENT)
goto fail;
if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1) {
if (errno == EBUSY) {
if (!wait_for_delete())
goto fail;
} else if (errno != ENOENT)
goto fail;
}
close(fd);
return 0;
fail:
close(fd);
return -1;
}
static int check_print_fmt(const char *event, const char *expected, int *check)
{
struct user_reg reg = {0};
char print_fmt[256];
int ret;
int fd;
/* Ensure cleared */
ret = clear(check);
if (ret != 0)
return ret;
fd = open(data_file, O_RDWR);
if (fd == -1)
return fd;
reg.size = sizeof(reg);
reg.name_args = (__u64)event;
reg.enable_bit = 31;
reg.enable_addr = (__u64)check;
reg.enable_size = sizeof(*check);
/* Register should work */
ret = ioctl(fd, DIAG_IOCSREG, &reg);
if (ret != 0) {
close(fd);
printf("Reg failed in fmt\n");
return ret;
}
/* Ensure correct print_fmt */
ret = get_print_fmt(print_fmt, sizeof(print_fmt));
close(fd);
if (ret != 0)
return ret;
return strcmp(print_fmt, expected);
}
FIXTURE(user) {
int status_fd;
int data_fd;
int enable_fd;
int check;
bool umount;
};
FIXTURE_SETUP(user) {
USER_EVENT_FIXTURE_SETUP(return, self->umount);
self->status_fd = open(status_file, O_RDONLY);
ASSERT_NE(-1, self->status_fd);
self->data_fd = open(data_file, O_RDWR);
ASSERT_NE(-1, self->data_fd);
self->enable_fd = -1;
}
FIXTURE_TEARDOWN(user) {
USER_EVENT_FIXTURE_TEARDOWN(self->umount);
close(self->status_fd);
close(self->data_fd);
if (self->enable_fd != -1) {
write(self->enable_fd, "0", sizeof("0"));
close(self->enable_fd);
}
if (clear(&self->check) != 0)
printf("WARNING: Clear didn't work!\n");
}
TEST_F(user, register_events) {
struct user_reg reg = {0};
struct user_unreg unreg = {0};
reg.size = sizeof(reg);
reg.name_args = (__u64)"__test_event u32 field1; u32 field2";
reg.enable_bit = 31;
reg.enable_addr = (__u64)&self->check;
reg.enable_size = sizeof(self->check);
unreg.size = sizeof(unreg);
unreg.disable_bit = 31;
unreg.disable_addr = (__u64)&self->check;
/* Register should work */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
/* Multiple registers to the same addr + bit should fail */
ASSERT_EQ(-1, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(EADDRINUSE, errno);
/* Multiple registers to same name should result in same index */
reg.enable_bit = 30;
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
/* Register without separator spacing should still match */
reg.enable_bit = 29;
reg.name_args = (__u64)"__test_event u32 field1;u32 field2";
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
/* Multiple registers to same name but different args should fail */
reg.enable_bit = 29;
reg.name_args = (__u64)"__test_event u32 field1;";
ASSERT_EQ(-1, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(EADDRINUSE, errno);
/* Ensure disabled */
self->enable_fd = open(enable_file, O_RDWR);
ASSERT_NE(-1, self->enable_fd);
ASSERT_NE(-1, write(self->enable_fd, "0", sizeof("0")))
/* Enable event and ensure bits updated in status */
ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1")))
ASSERT_EQ(1 << reg.enable_bit, self->check);
/* Disable event and ensure bits updated in status */
ASSERT_NE(-1, write(self->enable_fd, "0", sizeof("0")))
ASSERT_EQ(0, self->check);
/* File still open should return -EBUSY for delete */
ASSERT_EQ(-1, ioctl(self->data_fd, DIAG_IOCSDEL, "__test_event"));
ASSERT_EQ(EBUSY, errno);
/* Unregister */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSUNREG, &unreg));
unreg.disable_bit = 30;
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSUNREG, &unreg));
unreg.disable_bit = 29;
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSUNREG, &unreg));
/* Delete should have been auto-done after close and unregister */
close(self->data_fd);
ASSERT_EQ(true, wait_for_delete());
}
TEST_F(user, write_events) {
struct user_reg reg = {0};
struct iovec io[3];
__u32 field1, field2;
int before = 0, after = 0;
reg.size = sizeof(reg);
reg.name_args = (__u64)"__test_event u32 field1; u32 field2";
reg.enable_bit = 31;
reg.enable_addr = (__u64)&self->check;
reg.enable_size = sizeof(self->check);
field1 = 1;
field2 = 2;
io[0].iov_base = &reg.write_index;
io[0].iov_len = sizeof(reg.write_index);
io[1].iov_base = &field1;
io[1].iov_len = sizeof(field1);
io[2].iov_base = &field2;
io[2].iov_len = sizeof(field2);
/* Register should work */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
ASSERT_EQ(0, self->check);
/* Write should fail on invalid slot with ENOENT */
io[0].iov_base = &field2;
io[0].iov_len = sizeof(field2);
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3));
ASSERT_EQ(ENOENT, errno);
io[0].iov_base = &reg.write_index;
io[0].iov_len = sizeof(reg.write_index);
/* Write should return -EBADF when event is not enabled */
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3));
ASSERT_EQ(EBADF, errno);
/* Enable event */
self->enable_fd = open(enable_file, O_RDWR);
ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1")))
/* Event should now be enabled */
ASSERT_NE(1 << reg.enable_bit, self->check);
/* Write should make it out to ftrace buffers */
before = trace_bytes();
ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 3));
after = trace_bytes();
ASSERT_GT(after, before);
/* Negative index should fail with EINVAL */
reg.write_index = -1;
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3));
ASSERT_EQ(EINVAL, errno);
}
TEST_F(user, write_empty_events) {
struct user_reg reg = {0};
struct iovec io[1];
int before = 0, after = 0;
reg.size = sizeof(reg);
reg.name_args = (__u64)"__test_event";
reg.enable_bit = 31;
reg.enable_addr = (__u64)&self->check;
reg.enable_size = sizeof(self->check);
io[0].iov_base = &reg.write_index;
io[0].iov_len = sizeof(reg.write_index);
/* Register should work */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
ASSERT_EQ(0, self->check);
/* Enable event */
self->enable_fd = open(enable_file, O_RDWR);
ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1")))
/* Event should now be enabled */
ASSERT_EQ(1 << reg.enable_bit, self->check);
/* Write should make it out to ftrace buffers */
before = trace_bytes();
ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 1));
after = trace_bytes();
ASSERT_GT(after, before);
}
TEST_F(user, write_fault) {
struct user_reg reg = {0};
struct iovec io[2];
int l = sizeof(__u64);
void *anon;
reg.size = sizeof(reg);
reg.name_args = (__u64)"__test_event u64 anon";
reg.enable_bit = 31;
reg.enable_addr = (__u64)&self->check;
reg.enable_size = sizeof(self->check);
anon = mmap(NULL, l, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(MAP_FAILED, anon);
io[0].iov_base = &reg.write_index;
io[0].iov_len = sizeof(reg.write_index);
io[1].iov_base = anon;
io[1].iov_len = l;
/* Register should work */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
/* Enable event */
self->enable_fd = open(enable_file, O_RDWR);
ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1")))
/* Write should work normally */
ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 2));
/* Faulted data should zero fill and work */
ASSERT_EQ(0, madvise(anon, l, MADV_DONTNEED));
ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 2));
ASSERT_EQ(0, munmap(anon, l));
}
TEST_F(user, write_validator) {
struct user_reg reg = {0};
struct iovec io[3];
int loc, bytes;
char data[8];
int before = 0, after = 0;
reg.size = sizeof(reg);
reg.name_args = (__u64)"__test_event __rel_loc char[] data";
reg.enable_bit = 31;
reg.enable_addr = (__u64)&self->check;
reg.enable_size = sizeof(self->check);
/* Register should work */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
ASSERT_EQ(0, self->check);
io[0].iov_base = &reg.write_index;
io[0].iov_len = sizeof(reg.write_index);
io[1].iov_base = &loc;
io[1].iov_len = sizeof(loc);
io[2].iov_base = data;
bytes = snprintf(data, sizeof(data), "Test") + 1;
io[2].iov_len = bytes;
/* Undersized write should fail */
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 1));
ASSERT_EQ(EINVAL, errno);
/* Enable event */
self->enable_fd = open(enable_file, O_RDWR);
ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1")))
/* Event should now be enabled */
ASSERT_EQ(1 << reg.enable_bit, self->check);
/* Full in-bounds write should work */
before = trace_bytes();
loc = DYN_LOC(0, bytes);
ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 3));
after = trace_bytes();
ASSERT_GT(after, before);
/* Out of bounds write should fault (offset way out) */
loc = DYN_LOC(1024, bytes);
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3));
ASSERT_EQ(EFAULT, errno);
/* Out of bounds write should fault (offset 1 byte out) */
loc = DYN_LOC(1, bytes);
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3));
ASSERT_EQ(EFAULT, errno);
/* Out of bounds write should fault (size way out) */
loc = DYN_LOC(0, bytes + 1024);
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3));
ASSERT_EQ(EFAULT, errno);
/* Out of bounds write should fault (size 1 byte out) */
loc = DYN_LOC(0, bytes + 1);
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3));
ASSERT_EQ(EFAULT, errno);
/* Non-Null should fault */
memset(data, 'A', sizeof(data));
loc = DYN_LOC(0, bytes);
ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3));
ASSERT_EQ(EFAULT, errno);
}
TEST_F(user, print_fmt) {
int ret;
ret = check_print_fmt("__test_event __rel_loc char[] data",
"print fmt: \"data=%s\", __get_rel_str(data)",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event __data_loc char[] data",
"print fmt: \"data=%s\", __get_str(data)",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event s64 data",
"print fmt: \"data=%lld\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event u64 data",
"print fmt: \"data=%llu\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event s32 data",
"print fmt: \"data=%d\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event u32 data",
"print fmt: \"data=%u\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event int data",
"print fmt: \"data=%d\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event unsigned int data",
"print fmt: \"data=%u\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event s16 data",
"print fmt: \"data=%d\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event u16 data",
"print fmt: \"data=%u\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event short data",
"print fmt: \"data=%d\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event unsigned short data",
"print fmt: \"data=%u\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event s8 data",
"print fmt: \"data=%d\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event u8 data",
"print fmt: \"data=%u\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event char data",
"print fmt: \"data=%d\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event unsigned char data",
"print fmt: \"data=%u\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
ret = check_print_fmt("__test_event char[4] data",
"print fmt: \"data=%s\", REC->data",
&self->check);
ASSERT_EQ(0, ret);
}
int main(int argc, char **argv)
{
return test_harness_run(argc, argv);
}
@@ -0,0 +1,274 @@
// SPDX-License-Identifier: GPL-2.0
/*
* User Events Perf Events Test Program
*
* Copyright (c) 2021 Beau Belgrave <beaub@linux.microsoft.com>
*/
#include <errno.h>
#include <linux/user_events.h>
#include <linux/perf_event.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <asm/unistd.h>
#include "kselftest_harness.h"
#include "user_events_selftests.h"
const char *data_file = "/sys/kernel/tracing/user_events_data";
const char *id_file = "/sys/kernel/tracing/events/user_events/__test_event/id";
const char *fmt_file = "/sys/kernel/tracing/events/user_events/__test_event/format";
struct event {
__u32 index;
__u32 field1;
__u32 field2;
};
static long perf_event_open(struct perf_event_attr *pe, pid_t pid,
int cpu, int group_fd, unsigned long flags)
{
return syscall(__NR_perf_event_open, pe, pid, cpu, group_fd, flags);
}
static int get_id(void)
{
FILE *fp = fopen(id_file, "r");
int ret, id = 0;
if (!fp)
return -1;
ret = fscanf(fp, "%d", &id);
fclose(fp);
if (ret != 1)
return -1;
return id;
}
static int get_offset(void)
{
FILE *fp = fopen(fmt_file, "r");
int ret, c, last = 0, offset = 0;
if (!fp)
return -1;
/* Read until empty line */
while (true) {
c = getc(fp);
if (c == EOF)
break;
if (last == '\n' && c == '\n')
break;
last = c;
}
ret = fscanf(fp, "\tfield:u32 field1;\toffset:%d;", &offset);
fclose(fp);
if (ret != 1)
return -1;
return offset;
}
static int clear(int *check)
{
struct user_unreg unreg = {0};
int i, ret;
unreg.size = sizeof(unreg);
unreg.disable_bit = 31;
unreg.disable_addr = (__u64)check;
int fd = open(data_file, O_RDWR);
if (fd == -1)
return -1;
if (ioctl(fd, DIAG_IOCSUNREG, &unreg) == -1)
if (errno != ENOENT)
return -1;
/*
* Deleting the event drops its last reference, but the unregister
* above defers that put (and the freeing of the enabler) past an RCU
* grace period. The delete can therefore transiently fail with -EBUSY
* until that reference is dropped. Retry for up to ~10 seconds so the
* event is actually gone before the next test registers the same name.
*/
for (i = 0; i < 10000; ++i) {
ret = ioctl(fd, DIAG_IOCSDEL, "__test_event");
if (ret == 0 || errno == ENOENT) {
ret = 0;
break;
}
if (errno != EBUSY) {
close(fd);
return -1;
}
usleep(1000);
}
close(fd);
return ret;
}
FIXTURE(user) {
int data_fd;
int check;
bool umount;
};
FIXTURE_SETUP(user) {
USER_EVENT_FIXTURE_SETUP(return, self->umount);
self->data_fd = open(data_file, O_RDWR);
ASSERT_NE(-1, self->data_fd);
}
FIXTURE_TEARDOWN(user) {
USER_EVENT_FIXTURE_TEARDOWN(self->umount);
close(self->data_fd);
if (clear(&self->check) != 0)
printf("WARNING: Clear didn't work!\n");
}
TEST_F(user, perf_write) {
struct perf_event_attr pe = {0};
struct user_reg reg = {0};
struct event event;
struct perf_event_mmap_page *perf_page;
int page_size = sysconf(_SC_PAGESIZE);
int id, fd, offset;
__u32 *val;
reg.size = sizeof(reg);
reg.name_args = (__u64)"__test_event u32 field1; u32 field2";
reg.enable_bit = 31;
reg.enable_addr = (__u64)&self->check;
reg.enable_size = sizeof(self->check);
/* Register should work */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
ASSERT_EQ(0, self->check);
/* Id should be there */
id = get_id();
ASSERT_NE(-1, id);
offset = get_offset();
ASSERT_NE(-1, offset);
pe.type = PERF_TYPE_TRACEPOINT;
pe.size = sizeof(pe);
pe.config = id;
pe.sample_type = PERF_SAMPLE_RAW;
pe.sample_period = 1;
pe.wakeup_events = 1;
/* Tracepoint attach should work */
fd = perf_event_open(&pe, 0, -1, -1, 0);
ASSERT_NE(-1, fd);
perf_page = mmap(NULL, page_size * 2, PROT_READ, MAP_SHARED, fd, 0);
ASSERT_NE(MAP_FAILED, perf_page);
/* Status should be updated */
ASSERT_EQ(1 << reg.enable_bit, self->check);
event.index = reg.write_index;
event.field1 = 0xc001;
event.field2 = 0xc01a;
/* Ensure write shows up at correct offset */
ASSERT_NE(-1, write(self->data_fd, &event, sizeof(event)));
val = (void *)(((char *)perf_page) + perf_page->data_offset);
ASSERT_EQ(PERF_RECORD_SAMPLE, *val);
/* Skip over header and size, move to offset */
val += 3;
val = (void *)((char *)val) + offset;
/* Ensure correct */
ASSERT_EQ(event.field1, *val++);
ASSERT_EQ(event.field2, *val++);
munmap(perf_page, page_size * 2);
close(fd);
/* Status should be updated */
ASSERT_EQ(0, self->check);
}
TEST_F(user, perf_empty_events) {
struct perf_event_attr pe = {0};
struct user_reg reg = {0};
struct perf_event_mmap_page *perf_page;
int page_size = sysconf(_SC_PAGESIZE);
int id, fd;
__u32 *val;
reg.size = sizeof(reg);
reg.name_args = (__u64)"__test_event";
reg.enable_bit = 31;
reg.enable_addr = (__u64)&self->check;
reg.enable_size = sizeof(self->check);
/* Register should work */
ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, &reg));
ASSERT_EQ(0, reg.write_index);
ASSERT_EQ(0, self->check);
/* Id should be there */
id = get_id();
ASSERT_NE(-1, id);
pe.type = PERF_TYPE_TRACEPOINT;
pe.size = sizeof(pe);
pe.config = id;
pe.sample_type = PERF_SAMPLE_RAW;
pe.sample_period = 1;
pe.wakeup_events = 1;
/* Tracepoint attach should work */
fd = perf_event_open(&pe, 0, -1, -1, 0);
ASSERT_NE(-1, fd);
perf_page = mmap(NULL, page_size * 2, PROT_READ, MAP_SHARED, fd, 0);
ASSERT_NE(MAP_FAILED, perf_page);
/* Status should be updated */
ASSERT_EQ(1 << reg.enable_bit, self->check);
/* Ensure write shows up at correct offset */
ASSERT_NE(-1, write(self->data_fd, (void *)&reg.write_index,
sizeof(reg.write_index)));
val = (void *)(((char *)perf_page) + perf_page->data_offset);
ASSERT_EQ(PERF_RECORD_SAMPLE, *val);
munmap(perf_page, page_size * 2);
close(fd);
/* Status should be updated */
ASSERT_EQ(0, self->check);
}
int main(int argc, char **argv)
{
return test_harness_run(argc, argv);
}
@@ -0,0 +1 @@
timeout=90
@@ -0,0 +1,114 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _USER_EVENTS_SELFTESTS_H
#define _USER_EVENTS_SELFTESTS_H
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <unistd.h>
#include <errno.h>
#include "kselftest.h"
static inline void tracefs_unmount(void)
{
umount("/sys/kernel/tracing");
}
static inline bool tracefs_enabled(char **message, bool *fail, bool *umount)
{
struct stat buf;
int ret;
*message = "";
*fail = false;
*umount = false;
/* Ensure tracefs is installed */
ret = stat("/sys/kernel/tracing", &buf);
if (ret == -1) {
*message = "Tracefs is not installed";
return false;
}
/* Ensure mounted tracefs */
ret = stat("/sys/kernel/tracing/README", &buf);
if (ret == -1 && errno == ENOENT) {
if (mount(NULL, "/sys/kernel/tracing", "tracefs", 0, NULL) != 0) {
*message = "Cannot mount tracefs";
*fail = true;
return false;
}
*umount = true;
ret = stat("/sys/kernel/tracing/README", &buf);
}
if (ret == -1) {
*message = "Cannot access tracefs";
*fail = true;
return false;
}
return true;
}
static inline bool user_events_enabled(char **message, bool *fail, bool *umount)
{
struct stat buf;
int ret;
*message = "";
*fail = false;
*umount = false;
if (getuid() != 0) {
*message = "Must be run as root";
*fail = true;
return false;
}
if (!tracefs_enabled(message, fail, umount))
return false;
/* Ensure user_events is installed */
ret = stat("/sys/kernel/tracing/user_events_data", &buf);
if (ret == -1) {
switch (errno) {
case ENOENT:
*message = "user_events is not installed";
return false;
default:
*message = "Cannot access user_events_data";
*fail = true;
return false;
}
}
return true;
}
#define USER_EVENT_FIXTURE_SETUP(statement, umount) do { \
char *message; \
bool fail; \
if (!user_events_enabled(&message, &fail, &(umount))) { \
if (fail) { \
TH_LOG("Setup failed due to: %s", message); \
ASSERT_FALSE(fail); \
} \
SKIP(statement, "Skipping due to: %s", message); \
} \
} while (0)
#define USER_EVENT_FIXTURE_TEARDOWN(umount) do { \
if ((umount)) \
tracefs_unmount(); \
} while (0)
#endif /* _USER_EVENTS_SELFTESTS_H */