[RFC PATCH v2 09/14] selftests/kcov_dataflow: add ioctl interface selftest

Yunseong Kim <[email protected]>
Newsgroups org.kernel.vger.linux-kbuild,dev.linux.lists.llvm,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kernel.vger.rust-for-linux,org.kernel.vger.workflows,org.kvack.linux-mm
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.