[PATCH v5 2/6] alloc_tag: add ioctl filters to /proc/allocinfo

From: Abhishek Bapat

Date: Mon Jun 15 2026 - 19:05:30 EST


Extend the capability of the IOCTL mechanism to filter allocations based
on tag's module name, function name, file name and line number.

Signed-off-by: Abhishek Bapat <abhishekbapat@xxxxxxxxxx>
Acked-by: Hao Ge <hao.ge@xxxxxxxxx>
---
include/uapi/linux/alloc_tag.h | 26 ++++++++++++-
lib/alloc_tag.c | 68 ++++++++++++++++++++++++++++++++--
2 files changed, 89 insertions(+), 5 deletions(-)

diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h
index 0928e1a48d49..3b11877955b9 100644
--- a/include/uapi/linux/alloc_tag.h
+++ b/include/uapi/linux/alloc_tag.h
@@ -40,8 +40,32 @@ struct allocinfo_tag_data {
struct allocinfo_counter counter;
};

+enum {
+ ALLOCINFO_FILTER_MODNAME,
+ ALLOCINFO_FILTER_FUNCTION,
+ ALLOCINFO_FILTER_FILENAME,
+ ALLOCINFO_FILTER_LINENO,
+ __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO
+};
+
+#define ALLOCINFO_FILTER_MASK_MODNAME (1 << ALLOCINFO_FILTER_MODNAME)
+#define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION)
+#define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME)
+#define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO)
+
+#define ALLOCINFO_FILTER_MASKS \
+ ((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1)
+
+struct allocinfo_filter {
+ __u64 mask; /* bitmask of the filter fields used */
+ struct allocinfo_tag fields;
+};
+
struct allocinfo_get_at {
- __u64 pos; /* input */
+ /* inputs */
+ __u64 pos;
+ struct allocinfo_filter filter;
+ /* output */
struct allocinfo_tag_data data;
};

diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
index 82e3b5f32dff..5feb61d9fb92 100644
--- a/lib/alloc_tag.c
+++ b/lib/alloc_tag.c
@@ -49,6 +49,7 @@ struct allocinfo_private {
struct codetag_iterator iter;
struct codetag_iterator reported_iter;
bool print_header;
+ struct allocinfo_filter filter;
/* ioctl uses a separate iterator not to interfere with reads */
struct codetag_iterator ioctl_iter;
bool positioned; /* seq_open_private() sets to 0 */
@@ -188,6 +189,12 @@ static void allocinfo_copy_str(char *dest, const char *src)
strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE);
}

+/* Compare two strings and only consider the trimmed suffix if s1 is too long */
+static int allocinfo_cmp_str(const char *str, const char *template)
+{
+ return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE);
+}
+
/*
* Populates the UAPI allocinfo_tag_data structure with active runtime
* profiling counters extracted from the given kernel codetag.
@@ -227,6 +234,40 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg)
return 0;
}

+/*
+ * Verifies whether a given codetag satisfies the active filtering criteria by
+ * matching its characteristics against the specified filter.
+ */
+static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter)
+{
+ if (!filter || !filter->mask)
+ return true;
+
+ if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) {
+ /* user wants to filter by modname but ct->modname is NULL */
+ if (!ct->modname) {
+ /* validate if user was attempting to filter for built-in allocations */
+ if (filter->fields.modname[0] != '\0')
+ return false;
+ } else if (allocinfo_cmp_str(ct->modname, filter->fields.modname))
+ return false;
+ }
+
+ if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) &&
+ ct->function && allocinfo_cmp_str(ct->function, filter->fields.function))
+ return false;
+
+ if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) &&
+ ct->filename && allocinfo_cmp_str(ct->filename, filter->fields.filename))
+ return false;
+
+ if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) &&
+ ct->lineno != filter->fields.lineno)
+ return false;
+
+ return true;
+}
+
/*
* Seeks the ioctl iterator to the specified 0-indexed tag position, reads its
* profiling data and returns it to userspace.
@@ -235,29 +276,46 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg)
{
struct allocinfo_private *priv;
struct codetag *ct;
- __u64 pos;
struct allocinfo_get_at params = {0};
+ __u64 skip_count;

if (copy_from_user(&params, arg, sizeof(params)))
return -EFAULT;

+ if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS)
+ return -EINVAL;
+
priv = m->private;
- pos = params.pos;

mutex_lock(&priv->ioctl_lock);
codetag_lock_module_list(alloc_tag_cttype);

- if (pos >= codetag_get_count(alloc_tag_cttype)) {
+ if (params.pos >= codetag_get_count(alloc_tag_cttype)) {
codetag_unlock_module_list(alloc_tag_cttype);
mutex_unlock(&priv->ioctl_lock);
return -ENOENT;
}

+ skip_count = params.pos;
+
+ if (params.filter.mask)
+ priv->filter = params.filter;
+ else
+ priv->filter.mask = 0;
+
/* Find the codetag */
priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);
ct = codetag_next_ct(&priv->ioctl_iter);
- while (ct && pos--)
+
+ while (ct) {
+ if (matches_filter(ct, &priv->filter)) {
+ if (skip_count == 0)
+ break;
+ skip_count--;
+ }
ct = codetag_next_ct(&priv->ioctl_iter);
+ }
+
if (ct) {
allocinfo_to_params(ct, &params.data);
priv->positioned = true;
@@ -298,6 +356,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg)
}

ct = codetag_next_ct(&priv->ioctl_iter);
+ while (ct && !matches_filter(ct, &priv->filter))
+ ct = codetag_next_ct(&priv->ioctl_iter);
if (ct)
allocinfo_to_params(ct, &params);

--
2.54.0.1136.gdb2ca164c4-goog