[RFC PATCH 3/4] ftrace: Add exact task comm filtering

From: hu.shengming

Date: Sun Aug 30 2026 - 07:12:26 EST


From: Shengming Hu <hu.shengming@xxxxxxxxxx>

PID filters only select tasks that already have a known PID. This makes
it difficult to set up function tracing for a service before it starts
or to keep tracing it after it restarts with a different PID.

Add set_ftrace_comm and set_ftrace_notrace_comm to filter function and
function_graph tracing by task comm.

Comm filters share the existing sched_switch probe and per-CPU cached
task decision with PID filters. The filters are checked when a task is
scheduled in, so the function tracing fast path remains unchanged.

Comm names are matched exactly. Each write adds one name to the list.
A trailing newline is ignored, while embedded newlines and names longer
than TASK_COMM_LEN - 1 are rejected. Empty writes have no effect,
duplicate names are ignored, and opening the file with O_TRUNC clears
the list.

When both PID and comm include filters are set, a task must match both.
A match in either exclude filter prevents the task from being traced.
If a running task changes its comm, the new name takes effect the next
time the task is scheduled in.

Signed-off-by: Shengming Hu <hu.shengming@xxxxxxxxxx>
---
kernel/trace/Makefile | 1 +
kernel/trace/comm_list.c | 314 ++++++++++++++++++++++++++++++++++++
kernel/trace/comm_list.h | 17 ++
kernel/trace/ftrace.c | 336 ++++++++++++++++++++++++++++++++++++++-
kernel/trace/trace.c | 5 +
kernel/trace/trace.h | 32 ++++
6 files changed, 701 insertions(+), 4 deletions(-)
create mode 100644 kernel/trace/comm_list.c
create mode 100644 kernel/trace/comm_list.h

diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile
index f934ff586bd4..415cd9db9c3e 100644
--- a/kernel/trace/Makefile
+++ b/kernel/trace/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_TRACING) += trace_printk.o
obj-$(CONFIG_TRACING) += trace_pid.o
obj-$(CONFIG_TRACER_SNAPSHOT) += trace_snapshot.o
obj-$(CONFIG_TRACING) += pid_list.o
+obj-$(CONFIG_TRACING) += comm_list.o
obj-$(CONFIG_TRACING_MAP) += tracing_map.o
obj-$(CONFIG_PREEMPTIRQ_DELAY_TEST) += preemptirq_delay_test.o
obj-$(CONFIG_SYNTH_EVENT_GEN_TEST) += synth_event_gen_test.o
diff --git a/kernel/trace/comm_list.c b/kernel/trace/comm_list.c
new file mode 100644
index 000000000000..c94c53d7d70c
--- /dev/null
+++ b/kernel/trace/comm_list.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 ZTE Inc, Shengming Hu <hu.shengming@xxxxxxxxxx>
+ */
+
+#include <linux/limits.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include "trace.h"
+#include "comm_list.h"
+
+#define COMM_LIST_INIT_SIZE 4
+
+static int comm_cmp(const char a[TASK_COMM_LEN],
+ const char b[TASK_COMM_LEN])
+{
+ return memcmp(a, b, TASK_COMM_LEN);
+}
+
+/*
+ * Return true if @comm exists. @pos is either the matching position or the
+ * insertion position that keeps the array sorted.
+ */
+static bool trace_comm_list_find(struct trace_comm_list *comm_list,
+ const char comm[TASK_COMM_LEN],
+ unsigned int *pos)
+{
+ unsigned int low = 0;
+ unsigned int high;
+
+ if (!comm_list) {
+ *pos = 0;
+ return false;
+ }
+
+ high = comm_list->nr_comms;
+ while (low < high) {
+ unsigned int mid = low + (high - low) / 2;
+ int cmp = comm_cmp(comm_list->comms[mid], comm);
+
+ if (cmp < 0)
+ low = mid + 1;
+ else
+ high = mid;
+ }
+
+ *pos = low;
+ return low < comm_list->nr_comms &&
+ !comm_cmp(comm_list->comms[low], comm);
+}
+
+/**
+ * trace_comm_list_alloc - create a new comm_list
+ *
+ * Allocates a new comm_list to store comms into.
+ *
+ * Returns the comm_list on success, NULL otherwise.
+ */
+struct trace_comm_list *trace_comm_list_alloc(void)
+{
+ return kzalloc(sizeof(struct trace_comm_list), GFP_KERNEL);
+}
+
+/**
+ * trace_comm_list_free - Frees an allocated comm_list.
+ * @comm_list: The comm list to free.
+ *
+ * Frees the memory for a comm_list that was allocated.
+ */
+void trace_comm_list_free(struct trace_comm_list *comm_list)
+{
+ if (!comm_list)
+ return;
+
+ kfree(comm_list->comms);
+ kfree(comm_list);
+}
+
+/**
+ * trace_comm_list_is_set - test if the comm is set in the list
+ * @comm_list: The comm list to test
+ * @comm: The comm to see if set in the list.
+ *
+ * Tests if @comm is set in the @comm_list.
+ *
+ * Return true if the comm is in the list, false otherwise.
+ */
+bool trace_comm_list_is_set(struct trace_comm_list *comm_list,
+ const char comm[TASK_COMM_LEN])
+{
+ unsigned int pos;
+
+ return trace_comm_list_find(comm_list, comm, &pos);
+}
+
+static int trace_comm_list_grow(struct trace_comm_list *comm_list)
+{
+ char (*comms)[TASK_COMM_LEN];
+ unsigned int max_comms;
+
+ if (comm_list->nr_comms < comm_list->max_comms)
+ return 0;
+
+ if (!comm_list->max_comms) {
+ max_comms = COMM_LIST_INIT_SIZE;
+ } else {
+ if (comm_list->max_comms > UINT_MAX / 2)
+ return -E2BIG;
+ max_comms = comm_list->max_comms * 2;
+ }
+
+ comms = krealloc_array(comm_list->comms, max_comms,
+ sizeof(*comm_list->comms), GFP_KERNEL);
+ if (!comms)
+ return -ENOMEM;
+
+ comm_list->comms = comms;
+ comm_list->max_comms = max_comms;
+ return 0;
+}
+
+/**
+ * trace_comm_list_set - add a comm to the list
+ * @comm_list: The comm list to add the @comm to.
+ * @comm: The comm to add.
+ *
+ * Adds @comm to @comm_list. The comms are kept sorted and duplicate
+ * entries are ignored.
+ *
+ * Return 0 on success, negative otherwise.
+ */
+int trace_comm_list_set(struct trace_comm_list *comm_list,
+ const char comm[TASK_COMM_LEN])
+{
+ unsigned int pos;
+ int ret;
+
+ if (!comm_list)
+ return -ENODEV;
+
+ if (trace_comm_list_find(comm_list, comm, &pos))
+ return 0;
+
+ ret = trace_comm_list_grow(comm_list);
+ if (ret)
+ return ret;
+
+ if (pos < comm_list->nr_comms)
+ memmove(&comm_list->comms[pos + 1], &comm_list->comms[pos],
+ (comm_list->nr_comms - pos) * sizeof(*comm_list->comms));
+
+ memcpy(comm_list->comms[pos], comm, TASK_COMM_LEN);
+ comm_list->nr_comms++;
+
+ return 0;
+}
+
+/**
+ * trace_ignore_comm_task - should a task be ignored by comm filters
+ * @filtered_comms: The list of comms to trace
+ * @filtered_no_comms: The list of comms not to be traced
+ * @task: The task to test against the comm filters
+ *
+ * Checks whether @task should be ignored by the comm include/exclude
+ * filters.
+ *
+ * Returns true if @task should not be traced, false otherwise.
+ */
+bool trace_ignore_comm_task(struct trace_comm_list *filtered_comms,
+ struct trace_comm_list *filtered_no_comms,
+ struct task_struct *task)
+{
+ char comm[TASK_COMM_LEN];
+
+ get_task_comm(comm, task);
+
+ return (filtered_comms &&
+ !trace_comm_list_is_set(filtered_comms, comm)) ||
+ (filtered_no_comms &&
+ trace_comm_list_is_set(filtered_no_comms, comm));
+}
+
+/**
+ * trace_comm_start - start iterating over a comm list
+ * @comm_list: The comm list to show
+ * @pos: The position of the file
+ *
+ * Returns the comm at @pos, or NULL if there are no more comms.
+ */
+void *trace_comm_start(struct trace_comm_list *comm_list, loff_t *pos)
+{
+ if (!comm_list || *pos < 0 || *pos >= (loff_t)comm_list->nr_comms)
+ return NULL;
+
+ return comm_list->comms[*pos];
+}
+
+/**
+ * trace_comm_next - return the next comm in the list
+ * @comm_list: The comm list to show
+ * @v: The current comm
+ * @pos: The position of the file
+ *
+ * Returns the next comm in @comm_list, or NULL if there are no more comms.
+ */
+void *trace_comm_next(struct trace_comm_list *comm_list, void *v, loff_t *pos)
+{
+ (void)v;
+
+ (*pos)++;
+ return trace_comm_start(comm_list, pos);
+}
+
+/**
+ * trace_comm_show - show the current comm
+ * @m: The seq_file structure to write into
+ * @v: The comm to display
+ *
+ * Displays the current comm in the seq_file.
+ */
+int trace_comm_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "%s\n", (char *)v);
+ return 0;
+}
+
+static int trace_comm_from_user(char comm[TASK_COMM_LEN],
+ const char __user *ubuf, size_t cnt)
+{
+ char buf[TASK_COMM_LEN];
+ size_t len = cnt;
+
+ if (cnt > sizeof(buf))
+ return -EINVAL;
+
+ if (copy_from_user(buf, ubuf, cnt))
+ return -EFAULT;
+
+ if (len && buf[len - 1] == '\n')
+ len--;
+
+ if (!len) {
+ comm[0] = '\0';
+ return 0;
+ }
+
+ if (len >= TASK_COMM_LEN || memchr(buf, '\0', len) ||
+ memchr(buf, '\n', len))
+ return -EINVAL;
+
+ memset(comm, 0, TASK_COMM_LEN);
+ memcpy(comm, buf, len);
+
+ return 1;
+}
+
+/**
+ * trace_comm_write - add a comm to a comm list
+ * @filtered_comms: The current comm list
+ * @new_comm_list: The pointer to place the new comm list
+ * @ubuf: The user buffer containing the comm
+ * @cnt: The size of the user buffer
+ *
+ * Creates a new comm list containing the current comms and the comm
+ * specified by the user.
+ *
+ * Return the number of bytes written on success, negative otherwise.
+ */
+int trace_comm_write(struct trace_comm_list *filtered_comms,
+ struct trace_comm_list **new_comm_list,
+ const char __user *ubuf, size_t cnt)
+{
+ struct trace_comm_list *comm_list;
+ char comm[TASK_COMM_LEN];
+ unsigned int i;
+ int parsed;
+ int ret;
+
+ parsed = trace_comm_from_user(comm, ubuf, cnt);
+ if (parsed < 0)
+ return parsed;
+
+ if (!parsed || trace_comm_list_is_set(filtered_comms, comm)) {
+ *new_comm_list = filtered_comms;
+ return cnt;
+ }
+
+ comm_list = trace_comm_list_alloc();
+ if (!comm_list)
+ return -ENOMEM;
+
+ if (filtered_comms) {
+ for (i = 0; i < filtered_comms->nr_comms; i++) {
+ ret = trace_comm_list_set(comm_list,
+ filtered_comms->comms[i]);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+
+ ret = trace_comm_list_set(comm_list, comm);
+ if (ret < 0)
+ goto fail;
+
+ *new_comm_list = comm_list;
+ return cnt;
+
+fail:
+ trace_comm_list_free(comm_list);
+ return ret;
+}
diff --git a/kernel/trace/comm_list.h b/kernel/trace/comm_list.h
new file mode 100644
index 000000000000..1f2a6b329ed3
--- /dev/null
+++ b/kernel/trace/comm_list.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* Do not include this file directly. */
+
+#ifndef _TRACE_INTERNAL_COMM_LIST_H
+#define _TRACE_INTERNAL_COMM_LIST_H
+
+#include <linux/sched.h>
+
+struct trace_comm_list {
+ unsigned int nr_comms;
+ unsigned int max_comms;
+ char (*comms)[TASK_COMM_LEN];
+};
+
+#endif /* _TRACE_INTERNAL_COMM_LIST_H */
+
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 0c73abb8fec8..e2a466448a9b 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -108,7 +108,10 @@ bool ftrace_task_filters_enabled(struct ftrace_ops *ops)

tr = ops->private;

- return tr->function_pids != NULL || tr->function_no_pids != NULL;
+ return rcu_access_pointer(tr->function_pids) ||
+ rcu_access_pointer(tr->function_no_pids) ||
+ rcu_access_pointer(tr->function_comms) ||
+ rcu_access_pointer(tr->function_no_comms);
}

static void ftrace_update_trampoline(struct ftrace_ops *ops);
@@ -168,7 +171,7 @@ static inline void ftrace_ops_init(struct ftrace_ops *ops)
#endif
}

