xref: /linux/kernel/trace/trace_recursion_record.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <linux/seq_file.h>
4 #include <linux/kallsyms.h>
5 #include <linux/module.h>
6 #include <linux/ftrace.h>
7 #include <linux/fs.h>
8 
9 #include "trace_output.h"
10 
11 struct recursed_functions {
12 	unsigned long		ip;
13 	unsigned long		parent_ip;
14 };
15 
16 static struct recursed_functions recursed_functions[CONFIG_FTRACE_RECORD_RECURSION_SIZE];
17 static atomic_t nr_records;
18 
19 /*
20  * Cache the last found function. Yes, updates to this is racey, but
21  * so is memory cache ;-)
22  */
23 static unsigned long cached_function;
24 
ftrace_record_recursion(unsigned long ip,unsigned long parent_ip)25 void ftrace_record_recursion(unsigned long ip, unsigned long parent_ip)
26 {
27 	int index = 0;
28 	int i;
29 	unsigned long old;
30 
31  again:
32 	/* First check the last one recorded */
33 	if (ip == cached_function)
34 		return;
35 
36 	i = atomic_read(&nr_records);
37 	/* nr_records is -1 when clearing records */
38 	smp_mb__after_atomic();
39 	if (i < 0)
40 		return;
41 
42 	/*
43 	 * If there's two writers and this writer comes in second,
44 	 * the cmpxchg() below to update the ip will fail. Then this
45 	 * writer will try again. It is possible that index will now
46 	 * be greater than nr_records. This is because the writer
47 	 * that succeeded has not updated the nr_records yet.
48 	 * This writer could keep trying again until the other writer
49 	 * updates nr_records. But if the other writer takes an
50 	 * interrupt, and that interrupt locks up that CPU, we do
51 	 * not want this CPU to lock up due to the recursion protection,
52 	 * and have a bug report showing this CPU as the cause of
53 	 * locking up the computer. To not lose this record, this
54 	 * writer will simply use the next position to update the
55 	 * recursed_functions, and it will update the nr_records
56 	 * accordingly.
57 	 */
58 	if (index < i)
59 		index = i;
60 	if (index >= CONFIG_FTRACE_RECORD_RECURSION_SIZE)
61 		return;
62 
63 	for (i = index - 1; i >= 0; i--) {
64 		if (recursed_functions[i].ip == ip) {
65 			cached_function = ip;
66 			return;
67 		}
68 	}
69 
70 	cached_function = ip;
71 
72 	/*
73 	 * We only want to add a function if it hasn't been added before.
74 	 * Add to the current location before incrementing the count.
75 	 * If it fails to add, then increment the index (save in i)
76 	 * and try again.
77 	 */
78 	old = cmpxchg(&recursed_functions[index].ip, 0, ip);
79 	if (old != 0) {
80 		/* Did something else already added this for us? */
81 		if (old == ip)
82 			return;
83 		/* Try the next location (use i for the next index) */
84 		index++;
85 		goto again;
86 	}
87 
88 	recursed_functions[index].parent_ip = parent_ip;
89 
90 	/*
91 	 * It's still possible that we could race with the clearing
92 	 *    CPU0                                    CPU1
93 	 *    ----                                    ----
94 	 *                                       ip = func
95 	 *  nr_records = -1;
96 	 *  recursed_functions[0] = 0;
97 	 *                                       i = -1
98 	 *                                       if (i < 0)
99 	 *  nr_records = 0;
100 	 *  (new recursion detected)
101 	 *      recursed_functions[0] = func
102 	 *                                            cmpxchg(recursed_functions[0],
103 	 *                                                    func, 0)
104 	 *
105 	 * But the worse that could happen is that we get a zero in
106 	 * the recursed_functions array, and it's likely that "func" will
107 	 * be recorded again.
108 	 */
109 	i = atomic_read(&nr_records);
110 	smp_mb__after_atomic();
111 	if (i < 0)
112 		cmpxchg(&recursed_functions[index].ip, ip, 0);
113 	else if (i <= index)
114 		atomic_cmpxchg(&nr_records, i, index + 1);
115 }
116 EXPORT_SYMBOL_GPL(ftrace_record_recursion);
117 
118 static DEFINE_MUTEX(recursed_function_lock);
119 static struct trace_seq *tseq;
120 
recursed_function_seq_start(struct seq_file * m,loff_t * pos)121 static void *recursed_function_seq_start(struct seq_file *m, loff_t *pos)
122 {
123 	void *ret = NULL;
124 	int index;
125 
126 	mutex_lock(&recursed_function_lock);
127 	index = atomic_read(&nr_records);
128 	if (*pos < index) {
129 		ret = &recursed_functions[*pos];
130 	}
131 
132 	tseq = kzalloc(sizeof(*tseq), GFP_KERNEL);
133 	if (!tseq)
134 		return ERR_PTR(-ENOMEM);
135 
136 	trace_seq_init(tseq);
137 
138 	return ret;
139 }
140 
recursed_function_seq_next(struct seq_file * m,void * v,loff_t * pos)141 static void *recursed_function_seq_next(struct seq_file *m, void *v, loff_t *pos)
142 {
143 	int index;
144 	int p;
145 
146 	index = atomic_read(&nr_records);
147 	p = ++(*pos);
148 
149 	return p < index ? &recursed_functions[p] : NULL;
150 }
151 
recursed_function_seq_stop(struct seq_file * m,void * v)152 static void recursed_function_seq_stop(struct seq_file *m, void *v)
153 {
154 	kfree(tseq);
155 	mutex_unlock(&recursed_function_lock);
156 }
157 
recursed_function_seq_show(struct seq_file * m,void * v)158 static int recursed_function_seq_show(struct seq_file *m, void *v)
159 {
160 	struct recursed_functions *record = v;
161 	int ret = 0;
162 
163 	if (record) {
164 		trace_seq_print_sym(tseq, record->parent_ip, true);
165 		trace_seq_puts(tseq, ":\t");
166 		trace_seq_print_sym(tseq, record->ip, true);
167 		trace_seq_putc(tseq, '\n');
168 		ret = trace_print_seq(m, tseq);
169 	}
170 
171 	return ret;
172 }
173 
174 static const struct seq_operations recursed_function_seq_ops = {
175 	.start  = recursed_function_seq_start,
176 	.next   = recursed_function_seq_next,
177 	.stop   = recursed_function_seq_stop,
178 	.show   = recursed_function_seq_show
179 };
180 
recursed_function_open(struct inode * inode,struct file * file)181 static int recursed_function_open(struct inode *inode, struct file *file)
182 {
183 	int ret = 0;
184 
185 	mutex_lock(&recursed_function_lock);
186 	/* If this file was opened for write, then erase contents */
187 	if ((file->f_mode & FMODE_WRITE) && (file->f_flags & O_TRUNC)) {
188 		/* disable updating records */
189 		atomic_set(&nr_records, -1);
190 		smp_mb__after_atomic();
191 		memset(recursed_functions, 0, sizeof(recursed_functions));
192 		smp_wmb();
193 		/* enable them again */
194 		atomic_set(&nr_records, 0);
195 	}
196 	if (file->f_mode & FMODE_READ)
197 		ret = seq_open(file, &recursed_function_seq_ops);
198 	mutex_unlock(&recursed_function_lock);
199 
200 	return ret;
201 }
202 
recursed_function_write(struct file * file,const char __user * buffer,size_t count,loff_t * ppos)203 static ssize_t recursed_function_write(struct file *file,
204 				       const char __user *buffer,
205 				       size_t count, loff_t *ppos)
206 {
207 	return count;
208 }
209 
recursed_function_release(struct inode * inode,struct file * file)210 static int recursed_function_release(struct inode *inode, struct file *file)
211 {
212 	if (file->f_mode & FMODE_READ)
213 		seq_release(inode, file);
214 	return 0;
215 }
216 
217 static const struct file_operations recursed_functions_fops = {
218 	.open           = recursed_function_open,
219 	.write		= recursed_function_write,
220 	.read           = seq_read,
221 	.llseek         = seq_lseek,
222 	.release        = recursed_function_release,
223 };
224 
create_recursed_functions(void)225 __init static int create_recursed_functions(void)
226 {
227 
228 	trace_create_file("recursed_functions", TRACE_MODE_WRITE,
229 			  NULL, NULL, &recursed_functions_fops);
230 	return 0;
231 }
232 
233 fs_initcall(create_recursed_functions);
234