Add a kselftest that exercises the binder driver through binderfs with
kcov_dataflow recording active, checking that argument records are
captured at real driver ioctl boundaries rather than in a purpose-built
module.

The test mounts binderfs, creates a device with BINDER_CTL_ADD, enables
recording, and issues BINDER_VERSION and BINDER_SET_MAX_THREADS. It then
walks the buffer: every record must carry a known type (ENTRY/RET, or CMP
when comparisons are interleaved) and at least one value word, the walk
must end exactly at area[0], and at least one ENTRY/RET record must have
been produced.

It needs binder instrumented, i.e. KCOV_DATAFLOW := y in
drivers/android/Makefile or CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y, and
SKIPs cleanly when binderfs is unavailable.

Assisted-by: Claude:claude-opus-4-6 [kiro-chat]
Signed-off-by: Yunseong Kim <[email protected]>
---
 .../selftests/kcov_dataflow/binderfs/Makefile      |   5 +
 .../selftests/kcov_dataflow/binderfs/README.rst    |  13 ++
 .../kcov_dataflow/binderfs/binderfs_test.c         | 195 +++++++++++++++++++++
 3 files changed, 213 insertions(+)

diff --git a/tools/testing/selftests/kcov_dataflow/binderfs/Makefile 
b/tools/testing/selftests/kcov_dataflow/binderfs/Makefile
new file mode 100644
index 000000000000..b35de6264992
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/binderfs/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+# Standalone build of the binderfs test: make -C 
tools/testing/selftests/kcov_dataflow/binderfs
+TEST_GEN_PROGS := binderfs_test
+CFLAGS += -Wall -O2 $(KHDR_INCLUDES)
+include ../../lib.mk
diff --git a/tools/testing/selftests/kcov_dataflow/binderfs/README.rst 
b/tools/testing/selftests/kcov_dataflow/binderfs/README.rst
new file mode 100644
index 000000000000..7fcdce1955c1
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/binderfs/README.rst
@@ -0,0 +1,13 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+KCOV-Dataflow Selftests: binderfs
+=================================
+
+Exercises the binder driver via binderfs with kcov_dataflow recording
+active and verifies that argument records are captured at the binder
+ioctl boundaries. Needs CONFIG_ANDROID_BINDERFS=y and binder instrumented
+(``KCOV_DATAFLOW := y`` in drivers/android/Makefile or
+CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y); SKIPs without binderfs::
+
+  make -C tools/testing/selftests TARGETS=kcov_dataflow
+  tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test
diff --git a/tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test.c 
b/tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test.c
new file mode 100644
index 000000000000..650798e09b20
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * binderfs selftest for kcov_dataflow
+ *
+ * Exercises the binder driver via binderfs with kcov_dataflow recording
+ * active, then verifies that function argument records were captured at
+ * binder ioctl boundaries.
+ *
+ * Requires: CONFIG_ANDROID_BINDER_IPC=y (or _RUST), CONFIG_ANDROID_BINDERFS=y
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <linux/android/binder.h>
+#include <linux/android/binderfs.h>
+#include <linux/kcov_dataflow.h>
+
+
+#define BUF_SIZE       (1 << 20)
+#define BINDERFS_PATH  "/tmp/binderfs_test"
+#define BINDER_DEV     BINDERFS_PATH "/my_binder"
+
+static int setup_binderfs(void)
+{
+       struct binderfs_device dev = {};
+
+       mkdir(BINDERFS_PATH, 0755);
+
+       if (mount("binder", BINDERFS_PATH, "binder", 0, NULL)) {
+               if (errno == ENODEV || errno == ENOENT) {
+                       printf("SKIP: binderfs not available\n");
+                       return -1;
+               }
+               perror("mount binderfs");
+               return -1;
+       }
+
+       /* Create a binder device via BINDER_CTL_ADD ioctl */
+       int ctl_fd;
+
+       ctl_fd = open(BINDERFS_PATH "/binder-control", O_RDONLY);
+       if (ctl_fd < 0) {
+               perror("open binder-control");
+               umount(BINDERFS_PATH);
+               return -1;
+       }
+
+       strcpy(dev.name, "my_binder");
+       if (ioctl(ctl_fd, BINDER_CTL_ADD, &dev) && errno != EEXIST) {
+               perror("BINDER_CTL_ADD");
+               close(ctl_fd);
+               umount(BINDERFS_PATH);
+               return -1;
+       }
+       close(ctl_fd);
+       return 0;
+}
+
+static void cleanup_binderfs(void)
+{
+       umount(BINDERFS_PATH);
+       rmdir(BINDERFS_PATH);
+}
+
+int main(void)
+{
+       uint64_t *buf;
+       int df_fd, binder_fd;
+       uint64_t total;
+       int valid = 0;
+
+       printf("TAP version 13\n");
+       printf("1..3\n");
+
+       /* Setup binderfs */
+       if (setup_binderfs()) {
+               printf("ok 1 # SKIP binderfs not available\n");
+               printf("ok 2 # SKIP\n");
+               printf("ok 3 # SKIP\n");
+               return 0;
+       }
+
+       /* Open kcov_dataflow */
+       df_fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+       if (df_fd < 0) {
+               printf("not ok 1 cannot open kcov_dataflow\n");
+               cleanup_binderfs();
+               return 1;
+       }
+
+       if (ioctl(df_fd, KCOV_DF_INIT_TRACK, BUF_SIZE)) {
+               printf("not ok 1 INIT_TRACK failed\n");
+               close(df_fd);
+               cleanup_binderfs();
+               return 1;
+       }
+
+       buf = mmap(NULL, BUF_SIZE * sizeof(uint64_t),
+                  PROT_READ | PROT_WRITE, MAP_SHARED, df_fd, 0);
+       if (buf == MAP_FAILED) {
+               printf("not ok 1 mmap failed\n");
+               close(df_fd);
+               cleanup_binderfs();
+               return 1;
+       }
+
+       printf("ok 1 kcov_dataflow.binderfs_setup\n");
+
+       /* Open binder device */
+       binder_fd = open(BINDER_DEV, O_RDWR | O_CLOEXEC);
+       if (binder_fd < 0) {
+               printf("not ok 2 cannot open %s: %s\n", BINDER_DEV,
+                      strerror(errno));
+               munmap(buf, BUF_SIZE * sizeof(uint64_t));
+               close(df_fd);
+               cleanup_binderfs();
+               return 1;
+       }
+
+       /* Enable recording and exercise binder ioctls */
+       ioctl(df_fd, KCOV_DF_ENABLE, 0);
+       __atomic_store_n(&buf[0], 0, __ATOMIC_RELAXED);
+
+       /* BINDER_VERSION - simple ioctl that exercises the binder path */
+       struct binder_version ver = {};
+
+       ioctl(binder_fd, BINDER_VERSION, &ver);
+
+       /* BINDER_SET_MAX_THREADS */
+       uint32_t max_threads = 4;
+
+       ioctl(binder_fd, BINDER_SET_MAX_THREADS, &max_threads);
+
+       ioctl(df_fd, KCOV_DF_DISABLE, 0);
+
+       total = __atomic_load_n(&buf[0], __ATOMIC_RELAXED);
+       close(binder_fd);
+
+       if (total > 0)
+               printf("ok 2 kcov_dataflow.binderfs_captured # %lu words\n",
+                      (unsigned long)total);
+       else
+               printf("not ok 2 kcov_dataflow.binderfs_captured # 0 words\n");
+
+       /*
+        * Walk the records: every header must carry a known type and at least
+        * one value word, the walk must end exactly at area[0], and at least 
one
+        * ENTRY/RET record must come from the binder ioctls (CMP records are
+        * interleaved with CONFIG_KCOV_ENABLE_COMPARISONS=y).
+        */
+       if (total <= BUF_SIZE - 1) {
+               uint64_t pos = 1, end = 1 + total;
+               unsigned long nargs = 0;
+
+               while (pos + KCOV_DF_RECORD_HDR_WORDS <= end) {
+                       uint64_t hdr = buf[pos];
+                       uint32_t type = KCOV_DF_HDR_TYPE(hdr);
+                       uint32_t nvals = KCOV_DF_HDR_NVALS(hdr);
+
+                       if (nvals < 1 || (type != KCOV_DF_TYPE_ENTRY &&
+                                         type != KCOV_DF_TYPE_RET &&
+                                         type != KCOV_DF_TYPE_CMP))
+                               break;
+                       if (type != KCOV_DF_TYPE_CMP)
+                               nargs++;
+                       pos += KCOV_DF_RECORD_WORDS(nvals);
+               }
+               if (pos == end && nargs > 0)
+                       valid = 1;
+               else
+                       printf("# walk stopped at word %lu of %lu, %lu 
ENTRY/RET records\n",
+                              (unsigned long)pos, (unsigned long)end, nargs);
+       }
+
+       if (valid)
+               printf("ok 3 kcov_dataflow.binderfs_valid_records\n");
+       else
+               printf("not ok 3 kcov_dataflow.binderfs_valid_records\n");
+
+       printf("# Totals: pass:%d fail:%d skip:0\n",
+              valid ? 3 : 2, valid ? 0 : 1);
+
+       munmap(buf, BUF_SIZE * sizeof(uint64_t));
+       close(df_fd);
+       cleanup_binderfs();
+       return valid ? 0 : 1;
+}

-- 
2.47.3


Reply via email to