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 Acked-by: Hao Ge Acked-by: Suren Baghdasaryan --- include/uapi/linux/alloc_tag.h | 26 ++++++++++++- mm/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 ee6a023cbaf4..13e9b5916bf5 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -45,8 +45,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/mm/alloc_tag.c b/mm/alloc_tag.c index c73195000830..f00d731b81cf 100644 --- a/mm/alloc_tag.c +++ b/mm/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 */ @@ -191,6 +192,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. @@ -230,6 +237,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. @@ -238,29 +279,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(¶ms, 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, ¶ms.data); priv->positioned = true; @@ -301,6 +359,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, ¶ms); -- 2.55.0.rc2.803.g1fd1e6609c-goog