1367 lines · 35.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only#include <linux/alloc_tag.h>#include <linux/execmem.h>#include <linux/fs.h>#include <linux/gfp.h>#include <linux/kallsyms.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/compat.h>#include <linux/page_ext.h>#include <linux/pgalloc_tag.h>#include <linux/proc_fs.h>#include <linux/rcupdate.h>#include <linux/seq_buf.h>#include <linux/seq_file.h>#include <linux/string_choices.h>#include <linux/vmalloc.h>#include <linux/kmemleak.h>#include <uapi/linux/alloc_tag.h> #include "internal.h"#include "page_alloc.h" #define ALLOCINFO_FILE_NAME		"allocinfo"#define MODULE_ALLOC_TAG_VMAP_SIZE	(100000UL * sizeof(struct alloc_tag))#define SECTION_START(NAME)		(CODETAG_SECTION_START_PREFIX NAME)#define SECTION_STOP(NAME)		(CODETAG_SECTION_STOP_PREFIX NAME) #ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULTstatic bool mem_profiling_support = true;#elsestatic bool mem_profiling_support;#endif /* * Memory allocation profiling is permanently disabled and cannot be enabled. * Must be called after setup_early_mem_profiling(). */bool mem_alloc_profiling_permanently_disabled(void){	return !mem_profiling_support;} static struct codetag_type *alloc_tag_cttype; #ifdef CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPUDEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag);EXPORT_SYMBOL(_shared_alloc_tag);#endif DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT,			mem_alloc_profiling_key);EXPORT_SYMBOL(mem_alloc_profiling_key); DEFINE_STATIC_KEY_FALSE(mem_profiling_compressed); struct alloc_tag_kernel_section kernel_tags = { NULL, 0 };unsigned long alloc_tag_ref_mask;int alloc_tag_ref_offs; 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 */	struct mutex ioctl_lock;}; static void *allocinfo_start(struct seq_file *m, loff_t *pos){	struct allocinfo_private *priv;	loff_t node = *pos; 	priv = (struct allocinfo_private *)m->private;	codetag_lock_module_list(alloc_tag_cttype);	if (node == 0) {		priv->print_header = true;		priv->iter = codetag_get_ct_iter(alloc_tag_cttype);	} else {		priv->iter = priv->reported_iter;	}	codetag_next_ct(&priv->iter);	return priv->iter.ct ? priv : NULL;} static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos){	struct allocinfo_private *priv = (struct allocinfo_private *)arg;	struct codetag *ct; 	priv->reported_iter = priv->iter;	ct = codetag_next_ct(&priv->iter);	(*pos)++;	if (!ct)		return NULL; 	return priv;} static void allocinfo_stop(struct seq_file *m, void *arg){	codetag_unlock_module_list(alloc_tag_cttype);} static void print_allocinfo_header(struct seq_buf *buf){	/* Output format version, so we can change it. */	seq_buf_printf(buf, "allocinfo - version: 2.0\n");	seq_buf_printf(buf, "#     <size>  <calls> <tag info>\n");} static void alloc_tag_to_text(struct seq_buf *out, struct codetag *ct){	struct alloc_tag *tag = ct_to_alloc_tag(ct);	struct alloc_tag_counters counter = alloc_tag_read(tag);	s64 bytes = counter.bytes; 	seq_buf_printf(out, "%12lli %8llu ", bytes, counter.calls);	codetag_to_text(out, ct);	if (unlikely(alloc_tag_is_inaccurate(tag)))		seq_buf_printf(out, " accurate:no");	seq_buf_putc(out, ' ');	seq_buf_putc(out, '\n');} static int allocinfo_show(struct seq_file *m, void *arg){	struct allocinfo_private *priv = (struct allocinfo_private *)arg;	char *bufp;	size_t n = seq_get_buf(m, &bufp);	struct seq_buf buf; 	seq_buf_init(&buf, bufp, n);	if (priv->print_header) {		print_allocinfo_header(&buf);		priv->print_header = false;	}	alloc_tag_to_text(&buf, priv->iter.ct);	seq_commit(m, seq_buf_used(&buf));	return 0;} static const struct seq_operations allocinfo_seq_op = {	.start	= allocinfo_start,	.next	= allocinfo_next,	.stop	= allocinfo_stop,	.show	= allocinfo_show,}; /* * Initializes seq_file operations and allocates private state when opening * the /proc/allocinfo procfs entry. */static int allocinfo_open(struct inode *inode, struct file *file){	int ret; 	ret = seq_open_private(file, &allocinfo_seq_op,			       sizeof(struct allocinfo_private));	if (!ret) {		struct seq_file *m = file->private_data;		struct allocinfo_private *priv = m->private; 		mutex_init(&priv->ioctl_lock);	}	return ret;} /* * Cleans up the seq_file state and frees up the private state allocated in * allocinfo_open() when closing the /proc/allocinfo file descriptor. */static int allocinfo_release(struct inode *inode, struct file *file){	struct seq_file *m = file->private_data;	struct allocinfo_private *priv = m->private; 	mutex_destroy(&priv->ioctl_lock);	return seq_release_private(inode, file);} /* * Returns a pointer to the suffix of a string so that its length fits within * ALLOCINFO_STR_SIZE, preserving the trailing characters. * Function, file and module names often have the same prefixes, therefore * when filtering by these criteria, we compare the last 64 characters to * minimize the chances of name collisions */static const char *allocinfo_str(const char *str){	size_t len = strlen(str); 	/* Keep an extra space for the trailing NULL. */	if (len >= ALLOCINFO_STR_SIZE)		str += (len - ALLOCINFO_STR_SIZE) + 1;	return str;} /* Copy a string and trim from the beginning if it's too long */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);} /* Fetch the per-CPU counters */static inline struct alloc_tag_counters allocinfo_prefetch_counters(struct codetag *ct){	return alloc_tag_read(ct_to_alloc_tag(ct));} /* * Populates the UAPI allocinfo_tag_data structure with active runtime * profiling counters extracted from the given kernel codetag. */static void allocinfo_to_params(struct codetag *ct,				struct allocinfo_tag_data *data,				struct alloc_tag_counters *counters){	if (ct->modname)		allocinfo_copy_str(data->tag.modname, ct->modname);	else		data->tag.modname[0] = '\0';	allocinfo_copy_str(data->tag.function, ct->function);	allocinfo_copy_str(data->tag.filename, ct->filename);	data->tag.lineno = ct->lineno;	data->counter.bytes = counters->bytes;	data->counter.calls = counters->calls;	data->counter.accurate = !alloc_tag_is_inaccurate(ct_to_alloc_tag(ct));} /* * Retrieves the unique content ID representing the current allocation tag module * layout, allowing userspace to detect if modules were loaded / unloaded. */static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg){	struct allocinfo_content_id params; 	codetag_lock_module_list(alloc_tag_cttype);	params.id = codetag_get_content_id(alloc_tag_cttype);	codetag_unlock_module_list(alloc_tag_cttype);	if (copy_to_user(arg, &params, sizeof(params)))		return -EFAULT; 	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,			   struct alloc_tag_counters *counters,			   bool *fetched_counters){	bool inaccurate; 	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; 	if (filter->mask & ALLOCINFO_FILTER_MASK_INACCURATE) {		inaccurate = !!(ct->flags & CODETAG_FLAG_INACCURATE);		if (inaccurate != !!(filter->inaccurate))			return false;	} 	if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) {		if (!*fetched_counters) {			*counters = allocinfo_prefetch_counters(ct);			*fetched_counters = true;		}		if ((filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) &&		    counters->bytes < filter->min_size)			return false;		if ((filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) &&		    counters->bytes > filter->max_size)			return false;	} 	return true;} /* * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its * profiling data and returns it to userspace. */static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg){	struct allocinfo_private *priv;	struct codetag *ct;	struct allocinfo_get_at params = {0};	__u64 skip_count;	struct alloc_tag_counters counters;	bool fetched_counters; 	if (copy_from_user(&params, arg, sizeof(params)))		return -EFAULT; 	if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS)		return -EINVAL; 	if ((params.filter.mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) &&	    (params.filter.mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) &&	    params.filter.min_size > params.filter.max_size)		return -EINVAL; 	priv = m->private; 	mutex_lock(&priv->ioctl_lock);	codetag_lock_module_list(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) {		fetched_counters = false;		if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) {			if (skip_count == 0)				break;			skip_count--;		}		ct = codetag_next_ct(&priv->ioctl_iter);	} 	if (ct) {		if (!fetched_counters)			counters = allocinfo_prefetch_counters(ct);		allocinfo_to_params(ct, &params.data, &counters);		priv->positioned = true;	} 	codetag_unlock_module_list(alloc_tag_cttype);	mutex_unlock(&priv->ioctl_lock); 	if (!ct)		return -ENOENT; 	if (copy_to_user(arg, &params, sizeof(params)))		return -EFAULT; 	return 0;} /* * Advances the ioctl iterator to the next allocation tag in the sequence and * returns its profiling data to userspace. */static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg){	struct allocinfo_private *priv;	struct codetag *ct;	struct allocinfo_tag_data params;	int ret = 0;	struct alloc_tag_counters counters;	bool fetched_counters; 	memset(&params, 0, sizeof(params));	priv = m->private; 	mutex_lock(&priv->ioctl_lock);	codetag_lock_module_list(alloc_tag_cttype); 	if (!priv->positioned) {		priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype);		priv->positioned = true;	} 	ct = codetag_next_ct(&priv->ioctl_iter);	while (ct) {		fetched_counters = false;		if (matches_filter(ct, &priv->filter, &counters, &fetched_counters))			break;		ct = codetag_next_ct(&priv->ioctl_iter);	} 	if (ct) {		if (!fetched_counters)			counters = allocinfo_prefetch_counters(ct);		allocinfo_to_params(ct, &params, &counters);	}	if (!ct) {		priv->positioned = false;		ret = -ENOENT;	}	codetag_unlock_module_list(alloc_tag_cttype);	mutex_unlock(&priv->ioctl_lock); 	if (ret == 0) {		if (copy_to_user(arg, &params, sizeof(params)))			return -EFAULT;	}	return ret;} /* * Entry point ioctl function for /proc/allocinfo routing requests to fetch the * layout content ID, seek to a specific tag, or read sequential tags. */static long allocinfo_ioctl(struct file *file, unsigned int cmd,			    unsigned long __arg){	void __user *arg = (void __user *)__arg;	int ret; 	switch (cmd) {	case ALLOCINFO_IOC_CONTENT_ID:		ret = allocinfo_ioctl_get_content_id(file->private_data, arg);		break;	case ALLOCINFO_IOC_GET_AT:		ret = allocinfo_ioctl_get_at(file->private_data, arg);		break;	case ALLOCINFO_IOC_GET_NEXT:		ret = allocinfo_ioctl_get_next(file->private_data, arg);		break;	default:		ret = -ENOIOCTLCMD;		break;	} 	return ret;} #ifdef CONFIG_COMPATstatic long allocinfo_compat_ioctl(struct file *file, unsigned int cmd,				   unsigned long arg){	return allocinfo_ioctl(file, cmd, (unsigned long)compat_ptr(arg));}#endif static const struct proc_ops allocinfo_proc_ops = {	.proc_open		= allocinfo_open,	.proc_read_iter		= seq_read_iter,	.proc_lseek		= seq_lseek,	.proc_release		= allocinfo_release,	.proc_ioctl		= allocinfo_ioctl,#ifdef CONFIG_COMPAT	.proc_compat_ioctl	= allocinfo_compat_ioctl,#endif}; size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep){	struct codetag_iterator iter;	struct codetag *ct;	struct codetag_bytes n;	unsigned int i, nr = 0; 	if (IS_ERR_OR_NULL(alloc_tag_cttype))		return 0; 	if (can_sleep)		codetag_lock_module_list(alloc_tag_cttype);	else if (!codetag_trylock_module_list(alloc_tag_cttype))		return 0;

Lines 1–500 of 1367.