Add tools/kcov-dataflow/ with:

- trigger.c: userspace consumer that opens /sys/kernel/debug/kcov_dataflow,
  mmaps the buffer, enables recording, triggers a kernel path, and dumps
  the captured TLV records.

- kcov-view.py: visualization tool that parses and pretty-prints the
  binary TLV buffer with struct field expansion and symbol resolution.

- eight_args_c/eight_args_mod.c: stress test with 1-8 argument functions
  verifying correct capture of register and stack-passed arguments.

- eight_args_rust/eight_args_rust.rs: Rust equivalent of the 8-argument
  stress test, verifying Rust module dataflow support.

- deep_module/deep_chain_mod.c: 10-level deep call chain demonstrating
  taint propagation tracking across function boundaries.

Sample kcov-view.py output (C):

  func2+0x0 [eight_args_mod](arg[0]=0x11, arg[1]=0x22)
    ret = 0x33
  func8+0x0 [eight_args_mod](arg[0]=0x11, .., arg[7]=0x88)
    ret = 0x264

Sample kcov-view.py output (Rust):

  rfunc2+0x0 [eight_args_rust](arg[0]=0x11, arg[1]=0x22)
    ret = 0x33
  rfunc8+0x0 [eight_args_rust](arg[0]=0x11, .., arg[7]=0x88)
    ret = 0x264

Signed-off-by: Yunseong Kim <[email protected]>
---
 tools/kcov-dataflow/.gitignore                     |  12 +
 tools/kcov-dataflow/deep_module/Makefile           |   2 +
 tools/kcov-dataflow/deep_module/deep_chain_mod.c   | 224 +++++++++++++++++
 tools/kcov-dataflow/eight_args_c/Makefile          |   2 +
 tools/kcov-dataflow/eight_args_c/eight_args_mod.c  |  95 +++++++
 tools/kcov-dataflow/eight_args_rust/Makefile       |   2 +
 .../eight_args_rust/eight_args_rust.rs             | 114 +++++++++
 tools/kcov-dataflow/kcov-view.py                   | 272 +++++++++++++++++++++
 tools/kcov-dataflow/trigger.c                      | 125 ++++++++++
 9 files changed, 848 insertions(+)

