Exercise the binder driver via binderfs with kcov_dataflow recording
active. Verifies that function argument records are captured at binder
ioctl boundaries (BINDER_VERSION, BINDER_SET_MAX_THREADS).

Requires CONFIG_ANDROID_BINDER_IPC=y and CONFIG_ANDROID_BINDERFS=y.
Gracefully skips if binderfs is not available.

Build and test:

  export PATH=$PWD/../llvm-project/build/bin:$PATH
  vng --build \
    --configitem CONFIG_KCOV=y \
    --configitem CONFIG_KCOV_DATAFLOW_ARGS=y \
    --configitem CONFIG_KCOV_DATAFLOW_RET=y \
    --configitem CONFIG_KCOV_DATAFLOW_INSTRUMENT_ALL=y \
    --configitem CONFIG_DEBUG_INFO=y \
    --configitem CONFIG_ANDROID_BINDER_IPC=y \
    --configitem CONFIG_ANDROID_BINDERFS=y \
    LLVM=1 CC=clang

  make -C tools/testing/selftests/kcov_dataflow/binderfs
  vng --user root --exec \
    tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test

Result:

  TAP version 13
  1..3
  ok 1 kcov_dataflow.binderfs_setup
  ok 2 kcov_dataflow.binderfs_captured # 636 words
  ok 3 kcov_dataflow.binderfs_valid_records
  # Totals: pass:3 fail:0 skip:0
  #
  # Captured call records:
  #   ENTRY pc=0xffffffff... arg=0x4           (fd)
  #   ENTRY pc=0xffffffff... arg=0xc0046209    (BINDER_VERSION)
  #   ENTRY pc=0xffffffff... arg=0x0           (binder_get_thread)
  #   RET   pc=0xffffffff... ret=0x0           (success)
  #   ENTRY pc=0xffffffff... arg=0x40046205    (SET_MAX_THREADS)
  #   ENTRY pc=0xffffffff... arg=0x4           (_copy_from_user size)

Cc: Alexander Potapenko <[email protected]>
Assisted-by: Claude:claude-opus-4-6 [kiro-chat]
Signed-off-by: Yunseong Kim <[email protected]>
---
 tools/testing/selftests/kcov_dataflow/.gitignore   |   1 +
 .../selftests/kcov_dataflow/binderfs/Makefile      |   4 +
 .../kcov_dataflow/binderfs/binderfs_test.c         | 177 +++++++++++++++++++++
 .../selftests/kcov_dataflow/run_binderfs.sh        |  13 ++
 4 files changed, 195 insertions(+)

diff --git a/tools/testing/selftests/kcov_dataflow/.gitignore 
b/tools/testing/selftests/kcov_dataflow/.gitignore
index f71fc89580f8..da4c189ad3be 100644
--- a/tools/testing/selftests/kcov_dataflow/.gitignore
+++ b/tools/testing/selftests/kcov_dataflow/.gitignore
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 user_ioctl/user_ioctl
+binderfs/binderfs_test
 *.o
 *.ko
 *.mod
diff --git a/tools/testing/selftests/kcov_dataflow/binderfs/Makefile 
b/tools/testing/selftests/kcov_dataflow/binderfs/Makefile
new file mode 100644
index 000000000000..9f1588512dba
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/binderfs/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := binderfs_test
+CFLAGS += -Wall -O2
+include ../../lib.mk
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..ce9b49aa0b9f
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/binderfs/binderfs_test.c
@@ -0,0 +1,177 @@
+// 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>
+
+#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       (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");
+
+       /* Verify at least one record has valid header (type 0xE or 0xF) */
+
+       if (total > 3) {
+               uint64_t hdr = buf[1];
+               uint32_t type = (hdr >> 28) & 0xF;
+
+               if (type == 0xE || type == 0xF)
+                       valid = 1;
+       }
+
+       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;
+}
diff --git a/tools/testing/selftests/kcov_dataflow/run_binderfs.sh 
b/tools/testing/selftests/kcov_dataflow/run_binderfs.sh
new file mode 100755
index 000000000000..5376e5350061
--- /dev/null
+++ b/tools/testing/selftests/kcov_dataflow/run_binderfs.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Test binderfs ioctl capture via kcov_dataflow
+DIR="$(dirname "$0")"
+BIN="$DIR/binderfs/binderfs_test"
+
+if [ ! -f "$BIN" ]; then
+       echo "SKIP: $BIN not found"
+       echo "Build: make -C tools/testing/selftests/kcov_dataflow/binderfs"
+       exit 4
+fi
+
+exec "$BIN"

-- 
2.43.0


Reply via email to