From a0b1f42c19b7ea6bcce77b33f4581ca5b3ac56e5 Mon Sep 17 00:00:00 2001 From: Julien Desfossez Date: Tue, 28 Jun 2016 17:45:52 -0400 Subject: [PATCH] Test for select, poll and epoll syscall overrides MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This test for root_regression checks if the syscall overrides for select, pselect6, poll, ppoll, epoll_ctl, epoll_wait and epoll_pwait work as expected on arm and x86 (32 and 64-bit). There are 11 test cases that check for normal and abnormal behaviour. If the test system has the Babeltrace python bindings, the test validates the content of the events, otherwise only the presence of the generated events is checked. We also check if kernel OOPS, WARNING or BUG were generated during the test. Signed-off-by: Julien Desfossez Signed-off-by: Jérémie Galarneau --- tests/regression/kernel/Makefile.am | 8 +- tests/regression/kernel/select_poll_epoll.c | 921 ++++++++++++++++++ .../regression/kernel/test_select_poll_epoll | 412 ++++++++ .../kernel/validate_select_poll_epoll.py | 758 ++++++++++++++ tests/root_regression | 1 + 5 files changed, 2099 insertions(+), 1 deletion(-) create mode 100644 tests/regression/kernel/select_poll_epoll.c create mode 100755 tests/regression/kernel/test_select_poll_epoll create mode 100755 tests/regression/kernel/validate_select_poll_epoll.py diff --git a/tests/regression/kernel/Makefile.am b/tests/regression/kernel/Makefile.am index a8c397a85..cfdeeb93b 100644 --- a/tests/regression/kernel/Makefile.am +++ b/tests/regression/kernel/Makefile.am @@ -1,5 +1,11 @@ EXTRA_DIST = test_event_basic test_all_events test_syscall \ - test_clock_override test_rotation_destroy_flush + test_clock_override test_rotation_destroy_flush \ + test_select_poll_epoll + +noinst_PROGRAMS = select_poll_epoll +select_poll_epoll_SOURCES = select_poll_epoll.c +select_poll_epoll_LDADD = -lpthread -lpopt +select_poll_epoll_CFLAGS = -fno-stack-protector -D_FORTIFY_SOURCE=0 all-local: @if [ x"$(srcdir)" != x"$(builddir)" ]; then \ diff --git a/tests/regression/kernel/select_poll_epoll.c b/tests/regression/kernel/select_poll_epoll.c new file mode 100644 index 000000000..2a69d5e45 --- /dev/null +++ b/tests/regression/kernel/select_poll_epoll.c @@ -0,0 +1,921 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 256 +#define NB_FD 1 +#define MAX_FDS 2047 +#define NR_ITER 1000 /* for stress-tests */ + +#define MIN_NR_FDS 5 /* the minimum number of open FDs required for the test to run */ +#define BIG_SELECT_FD 1022 + +#define MSEC_PER_USEC 1000 +#define MSEC_PER_NSEC (MSEC_PER_USEC * 1000) + +static int timeout; /* seconds, -1 to disable */ +volatile static int stop_thread; +static int wait_fd; + +struct ppoll_thread_data { + struct pollfd *ufds; + int value; +}; + +void test_select_big(void) +{ + fd_set rfds, wfds, exfds; + struct timeval tv; + int ret; + int fd2; + char buf[BUF_SIZE]; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&exfds); + + fd2 = dup2(wait_fd, BIG_SELECT_FD); + if (fd2 < 0) { + perror("dup2"); + goto end; + } + FD_SET(fd2, &rfds); + + tv.tv_sec = 0; + tv.tv_usec = timeout * MSEC_PER_USEC; + + if (timeout > 0) { + ret = select(fd2 + 1, &rfds, &wfds, &exfds, &tv); + } else { + ret = select(fd2 + 1, &rfds, &wfds, &exfds, NULL); + } + + if (ret == -1) { + perror("select()"); + } else if (ret) { + printf("# [select] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[select] read"); + } + } else { + printf("# [select] timeout\n"); + } + + ret = close(BIG_SELECT_FD); + if (ret) { + perror("close"); + } + +end: + return; +} + +void test_pselect(void) +{ + fd_set rfds; + struct timespec tv; + int ret; + char buf[BUF_SIZE]; + + FD_ZERO(&rfds); + FD_SET(wait_fd, &rfds); + + tv.tv_sec = 0; + tv.tv_nsec = timeout * MSEC_PER_NSEC; + + if (timeout > 0) { + ret = pselect(1, &rfds, NULL, NULL, &tv, NULL); + } else { + ret = pselect(1, &rfds, NULL, NULL, NULL, NULL); + } + + if (ret == -1) { + perror("pselect()"); + } else if (ret) { + printf("# [pselect] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[pselect] read"); + } + } else { + printf("# [pselect] timeout\n"); + } + +} + +void test_select(void) +{ + fd_set rfds; + struct timeval tv; + int ret; + char buf[BUF_SIZE]; + + FD_ZERO(&rfds); + FD_SET(wait_fd, &rfds); + + tv.tv_sec = 0; + tv.tv_usec = timeout * MSEC_PER_USEC; + + if (timeout > 0) { + ret = select(1, &rfds, NULL, NULL, &tv); + } else { + ret = select(1, &rfds, NULL, NULL, NULL); + } + + if (ret == -1) { + perror("select()"); + } else if (ret) { + printf("# [select] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[select] read"); + } + } else { + printf("# [select] timeout\n"); + } + +} + +void test_poll(void) +{ + struct pollfd ufds[NB_FD]; + char buf[BUF_SIZE]; + int ret; + + ufds[0].fd = wait_fd; + ufds[0].events = POLLIN|POLLPRI; + + ret = poll(ufds, 1, timeout); + + if (ret < 0) { + perror("poll"); + } else if (ret > 0) { + printf("# [poll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[poll] read"); + } + } else { + printf("# [poll] timeout\n"); + } +} + +void test_ppoll(void) +{ + struct pollfd ufds[NB_FD]; + char buf[BUF_SIZE]; + int ret; + struct timespec ts; + + ufds[0].fd = wait_fd; + ufds[0].events = POLLIN|POLLPRI; + + if (timeout > 0) { + ts.tv_sec = 0; + ts.tv_nsec = timeout * MSEC_PER_NSEC; + ret = ppoll(ufds, 1, &ts, NULL); + } else { + ret = ppoll(ufds, 1, NULL, NULL); + } + + + if (ret < 0) { + perror("ppoll"); + } else if (ret > 0) { + printf("# [ppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[ppoll] read"); + } + } else { + printf("# [ppoll] timeout\n"); + } +} + +void test_ppoll_big(void) +{ + struct pollfd ufds[MAX_FDS]; + char buf[BUF_SIZE]; + int ret, i, fds[MAX_FDS]; + + for (i = 0; i < MAX_FDS; i++) { + fds[i] = dup(wait_fd); + if (fds[i] < 0) { + perror("dup"); + } + ufds[i].fd = fds[i]; + ufds[i].events = POLLIN|POLLPRI; + } + + ret = ppoll(ufds, MAX_FDS, NULL, NULL); + + if (ret < 0) { + perror("ppoll"); + } else if (ret > 0) { + printf("# [ppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[ppoll] read"); + } + } else { + printf("# [ppoll] timeout\n"); + } + + for (i = 0; i < MAX_FDS; i++) { + ret = close(fds[i]); + if (ret != 0) { + perror("close"); + } + } + + return; +} + +void test_epoll(void) +{ + int ret, epollfd; + char buf[BUF_SIZE]; + struct epoll_event epoll_event; + + epollfd = epoll_create(NB_FD); + if (epollfd < 0) { + perror("[epoll] create"); + goto end; + } + + epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event.data.fd = wait_fd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); + if (ret < 0) { + perror("[epoll] add"); + goto end; + } + + if (timeout > 0) { + ret = epoll_wait(epollfd, &epoll_event, 1, timeout); + } else { + ret = epoll_wait(epollfd, &epoll_event, 1, -1); + } + + if (ret == 1) { + printf("# [epoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[epoll] read"); + } + } else if (ret == 0) { + printf("# [epoll] timeout\n"); + } else { + perror("epoll_wait"); + } + +end: + return; +} + +void test_pepoll(void) +{ + int ret, epollfd; + char buf[BUF_SIZE]; + struct epoll_event epoll_event; + + epollfd = epoll_create(NB_FD); + if (epollfd < 0) { + perror("[eppoll] create"); + goto end; + } + + epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event.data.fd = wait_fd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); + if (ret < 0) { + perror("[eppoll] add"); + goto end; + } + + if (timeout > 0) { + ret = epoll_pwait(epollfd, &epoll_event, 1, timeout, NULL); + } else { + ret = epoll_pwait(epollfd, &epoll_event, 1, -1, NULL); + } + + if (ret == 1) { + printf("# [eppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[eppoll] read"); + } + } else if (ret == 0) { + printf("# [eppoll] timeout\n"); + } else { + perror("epoll_pwait"); + } + +end: + return; +} + +void run_working_cases(void) +{ + int ret; + int pipe_fds[2]; + + if (timeout > 0) { + /* + * We need an input pipe for some cases and stdin might + * have random data, so we create a dummy pipe for this + * test to make sure we are running under clean conditions. + */ + ret = pipe(pipe_fds); + if (ret != 0) { + perror("pipe"); + goto end; + } + wait_fd = pipe_fds[0]; + } + test_select(); + test_pselect(); + test_select_big(); + test_poll(); + test_ppoll(); + test_epoll(); + test_pepoll(); + + if (timeout > 0) { + ret = close(pipe_fds[0]); + if (ret) { + perror("close"); + } + ret = close(pipe_fds[1]); + if (ret) { + perror("close"); + } + } + +end: + return; +} + +/* + * Ask for 100 FDs in a buffer for allocated for only 1 FD, should + * segfault (eventually with a "*** stack smashing detected ***" message). + * The event should contain an array of 100 FDs filled with garbage. + */ +void ppoll_fds_buffer_overflow(void) +{ + struct pollfd ufds[NB_FD]; + char buf[BUF_SIZE]; + int ret; + + ufds[0].fd = wait_fd; + ufds[0].events = POLLIN|POLLPRI; + + ret = syscall(SYS_ppoll, ufds, 100, NULL, NULL); + + if (ret < 0) { + perror("ppoll"); + } else if (ret > 0) { + printf("# [ppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[ppoll] read"); + } + } else { + printf("# [ppoll] timeout\n"); + } + + return; +} + +/* + * Ask for ULONG_MAX FDs in a buffer for allocated for only 1 FD, should + * cleanly fail with a "Invalid argument". + * The event should contain an empty array of FDs and overflow = 1. + */ +void ppoll_fds_ulong_max(void) +{ + struct pollfd ufds[NB_FD]; + char buf[BUF_SIZE]; + int ret; + + ufds[0].fd = wait_fd; + ufds[0].events = POLLIN|POLLPRI; + + ret = syscall(SYS_ppoll, ufds, ULONG_MAX, NULL, NULL); + + if (ret < 0) { + perror("# ppoll"); + } else if (ret > 0) { + printf("# [ppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[ppoll] read"); + } + } else { + printf("# [ppoll] timeout\n"); + } + + return; +} + +/* + * Select is limited to 1024 FDs, should output a pselect event + * with 0 FDs. + */ +void pselect_fd_too_big(void) +{ + fd_set rfds; + int ret; + int fd2; + char buf[BUF_SIZE]; + + /* + * Test if nfds > 1024. + * Make sure ulimit is set correctly (ulimit -n 2048). + */ + fd2 = dup2(wait_fd, 2047); + if (fd2 != 2047) { + perror("dup2"); + return; + } + FD_ZERO(&rfds); + FD_SET(fd2, &rfds); + + ret = syscall(SYS_pselect6, fd2 + 1, &rfds, NULL, NULL, NULL, NULL); + + if (ret == -1) { + perror("# pselect()"); + } else if (ret) { + printf("# [pselect] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[pselect] read"); + } + } else { + printf("# [pselect] timeout\n"); + } + +} + +/* + * Invalid pointer as writefds, should output a ppoll event + * with 0 FDs. + */ +void pselect_invalid_pointer(void) +{ + fd_set rfds; + int ret; + char buf[BUF_SIZE]; + void *invalid = (void *) 0x42; + + FD_ZERO(&rfds); + FD_SET(wait_fd, &rfds); + + ret = syscall(SYS_pselect6, 1, &rfds, (fd_set *) invalid, NULL, NULL, + NULL); + + if (ret == -1) { + perror("# pselect()"); + } else if (ret) { + printf("# [pselect] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[pselect] read"); + } + } else { + printf("# [pselect] timeout\n"); + } + +} + +/* + * Pass an invalid pointer to epoll_pwait, should fail with + * "Bad address", the event returns 0 FDs. + */ +void epoll_pwait_invalid_pointer(void) +{ + int ret, epollfd; + char buf[BUF_SIZE]; + struct epoll_event epoll_event; + void *invalid = (void *) 0x42; + + epollfd = epoll_create(NB_FD); + if (epollfd < 0) { + perror("[eppoll] create"); + goto end; + } + + epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event.data.fd = wait_fd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); + if (ret < 0) { + perror("[eppoll] add"); + goto end; + } + + ret = syscall(SYS_epoll_pwait, epollfd, + (struct epoll_event *) invalid, 1, -1, NULL); + + if (ret == 1) { + printf("# [eppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[eppoll] read"); + } + } else if (ret == 0) { + printf("# [eppoll] timeout\n"); + } else { + perror("# epoll_pwait"); + } + +end: + return; +} + +/* + * Set maxevents to INT_MAX, should output "Invalid argument" + * The event should return an empty array. + */ +void epoll_pwait_int_max(void) +{ + int ret, epollfd; + char buf[BUF_SIZE]; + struct epoll_event epoll_event; + + epollfd = epoll_create(NB_FD); + if (epollfd < 0) { + perror("[eppoll] create"); + goto end; + } + + epoll_event.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event.data.fd = wait_fd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, wait_fd, &epoll_event); + if (ret < 0) { + perror("[eppoll] add"); + goto end; + } + + ret = syscall(SYS_epoll_pwait, epollfd, &epoll_event, INT_MAX, -1, + NULL); + + if (ret == 1) { + printf("# [eppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[eppoll] read"); + } + } else if (ret == 0) { + printf("# [eppoll] timeout\n"); + } else { + perror("# epoll_pwait"); + } + +end: + return; +} + +void *ppoll_writer(void *arg) +{ + struct ppoll_thread_data *data = (struct ppoll_thread_data *) arg; + + while (!stop_thread) { + memset(data->ufds, data->value, + MAX_FDS * sizeof(struct pollfd)); + usleep(100); + } + + return NULL; +} + +void do_ppoll(int *fds, struct pollfd *ufds) +{ + int i, ret; + struct timespec ts; + char buf[BUF_SIZE]; + + ts.tv_sec = 0; + ts.tv_nsec = 1 * MSEC_PER_NSEC; + + for (i = 0; i < MAX_FDS; i++) { + ufds[i].fd = fds[i]; + ufds[i].events = POLLIN|POLLPRI; + } + + ret = ppoll(ufds, MAX_FDS, &ts, NULL); + + if (ret < 0) { + perror("ppoll"); + } else if (ret > 0) { + printf("# [ppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[ppoll] read"); + } + } else { + printf("# [ppoll] timeout\n"); + } +} + +void stress_ppoll(int *fds, int value) +{ + pthread_t writer; + int iter, ret; + struct ppoll_thread_data thread_data; + struct pollfd ufds[MAX_FDS]; + + thread_data.ufds = ufds; + thread_data.value = value; + + stop_thread = 0; + ret = pthread_create(&writer, NULL, &ppoll_writer, (void *) &thread_data); + if (ret != 0) { + fprintf(stderr, "[error] pthread_create\n"); + goto end; + } + for (iter = 0; iter < NR_ITER; iter++) { + do_ppoll(fds, ufds); + } + stop_thread = 1; + pthread_join(writer, NULL); +end: + return; +} + +/* + * 3 rounds of NR_ITER iterations with concurrent updates of the pollfd + * structure: + * - memset to 0 + * - memset to 1 + * - memset to INT_MAX + * Waits for input, but also set a timeout in case the input FD is overwritten + * before entering in the syscall. We use MAX_FDS FDs (dup of stdin), so the + * resulting trace is big (20MB). + * + * ppoll should work as expected and the trace should be readable at the end. + */ +void ppoll_concurrent_write(void) +{ + int i, ret, fds[MAX_FDS]; + + for (i = 0; i < MAX_FDS; i++) { + fds[i] = dup(wait_fd); + if (fds[i] < 0) { + perror("dup"); + } + } + + stress_ppoll(fds, 0); + stress_ppoll(fds, 1); + stress_ppoll(fds, INT_MAX); + + for (i = 0; i < MAX_FDS; i++) { + ret = close(fds[i]); + if (ret != 0) { + perror("close"); + } + } + + return; +} + +void *epoll_pwait_writer(void *addr) +{ + srand(time(NULL)); + + while (!stop_thread) { + usleep(rand() % 30); + munmap(addr, MAX_FDS * sizeof(struct epoll_event)); + } + + return NULL; +} + +/* + * epoll_pwait on MAX_FDS fds while a concurrent thread munmaps the + * buffer allocated for the returned data. This should randomly segfault. + * The trace should be readable and no kernel OOPS should occur. + */ +void epoll_pwait_concurrent_munmap(void) +{ + int ret, epollfd, i, fds[MAX_FDS]; + char buf[BUF_SIZE]; + struct epoll_event *epoll_event; + void *addr = NULL; + pthread_t writer; + + + epollfd = epoll_create(MAX_FDS); + if (epollfd < 0) { + perror("[eppoll] create"); + goto end; + } + + epoll_event = mmap(addr, MAX_FDS * sizeof(struct epoll_event), + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (epoll_event == MAP_FAILED) { + perror("mmap"); + goto end; + } + + for (i = 0; i < MAX_FDS; i++) { + fds[i] = dup(wait_fd); + if (fds[i] < 0) { + perror("dup"); + } + epoll_event[i].events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_event[i].data.fd = fds[i]; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fds[i], epoll_event); + if (ret < 0) { + perror("[eppoll] add"); + goto end_unmap; + } + } + stop_thread = 0; + pthread_create(&writer, NULL, &epoll_pwait_writer, (void *) epoll_event); + + ret = epoll_pwait(epollfd, epoll_event, 1, 1, NULL); + + if (ret == 1) { + printf("# [eppoll] data available\n"); + ret = read(wait_fd, buf, BUF_SIZE); + if (ret < 0) { + perror("[eppoll] read"); + } + } else if (ret == 0) { + printf("# [eppoll] timeout\n"); + } else { + perror("# epoll_pwait"); + } + + stop_thread = 1; + pthread_join(writer, NULL); + +end_unmap: + for (i = 0; i < MAX_FDS; i++) { + ret = close(fds[i]); + if (ret != 0) { + perror("close"); + } + } + + ret = munmap(addr, MAX_FDS * sizeof(struct epoll_event)); + if (ret != 0) { + perror("munmap"); + } + +end: + return; +} + +void usage(poptContext optCon, int exitcode, char *error, char *addl) +{ + poptPrintUsage(optCon, stderr, 0); + if (error) { + fprintf(stderr, "%s: %s\n", error, addl); + } + exit(exitcode); +} + +void print_list(void) +{ + fprintf(stderr, "Test list (-t X):\n"); + fprintf(stderr, "\t1: Working cases for select, pselect6, poll, ppoll " + "and epoll, waiting for input\n"); + fprintf(stderr, "\t2: Timeout cases (1ms) for select, pselect6, poll, " + "ppoll and epoll\n"); + fprintf(stderr, "\t3: pselect with a FD > 1023\n"); + fprintf(stderr, "\t4: ppoll with %d FDs\n", MAX_FDS); + fprintf(stderr, "\t5: ppoll buffer overflow, should segfault, waits " + "for input\n"); + fprintf(stderr, "\t6: pselect with invalid pointer, waits for " + "input\n"); + fprintf(stderr, "\t7: ppoll with ulong_max fds, waits for input\n"); + fprintf(stderr, "\t8: epoll_pwait with invalid pointer, waits for " + "input\n"); + fprintf(stderr, "\t9: epoll_pwait with maxevents set to INT_MAX, " + "waits for input\n"); + fprintf(stderr, "\t10: ppoll with concurrent updates of the structure " + "from user-space, stress test (3000 iterations), " + "waits for input + timeout 1ms\n"); + fprintf(stderr, "\t11: epoll_pwait with concurrent munmap of the buffer " + "from user-space, should randomly segfault, run " + "multiple times, waits for input + timeout 1ms\n"); +} + +int main(int argc, const char **argv) +{ + int c, ret, test = -1; + poptContext optCon; + struct rlimit open_lim; + + struct poptOption optionsTable[] = { + { "test", 't', POPT_ARG_INT, &test, 0, + "Test to run", NULL }, + { "list", 'l', 0, 0, 'l', + "List of tests (-t X)", NULL }, + POPT_AUTOHELP + { NULL, 0, 0, NULL, 0 } + }; + + optCon = poptGetContext(NULL, argc, argv, optionsTable, 0); + + if (argc < 2) { + poptPrintUsage(optCon, stderr, 0); + ret = -1; + goto end; + } + + ret = 0; + + while ((c = poptGetNextOpt(optCon)) >= 0) { + switch(c) { + case 'l': + print_list(); + goto end; + } + } + + open_lim.rlim_cur = MAX_FDS + MIN_NR_FDS; + open_lim.rlim_max = MAX_FDS + MIN_NR_FDS; + + ret = setrlimit(RLIMIT_NOFILE, &open_lim); + if (ret < 0) { + perror("setrlimit"); + goto end; + } + + /* + * Some tests might segfault, but we need the getpid() to be output + * for the validation, disabling the buffering on stdout works. + */ + setbuf(stdout, NULL); + printf("%d\n", getpid()); + + wait_fd = STDIN_FILENO; + + switch(test) { + case 1: + timeout = -1; + run_working_cases(); + break; + case 2: + timeout = 1; + run_working_cases(); + break; + case 3: + pselect_fd_too_big(); + break; + case 4: + test_ppoll_big(); + break; + case 5: + ppoll_fds_buffer_overflow(); + break; + case 6: + pselect_invalid_pointer(); + break; + case 7: + ppoll_fds_ulong_max(); + break; + case 8: + epoll_pwait_invalid_pointer(); + break; + case 9: + epoll_pwait_int_max(); + break; + case 10: + ppoll_concurrent_write(); + break; + case 11: + epoll_pwait_concurrent_munmap(); + break; + default: + poptPrintUsage(optCon, stderr, 0); + ret = -1; + break; + } + +end: + poptFreeContext(optCon); + return ret; +} diff --git a/tests/regression/kernel/test_select_poll_epoll b/tests/regression/kernel/test_select_poll_epoll new file mode 100755 index 000000000..e01866f75 --- /dev/null +++ b/tests/regression/kernel/test_select_poll_epoll @@ -0,0 +1,412 @@ +#!/bin/bash +# +# Copyright (C) - 2016 Julien Desfossez +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License, version 2 only, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +TEST_DESC="Kernel tracer - select, poll and epoll payload extraction" + +CURDIR=$(dirname $0)/ +TESTDIR=$CURDIR/../.. +VALIDATE_SCRIPT="$CURDIR/validate_select_poll_epoll.py" +NUM_TESTS=102 + +# Only run this test on x86 and arm +uname -m | grep -E "x86|i686|arm|aarch64" >/dev/null 2>&1 +if test $? != 0; then + exit 0 +fi + +DISABLE_VALIDATE=0 +# Babeltrace python bindings are required for the validation, but +# it is not a mandatory dependancy of the project, so fail run the +# without the content validation, at least we test that we are not +# crashing the kernel. +$VALIDATE_SCRIPT --help >/dev/null 2>&1 +if test $? != 0; then + echo "# Failed to run the validation script, Babeltrace Python bindings might be missing" + DISABLE_VALIDATE=1 +fi + +LAST_WARNING=$(dmesg | grep " WARNING:" | cut -d' ' -f1 | tail -1) +LAST_OOPS=$(dmesg | grep " OOPS:" | cut -d' ' -f1 | tail -1) +LAST_BUG=$(dmesg | grep " BUG:" | cut -d' ' -f1 | tail -1) + +source $TESTDIR/utils/utils.sh + +function check_trace_content() +{ + if test $DISABLE_VALIDATE == 1; then + ok 0 "Validation skipped" + return + fi + + $VALIDATE_SCRIPT $@ + if test $? = 0; then + ok 0 "Validation success" + else + fail "Validation" + fi +} + +function test_working_cases() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + + # arm64 does not have epoll_wait + uname -m | grep -E "aarch64" >/dev/null 2>&1 + if test $? = 0; then + SYSCALL_LIST="select,pselect6,poll,ppoll,epoll_ctl,epoll_pwait" + else + SYSCALL_LIST="select,pselect6,poll,ppoll,epoll_ctl,epoll_wait,epoll_pwait" + fi + + diag "Working cases for select, pselect6, poll, ppoll and epoll, waiting for input" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$(yes | $CURDIR/select_poll_epoll -t 1); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 1 -p $pid $TRACE_PATH + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_timeout_cases() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + + # arm64 does not have epoll_wait + uname -m | grep -E "aarch64" >/dev/null 2>&1 + if test $? = 0; then + SYSCALL_LIST="select,pselect6,poll,ppoll,epoll_ctl,epoll_pwait" + else + SYSCALL_LIST="select,pselect6,poll,ppoll,epoll_ctl,epoll_wait,epoll_pwait" + fi + + diag "Timeout cases (1ms) for select, pselect6, poll, ppoll and epoll" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME "$SYSCALL_LIST" + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$($CURDIR/select_poll_epoll -t 2); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 2 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_big_pselect() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="pselect6" + + diag "pselect with a FD > 1023" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$($CURDIR/select_poll_epoll -t 3); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 3 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_big_ppoll() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="ppoll" + + diag "ppoll with 2047 FDs" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$(yes | $CURDIR/select_poll_epoll -t 4); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 4 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_ppoll_overflow() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="ppoll" + + diag "ppoll buffer overflow, should segfault, waits for input" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + diag "Expect segfaults" + { out=$(yes | $CURDIR/select_poll_epoll -t 5); } 2>/dev/null + stop_lttng_tracing_ok + echo $out + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + + check_trace_content -t 5 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_pselect_invalid_ptr() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="pselect6" + + diag "pselect with invalid pointer, waits for input" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$(yes | $CURDIR/select_poll_epoll -t 6); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 6 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_ppoll_ulong_max() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="ppoll" + + diag "ppoll with ulong_max fds, waits for input" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$(yes | $CURDIR/select_poll_epoll -t 7); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 7 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_epoll_pwait_invalid_ptr() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="epoll_pwait" + + diag "epoll_pwait with invalid pointer, waits for input" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$(yes | $CURDIR/select_poll_epoll -t 8); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 8 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_epoll_pwait_int_max() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="epoll_pwait" + + diag "epoll_pwait with maxevents set to INT_MAX, waits for input" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$(yes | $CURDIR/select_poll_epoll -t 9); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 9 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_ppoll_concurrent() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="ppoll" + + diag "ppoll with concurrent updates of the structure from user-space, stress test (3000 iterations), waits for input + timeout 1ms" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + { out=$(yes | $CURDIR/select_poll_epoll -t 10); } 2>/dev/null + stop_lttng_tracing_ok + pid=$(echo $out | cut -d' ' -f1) + + validate_trace "$SYSCALL_LIST" $TRACE_PATH + check_trace_content -t 10 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +function test_epoll_pwait_concurrent() +{ + TRACE_PATH=$(mktemp -d) + SESSION_NAME="syscall_payload" + SYSCALL_LIST="epoll_ctl,epoll_pwait" + + diag "epoll_pwait with concurrent munmap of the buffer from user-space, should randomly segfault, run multiple times, waits for input + timeout 1ms" + + create_lttng_session_ok $SESSION_NAME $TRACE_PATH + + lttng_enable_kernel_syscall_ok $SESSION_NAME $SYSCALL_LIST + add_context_kernel_ok $SESSION_NAME channel0 pid + + start_lttng_tracing_ok + diag "Expect segfaults" + for i in $(seq 1 100); do + { out=$($CURDIR/select_poll_epoll -t 11); } 2>/dev/null + done + pid=$(echo $out | cut -d' ' -f1) + stop_lttng_tracing_ok + + # epoll_wait is not always generated in the trace (stress test) + validate_trace "epoll_ctl" $TRACE_PATH + check_trace_content -t 11 -p $pid $TRACE_PATH 2>/dev/null + + destroy_lttng_session_ok $SESSION_NAME + + rm -rf $TRACE_PATH +} + +# MUST set TESTDIR before calling those functions +plan_tests $NUM_TESTS + +print_test_banner "$TEST_DESC" + +if [ "$(id -u)" == "0" ]; then + isroot=1 +else + isroot=0 +fi + +skip $isroot "Root access is needed. Skipping all tests." $NUM_TESTS || +{ + start_lttng_sessiond + + test_working_cases + test_timeout_cases + test_big_pselect + test_big_ppoll + test_ppoll_overflow + test_pselect_invalid_ptr + test_ppoll_ulong_max + test_epoll_pwait_invalid_ptr + test_epoll_pwait_int_max + test_ppoll_concurrent + test_epoll_pwait_concurrent + + stop_lttng_sessiond + + NEW_WARNING=$(dmesg | grep " WARNING:" | cut -d' ' -f1 | tail -1) + NEW_OOPS=$(dmesg | grep " OOPS:" | cut -d' ' -f1 | tail -1) + NEW_BUG=$(dmesg | grep " BUG:" | cut -d' ' -f1 | tail -1) + + if test "$LAST_WARNING" != "$NEW_WARNING"; then + fail "New WARNING generated" + fi + if test "$LAST_OOPS" != "$NEW_OOPS"; then + fail "New OOPS generated" + fi + if test "$LAST_BUG" != "$NEW_BUG"; then + fail "New BUG generated" + fi +} diff --git a/tests/regression/kernel/validate_select_poll_epoll.py b/tests/regression/kernel/validate_select_poll_epoll.py new file mode 100755 index 000000000..f4946e73a --- /dev/null +++ b/tests/regression/kernel/validate_select_poll_epoll.py @@ -0,0 +1,758 @@ +#!/usr/bin/env python3 + +import sys +import time +import argparse + +NSEC_PER_SEC = 1000000000 + +try: + from babeltrace import TraceCollection +except ImportError: + # quick fix for debian-based distros + sys.path.append("/usr/local/lib/python%d.%d/site-packages" % + (sys.version_info.major, sys.version_info.minor)) + from babeltrace import TraceCollection + + +class TraceParser: + def __init__(self, trace, pid): + self.trace = trace + self.pid = pid + self.expect = {} + + def ns_to_hour_nsec(self, ns): + d = time.localtime(ns/NSEC_PER_SEC) + return "%02d:%02d:%02d.%09d" % (d.tm_hour, d.tm_min, d.tm_sec, + ns % NSEC_PER_SEC) + + def parse(self): + # iterate over all the events + for event in self.trace.events: + if self.pid is not None and event["pid"] != self.pid: + continue + + method_name = "handle_%s" % event.name.replace(":", "_").replace( + "+", "_") + # call the function to handle each event individually + if hasattr(TraceParser, method_name): + func = getattr(TraceParser, method_name) + func(self, event) + + ret = 0 + for i in self.expect.keys(): + if self.expect[i] == 0: + print("%s not validated" % i) + ret = 1 + + return ret + + # epoll_ctl + def handle_compat_syscall_entry_epoll_ctl(self, event): + self.epoll_ctl_entry(event) + + def handle_compat_syscall_exit_epoll_ctl(self, event): + self.epoll_ctl_exit(event) + + def handle_syscall_entry_epoll_ctl(self, event): + self.epoll_ctl_entry(event) + + def handle_syscall_exit_epoll_ctl(self, event): + self.epoll_ctl_exit(event) + + def epoll_ctl_entry(self, event): + pass + + def epoll_ctl_exit(self, event): + pass + + # epoll_wait + epoll_pwait + def handle_compat_syscall_entry_epoll_wait(self, event): + self.epoll_wait_entry(event) + + def handle_compat_syscall_exit_epoll_wait(self, event): + self.epoll_wait_exit(event) + + def handle_syscall_entry_epoll_wait(self, event): + self.epoll_wait_entry(event) + + def handle_syscall_exit_epoll_wait(self, event): + self.epoll_wait_exit(event) + + def handle_compat_syscall_entry_epoll_pwait(self, event): + self.epoll_wait_entry(event) + + def handle_compat_syscall_exit_epoll_pwait(self, event): + self.epoll_wait_exit(event) + + def handle_syscall_entry_epoll_pwait(self, event): + self.epoll_wait_entry(event) + + def handle_syscall_exit_epoll_pwait(self, event): + self.epoll_wait_exit(event) + + def epoll_wait_entry(self, event): + pass + + def epoll_wait_exit(self, event): + pass + + ## poll + ppoll + def handle_compat_syscall_entry_poll(self, event): + self.poll_entry(event) + + def handle_compat_syscall_exit_poll(self, event): + self.poll_exit(event) + + def handle_syscall_entry_poll(self, event): + self.poll_entry(event) + + def handle_syscall_exit_poll(self, event): + self.poll_exit(event) + + def handle_compat_syscall_entry_ppoll(self, event): + self.poll_entry(event) + + def handle_compat_syscall_exit_ppoll(self, event): + self.poll_exit(event) + + def handle_syscall_entry_ppoll(self, event): + self.poll_entry(event) + + def handle_syscall_exit_ppoll(self, event): + self.poll_exit(event) + + def poll_entry(self, event): + pass + + def poll_exit(self, event): + pass + + # epoll_create + def handle_compat_syscall_entry_epoll_create1(self, event): + self.epoll_create_entry(event) + + def handle_compat_syscall_exit_epoll_create1(self, event): + self.epoll_create_exit(event) + + def handle_compat_syscall_entry_epoll_create(self, event): + self.epoll_create_entry(event) + + def handle_compat_syscall_exit_epoll_create(self, event): + self.epoll_create_exit(event) + + def handle_syscall_entry_epoll_create1(self, event): + self.epoll_create_entry(event) + + def handle_syscall_exit_epoll_create1(self, event): + self.epoll_create_exit(event) + + def handle_syscall_entry_epoll_create(self, event): + self.epoll_create_entry(event) + + def handle_syscall_exit_epoll_create(self, event): + self.epoll_create_exit(event) + + def epoll_create_entry(self, event): + pass + + def epoll_create_exit(self, event): + pass + + # select + pselect6 + def handle_syscall_entry_pselect6(self, event): + self.select_entry(event) + + def handle_syscall_exit_pselect6(self, event): + self.select_exit(event) + + def handle_compat_syscall_entry_pselect6(self, event): + self.select_entry(event) + + def handle_compat_syscall_exit_pselect6(self, event): + self.select_exit(event) + + def handle_syscall_entry_select(self, event): + self.select_entry(event) + + def handle_syscall_exit_select(self, event): + self.select_exit(event) + + def handle_compat_syscall_entry_select(self, event): + self.select_entry(event) + + def handle_compat_syscall_exit_select(self, event): + self.select_exit(event) + + def select_entry(self, event): + pass + + def select_exit(self, event): + pass + + +class Test1(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["select_in_fd0"] = 0 + self.expect["select_in_fd1023"] = 0 + self.expect["select_out_fd0"] = 0 + self.expect["select_out_fd1023"] = 0 + self.expect["poll_in_nfds1"] = 0 + self.expect["poll_out_nfds1"] = 0 + self.expect["epoll_ctl_in_add"] = 0 + self.expect["epoll_ctl_out_ok"] = 0 + self.expect["epoll_wait_in_ok"] = 0 + self.expect["epoll_wait_out_fd0"] = 0 + + def select_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + n = event["n"] + overflow = event["overflow"] + tvp = event["tvp"] + _readfds_length = event["_readfds_length"] + readfds = event["readfds"] + _writefds_length = event["_writefds_length"] + writefds = event["writefds"] + _exceptfds_length = event["_exceptfds_length"] + exceptfds = event["exceptfds"] + + # check that the FD 0 is actually set in the readfds + if n == 1 and readfds[0] == 1: + self.expect["select_in_fd0"] = 1 + if n == 1023: + # check that the FD 1023 is actually set in the readfds + if readfds[127] == 0x40 and writefds[127] == 0 and \ + exceptfds[127] == 0 and overflow == 0: + self.expect["select_in_fd1023"] = 1 + + def select_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + overflow = event["overflow"] + tvp = event["tvp"] + _readfds_length = event["_readfds_length"] + readfds = event["readfds"] + _writefds_length = event["_writefds_length"] + writefds = event["writefds"] + _exceptfds_length = event["_exceptfds_length"] + exceptfds = event["exceptfds"] + + if ret == 1: + # check that the FD 0 is actually set in the readfds + if readfds[0] == 1: + self.expect["select_out_fd0"] = 1 + # check that the FD 1023 is actually set in the readfds + if _readfds_length == 128 and readfds[127] == 0x40 and \ + writefds[127] == 0 and exceptfds[127] == 0 and tvp == 0: + self.expect["select_out_fd1023"] = 1 + + def poll_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + nfds = event["nfds"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # check that only one FD is set, that it has the POLLIN flag and that + # the raw value matches the events bit field. + if nfds == 1 and fds_length == 1 and fds[0]["raw_events"] == 0x3 \ + and fds[0]["events"]["POLLIN"] == 1 and \ + fds[0]["events"]["padding"] == 0: + self.expect["poll_in_nfds1"] = 1 + + def poll_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + nfds = event["nfds"] + fds_length = event["fds_length"] + fds = event["fds"] + + # check that only one FD is set, that it has the POLLIN flag and that + # the raw value matches the events bit field. + if ret == 1 and fds_length == 1 and fds[0]["raw_events"] == 0x1 \ + and fds[0]["events"]["POLLIN"] == 1 and \ + fds[0]["events"]["padding"] == 0: + self.expect["poll_out_nfds1"] = 1 + + def epoll_ctl_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + epfd = event["epfd"] + op_enum = event["op_enum"] + fd = event["fd"] + _event = event["event"] + + # check that we have FD 0 waiting for EPOLLIN|EPOLLPRI and that + # data.fd = 0 + if epfd == 3 and op_enum == "EPOLL_CTL_ADD" and fd == 0 and \ + _event["data_union"]["fd"] == 0 and \ + _event["events"]["EPOLLIN"] == 1 and \ + _event["events"]["EPOLLPRI"] == 1: + self.expect["epoll_ctl_in_add"] = 1 + + def epoll_ctl_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + + if ret == 0: + self.expect["epoll_ctl_out_ok"] = 1 + + def epoll_wait_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + epfd = event["epfd"] + maxevents = event["maxevents"] + timeout = event["timeout"] + + if epfd == 3 and maxevents == 1 and timeout == -1: + self.expect["epoll_wait_in_ok"] = 1 + + def epoll_wait_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # check that FD 0 returned with EPOLLIN and the right data.fd + if ret == 1 and fds_length == 1 and overflow == 0 and \ + fds[0]["data_union"]["fd"] == 0 and \ + fds[0]["events"]["EPOLLIN"] == 1: + self.expect["epoll_wait_out_fd0"] = 1 + + +class Test2(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["select_timeout_in_fd0"] = 0 + self.expect["select_timeout_in_fd1023"] = 0 + self.expect["select_timeout_out"] = 0 + self.expect["poll_timeout_in"] = 0 + self.expect["poll_timeout_out"] = 0 + self.expect["epoll_ctl_timeout_in_add"] = 0 + self.expect["epoll_ctl_timeout_out_ok"] = 0 + self.expect["epoll_wait_timeout_in"] = 0 + self.expect["epoll_wait_timeout_out"] = 0 + + def select_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + n = event["n"] + overflow = event["overflow"] + tvp = event["tvp"] + _readfds_length = event["_readfds_length"] + readfds = event["readfds"] + _writefds_length = event["_writefds_length"] + writefds = event["writefds"] + _exceptfds_length = event["_exceptfds_length"] + exceptfds = event["exceptfds"] + + if n == 1 and tvp != 0: + self.expect["select_timeout_in_fd0"] = 1 + if n == 1023: + if readfds[127] == 0x40 and writefds[127] == 0 and \ + exceptfds[127] == 0 and tvp != 0: + self.expect["select_timeout_in_fd1023"] = 1 + + def select_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + overflow = event["overflow"] + tvp = event["tvp"] + _readfds_length = event["_readfds_length"] + readfds = event["readfds"] + _writefds_length = event["_writefds_length"] + writefds = event["writefds"] + _exceptfds_length = event["_exceptfds_length"] + exceptfds = event["exceptfds"] + + if ret == 0 and tvp != 0: + self.expect["select_timeout_out"] = 1 + + def poll_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + nfds = event["nfds"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # check that we wait on FD 0 for POLLIN and that the raw_events + # field matches the value of POLLIN + if nfds == 1 and fds_length == 1 and fds[0]["raw_events"] == 0x3 \ + and fds[0]["events"]["POLLIN"] == 1 and \ + fds[0]["events"]["padding"] == 0: + self.expect["poll_timeout_in"] = 1 + + def poll_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + nfds = event["nfds"] + fds_length = event["fds_length"] + fds = event["fds"] + + if ret == 0 and nfds == 1 and fds_length == 0: + self.expect["poll_timeout_out"] = 1 + + def epoll_ctl_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + epfd = event["epfd"] + op_enum = event["op_enum"] + fd = event["fd"] + _event = event["event"] + + # make sure we see a EPOLLIN|EPOLLPRI + if op_enum == "EPOLL_CTL_ADD" and \ + _event["events"]["EPOLLIN"] == 1 and \ + _event["events"]["EPOLLPRI"] == 1: + self.expect["epoll_ctl_timeout_in_add"] = 1 + + def epoll_ctl_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + + if ret == 0: + self.expect["epoll_ctl_timeout_out_ok"] = 1 + + def epoll_wait_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + epfd = event["epfd"] + maxevents = event["maxevents"] + timeout = event["timeout"] + + if maxevents == 1 and timeout == 1: + self.expect["epoll_wait_timeout_in"] = 1 + + def epoll_wait_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + if ret == 0 and fds_length == 0 and overflow == 0: + self.expect["epoll_wait_timeout_out"] = 1 + + +class Test3(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["select_too_big_in"] = 0 + self.expect["select_too_big_out"] = 0 + + def select_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + n = event["n"] + overflow = event["overflow"] + tvp = event["tvp"] + _readfds_length = event["_readfds_length"] + readfds = event["readfds"] + _writefds_length = event["_writefds_length"] + writefds = event["writefds"] + _exceptfds_length = event["_exceptfds_length"] + exceptfds = event["exceptfds"] + + # make sure an invalid value still produces a valid event + if n == 2048 and overflow == 0 and _readfds_length == 0: + self.expect["select_too_big_in"] = 1 + + def select_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + overflow = event["overflow"] + tvp = event["tvp"] + _readfds_length = event["_readfds_length"] + readfds = event["readfds"] + _writefds_length = event["_writefds_length"] + writefds = event["writefds"] + _exceptfds_length = event["_exceptfds_length"] + exceptfds = event["exceptfds"] + + # make sure an invalid value still produces a valid event + if ret == -9 and overflow == 0 and _readfds_length == 0: + self.expect["select_too_big_out"] = 1 + + +class Test4(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["big_poll_in"] = 0 + self.expect["big_poll_out"] = 0 + + def poll_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + nfds = event["nfds"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # test of big list of FDs and the behaviour of the overflow + if nfds == 2047 and fds_length == 512 and overflow == 1 and \ + fds[0]["raw_events"] == 0x3 \ + and fds[0]["events"]["POLLIN"] == 1 and \ + fds[0]["events"]["padding"] == 0 and \ + fds[511]["events"]["POLLIN"] == 1 and \ + fds[511]["events"]["POLLPRI"] == 1: + self.expect["big_poll_in"] = 1 + + def poll_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + nfds = event["nfds"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # test of big list of FDs and the behaviour of the overflow + if ret == 2047 and nfds == 2047 and fds_length == 512 and \ + overflow == 1 and fds[0]["events"]["POLLIN"] == 1 and \ + fds[511]["events"]["POLLIN"] == 1: + self.expect["big_poll_out"] = 1 + + +class Test5(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["poll_overflow_in"] = 0 + self.expect["poll_overflow_out"] = 0 + + def poll_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + nfds = event["nfds"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # test that event in valid even though the target buffer is too small + # and the program segfaults + if nfds == 100 and fds_length == 100 and overflow == 0 and \ + fds[0]["events"]["POLLIN"] == 1: + self.expect["poll_overflow_in"] = 1 + + def poll_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + nfds = event["nfds"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # test that event in valid even though the target buffer is too small + # and the program segfaults + if nfds == 100 and overflow == 0: + self.expect["poll_overflow_out"] = 1 + + +class Test6(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["pselect_invalid_in"] = 0 + self.expect["pselect_invalid_out"] = 0 + + def select_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + n = event["n"] + overflow = event["overflow"] + tvp = event["tvp"] + _readfds_length = event["_readfds_length"] + readfds = event["readfds"] + _writefds_length = event["_writefds_length"] + writefds = event["writefds"] + _exceptfds_length = event["_exceptfds_length"] + exceptfds = event["exceptfds"] + + # test that event in valid even though the target buffer pointer is + # invalid and the program segfaults + if n == 1 and overflow == 0 and _readfds_length == 0: + self.expect["pselect_invalid_in"] = 1 + + def select_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + overflow = event["overflow"] + tvp = event["tvp"] + _readfds_length = event["_readfds_length"] + readfds = event["readfds"] + _writefds_length = event["_writefds_length"] + writefds = event["writefds"] + _exceptfds_length = event["_exceptfds_length"] + exceptfds = event["exceptfds"] + + # test that event in valid even though the target buffer pointer is + # invalid and the program segfaults + if ret == -14 and overflow == 0 and _readfds_length == 0: + self.expect["pselect_invalid_out"] = 1 + + +class Test7(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["poll_max_in"] = 0 + self.expect["poll_max_out"] = 0 + + def poll_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + nfds = event["nfds"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # check the proper working of INT_MAX maxevent value + if nfds == 4294967295 and overflow == 1: + self.expect["poll_max_in"] = 1 + + def poll_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + nfds = event["nfds"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # check the proper working of UINT_MAX maxevent value + if ret == -22 and nfds == 4294967295 and overflow == 0: + self.expect["poll_max_out"] = 1 + + +class Test8(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["epoll_wait_invalid_in"] = 0 + self.expect["epoll_wait_invalid_out"] = 0 + + def epoll_wait_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + epfd = event["epfd"] + maxevents = event["maxevents"] + timeout = event["timeout"] + + # test that event in valid even though the target buffer pointer is + # invalid and the program segfaults + if epfd == 3 and maxevents == 1 and timeout == -1: + self.expect["epoll_wait_invalid_in"] = 1 + + def epoll_wait_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # test that event in valid even though the target buffer pointer is + # invalid and the program segfaults + if ret == -14 and fds_length == 0 and overflow == 0: + self.expect["epoll_wait_invalid_out"] = 1 + + +class Test9(TraceParser): + def __init__(self, trace, pid): + super().__init__(trace, pid) + self.expect["epoll_wait_max_in"] = 0 + self.expect["epoll_wait_max_out"] = 0 + + def epoll_wait_entry(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + epfd = event["epfd"] + maxevents = event["maxevents"] + timeout = event["timeout"] + + # check the proper working of INT_MAX maxevent value + if epfd == 3 and maxevents == 2147483647 and timeout == -1: + self.expect["epoll_wait_max_in"] = 1 + + def epoll_wait_exit(self, event): + timestamp = event.timestamp + cpu_id = event["cpu_id"] + ret = event["ret"] + fds_length = event["fds_length"] + overflow = event["overflow"] + fds = event["fds"] + + # check the proper working of INT_MAX maxevent value + if ret == -22 and fds_length == 0 and overflow == 0: + self.expect["epoll_wait_max_out"] = 1 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Trace parser') + parser.add_argument('path', metavar="", help='Trace path') + parser.add_argument('-t', '--test', type=int, help='Test to validate') + parser.add_argument('-p', '--pid', type=int, help='PID of the app') + args = parser.parse_args() + + if not args.test: + print("Need to pass a test to validate (-t)") + sys.exit(1) + + if not args.pid: + print("Need to pass the PID to check (-p)") + sys.exit(1) + + traces = TraceCollection() + handle = traces.add_traces_recursive(args.path, "ctf") + if handle is None: + sys.exit(1) + + t = None + + if args.test == 1: + t = Test1(traces, args.pid) + elif args.test == 2: + t = Test2(traces, args.pid) + elif args.test == 3: + t = Test3(traces, args.pid) + elif args.test == 4: + t = Test4(traces, args.pid) + elif args.test == 5: + t = Test5(traces, args.pid) + elif args.test == 6: + t = Test6(traces, args.pid) + elif args.test == 7: + t = Test7(traces, args.pid) + elif args.test == 8: + t = Test8(traces, args.pid) + elif args.test == 9: + t = Test9(traces, args.pid) + elif args.test == 10: + # stress test, nothing reliable to check + ret = 0 + elif args.test == 11: + # stress test, nothing reliable to check + ret = 0 + else: + print("Invalid test case") + sys.exit(1) + + if t is not None: + ret = t.parse() + + for h in handle.values(): + traces.remove_trace(h) + + sys.exit(ret) diff --git a/tests/root_regression b/tests/root_regression index 2981dc6d4..19af1ed9a 100644 --- a/tests/root_regression +++ b/tests/root_regression @@ -3,6 +3,7 @@ regression/kernel/test_event_basic regression/kernel/test_syscall regression/kernel/test_clock_override regression/kernel/test_rotation_destroy_flush +regression/kernel/test_select_poll_epoll regression/tools/live/test_kernel regression/tools/live/test_lttng_kernel regression/tools/streaming/test_high_throughput_limits -- 2.34.1