diff --git a/tools/kcov-dataflow/.gitignore b/tools/kcov-dataflow/.gitignore
new file mode 100644
index 000000000000..1f35df8fbd07
--- /dev/null
+++ b/tools/kcov-dataflow/.gitignore
@@ -0,0 +1,12 @@
+# Built binaries
+test_mock
+test_mock_binary
+trigger
+*.o
+*.ko
+*.mod
+*.mod.c
+Module.symvers
+modules.order
+.module-common.o
+*.ll
diff --git a/tools/kcov-dataflow/deep_module/Makefile 
b/tools/kcov-dataflow/deep_module/Makefile
new file mode 100644
index 000000000000..6afed580dc9a
--- /dev/null
+++ b/tools/kcov-dataflow/deep_module/Makefile
@@ -0,0 +1,2 @@
+obj-m := deep_chain_mod.o
+KCOV_DATAFLOW_deep_chain_mod.o := y
diff --git a/tools/kcov-dataflow/deep_module/deep_chain_mod.c 
b/tools/kcov-dataflow/deep_module/deep_chain_mod.c
new file mode 100644
index 000000000000..786e23c5d213
--- /dev/null
+++ b/tools/kcov-dataflow/deep_module/deep_chain_mod.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * deep_chain_mod.c - Demonstrates kcov_dataflow tracing through 10 nested
+ * function calls. An attacker-controlled "offset" value propagates from
+ * the entry point through transformations until it causes an OOB write
+ * in the deepest function.
+ *
+ * Call chain:
+ *   entry_handler → parse_request → validate_header → extract_payload →
+ *   transform_data → apply_filter → compute_index → lookup_slot →
+ *   write_slot → commit_write (BUG: OOB here)
+ */
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+
+/* Simulated protocol structures */
+struct request_header {
+       u32 magic;
+       u32 version;
+       u32 payload_offset;  /* ← attacker controls this */
+       u32 payload_size;
+};
+
+struct payload {
+       u64 session_id;
+       u32 transform_key;
+       u32 filter_mask;
+       u8  data[32];
+};
+
+struct slot_table {
+       u32 num_slots;
+       u64 slots[8];  /* only 8 slots! */
+};
+
+static struct proc_dir_entry *proc_deep;
+
+/* === 10 nested functions: deepest first === */
+
+/* Function 10 (DEEPEST): The vulnerable write */
+static noinline int commit_write(struct slot_table *table, u32 index, u64 
value)
+{
+       /* BUG: no bounds check on index — if index >= 8, OOB write */
+       table->slots[index] = value;
+       return 0;
+}
+
+/* Function 9 */
+static noinline int write_slot(struct slot_table *table, u32 slot_idx,
+                              u64 session_id)
+{
+       u64 combined = session_id ^ (u64)slot_idx;
+
+       return commit_write(table, slot_idx, combined);
+}
+
+/* Function 8 */
+static noinline u32 lookup_slot(struct slot_table *table, u32 computed_idx)
+{
+       /* Pass through — in real code this might do hash lookup */
+       u32 final_idx = computed_idx % 16;  /* BUG: should be % 8 */
+
+       write_slot(table, final_idx, 0xDEADC0DE00000000ULL | final_idx);
+       return final_idx;
+}
+
+/* Function 7 */
+static noinline u32 compute_index(u32 transform_result, u32 filter_output)
+{
+       /* Combines two values into an index */
+       return (transform_result + filter_output) & 0xF;  /* 0-15, but table 
has 8 */
+}
+
+/* Function 6 */
+static noinline u32 apply_filter(struct payload *pl, u32 transformed_val)
+{
+       u32 filtered = transformed_val & pl->filter_mask;
+
+       return filtered >> 1;
+}
+
+/* Function 5 */
+static noinline u32 transform_data(struct payload *pl, u32 raw_offset)
+{
+       /* Transforms the offset using the payload's key */
+       return raw_offset * pl->transform_key;
+}
+
+/* Function 4 */
+static noinline struct payload *extract_payload(void *buf, u32 offset, u32 
size)
+{
+       /* In real code: validates and extracts payload from buffer */
+       return (struct payload *)((u8 *)buf + offset);
+}
+
+/* Function 3 */
+static noinline int validate_header(struct request_header *hdr)
+{
+       if (hdr->magic != 0x50524F54)  /* "PROT" */
+               return -1;
+       if (hdr->version > 2)
+               return -1;
+       /* BUG: doesn't validate payload_offset bounds! */
+       return 0;
+}
+
+/* Function 2 */
+static noinline int parse_request(void *buf, u32 buf_size,
+                                 struct request_header **out_hdr,
+                                 struct payload **out_payload)
+{
+       struct request_header *hdr = (struct request_header *)buf;
+
+       if (validate_header(hdr) < 0)
+               return -1;
+
+       *out_hdr = hdr;
+       *out_payload = extract_payload(buf, hdr->payload_offset, 
hdr->payload_size);
+       return 0;
+}
+
+/* Function 1 (ENTRY): The syscall handler */
+static noinline int entry_handler(void *user_buf, u32 user_size)
+{
+       struct request_header *hdr;
+       struct payload *pl;
+       struct slot_table *table;
+       u32 transformed, filtered, index, slot;
+
+       if (parse_request(user_buf, user_size, &hdr, &pl) < 0)
+               return -1;
+
+       table = kzalloc(sizeof(*table), GFP_KERNEL);
+       if (!table)
+               return -ENOMEM;
+       table->num_slots = 8;
+
+       /* The tainted data flow:
+        * hdr->payload_offset → extract_payload → pl
+        * pl->transform_key + payload_offset → transform_data → transformed
+        * transformed + pl->filter_mask → apply_filter → filtered
+        * transformed + filtered → compute_index → index
+        * index → lookup_slot → slot (% 16, should be % 8)
+        * slot → write_slot → commit_write (OOB if slot >= 8)
+        */
+       transformed = transform_data(pl, hdr->payload_offset);
+       filtered = apply_filter(pl, transformed);
+       index = compute_index(transformed, filtered);
+       slot = lookup_slot(table, index);
+
+       pr_info("deep_chain: slot=%u (OOB if >= 8)\n", slot);
+
+       kfree(table);
+       return 0;
+}
+
+/* Trigger: constructs a malicious request that causes index=12 (OOB) */
+static ssize_t deep_trigger_write(struct file *file, const char __user *ubuf,
+                                 size_t count, loff_t *ppos)
+{
+       u8 *buf;
+       struct request_header *hdr;
+       struct payload *pl;
+
+       buf = kzalloc(256, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       /* Craft malicious request */
+       hdr = (struct request_header *)buf;
+       hdr->magic = 0x50524F54;       /* valid magic */
+       hdr->version = 1;              /* valid version */
+       hdr->payload_offset = 16;      /* offset to payload (valid position) */
+       hdr->payload_size = sizeof(struct payload);
+
+       /* Craft payload that will produce OOB index */
+       pl = (struct payload *)(buf + 16);
+       pl->session_id = 0xAAAABBBBCCCCDDDDULL;
+       pl->transform_key = 3;         /* multiplier */
+       pl->filter_mask = 0xFFFFFFFF;  /* no filtering */
+       memcpy(pl->data, "ATTACKER_PAYLOAD_DATA!!!", 24);
+
+       /*
+        * Trace: payload_offset=16, transform_key=3
+        * transformed = 16 * 3 = 48
+        * filtered = (48 & 0xFFFFFFFF) >> 1 = 24
+        * index = (48 + 24) & 0xF = 72 & 0xF = 8
+        * lookup_slot: final_idx = 8 % 16 = 8  ← OOB! (table has slots[0..7])
+        */
+
+       pr_info("deep_chain: triggering 10-deep call chain with offset=%u\n",
+               hdr->payload_offset);
+
+       entry_handler(buf, 256);
+
+       kfree(buf);
+       return count;
+}
+
+static const struct proc_ops deep_proc_ops = {
+       .proc_write = deep_trigger_write,
+};
+
+static int __init deep_chain_init(void)
+{
+       proc_deep = proc_create("deep_trigger", 0200, NULL, &deep_proc_ops);
+       if (!proc_deep)
+               return -ENOMEM;
+       pr_info("deep_chain_mod: loaded. echo x > /proc/deep_trigger\n");
+       return 0;
+}
+
+static void __exit deep_chain_exit(void)
+{
+       proc_remove(proc_deep);
+}
+
+module_init(deep_chain_init);
+module_exit(deep_chain_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("10-deep call chain for kcov_dataflow visualization");
diff --git a/tools/kcov-dataflow/eight_args_c/Makefile 
b/tools/kcov-dataflow/eight_args_c/Makefile
new file mode 100644
index 000000000000..de35bb541f07
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_c/Makefile
@@ -0,0 +1,2 @@
+obj-m := eight_args_mod.o
+KCOV_DATAFLOW_eight_args_mod.o := y
diff --git a/tools/kcov-dataflow/eight_args_c/eight_args_mod.c 
b/tools/kcov-dataflow/eight_args_c/eight_args_mod.c
new file mode 100644
index 000000000000..660b27033756
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_c/eight_args_mod.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * eight_args_mod.c - Verify kcov_dataflow captures 1 through 8 argument 
functions.
+ */
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KCOV dataflow 8-argument stress test module");
+
+noinline u64 func1(u64 a1)
+{
+       return a1;
+}
+EXPORT_SYMBOL(func1);
+
+noinline u64 func2(u64 a1, u64 a2)
+{
+       return a1 + a2;
+}
+EXPORT_SYMBOL(func2);
+
+noinline u64 func3(u64 a1, u64 a2, u64 a3)
+{
+       return a1 + a2 + a3;
+}
+EXPORT_SYMBOL(func3);
+
+noinline u64 func4(u64 a1, u64 a2, u64 a3, u64 a4)
+{
+       return a1 + a2 + a3 + a4;
+}
+EXPORT_SYMBOL(func4);
+
+noinline u64 func5(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5)
+{
+       return a1 + a2 + a3 + a4 + a5;
+}
+EXPORT_SYMBOL(func5);
+
+noinline u64 func6(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6)
+{
+       return a1 + a2 + a3 + a4 + a5 + a6;
+}
+EXPORT_SYMBOL(func6);
+
+noinline u64 func7(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6,
+                  u64 a7)
+{
+       return a1 + a2 + a3 + a4 + a5 + a6 + a7;
+}
+EXPORT_SYMBOL(func7);
+
+noinline u64 func8(u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6,
+                  u64 a7, u64 a8)
+{
+       return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8;
+}
+EXPORT_SYMBOL(func8);
+
+static ssize_t trigger_write(struct file *f, const char __user *buf,
+                            size_t count, loff_t *ppos)
+{
+       pr_info("func1(0x11)=0x%llx\n", func1(0x11));
+       pr_info("func2(0x11,0x22)=0x%llx\n", func2(0x11, 0x22));
+       pr_info("func3(0x11,0x22,0x33)=0x%llx\n",
+               func3(0x11, 0x22, 0x33));
+       pr_info("func4(0x11,..,0x44)=0x%llx\n",
+               func4(0x11, 0x22, 0x33, 0x44));
+       pr_info("func5(0x11,..,0x55)=0x%llx\n",
+               func5(0x11, 0x22, 0x33, 0x44, 0x55));
+       pr_info("func6(0x11,..,0x66)=0x%llx\n",
+               func6(0x11, 0x22, 0x33, 0x44, 0x55, 0x66));
+       pr_info("func7(0x11,..,0x77)=0x%llx\n",
+               func7(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77));
+       pr_info("func8(0x11,..,0x88)=0x%llx\n",
+               func8(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88));
+       return count;
+}
+
+static const struct proc_ops ops = { .proc_write = trigger_write };
+
+static int __init init_mod(void)
+{
+       proc_create("test_args", 0200, NULL, &ops);
+       return 0;
+}
+
+static void __exit exit_mod(void)
+{
+       remove_proc_entry("test_args", NULL);
+}
+
+module_init(init_mod);
+module_exit(exit_mod);
diff --git a/tools/kcov-dataflow/eight_args_rust/Makefile 
b/tools/kcov-dataflow/eight_args_rust/Makefile
new file mode 100644
index 000000000000..8881d369e670
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_rust/Makefile
@@ -0,0 +1,2 @@
+obj-m := eight_args_rust.o
+KCOV_DATAFLOW_eight_args_rust.o := y
diff --git a/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs 
b/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs
new file mode 100644
index 000000000000..11bbe1449eaf
--- /dev/null
+++ b/tools/kcov-dataflow/eight_args_rust/eight_args_rust.rs
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0
+//! Verify kcov_dataflow captures 1-arg through 8-arg functions.
+//! Write to /sys/kernel/debug/test_args_rust to trigger all 8.
+#![allow(missing_docs)]
+
+use kernel::prelude::*;
+use kernel::c_str;
+
+module! {
+    type: ArgsModule,
+    name: "eight_args_rust",
+    authors: ["kcov-dataflow"],
+    description: "1-8 arg verification",
+    license: "GPL",
+}
+
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc1(a1: u64) -> u64 { a1 }
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc2(a1: u64, a2: u64) -> u64 { a1 + a2 }
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc3(a1: u64, a2: u64, a3: u64) -> u64 {
+    a1 + a2 + a3
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc4(a1: u64, a2: u64, a3: u64, a4: u64) -> u64 {
+    a1 + a2 + a3 + a4
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc5(
+    a1: u64, a2: u64, a3: u64, a4: u64, a5: u64,
+) -> u64 {
+    a1 + a2 + a3 + a4 + a5
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc6(
+    a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64,
+) -> u64 {
+    a1 + a2 + a3 + a4 + a5 + a6
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc7(
+    a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64, a7: u64,
+) -> u64 {
+    a1 + a2 + a3 + a4 + a5 + a6 + a7
+}
+#[no_mangle]
+#[inline(never)]
+pub extern "C" fn rfunc8(
+    a1: u64, a2: u64, a3: u64, a4: u64, a5: u64, a6: u64, a7: u64,
+    a8: u64,
+) -> u64 {
+    a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8
+}
+
+unsafe extern "C" fn write_handler(
+    _file: *mut kernel::bindings::file,
+    _buf: *const core::ffi::c_char,
+    count: usize,
+    _ppos: *mut kernel::bindings::loff_t,
+) -> kernel::ffi::c_long {
+    let r1 = rfunc1(0x11);
+    pr_info!("rfunc1: ret=0x{:x}\n", r1);
+    let r2 = rfunc2(0x11, 0x22);
+    pr_info!("rfunc2: ret=0x{:x}\n", r2);
+    let r3 = rfunc3(0x11, 0x22, 0x33);
+    pr_info!("rfunc3: ret=0x{:x}\n", r3);
+    let r4 = rfunc4(0x11, 0x22, 0x33, 0x44);
+    pr_info!("rfunc4: ret=0x{:x}\n", r4);
+    let r5 = rfunc5(0x11, 0x22, 0x33, 0x44, 0x55);
+    pr_info!("rfunc5: ret=0x{:x}\n", r5);
+    let r6 = rfunc6(0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
+    pr_info!("rfunc6: ret=0x{:x}\n", r6);
+    let r7 = rfunc7(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77);
+    pr_info!("rfunc7: ret=0x{:x}\n", r7);
+    let r8 = rfunc8(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88);
+    pr_info!("rfunc8: ret=0x{:x}\n", r8);
+    count as kernel::ffi::c_long
+}
+
+#[repr(transparent)]
+struct SyncFops(kernel::bindings::file_operations);
+unsafe impl Sync for SyncFops {}
+
+static FOPS: SyncFops = SyncFops(kernel::bindings::file_operations {
+    write: Some(unsafe { core::mem::transmute(write_handler as *const ()) }),
+    ..unsafe { core::mem::zeroed() }
+});
+
+struct ArgsModule { d: *mut kernel::bindings::dentry }
+
+impl kernel::Module for ArgsModule {
+    fn init(_module: &'static ThisModule) -> Result<Self> {
+        let d = unsafe {
+            kernel::bindings::debugfs_create_file_unsafe(
+                c_str!("test_args_rust").as_char_ptr(),
+                0o222, core::ptr::null_mut(), core::ptr::null_mut(), &FOPS.0,
+            )
+        };
+        Ok(Self { d })
+    }
+}
+impl Drop for ArgsModule {
+    fn drop(&mut self) { unsafe { kernel::bindings::debugfs_remove(self.d) }; }
+}
+unsafe impl Send for ArgsModule {}
+unsafe impl Sync for ArgsModule {}
diff --git a/tools/kcov-dataflow/kcov-view.py b/tools/kcov-dataflow/kcov-view.py
new file mode 100755
index 000000000000..70acb5474f5e
--- /dev/null
+++ b/tools/kcov-dataflow/kcov-view.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+kcov-view.py - Merged KCOV + KCOV_DATAFLOW viewer
+
+Reads both /sys/kernel/debug/kcov (PC trace) and 
/sys/kernel/debug/kcov_dataflow
+(args/ret), correlates by PC, and produces a human-readable call trace with
+argument values and struct field expansion.
+
+Usage (inside guest or with appropriate permissions):
+    python3 kcov-view.py <trigger_command>
+
+Example:
+    python3 kcov-view.py "echo x > /proc/uaf_trigger"
+
+Output:
+    func+0x0 [module]
+      → a(arg[0]=0x1, arg[1]=0x2, arg[2]=0x3, arg[3]=struct{.f[0]=1, .f[1]=2, 
.f[2]=3})
+        ← ret = struct{.f[0]=1, .f[1]=2, .f[2]=3}
+      → a(arg[0]=0x0, arg[1]=0x0, arg[2]=0x1, arg[3]=NULL)
+        ← ret = 0x0
+"""
+import os, sys, struct, mmap, fcntl, subprocess, re, ctypes
+from collections import defaultdict
+
+# Ioctl definitions (x86_64)
+KCOV_INIT_TRACE = 0x80086301   # _IOR('c', 1, unsigned long)
+KCOV_ENABLE = 0x6364           # _IO('c', 100)
+KCOV_DISABLE = 0x6365          # _IO('c', 101)
+KCOV_TRACE_PC = 0
+
+KCOV_DF_INIT_TRACE = 0x80086401  # _IOR('d', 1, unsigned long)
+KCOV_DF_ENABLE = 0x6464          # _IO('d', 100)
+KCOV_DF_DISABLE = 0x6465         # _IO('d', 101)
+
+BUF_SIZE = 65536  # 65536 * 8 = 512KB = 128 pages (page-aligned)
+
+# Load kallsyms for symbolization
+def load_kallsyms():
+    syms = {}
+    try:
+        with open("/proc/kallsyms") as f:
+            for line in f:
+                parts = line.split()
+                if len(parts) >= 3:
+                    addr = int(parts[0], 16)
+                    name = parts[2]
+                    mod = parts[3].strip("[]") if len(parts) > 3 else ""
+                    syms[addr] = (name, mod)
+    except:
+        pass
+    return syms
+
+def symbolize(pc, syms):
+    """Find nearest symbol <= pc"""
+    best_addr = 0
+    best_name = f"0x{pc:x}"
+    best_mod = ""
+    for addr, (name, mod) in syms.items():
+        if addr <= pc and addr > best_addr:
+            best_addr = addr
+            best_name = name
+            best_mod = mod
+    offset = pc - best_addr
+    if best_mod:
+        return f"{best_name}+0x{offset:x} [{best_mod}]"
+    return f"{best_name}+0x{offset:x}"
+
+def parse_dataflow(buf, n):
+    """Parse TLV records from kcov_dataflow buffer into a list of events."""
+    events = []
+    i = 1
+    while i <= n and i < BUF_SIZE:
+        hdr = buf[i]
+        typ = hdr & 0xF0000000
+        seq = hdr & 0x00FFFFFF
+
+        if typ not in (0xE0000000, 0xF0000000):
+            i += 1
+            continue
+
+        pc = buf[i + 1]
+        meta = buf[i + 2]
+        i += 3
+
+        # Collect field values
+        fields = []
+        while i <= n and i < BUF_SIZE:
+            v = buf[i]
+            vtype = v & 0xF0000000
+            if vtype == 0xE0000000 or vtype == 0xF0000000:
+                break
+            fields.append(v)
+            i += 1
+
+        if typ == 0xE0000000:
+            arg_idx = (meta >> 56) & 0xFF
+            arg_sz = (meta >> 48) & 0xFF
+            ptr = meta & 0xFFFFFFFFFFFF
+            events.append({
+                "type": "entry", "seq": seq, "pc": pc,
+                "arg_idx": arg_idx, "arg_size": arg_sz,
+                "ptr": ptr, "fields": fields
+            })
+        else:
+            ret_sz = (meta >> 48) & 0xFF
+            ptr = meta & 0xFFFFFFFFFFFF
+            events.append({
+                "type": "ret", "seq": seq, "pc": pc,
+                "ret_size": ret_sz, "ptr": ptr, "fields": fields
+            })
+    return events
+
+def format_value(val):
+    if val == 0xBADADD85:
+        return "FAULT"
+    if val == 0:
+        return "0"
+    return f"0x{val:x}"
+
+def format_entry(ev):
+    """Format an entry event as a function argument."""
+    if len(ev["fields"]) > 1:
+        # Struct: multiple fields
+        flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in 
enumerate(ev["fields"]))
+        return f"struct{{{flds}}}"
+    elif len(ev["fields"]) == 1:
+        v = ev["fields"][0]
+        if v == 0 and ev["ptr"] == 0:
+            return "NULL"
+        return format_value(v)
+    return format_value(ev["ptr"])
+
+def merge_and_display(pc_trace, df_events, syms):
+    """Display dataflow events with symbolization."""
+    print("\n╔═══════════════════════════════════════════════════════════╗")
+    print("║  Merged KCOV Coverage + Dataflow View                    ║")
+    print("╚═══════════════════════════════════════════════════════════╝\n")
+
+    if not df_events:
+        print("  (no dataflow events captured)")
+        return
+
+    # Group events into calls: consecutive entries for same PC followed by a 
ret
+    calls = []
+    current_args = []
+    current_pc = None
+
+    for ev in df_events:
+        if ev["type"] == "entry":
+            if current_pc is not None and ev["pc"] != current_pc:
+                calls.append({"pc": current_pc, "args": current_args, "ret": 
None})
+                current_args = []
+            current_pc = ev["pc"]
+            current_args.append(ev)
+        elif ev["type"] == "ret":
+            if current_pc == ev["pc"]:
+                calls.append({"pc": current_pc, "args": current_args, "ret": 
ev})
+                current_args = []
+                current_pc = None
+            else:
+                if current_args:
+                    calls.append({"pc": current_pc, "args": current_args, 
"ret": None})
+                    current_args = []
+                calls.append({"pc": ev["pc"], "args": [], "ret": ev})
+                current_pc = None
+
+    if current_args:
+        calls.append({"pc": current_pc, "args": current_args, "ret": None})
+
+    for call in calls:
+        sym = symbolize(call["pc"], syms)
+        args_parts = []
+        for a in call["args"]:
+            idx = a["arg_idx"]
+            if len(a["fields"]) > 1:
+                flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in 
enumerate(a["fields"]))
+                args_parts.append(f"arg[{idx}]=struct{{{flds}}}")
+            elif len(a["fields"]) == 1:
+                args_parts.append(f"arg[{idx}]={format_value(a['fields'][0])}")
+            else:
+                args_parts.append(f"arg[{idx}]=?")
+
+        print(f"  → {sym}({', '.join(args_parts)})")
+
+        if call["ret"]:
+            r = call["ret"]
+            if len(r["fields"]) > 1:
+                flds = ", ".join(f".f[{i}]={format_value(v)}" for i, v in 
enumerate(r["fields"]))
+                print(f"    ← ret = struct{{{flds}}}")
+            elif len(r["fields"]) == 1:
+                print(f"    ← ret = {format_value(r['fields'][0])}")
+        print()
+
+def main():
+    if len(sys.argv) < 2:
+        print(f"Usage: {sys.argv[0]} <trigger_command>")
+        print(f"Example: {sys.argv[0]} 'echo x > /proc/uaf_trigger'")
+        sys.exit(1)
+
+    trigger_cmd = sys.argv[1]
+    syms = load_kallsyms()
+
+    # Setup ctypes mmap
+    libc = ctypes.CDLL("libc.so.6", use_errno=True)
+    libc.mmap.restype = ctypes.c_void_p
+    libc.mmap.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int,
+                          ctypes.c_int, ctypes.c_int, ctypes.c_long]
+    PROT_RW = 0x3  # PROT_READ | PROT_WRITE
+    MAP_SHARED = 0x01
+
+    # Open both devices
+    kcov_fd = -1
+    df_fd = -1
+    kcov_arr = None
+    df_arr = None
+
+    # Legacy kcov (PC trace) - skip for now, use kallsyms for symbolization
+    kcov_arr = None
+
+    # Dataflow device - required
+    df_fd = os.open("/sys/kernel/debug/kcov_dataflow", os.O_RDWR)
+    fcntl.ioctl(df_fd, KCOV_DF_INIT_TRACE, BUF_SIZE)
+    df_ptr = libc.mmap(None, BUF_SIZE * 8, PROT_RW, MAP_SHARED, df_fd, 0)
+    if df_ptr == ctypes.c_void_p(-1).value:
+        print("Error: kcov_dataflow mmap failed")
+        sys.exit(1)
+    df_arr = (ctypes.c_uint64 * BUF_SIZE).from_address(df_ptr)
+
+    # Enable both
+    if kcov_arr:
+        fcntl.ioctl(kcov_fd, KCOV_ENABLE, KCOV_TRACE_PC)
+        kcov_arr[0] = 0
+
+    fcntl.ioctl(df_fd, KCOV_DF_ENABLE, 0)
+    df_arr[0] = 0
+
+    # Trigger - must happen in THIS process (kcov_dataflow is per-task)
+    if ">" in trigger_cmd:
+        target = trigger_cmd.split(">")[-1].strip()
+    else:
+        target = trigger_cmd
+    try:
+        fd_t = os.open(target, os.O_WRONLY)
+        os.write(fd_t, b"x")
+        os.close(fd_t)
+    except Exception as e:
+        print(f"Trigger failed: {e}")
+
+    # Read results
+    pc_trace = []
+    if kcov_arr:
+        n_pcs = kcov_arr[0]
+        for i in range(1, min(int(n_pcs) + 1, BUF_SIZE)):
+            pc_trace.append(kcov_arr[i])
+        fcntl.ioctl(kcov_fd, KCOV_DISABLE, 0)
+
+    n_df = int(df_arr[0])
+    df_raw = [int(df_arr[i]) for i in range(min(n_df + 10, BUF_SIZE))]
+    fcntl.ioctl(df_fd, KCOV_DF_DISABLE, 0)
+
+    # Parse and display
+    df_events = parse_dataflow(df_raw, int(n_df))
+    merge_and_display(pc_trace, df_events, syms)
+
+    # Cleanup
+    if kcov_arr:
+        os.close(kcov_fd)
+    os.close(df_fd)
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/kcov-dataflow/trigger.c b/tools/kcov-dataflow/trigger.c
new file mode 100644
index 000000000000..7fa7b4414770
--- /dev/null
+++ b/tools/kcov-dataflow/trigger.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * trigger.c - Uses /sys/kernel/debug/kcov_dataflow to capture
+ * function args/ret TLV records. Completely independent from legacy kcov.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#define KCOV_DF_INIT_TRACE     _IOR('d', 1, unsigned long)
+#define KCOV_DF_ENABLE         _IO('d', 100)
+#define KCOV_DF_DISABLE                _IO('d', 101)
+
+#define COVER_SIZE (64 * 1024)  /* 64K u64 words = 512KB */
+
+static void dump_buffer(uint64_t *cover, uint64_t n)
+{
+       uint64_t i = 1;
+
+       printf("=== KCOV Dataflow TLV Dump (%lu words) ===\n", n);
+       while (i <= n && i < COVER_SIZE) {
+               uint64_t hdr = cover[i];
+               uint64_t type = hdr & 0xF0000000ULL;
+               uint64_t seq = hdr & 0x00FFFFFFULL;
+               uint64_t pc = cover[i + 1];
+               uint64_t meta = cover[i + 2];
+
+               if (type == 0xE0000000ULL) {
+                       uint32_t arg_idx = (meta >> 56) & 0xFF;
+                       uint32_t arg_sz = (meta >> 48) & 0xFF;
+                       uint64_t ptr = meta & 0xFFFFFFFFFFFFULL;
+
+                       printf("[ENTRY] seq=%lu pc=0x%lx arg[%u](%u) 
ptr=0x%lx\n",
+                              seq, pc, arg_idx, arg_sz, ptr);
+               } else if (type == 0xF0000000ULL) {
+                       uint32_t ret_sz = (meta >> 48) & 0xFF;
+                       uint64_t ptr = meta & 0xFFFFFFFFFFFFULL;
+
+                       printf("[RET]   seq=%lu pc=0x%lx ret(%u) ptr=0x%lx\n",
+                              seq, pc, ret_sz, ptr);
+               } else {
+                       i++;
+                       continue;
+               }
+
+               /* Print field values */
+               i += 3;
+               while (i <= n && i < COVER_SIZE) {
+                       uint64_t next = cover[i];
+                       uint64_t next_type = next & 0xF0000000ULL;
+
+                       if (next_type == 0xE0000000ULL || next_type == 
0xF0000000ULL)
+                               break;
+                       if (next == 0xBADADD85ULL)
+                               printf("  val = FAULT\n");
+                       else
+                               printf("  val = 0x%lx\n", next);
+                       i++;
+               }
+       }
+       printf("=== Done ===\n");
+}
+
+int main(int argc, char **argv)
+{
+       const char *trigger_path = "/proc/uaf_trigger";
+       int fd, tfd;
+       uint64_t *cover;
+       uint64_t n;
+
+       if (argc > 1)
+               trigger_path = argv[1];
+
+       fd = open("/sys/kernel/debug/kcov_dataflow", O_RDWR);
+       if (fd < 0) {
+               perror("open kcov_dataflow");
+               return 1;
+       }
+
+       if (ioctl(fd, KCOV_DF_INIT_TRACE, COVER_SIZE)) {
+               perror("KCOV_DF_INIT_TRACE");
+               close(fd);
+               return 1;
+       }
+
+       cover = mmap(NULL, COVER_SIZE * sizeof(uint64_t),
+                    PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+       if (cover == MAP_FAILED) {
+               perror("mmap");
+               close(fd);
+               return 1;
+       }
+
+       if (ioctl(fd, KCOV_DF_ENABLE, 0)) {
+               perror("KCOV_DF_ENABLE");
+               munmap(cover, COVER_SIZE * sizeof(uint64_t));
+               close(fd);
+               return 1;
+       }
+
+       /* Reset */
+       __atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);
+
+       /* Trigger */
+       tfd = open(trigger_path, O_WRONLY);
+       if (tfd >= 0) {
+               write(tfd, "x", 1);
+               close(tfd);
+       }
+
+       n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);
+
+       ioctl(fd, KCOV_DF_DISABLE, 0);
+
+       dump_buffer(cover, n);
+
+       munmap(cover, COVER_SIZE * sizeof(uint64_t));
+       close(fd);
+       return 0;
+}

-- 
2.43.0



Reply via email to