Hi Shuah,

Could you take a look at this patch?

Thanks!

On Fri, Oct 17, 2025 at 1:40 AM Kuniyuki Iwashima <[email protected]> wrote:
>
> While writing a selftest with kselftest_harness.h, I often want to
> check which paths are actually exercised.
>
> Let's support generating KCOV coverage data.
>
> We can specify the output directory via the KCOV_OUTPUT environment
> variable, and the number of instructions to collect via the KCOV_SLOTS
> environment variable.
>
>   # KCOV_OUTPUT=$PWD/kcov KCOV_SLOTS=$((4096 * 2)) \
>     ./tools/testing/selftests/net/af_unix/scm_inq
>
> Both variables can also be specified as the make variable.
>
>   # make -C tools/testing/selftests/ \
>     KCOV_OUTPUT=$PWD/kcov KCOV_SLOTS=$((4096 * 4)) \
>     kselftest_override_timeout=60 TARGETS=net/af_unix run_tests
>
> The coverage data can be simply decoded with addr2line:
>
>   $ cat kcov/* | sort | uniq | addr2line -e vmlinux | grep unix
>   net/unix/af_unix.c:1056
>   net/unix/af_unix.c:3138
>   net/unix/af_unix.c:3834
>   net/unix/af_unix.c:3838
>   net/unix/af_unix.c:311 (discriminator 2)
>   ...
>
> or more nicely with a script embedded in vock [0]:
>
>   $ cat kcov/* | sort | uniq > local.log
>   $ python3 ~/kernel/tools/vock/report.py \
>     --kernel-src ./ --vmlinux ./vmlinux \
>     --mode local --local-log local.log --filter unix
>   ...
>   ------------------------------- Coverage Report 
> --------------------------------
>   📄 net/unix/af_unix.c (276 lines)
>    ...
>   942 | static int unix_setsockopt(struct socket *sock, int level, int 
> optname,
>   943 |                            sockptr_t optval, unsigned int optlen)
>   944 | {
>    ...
>   961 |         switch (optname) {
>   962 |         case SO_INQ:
>   963 >                 if (sk->sk_type != SOCK_STREAM)
>   964 |                         return -EINVAL;
>   965 |
>   966 >                 if (val > 1 || val < 0)
>   967 |                         return -EINVAL;
>   968 |
>   969 >                 WRITE_ONCE(u->recvmsg_inq, val);
>   970 |                 break;
>
> Link: 
> https://github.com/kzall0c/vock/blob/f3d97de9954f9df758c0ab287ca7e24e654288c7/report.py
>  #[0]
> Signed-off-by: Kuniyuki Iwashima <[email protected]>
> ---
>  Documentation/dev-tools/kselftest.rst       |  41 +++++++
>  tools/testing/selftests/Makefile            |  14 ++-
>  tools/testing/selftests/kselftest_harness.h | 128 +++++++++++++++++++-
>  3 files changed, 174 insertions(+), 9 deletions(-)
>
> diff --git a/Documentation/dev-tools/kselftest.rst 
> b/Documentation/dev-tools/kselftest.rst
> index 18c2da67fae42..5c2b92ac4a300 100644
> --- a/Documentation/dev-tools/kselftest.rst
> +++ b/Documentation/dev-tools/kselftest.rst
> @@ -200,6 +200,47 @@ You can look at the TAP output to see if you ran into 
> the timeout. Test
>  runners which know a test must run under a specific time can then optionally
>  treat these timeouts then as fatal.
>
> +KCOV for selftests
> +==================
> +
> +Selftests built with `kselftest_harness.h` natively support generating
> +KCOV coverage data.  See :doc:`KCOV: code coverage for fuzzing 
> </dev-tools/kcov>`
> +for prerequisites.
> +
> +You can specify the output directory with the `KCOV_OUTPUT` environment
> +variable.  Additionally, you can specify the number of instructions to
> +collect with the `KCOV_SLOTS` environment variable ::
> +
> +  # KCOV_OUTPUT=$PWD/kcov KCOV_SLOTS=$((4096 * 2)) \
> +        ./tools/testing/selftests/net/af_unix/scm_inq
> +
> +In the output directory, a coverage file is generated for each test
> +case in the selftest ::
> +
> +  $ ls kcov/
> +  scm_inq.dgram.basic  scm_inq.seqpacket.basic  scm_inq.stream.basic
> +
> +The default value of `KCOV_SLOTS` is `4096`, and `KCOV_SLOTS` multiplied
> +by `sizeof(unsigned long)` must be multiple of `4096`, so the smallest
> +value is `512`.
> +
> +Both `KCOV_OUTPUT` and `KCOV_SLOTS` can be specified as the variables
> +on the `make` command line ::
> +
> +  # make -C tools/testing/selftests/ \
> +        kselftest_override_timeout=60 \
> +        KCOV_OUTPUT=$PWD/kcov KCOV_SLOTS=$((4096 * 4)) \
> +        TARGETS=net/af_unix run_tests
> +
> +The collected data can be decoded with `addr2line` ::
> +
> +  $ cat kcov/* | sort | uniq | addr2line -e vmlinux | grep unix
> +  net/unix/af_unix.c:1056
> +  net/unix/af_unix.c:3138
> +  net/unix/af_unix.c:3834
> +  net/unix/af_unix.c:3838
> +  ...
> +
>  Packaging selftests
>  ===================
>
> diff --git a/tools/testing/selftests/Makefile 
> b/tools/testing/selftests/Makefile
> index c46ebdb9b8ef7..40e70fb1a3478 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -218,12 +218,14 @@ all:
>         done; exit $$ret;
>
>  run_tests: all
> -       @for TARGET in $(TARGETS); do \
> -               BUILD_TARGET=$$BUILD/$$TARGET;  \
> -               $(MAKE) OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests \
> -                               SRC_PATH=$(shell readlink -e $$(pwd)) \
> -                               OBJ_PATH=$(BUILD)                   \
> -                               O=$(abs_objtree);                   \
> +       @for TARGET in $(TARGETS); do                           \
> +               BUILD_TARGET=$$BUILD/$$TARGET;                  \
> +               $(MAKE) OUTPUT=$$BUILD_TARGET                   \
> +                       KCOV_OUTPUT=$(abspath $(KCOV_OUTPUT))   \
> +                       -C $$TARGET run_tests                   \
> +                       SRC_PATH=$(shell readlink -e $$(pwd))   \
> +                       OBJ_PATH=$(BUILD)                       \
> +                       O=$(abs_objtree);                       \
>         done;
>
>  hotplug:
> diff --git a/tools/testing/selftests/kselftest_harness.h 
> b/tools/testing/selftests/kselftest_harness.h
> index 3f66e862e83eb..cba8020853b5d 100644
> --- a/tools/testing/selftests/kselftest_harness.h
> +++ b/tools/testing/selftests/kselftest_harness.h
> @@ -56,6 +56,8 @@
>  #include <asm/types.h>
>  #include <ctype.h>
>  #include <errno.h>
> +#include <fcntl.h>
> +#include <linux/kcov.h>
>  #include <linux/unistd.h>
>  #include <poll.h>
>  #include <stdbool.h>
> @@ -63,7 +65,9 @@
>  #include <stdio.h>
>  #include <stdlib.h>
>  #include <string.h>
> +#include <sys/ioctl.h>
>  #include <sys/mman.h>
> +#include <sys/stat.h>
>  #include <sys/types.h>
>  #include <sys/wait.h>
>  #include <unistd.h>
> @@ -401,7 +405,8 @@
>                 const FIXTURE_VARIANT(fixture_name) *variant); \
>         static void wrapper_##fixture_name##_##test_name( \
>                 struct __test_metadata *_metadata, \
> -               struct __fixture_variant_metadata *variant) \
> +               struct __fixture_variant_metadata *variant, \
> +               char *test_full_name) \
>         { \
>                 /* fixture data is alloced, setup, and torn down per call. */ 
> \
>                 FIXTURE_DATA(fixture_name) self_private, *self = NULL; \
> @@ -430,7 +435,9 @@
>                         if (_metadata->exit_code) \
>                                 _exit(0); \
>                         *_metadata->no_teardown = false; \
> +                       enable_kcov(_metadata); \
>                         fixture_name##_##test_name(_metadata, self, 
> variant->data); \
> +                       disable_kcov(_metadata, test_full_name); \
>                         _metadata->teardown_fn(false, _metadata, self, 
> variant->data); \
>                         _exit(0); \
>                 } else if (child < 0 || child != waitpid(child, &status, 0)) 
> { \
> @@ -470,6 +477,8 @@
>                 object->teardown_fn = 
> &wrapper_##fixture_name##_##test_name##_teardown; \
>                 object->termsig = signal; \
>                 object->timeout = tmout; \
> +               object->kcov_fd = -1; \
> +               object->kcov_slots = -1; \
>                 _##fixture_name##_##test_name##_object = object; \
>                 __register_test(object); \
>         } \
> @@ -908,7 +917,8 @@ __register_fixture_variant(struct __fixture_metadata *f,
>  struct __test_metadata {
>         const char *name;
>         void (*fn)(struct __test_metadata *,
> -                  struct __fixture_variant_metadata *);
> +                  struct __fixture_variant_metadata *,
> +                  char *test_name);
>         pid_t pid;      /* pid of test when being run */
>         struct __fixture_metadata *fixture;
>         void (*teardown_fn)(bool in_parent, struct __test_metadata *_metadata,
> @@ -923,6 +933,10 @@ struct __test_metadata {
>         const void *variant;
>         struct __test_results *results;
>         struct __test_metadata *prev, *next;
> +       int kcov_fd;
> +       int kcov_slots;
> +       char *kcov_dir;
> +       unsigned long *kcov_mem;
>  };
>
>  static inline bool __test_passed(struct __test_metadata *metadata)
> @@ -1185,6 +1199,114 @@ static bool test_enabled(int argc, char **argv,
>         return !has_positive;
>  }
>
> +#define KCOV_SLOTS 4096
> +
> +static void enable_kcov(struct __test_metadata *t)
> +{
> +       char *slots;
> +       int err;
> +
> +       t->kcov_dir = getenv("KCOV_OUTPUT");
> +       if (!t->kcov_dir || *t->kcov_dir == '\0')
> +               return;
> +
> +       slots = getenv("KCOV_SLOTS");
> +       if (slots && *slots != '\0')
> +               sscanf(slots, "%d", &t->kcov_slots);
> +       if (t->kcov_slots <= 0)
> +               t->kcov_slots = KCOV_SLOTS;
> +
> +       t->kcov_fd = open("/sys/kernel/debug/kcov", O_RDWR);
> +       if (t->kcov_fd < 0) {
> +               ksft_print_msg("ERROR OPENING KCOV FD\n");
> +               goto err;
> +       }
> +
> +       err = ioctl(t->kcov_fd, KCOV_INIT_TRACE, t->kcov_slots);
> +       if (err) {
> +               ksft_print_msg("ERROR INITIALISING KCOV\n");
> +               goto err;
> +       }
> +
> +       t->kcov_mem = mmap(NULL, sizeof(unsigned long) * t->kcov_slots,
> +                          PROT_READ | PROT_WRITE, MAP_SHARED, t->kcov_fd, 0);
> +       if ((void *)t->kcov_mem == MAP_FAILED) {
> +               ksft_print_msg("ERROR ALLOCATING MEMORY FOR KCOV\n");
> +               goto err;
> +       }
> +
> +       err = ioctl(t->kcov_fd, KCOV_ENABLE, KCOV_TRACE_PC);
> +       if (err) {
> +               ksft_print_msg("ERROR ENABLING KCOV\n");
> +               goto err;
> +       }
> +
> +       __atomic_store_n(&t->kcov_mem[0], 0, __ATOMIC_RELAXED);
> +       return;
> +err:
> +       t->exit_code = KSFT_FAIL;
> +       _exit(KSFT_FAIL);
> +}
> +
> +static void disable_kcov(struct __test_metadata *t, char *test_name)
> +{
> +       int slots, err, dir, fd, i;
> +
> +       if (t->kcov_fd == -1)
> +               return;
> +
> +       slots = __atomic_load_n(&t->kcov_mem[0], __ATOMIC_RELAXED);
> +       if (slots == t->kcov_slots - 1)
> +               ksft_print_msg("Set KCOV_SLOTS to a value greater than %d\n", 
> t->kcov_slots);
> +
> +       err = ioctl(t->kcov_fd, KCOV_DISABLE, 0);
> +       if (err) {
> +               ksft_print_msg("ERROR DISABLING KCOV\n");
> +               goto out;
> +       }
> +
> +       err = mkdir(t->kcov_dir, 0755);
> +       if (err == -1 && errno != EEXIST) {
> +               ksft_print_msg("ERROR CREATING '%s'\n", t->kcov_dir);
> +               goto out;
> +       }
> +       err = 0;
> +
> +       dir = open(t->kcov_dir, O_DIRECTORY);
> +       if (dir < 0) {
> +               ksft_print_msg("ERROR OPENING %s\n", t->kcov_dir);
> +               err = dir;
> +               goto out;
> +       }
> +
> +       fd = openat(dir, test_name, O_RDWR | O_CREAT | O_TRUNC);
> +
> +       close(dir);
> +
> +       if (fd == -1) {
> +               ksft_print_msg("ERROR CREATING '%s' at '%s'\n", test_name, 
> t->kcov_dir);
> +               err = fd;
> +               goto out;
> +       }
> +
> +       for (i = 0; i < slots; i++) {
> +               char buf[64];
> +               int size;
> +
> +               size = snprintf(buf, 64, "0x%lx\n", t->kcov_mem[i + 1]);
> +               write(fd, buf, size);
> +       }
> +
> +out:
> +       munmap(t->kcov_mem, sizeof(t->kcov_mem[0]) * t->kcov_slots);
> +       close(t->kcov_fd);
> +
> +       if (err) {
> +               t->exit_code = KSFT_FAIL;
> +               _exit(KSFT_FAIL);
> +       }
> +}
> +
>  static void __run_test(struct __fixture_metadata *f,
>                        struct __fixture_variant_metadata *variant,
>                        struct __test_metadata *t)
> @@ -1216,7 +1338,7 @@ static void __run_test(struct __fixture_metadata *f,
>                 t->exit_code = KSFT_FAIL;
>         } else if (child == 0) {
>                 setpgrp();
> -               t->fn(t, variant);
> +               t->fn(t, variant, test_name);
>                 _exit(t->exit_code);
>         } else {
>                 t->pid = child;
> --
> 2.51.0.858.gf9c4a03a3a-goog
>

Reply via email to