Add kselftest_harness-based test in user_ioctl/ covering the kcov_dataflow ioctl interface (9 TAP cases): init, mmap, enable, disable, error paths, double-enable rejection, and record capture.
Test: make -C tools/testing/selftests/kcov_dataflow ./user_ioctl/user_ioctl Result: TAP version 13 1..9 # Starting 9 tests from 1 test cases. # RUN kcov_dataflow.init_track ... # OK kcov_dataflow.init_track ok 1 kcov_dataflow.init_track # RUN kcov_dataflow.init_track_too_small ... # OK kcov_dataflow.init_track_too_small ok 2 kcov_dataflow.init_track_too_small # RUN kcov_dataflow.init_track_double ... # OK kcov_dataflow.init_track_double ok 3 kcov_dataflow.init_track_double # RUN kcov_dataflow.mmap_before_init ... # OK kcov_dataflow.mmap_before_init ok 4 kcov_dataflow.mmap_before_init # RUN kcov_dataflow.enable_disable ... # OK kcov_dataflow.enable_disable ok 5 kcov_dataflow.enable_disable # RUN kcov_dataflow.enable_without_mmap ... # OK kcov_dataflow.enable_without_mmap ok 6 kcov_dataflow.enable_without_mmap # RUN kcov_dataflow.disable_without_enable ... # OK kcov_dataflow.disable_without_enable ok 7 kcov_dataflow.disable_without_enable # RUN kcov_dataflow.double_enable ... # OK kcov_dataflow.double_enable ok 8 kcov_dataflow.double_enable # RUN kcov_dataflow.records_captured ... # OK kcov_dataflow.records_captured Cc: Alexander Potapenko <[email protected]> Assisted-by: Claude:claude-opus-4-6 [kiro-chat] Link: https://github.com/yskzalloc/kcov-dataflow/actions Signed-off-by: Yunseong Kim <[email protected]> --- tools/testing/selftests/kcov_dataflow/.gitignore | 8 ++ tools/testing/selftests/kcov_dataflow/Makefile | 3 + tools/testing/selftests/kcov_dataflow/README.rst | 37 +++++ .../kcov_dataflow/user_ioctl/user_ioctl.c | 156 +++++++++++++++++++++ 4 files changed, 204 insertions(+) diff --git a/tools/testing/selftests/kcov_dataflow/.gitignore b/tools/testing/selftests/kcov_dataflow/.gitignore new file mode 100644 index 000000000000..f71fc89580f8 --- /dev/null +++ b/tools/testing/selftests/kcov_dataflow/.gitignore @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +user_ioctl/user_ioctl +*.o +*.ko +*.mod +*.mod.c +Module.symvers +modules.order diff --git a/tools/testing/selftests/kcov_dataflow/Makefile b/tools/testing/selftests/kcov_dataflow/Makefile new file mode 100644 index 000000000000..b9fc1c5f0104 --- /dev/null +++ b/tools/testing/selftests/kcov_dataflow/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := user_ioctl/user_ioctl +include ../lib.mk diff --git a/tools/testing/selftests/kcov_dataflow/README.rst b/tools/testing/selftests/kcov_dataflow/README.rst new file mode 100644 index 000000000000..8b650a62acb1 --- /dev/null +++ b/tools/testing/selftests/kcov_dataflow/README.rst @@ -0,0 +1,37 @@ +.. SPDX-License-Identifier: GPL-2.0 + +KCOV-Dataflow Selftests +======================== + +This directory contains selftests for the KCOV-Dataflow subsystem +(``/sys/kernel/debug/kcov_dataflow``). + +Prerequisites +------------- + +Build the kernel with:: + + CONFIG_KCOV=y + CONFIG_KCOV_DATAFLOW_ARGS=y + CONFIG_KCOV_DATAFLOW_RET=y + CONFIG_DEBUG_INFO=y + +For full capture, also enable:: + + CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y + +Tests +----- + +user_ioctl/user_ioctl.c + Automated ioctl interface test (9 TAP cases):: + + make -C tools/testing/selftests/kcov_dataflow + ./user_ioctl/user_ioctl + +trigger-view.py + Loads a test module via finit_module() with recording active, + prints captured records with symbol resolution:: + + python3 trigger-view.py <module_name> + python3 trigger-view.py <module_name> --raw diff --git a/tools/testing/selftests/kcov_dataflow/user_ioctl/user_ioctl.c b/tools/testing/selftests/kcov_dataflow/user_ioctl/user_ioctl.c new file mode 100644 index 000000000000..48448bc02d2f --- /dev/null +++ b/tools/testing/selftests/kcov_dataflow/user_ioctl/user_ioctl.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * kcov_dataflow_test.c - Selftest for /sys/kernel/debug/kcov_dataflow + * + * Verifies the ioctl interface: open, INIT_TRACK, mmap, ENABLE, DISABLE. + * With INSTRUMENT_ALL, also verifies that records are produced for + * syscalls executed while recording is active. + */ +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include "../../kselftest_harness.h" + +#define KCOV_DF_INIT_TRACK _IOR('d', 1, unsigned long) +#define KCOV_DF_ENABLE _IO('d', 100) +#define KCOV_DF_DISABLE _IO('d', 101) + +#define BUF_SIZE 65536 + +#define DF_TYPE_ENTRY 0xE +#define DF_TYPE_RET 0xF + +FIXTURE(kcov_dataflow) { + int fd; + uint64_t *buf; +}; + +FIXTURE_SETUP(kcov_dataflow) +{ + self->fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR); + if (self->fd < 0) + SKIP(return, "kcov_dataflow not available (need CONFIG_KCOV_DATAFLOW_ARGS)"); + self->buf = MAP_FAILED; +} + +FIXTURE_TEARDOWN(kcov_dataflow) +{ + if (self->buf != MAP_FAILED) + munmap(self->buf, BUF_SIZE * sizeof(uint64_t)); + if (self->fd >= 0) + close(self->fd); +} + +TEST_F(kcov_dataflow, init_track) +{ + int ret = ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE); + + ASSERT_EQ(0, ret); +} + +TEST_F(kcov_dataflow, init_track_too_small) +{ + int ret = ioctl(self->fd, KCOV_DF_INIT_TRACK, 1UL); + + ASSERT_EQ(-1, ret); + ASSERT_EQ(EINVAL, errno); +} + +TEST_F(kcov_dataflow, init_track_double) +{ + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE)); + ASSERT_EQ(-1, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE)); + ASSERT_EQ(EBUSY, errno); +} + +TEST_F(kcov_dataflow, mmap_before_init) +{ + self->buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t), + PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, 0); + ASSERT_EQ(MAP_FAILED, self->buf); +} + +TEST_F(kcov_dataflow, enable_disable) +{ + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE)); + self->buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t), + PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, 0); + ASSERT_NE(MAP_FAILED, self->buf); + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_ENABLE, 0)); + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_DISABLE, 0)); +} + +TEST_F(kcov_dataflow, enable_without_mmap) +{ + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE)); + /* enable works even without mmap (mmap is optional for setup) */ + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_ENABLE, 0)); + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_DISABLE, 0)); +} + +TEST_F(kcov_dataflow, disable_without_enable) +{ + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE)); + ASSERT_EQ(-1, ioctl(self->fd, KCOV_DF_DISABLE, 0)); + ASSERT_EQ(EINVAL, errno); +} + +TEST_F(kcov_dataflow, double_enable) +{ + int fd2; + + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE)); + self->buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t), + PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, 0); + ASSERT_NE(MAP_FAILED, self->buf); + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_ENABLE, 0)); + + /* Second fd should fail to enable (task already active) */ + fd2 = open("/sys/kernel/debug/kcov_dataflow", O_RDWR); + ASSERT_GE(fd2, 0); + ASSERT_EQ(0, ioctl(fd2, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE)); + ASSERT_EQ(-1, ioctl(fd2, KCOV_DF_ENABLE, 0)); + ASSERT_EQ(EBUSY, errno); + close(fd2); + + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_DISABLE, 0)); +} + +TEST_F(kcov_dataflow, records_captured) +{ + uint64_t count; + + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_INIT_TRACK, (unsigned long)BUF_SIZE)); + self->buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t), + PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, 0); + ASSERT_NE(MAP_FAILED, self->buf); + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_ENABLE, 0)); + + /* Trigger some kernel code in this task */ + getpid(); + + ASSERT_EQ(0, ioctl(self->fd, KCOV_DF_DISABLE, 0)); + + count = self->buf[0]; + /* + * With INSTRUMENT_ALL, getpid() produces records. + * Without it, count may be 0 (no instrumented code). + * Either way, the interface works correctly. + */ + if (count > 0) { + uint64_t hdr = self->buf[1]; + unsigned int type = (hdr >> 28) & 0xF; + + /* First record should be ENTRY or RET */ + ASSERT_TRUE(type == DF_TYPE_ENTRY || type == DF_TYPE_RET); + } +} + +TEST_HARNESS_MAIN -- 2.43.0