-/* Call this function for when a callback filters on set_ftrace_pid */
+/* Call this function for when a callback uses task filters */
static void ftrace_pid_func(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *op, struct ftrace_regs *fregs)
{
@@ -8642,11 +8645,32 @@ static bool ftrace_task_filters_active(struct trace_array *tr)
return rcu_dereference_protected(tr->function_pids,
lockdep_is_held(&ftrace_lock)) ||
rcu_dereference_protected(tr->function_no_pids,
+ lockdep_is_held(&ftrace_lock)) ||
+ rcu_dereference_protected(tr->function_comms,
+ lockdep_is_held(&ftrace_lock)) ||
+ rcu_dereference_protected(tr->function_no_comms,
lockdep_is_held(&ftrace_lock));
}

static void ignore_task_cpu(void *data);

+static bool __ftrace_ignore_task(struct trace_pid_list *pid_list,
+ struct trace_pid_list *no_pid_list,
+ struct trace_comm_list *comm_filter,
+ struct trace_comm_list *no_comm_filter,
+ struct task_struct *task)
+{
+ if ((pid_list || no_pid_list) &&
+ trace_ignore_pid_task(pid_list, no_pid_list, task))
+ return true;
+
+ if ((comm_filter || no_comm_filter) &&
+ trace_ignore_comm_task(comm_filter, no_comm_filter, task))
+ return true;
+
+ return false;
+}
+
static void
ftrace_filter_task_sched_switch_probe(void *data, bool preempt,
struct task_struct *prev,
@@ -8656,11 +8680,15 @@ ftrace_filter_task_sched_switch_probe(void *data, bool preempt,
struct trace_array *tr = data;
struct trace_pid_list *pid_list;
struct trace_pid_list *no_pid_list;
+ struct trace_comm_list *comm_filter;
+ struct trace_comm_list *no_comm_filter;

pid_list = rcu_dereference_sched(tr->function_pids);
no_pid_list = rcu_dereference_sched(tr->function_no_pids);

- if (trace_ignore_pid_task(pid_list, no_pid_list, next))
+ comm_filter = rcu_dereference_sched(tr->function_comms);
+ no_comm_filter = rcu_dereference_sched(tr->function_no_comms);
+ if (__ftrace_ignore_task(pid_list, no_pid_list, comm_filter, no_comm_filter, next))
this_cpu_write(tr->array_buffer.data->ftrace_ignore_pid,
FTRACE_PID_IGNORE);
else
@@ -8788,6 +8816,54 @@ static void ftrace_pid_reset(struct trace_array *tr, int type)
mutex_unlock(&ftrace_lock);
}

+static void clear_ftrace_comms(struct trace_array *tr, int type)
+{
+ struct trace_comm_list *comm_filter;
+ struct trace_comm_list *no_comm_filter;
+ bool task_filters_enabled;
+
+ comm_filter = rcu_dereference_protected(tr->function_comms,
+ lockdep_is_held(&ftrace_lock));
+ no_comm_filter = rcu_dereference_protected(tr->function_no_comms,
+ lockdep_is_held(&ftrace_lock));
+
+ if (!comm_type_enabled(type, comm_filter, no_comm_filter))
+ return;
+
+ task_filters_enabled = ftrace_task_filters_active(tr);
+
+ if (type & TRACE_COMMS)
+ rcu_assign_pointer(tr->function_comms, NULL);
+
+ if (type & TRACE_NO_COMMS)
+ rcu_assign_pointer(tr->function_no_comms, NULL);
+
+ ftrace_task_filters_changed(tr, task_filters_enabled);
+ synchronize_rcu();
+
+ if ((type & TRACE_COMMS) && comm_filter)
+ trace_comm_list_free(comm_filter);
+
+ if ((type & TRACE_NO_COMMS) && no_comm_filter)
+ trace_comm_list_free(no_comm_filter);
+}
+
+void ftrace_clear_comms(struct trace_array *tr)
+{
+ mutex_lock(&ftrace_lock);
+ clear_ftrace_comms(tr, TRACE_COMMS | TRACE_NO_COMMS);
+ mutex_unlock(&ftrace_lock);
+}
+
+static void ftrace_comm_reset(struct trace_array *tr, int type)
+{
+ mutex_lock(&ftrace_lock);
+ clear_ftrace_comms(tr, type);
+ ftrace_update_pid_func();
+ ftrace_startup_all(0);
+ mutex_unlock(&ftrace_lock);
+}
+
/* Greater than any max PID */
#define FTRACE_NO_PIDS (void *)(PID_MAX_LIMIT + 1)

@@ -8880,6 +8956,102 @@ static const struct seq_operations ftrace_no_pid_sops = {
.show = fpid_show,
};

+/* Not a valid comm pointer */
+#define FTRACE_NO_COMM ((void *)1)
+
+static void *fcomm_start(struct seq_file *m, loff_t *pos)
+ __acquires(RCU)
+{
+ struct trace_comm_list *comm_list;
+ struct trace_array *tr = m->private;
+
+ mutex_lock(&ftrace_lock);
+ rcu_read_lock_sched();
+
+ comm_list = rcu_dereference_sched(tr->function_comms);
+
+ if (!comm_list)
+ return !(*pos) ? FTRACE_NO_COMM : NULL;
+
+ return trace_comm_start(comm_list, pos);
+}
+
+static void *fcomm_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ struct trace_array *tr = m->private;
+ struct trace_comm_list *comm_list;
+
+ if (v == FTRACE_NO_COMM) {
+ (*pos)++;
+ return NULL;
+ }
+
+ comm_list = rcu_dereference_sched(tr->function_comms);
+ return trace_comm_next(comm_list, v, pos);
+}
+
+static void fcomm_stop(struct seq_file *m, void *p)
+ __releases(RCU)
+{
+ rcu_read_unlock_sched();
+ mutex_unlock(&ftrace_lock);
+}
+
+static int fcomm_show(struct seq_file *m, void *v)
+{
+ if (v == FTRACE_NO_COMM) {
+ seq_puts(m, "no comm\n");
+ return 0;
+ }
+
+ return trace_comm_show(m, v);
+}
+
+static const struct seq_operations ftrace_comm_sops = {
+ .start = fcomm_start,
+ .next = fcomm_next,
+ .stop = fcomm_stop,
+ .show = fcomm_show,
+};
+
+static void *fncomm_start(struct seq_file *m, loff_t *pos)
+ __acquires(RCU)
+{
+ struct trace_comm_list *comm_list;
+ struct trace_array *tr = m->private;
+
+ mutex_lock(&ftrace_lock);
+ rcu_read_lock_sched();
+
+ comm_list = rcu_dereference_sched(tr->function_no_comms);
+
+ if (!comm_list)
+ return !(*pos) ? FTRACE_NO_COMM : NULL;
+
+ return trace_comm_start(comm_list, pos);
+}
+
+static void *fncomm_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ struct trace_array *tr = m->private;
+ struct trace_comm_list *comm_list;
+
+ if (v == FTRACE_NO_COMM) {
+ (*pos)++;
+ return NULL;
+ }
+
+ comm_list = rcu_dereference_sched(tr->function_no_comms);
+ return trace_comm_next(comm_list, v, pos);
+}
+
+static const struct seq_operations ftrace_no_comm_sops = {
+ .start = fncomm_start,
+ .next = fncomm_next,
+ .stop = fcomm_stop,
+ .show = fcomm_show,
+};
+
static int pid_open(struct inode *inode, struct file *file, int type)
{
const struct seq_operations *seq_ops;
@@ -8932,11 +9104,62 @@ ftrace_no_pid_open(struct inode *inode, struct file *file)
return pid_open(inode, file, TRACE_NO_PIDS);
}

