233 lines · 6.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0 #include <linux/seq_file.h>#include <linux/kallsyms.h>#include <linux/module.h>#include <linux/ftrace.h>#include <linux/fs.h> #include "trace_output.h" struct recursed_functions {	unsigned long		ip;	unsigned long		parent_ip;}; static struct recursed_functions recursed_functions[CONFIG_FTRACE_RECORD_RECURSION_SIZE];static atomic_t nr_records; /* * Cache the last found function. Yes, updates to this is racey, but * so is memory cache ;-) */static unsigned long cached_function; void ftrace_record_recursion(unsigned long ip, unsigned long parent_ip){	int index = 0;	int i;	unsigned long old;  again:	/* First check the last one recorded */	if (ip == cached_function)		return; 	i = atomic_read(&nr_records);	/* nr_records is -1 when clearing records */	smp_mb__after_atomic();	if (i < 0)		return; 	/*	 * If there's two writers and this writer comes in second,	 * the cmpxchg() below to update the ip will fail. Then this	 * writer will try again. It is possible that index will now	 * be greater than nr_records. This is because the writer	 * that succeeded has not updated the nr_records yet.	 * This writer could keep trying again until the other writer	 * updates nr_records. But if the other writer takes an	 * interrupt, and that interrupt locks up that CPU, we do	 * not want this CPU to lock up due to the recursion protection,	 * and have a bug report showing this CPU as the cause of	 * locking up the computer. To not lose this record, this	 * writer will simply use the next position to update the	 * recursed_functions, and it will update the nr_records	 * accordingly.	 */	if (index < i)		index = i;	if (index >= CONFIG_FTRACE_RECORD_RECURSION_SIZE)		return; 	for (i = index - 1; i >= 0; i--) {		if (recursed_functions[i].ip == ip) {			cached_function = ip;			return;		}	} 	cached_function = ip; 	/*	 * We only want to add a function if it hasn't been added before.	 * Add to the current location before incrementing the count.	 * If it fails to add, then increment the index (save in i)	 * and try again.	 */	old = cmpxchg(&recursed_functions[index].ip, 0, ip);	if (old != 0) {		/* Did something else already added this for us? */		if (old == ip)			return;		/* Try the next location (use i for the next index) */		index++;		goto again;	} 	recursed_functions[index].parent_ip = parent_ip; 	/*	 * It's still possible that we could race with the clearing	 *    CPU0                                    CPU1	 *    ----                                    ----	 *                                       ip = func	 *  nr_records = -1;	 *  recursed_functions[0] = 0;	 *                                       i = -1	 *                                       if (i < 0)	 *  nr_records = 0;	 *  (new recursion detected)	 *      recursed_functions[0] = func	 *                                            cmpxchg(recursed_functions[0],	 *                                                    func, 0)	 *	 * But the worse that could happen is that we get a zero in	 * the recursed_functions array, and it's likely that "func" will	 * be recorded again.	 */	i = atomic_read(&nr_records);	smp_mb__after_atomic();	if (i < 0)		cmpxchg(&recursed_functions[index].ip, ip, 0);	else if (i <= index)		atomic_cmpxchg(&nr_records, i, index + 1);}EXPORT_SYMBOL_GPL(ftrace_record_recursion); static DEFINE_MUTEX(recursed_function_lock);static struct trace_seq *tseq; static void *recursed_function_seq_start(struct seq_file *m, loff_t *pos){	void *ret = NULL;	int index; 	mutex_lock(&recursed_function_lock);	index = atomic_read(&nr_records);	if (*pos < index) {		ret = &recursed_functions[*pos];	} 	tseq = kzalloc(sizeof(*tseq), GFP_KERNEL);	if (!tseq)		return ERR_PTR(-ENOMEM); 	trace_seq_init(tseq); 	return ret;} static void *recursed_function_seq_next(struct seq_file *m, void *v, loff_t *pos){	int index;	int p; 	index = atomic_read(&nr_records);	p = ++(*pos); 	return p < index ? &recursed_functions[p] : NULL;} static void recursed_function_seq_stop(struct seq_file *m, void *v){	kfree(tseq);	mutex_unlock(&recursed_function_lock);} static int recursed_function_seq_show(struct seq_file *m, void *v){	struct recursed_functions *record = v;	int ret = 0; 	if (record) {		trace_seq_print_sym(tseq, record->parent_ip, true);		trace_seq_puts(tseq, ":\t");		trace_seq_print_sym(tseq, record->ip, true);		trace_seq_putc(tseq, '\n');		ret = trace_print_seq(m, tseq);	} 	return ret;} static const struct seq_operations recursed_function_seq_ops = {	.start  = recursed_function_seq_start,	.next   = recursed_function_seq_next,	.stop   = recursed_function_seq_stop,	.show   = recursed_function_seq_show}; static int recursed_function_open(struct inode *inode, struct file *file){	int ret = 0; 	mutex_lock(&recursed_function_lock);	/* If this file was opened for write, then erase contents */	if ((file->f_mode & FMODE_WRITE) && (file->f_flags & O_TRUNC)) {		/* disable updating records */		atomic_set(&nr_records, -1);		smp_mb__after_atomic();		memset(recursed_functions, 0, sizeof(recursed_functions));		smp_wmb();		/* enable them again */		atomic_set(&nr_records, 0);	}	if (file->f_mode & FMODE_READ)		ret = seq_open(file, &recursed_function_seq_ops);	mutex_unlock(&recursed_function_lock); 	return ret;} static ssize_t recursed_function_write(struct file *file,				       const char __user *buffer,				       size_t count, loff_t *ppos){	return count;} static int recursed_function_release(struct inode *inode, struct file *file){	if (file->f_mode & FMODE_READ)		seq_release(inode, file);	return 0;} static const struct file_operations recursed_functions_fops = {	.open           = recursed_function_open,	.write		= recursed_function_write,	.read           = seq_read,	.llseek         = seq_lseek,	.release        = recursed_function_release,}; __init static int create_recursed_functions(void){ 	trace_create_file("recursed_functions", TRACE_MODE_WRITE,			  NULL, NULL, &recursed_functions_fops);	return 0;} fs_initcall(create_recursed_functions);