[PATCH RFC v3 5/8] selftests/kcov_dataflow: add binderfs selftest

0 views
Skip to first unread message

Yunseong Kim

unread,
Sep 2, 2026, 12:30:13 PM (8 days ago) Sep 2
to linux-...@vger.kernel.org, kasa...@googlegroups.com, linu...@kvack.org, linux-...@vger.kernel.org, rust-fo...@vger.kernel.org, ll...@lists.linux.dev, work...@vger.kernel.org, linu...@vger.kernel.org, linux-k...@vger.kernel.org, Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider, K Prateek Nayak, Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Kees Cook, Nathan Chancellor, Nicolas Schier, Josh Poimboeuf, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein, Alexandre Courbot, Onur Özkan, Nick Desaulniers, Bill Wendling, Marco Elver, Nikita Popov, Matt Arsenault, Justin Stitt, Jonathan Corbet, Shuah Khan, Randy Dunlap, Shuah Khan, Yeoreum Yun, Yunseong Kim, Yunseong Kim
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 <yunseo...@est.tech>
---
.../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 all
Reply to author
Forward
0 new messages