+static int comm_open(struct inode *inode, struct file *file, int type)
+{
+ const struct seq_operations *seq_ops;
+ struct trace_array *tr = inode->i_private;
+ struct seq_file *m;
+ int ret = 0;
+
+ ret = tracing_check_open_get_tr(tr);
+ if (ret)
+ return ret;
+
+ if ((file->f_mode & FMODE_WRITE) &&
+ (file->f_flags & O_TRUNC))
+ ftrace_comm_reset(tr, type);
+
+ switch (type) {
+ case TRACE_COMMS:
+ seq_ops = &ftrace_comm_sops;
+ break;
+ case TRACE_NO_COMMS:
+ seq_ops = &ftrace_no_comm_sops;
+ break;
+ default:
+ trace_array_put(tr);
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ ret = seq_open(file, seq_ops);
+ if (ret < 0) {
+ trace_array_put(tr);
+ } else {
+ m = file->private_data;
+ m->private = tr;
+ }
+
+ return ret;
+}
+
+static int ftrace_comm_open(struct inode *inode, struct file *file)
+{
+ return comm_open(inode, file, TRACE_COMMS);
+}
+
+static int ftrace_no_comm_open(struct inode *inode, struct file *file)
+{
+ return comm_open(inode, file, TRACE_NO_COMMS);
+}
+
static void ignore_task_cpu(void *data)
{
struct trace_array *tr = data;
struct trace_pid_list *pid_list;
struct trace_pid_list *no_pid_list;
+ struct trace_comm_list *comm_filter;
+ struct trace_comm_list *no_comm_filter;

/*
* This function is called by on_each_cpu() while the
@@ -8947,7 +9170,12 @@ static void ignore_task_cpu(void *data)
no_pid_list = rcu_dereference_protected(tr->function_no_pids,
mutex_is_locked(&ftrace_lock));

- if (trace_ignore_pid_task(pid_list, no_pid_list, current))
+ comm_filter = rcu_dereference_protected(tr->function_comms,
+ mutex_is_locked(&ftrace_lock));
+ no_comm_filter = rcu_dereference_protected(tr->function_no_comms,
+ mutex_is_locked(&ftrace_lock));
+
+ if (__ftrace_ignore_task(pid_list, no_pid_list, comm_filter, no_comm_filter, current))
this_cpu_write(tr->array_buffer.data->ftrace_ignore_pid,
FTRACE_PID_IGNORE);
else
@@ -9022,6 +9250,86 @@ ftrace_pid_write(struct file *filp, const char __user *ubuf,
return pid_write(filp, ubuf, cnt, ppos, TRACE_PIDS);
}

+static ssize_t comm_write(struct file *filp, const char __user *ubuf,
+ size_t cnt, loff_t *ppos, int type)
+{
+ struct seq_file *m = filp->private_data;
+ struct trace_array *tr = m->private;
+ struct trace_comm_list *filtered_comms;
+ struct trace_comm_list *comm_list;
+ bool task_filters_enabled;
+ ssize_t ret;
+
+ if (!cnt)
+ return 0;
+
+ guard(mutex)(&ftrace_lock);
+ task_filters_enabled = ftrace_task_filters_active(tr);
+
+ switch (type) {
+ case TRACE_COMMS:
+ filtered_comms = rcu_dereference_protected(tr->function_comms,
+ lockdep_is_held(&ftrace_lock));
+ break;
+ case TRACE_NO_COMMS:
+ filtered_comms = rcu_dereference_protected(tr->function_no_comms,
+ lockdep_is_held(&ftrace_lock));
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ ret = trace_comm_write(filtered_comms, &comm_list, ubuf, cnt);
+ if (ret < 0)
+ return ret;
+
+ if (comm_list == filtered_comms) {
+ *ppos += ret;
+ return ret;
+ }
+
+ if (type == TRACE_COMMS)
+ rcu_assign_pointer(tr->function_comms, comm_list);
+ else
+ rcu_assign_pointer(tr->function_no_comms, comm_list);
+
+ if (filtered_comms) {
+ synchronize_rcu();
+ trace_comm_list_free(filtered_comms);
+ }
+
+ ftrace_task_filters_changed(tr, task_filters_enabled);
+ ftrace_update_pid_func();
+ ftrace_startup_all(0);
+
+ *ppos += ret;
+ return ret;
+}
+
+static ssize_t ftrace_comm_write(struct file *filp, const char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ return comm_write(filp, ubuf, cnt, ppos, TRACE_COMMS);
+}
+
+static ssize_t ftrace_no_comm_write(struct file *filp,
+ const char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ return comm_write(filp, ubuf, cnt, ppos, TRACE_NO_COMMS);
+}
+
+static int
+ftrace_comm_release(struct inode *inode, struct file *file)
+{
+ struct trace_array *tr = inode->i_private;
+
+ trace_array_put(tr);
+
+ return seq_release(inode, file);
+}
+
static ssize_t
ftrace_no_pid_write(struct file *filp, const char __user *ubuf,
size_t cnt, loff_t *ppos)
@@ -9055,12 +9363,32 @@ static const struct file_operations ftrace_no_pid_fops = {
.release = ftrace_pid_release,
};

+static const struct file_operations ftrace_comm_fops = {
+ .open = ftrace_comm_open,
+ .write = ftrace_comm_write,
+ .read = seq_read,
+ .llseek = tracing_lseek,
+ .release = ftrace_comm_release,
+};
+
+static const struct file_operations ftrace_no_comm_fops = {
+ .open = ftrace_no_comm_open,
+ .write = ftrace_no_comm_write,
+ .read = seq_read,
+ .llseek = tracing_lseek,
+ .release = ftrace_comm_release,
+};
+
void ftrace_init_tracefs(struct trace_array *tr, struct dentry *d_tracer)
{
trace_create_file("set_ftrace_pid", TRACE_MODE_WRITE, d_tracer,
tr, &ftrace_pid_fops);
trace_create_file("set_ftrace_notrace_pid", TRACE_MODE_WRITE,
d_tracer, tr, &ftrace_no_pid_fops);
+ trace_create_file("set_ftrace_comm", TRACE_MODE_WRITE, d_tracer,
+ tr, &ftrace_comm_fops);
+ trace_create_file("set_ftrace_notrace_comm", TRACE_MODE_WRITE,
+ d_tracer, tr, &ftrace_no_comm_fops);
}

void __init ftrace_init_tracefs_toplevel(struct trace_array *tr,
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 3e0907aef172..8cbfaed24810 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4261,6 +4261,10 @@ static const char readme_msg[] =
"\t\t (function)\n"
" set_ftrace_notrace_pid\t- Write pid(s) to not function trace those pids\n"
"\t\t (function)\n"
+ " set_ftrace_comm\t- Write task comms to only function trace those tasks\n"
+ "\t\t (function)\n"
+ " set_ftrace_notrace_comm\t- Write task comms to not function trace those tasks\n"
+ "\t\t (function)\n"
#endif
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
" set_graph_function\t- Trace the nested calls of a function (function_graph)\n"
@@ -8789,6 +8793,7 @@ static int __remove_instance(struct trace_array *tr)
clear_ftrace_function_probes(tr);
event_trace_del_tracer(tr);
ftrace_clear_pids(tr);
+ ftrace_clear_comms(tr);
ftrace_destroy_function_files(tr);
tracefs_remove(tr->dir);
free_percpu(tr->last_func_repeats);
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index f73913eed307..218bd26dc73a 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -182,6 +182,7 @@ struct fexit_trace_entry_head {
#define TRACE_BUF_SIZE 1024

struct trace_array;
+struct trace_comm_list;

/*
* The CPU trace array - it consists of thousands of trace entries
@@ -237,12 +238,32 @@ int trace_pid_list_clear(struct trace_pid_list *pid_list, unsigned int pid);
int trace_pid_list_first(struct trace_pid_list *pid_list, unsigned int *pid);
int trace_pid_list_next(struct trace_pid_list *pid_list, unsigned int pid,
unsigned int *next);
+struct trace_comm_list *trace_comm_list_alloc(void);
+void trace_comm_list_free(struct trace_comm_list *comm_list);
+bool trace_comm_list_is_set(struct trace_comm_list *comm_list,
+ const char comm[TASK_COMM_LEN]);
+int trace_comm_list_set(struct trace_comm_list *comm_list,
+ const char comm[TASK_COMM_LEN]);
+bool trace_ignore_comm_task(struct trace_comm_list *filtered_comms,
+ struct trace_comm_list *filtered_no_comms,
+ struct task_struct *task);
+void *trace_comm_next(struct trace_comm_list *comm_list, void *v, loff_t *pos);
+void *trace_comm_start(struct trace_comm_list *comm_list, loff_t *pos);
+int trace_comm_show(struct seq_file *m, void *v);
+int trace_comm_write(struct trace_comm_list *filtered_comms,
+ struct trace_comm_list **new_comm_list,
+ const char __user *ubuf, size_t cnt);

enum {
TRACE_PIDS = BIT(0),
TRACE_NO_PIDS = BIT(1),
};

+enum {
+ TRACE_COMMS = BIT(0),
+ TRACE_NO_COMMS = BIT(1),
+};
+
static inline bool pid_type_enabled(int type, struct trace_pid_list *pid_list,
struct trace_pid_list *no_pid_list)
{
@@ -251,6 +272,13 @@ static inline bool pid_type_enabled(int type, struct trace_pid_list *pid_list,
((type & TRACE_NO_PIDS) && no_pid_list);
}

+static inline bool comm_type_enabled(int type, struct trace_comm_list *comm_list,
+ struct trace_comm_list *no_comm_list)
+{
+ return ((type & TRACE_COMMS) && comm_list) ||
+ ((type & TRACE_NO_COMMS) && no_comm_list);
+}
+
static inline bool still_need_pid_events(int type, struct trace_pid_list *pid_list,
struct trace_pid_list *no_pid_list)
{
@@ -434,6 +462,8 @@ struct trace_array {
struct ftrace_ops *ops;
struct trace_pid_list __rcu *function_pids;
struct trace_pid_list __rcu *function_no_pids;
+ struct trace_comm_list __rcu *function_comms;
+ struct trace_comm_list __rcu *function_no_comms;
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
struct fgraph_ops *gops;
#endif
@@ -1267,6 +1297,7 @@ void ftrace_init_tracefs(struct trace_array *tr, struct dentry *d_tracer);
void ftrace_init_tracefs_toplevel(struct trace_array *tr,
struct dentry *d_tracer);
void ftrace_clear_pids(struct trace_array *tr);
+void ftrace_clear_comms(struct trace_array *tr);
int init_function_trace(void);
void ftrace_pid_follow_fork(struct trace_array *tr, bool enable);
#else
@@ -1289,6 +1320,7 @@ static inline void ftrace_reset_array_ops(struct trace_array *tr) { }
static inline void ftrace_init_tracefs(struct trace_array *tr, struct dentry *d) { }
static inline void ftrace_init_tracefs_toplevel(struct trace_array *tr, struct dentry *d) { }
static inline void ftrace_clear_pids(struct trace_array *tr) { }
+static inline void ftrace_clear_comms(struct trace_array *tr) { }
static inline int init_function_trace(void) { return 0; }
static inline void ftrace_pid_follow_fork(struct trace_array *tr, bool enable) { }
/* ftace_func_t type is not defined, use macro instead of static inline */
--
2.25.1