Skip to content

Commit 6c3edaf

Browse files
congwangrostedt
authored andcommitted
tracing: Introduce trace event injection
We have been trying to use rasdaemon to monitor hardware errors like correctable memory errors. rasdaemon uses trace events to monitor various hardware errors. In order to test it, we have to inject some hardware errors, unfortunately not all of them provide error injections. MCE does provide a way to inject MCE errors, but errors like PCI error and devlink error don't, it is not easy to add error injection to each of them. Instead, it is relatively easier to just allow users to inject trace events in a generic way so that all trace events can be injected. This patch introduces trace event injection, where a new 'inject' is added to each tracepoint directory. Users could write into this file with key=value pairs to specify the value of each fields of the trace event, all unspecified fields are set to zero values by default. For example, for the net/net_dev_queue tracepoint, we can inject: INJECT=/sys/kernel/debug/tracing/events/net/net_dev_queue/inject echo "" > $INJECT echo "name='test'" > $INJECT echo "name='test' len=1024" > $INJECT cat /sys/kernel/debug/tracing/trace ... <...>-614 [000] .... 36.571483: net_dev_queue: dev= skbaddr=00000000fbf338c2 len=0 <...>-614 [001] .... 136.588252: net_dev_queue: dev=test skbaddr=00000000fbf338c2 len=0 <...>-614 [001] .N.. 208.431878: net_dev_queue: dev=test skbaddr=00000000fbf338c2 len=1024 Triggers could be triggered as usual too: echo "stacktrace if len == 1025" > /sys/kernel/debug/tracing/events/net/net_dev_queue/trigger echo "len=1025" > $INJECT cat /sys/kernel/debug/tracing/trace ... bash-614 [000] .... 36.571483: net_dev_queue: dev= skbaddr=00000000fbf338c2 len=0 bash-614 [001] .... 136.588252: net_dev_queue: dev=test skbaddr=00000000fbf338c2 len=0 bash-614 [001] .N.. 208.431878: net_dev_queue: dev=test skbaddr=00000000fbf338c2 len=1024 bash-614 [001] .N.1 284.236349: <stack trace> => event_inject_write => vfs_write => ksys_write => do_syscall_64 => entry_SYSCALL_64_after_hwframe The only thing that can't be injected is string pointers as they require constant string pointers, this can't be done at run time. Link: http://lkml.kernel.org/r/[email protected] Cc: Ingo Molnar <[email protected]> Signed-off-by: Cong Wang <[email protected]> Signed-off-by: Steven Rostedt (VMware) <[email protected]>
1 parent 02a65a0 commit 6c3edaf

File tree

5 files changed

+348
-0
lines changed

5 files changed

+348
-0
lines changed

kernel/trace/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,15 @@ config HIST_TRIGGERS
672672
See Documentation/trace/histogram.rst.
673673
If in doubt, say N.
674674

675+
config TRACE_EVENT_INJECT
676+
bool "Trace event injection"
677+
depends on TRACING
678+
help
679+
Allow user-space to inject a specific trace event into the ring
680+
buffer. This is mainly used for testing purpose.
681+
682+
If unsure, say N.
683+
675684
config MMIOTRACE_TEST
676685
tristate "Test module for mmiotrace"
677686
depends on MMIOTRACE && m

kernel/trace/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ obj-$(CONFIG_EVENT_TRACING) += trace_event_perf.o
6969
endif
7070
obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o
7171
obj-$(CONFIG_EVENT_TRACING) += trace_events_trigger.o
72+
obj-$(CONFIG_TRACE_EVENT_INJECT) += trace_events_inject.o
7273
obj-$(CONFIG_HIST_TRIGGERS) += trace_events_hist.o
7374
obj-$(CONFIG_BPF_EVENTS) += bpf_trace.o
7475
obj-$(CONFIG_KPROBE_EVENTS) += trace_kprobe.o

kernel/trace/trace.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,7 @@ extern struct list_head ftrace_events;
16011601

16021602
extern const struct file_operations event_trigger_fops;
16031603
extern const struct file_operations event_hist_fops;
1604+
extern const struct file_operations event_inject_fops;
16041605

16051606
#ifdef CONFIG_HIST_TRIGGERS
16061607
extern int register_trigger_hist_cmd(void);

kernel/trace/trace_events.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,6 +2044,12 @@ event_create_dir(struct dentry *parent, struct trace_event_file *file)
20442044
trace_create_file("format", 0444, file->dir, call,
20452045
&ftrace_event_format_fops);
20462046

2047+
#ifdef CONFIG_TRACE_EVENT_INJECT
2048+
if (call->event.type && call->class->reg)
2049+
trace_create_file("inject", 0200, file->dir, file,
2050+
&event_inject_fops);
2051+
#endif
2052+
20472053
return 0;
20482054
}
20492055

kernel/trace/trace_events_inject.c

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* trace_events_inject - trace event injection
4+
*
5+
* Copyright (C) 2019 Cong Wang <[email protected]>
6+
*/
7+
8+
#include <linux/module.h>
9+
#include <linux/ctype.h>
10+
#include <linux/mutex.h>
11+
#include <linux/slab.h>
12+
#include <linux/rculist.h>
13+
14+
#include "trace.h"
15+
16+
static int
17+
trace_inject_entry(struct trace_event_file *file, void *rec, int len)
18+
{
19+
struct trace_event_buffer fbuffer;
20+
struct ring_buffer *buffer;
21+
int written = 0;
22+
void *entry;
23+
24+
rcu_read_lock_sched();
25+
buffer = file->tr->trace_buffer.buffer;
26+
entry = trace_event_buffer_reserve(&fbuffer, file, len);
27+
if (entry) {
28+
memcpy(entry, rec, len);
29+
written = len;
30+
trace_event_buffer_commit(&fbuffer);
31+
}
32+
rcu_read_unlock_sched();
33+
34+
return written;
35+
}
36+
37+
static int
38+
parse_field(char *str, struct trace_event_call *call,
39+
struct ftrace_event_field **pf, u64 *pv)
40+
{
41+
struct ftrace_event_field *field;
42+
char *field_name;
43+
int s, i = 0;
44+
int len;
45+
u64 val;
46+
47+
if (!str[i])
48+
return 0;
49+
/* First find the field to associate to */
50+
while (isspace(str[i]))
51+
i++;
52+
s = i;
53+
while (isalnum(str[i]) || str[i] == '_')
54+
i++;
55+
len = i - s;
56+
if (!len)
57+
return -EINVAL;
58+
59+
field_name = kmemdup_nul(str + s, len, GFP_KERNEL);
60+
if (!field_name)
61+
return -ENOMEM;
62+
field = trace_find_event_field(call, field_name);
63+
kfree(field_name);
64+
if (!field)
65+
return -ENOENT;
66+
67+
*pf = field;
68+
while (isspace(str[i]))
69+
i++;
70+
if (str[i] != '=')
71+
return -EINVAL;
72+
i++;
73+
while (isspace(str[i]))
74+
i++;
75+
s = i;
76+
if (isdigit(str[i]) || str[i] == '-') {
77+
char *num, c;
78+
int ret;
79+
80+
/* Make sure the field is not a string */
81+
if (is_string_field(field))
82+
return -EINVAL;
83+
84+
if (str[i] == '-')
85+
i++;
86+
87+
/* We allow 0xDEADBEEF */
88+
while (isalnum(str[i]))
89+
i++;
90+
num = str + s;
91+
c = str[i];
92+
if (c != '\0' && !isspace(c))
93+
return -EINVAL;
94+
str[i] = '\0';
95+
/* Make sure it is a value */
96+
if (field->is_signed)
97+
ret = kstrtoll(num, 0, &val);
98+
else
99+
ret = kstrtoull(num, 0, &val);
100+
str[i] = c;
101+
if (ret)
102+
return ret;
103+
104+
*pv = val;
105+
return i;
106+
} else if (str[i] == '\'' || str[i] == '"') {
107+
char q = str[i];
108+
109+
/* Make sure the field is OK for strings */
110+
if (!is_string_field(field))
111+
return -EINVAL;
112+
113+
for (i++; str[i]; i++) {
114+
if (str[i] == '\\' && str[i + 1]) {
115+
i++;
116+
continue;
117+
}
118+
if (str[i] == q)
119+
break;
120+
}
121+
if (!str[i])
122+
return -EINVAL;
123+
124+
/* Skip quotes */
125+
s++;
126+
len = i - s;
127+
if (len >= MAX_FILTER_STR_VAL)
128+
return -EINVAL;
129+
130+
*pv = (unsigned long)(str + s);
131+
str[i] = 0;
132+
/* go past the last quote */
133+
i++;
134+
return i;
135+
}
136+
137+
return -EINVAL;
138+
}
139+
140+
static int trace_get_entry_size(struct trace_event_call *call)
141+
{
142+
struct ftrace_event_field *field;
143+
struct list_head *head;
144+
int size = 0;
145+
146+
head = trace_get_fields(call);
147+
list_for_each_entry(field, head, link) {
148+
if (field->size + field->offset > size)
149+
size = field->size + field->offset;
150+
}
151+
152+
return size;
153+
}
154+
155+
static void *trace_alloc_entry(struct trace_event_call *call, int *size)
156+
{
157+
int entry_size = trace_get_entry_size(call);
158+
struct ftrace_event_field *field;
159+
struct list_head *head;
160+
void *entry = NULL;
161+
162+
/* We need an extra '\0' at the end. */
163+
entry = kzalloc(entry_size + 1, GFP_KERNEL);
164+
if (!entry)
165+
return NULL;
166+
167+
head = trace_get_fields(call);
168+
list_for_each_entry(field, head, link) {
169+
if (!is_string_field(field))
170+
continue;
171+
if (field->filter_type == FILTER_STATIC_STRING)
172+
continue;
173+
if (field->filter_type == FILTER_DYN_STRING) {
174+
u32 *str_item;
175+
int str_loc = entry_size & 0xffff;
176+
177+
str_item = (u32 *)(entry + field->offset);
178+
*str_item = str_loc; /* string length is 0. */
179+
} else {
180+
char **paddr;
181+
182+
paddr = (char **)(entry + field->offset);
183+
*paddr = "";
184+
}
185+
}
186+
187+
*size = entry_size + 1;
188+
return entry;
189+
}
190+
191+
#define INJECT_STRING "STATIC STRING CAN NOT BE INJECTED"
192+
193+
/* Caller is responsible to free the *pentry. */
194+
static int parse_entry(char *str, struct trace_event_call *call, void **pentry)
195+
{
196+
struct ftrace_event_field *field;
197+
unsigned long irq_flags;
198+
void *entry = NULL;
199+
int entry_size;
200+
u64 val;
201+
int len;
202+
203+
entry = trace_alloc_entry(call, &entry_size);
204+
*pentry = entry;
205+
if (!entry)
206+
return -ENOMEM;
207+
208+
local_save_flags(irq_flags);
209+
tracing_generic_entry_update(entry, call->event.type, irq_flags,
210+
preempt_count());
211+
212+
while ((len = parse_field(str, call, &field, &val)) > 0) {
213+
if (is_function_field(field))
214+
return -EINVAL;
215+
216+
if (is_string_field(field)) {
217+
char *addr = (char *)(unsigned long) val;
218+
219+
if (field->filter_type == FILTER_STATIC_STRING) {
220+
strlcpy(entry + field->offset, addr, field->size);
221+
} else if (field->filter_type == FILTER_DYN_STRING) {
222+
int str_len = strlen(addr) + 1;
223+
int str_loc = entry_size & 0xffff;
224+
u32 *str_item;
225+
226+
entry_size += str_len;
227+
*pentry = krealloc(entry, entry_size, GFP_KERNEL);
228+
if (!*pentry) {
229+
kfree(entry);
230+
return -ENOMEM;
231+
}
232+
entry = *pentry;
233+
234+
strlcpy(entry + (entry_size - str_len), addr, str_len);
235+
str_item = (u32 *)(entry + field->offset);
236+
*str_item = (str_len << 16) | str_loc;
237+
} else {
238+
char **paddr;
239+
240+
paddr = (char **)(entry + field->offset);
241+
*paddr = INJECT_STRING;
242+
}
243+
} else {
244+
switch (field->size) {
245+
case 1: {
246+
u8 tmp = (u8) val;
247+
248+
memcpy(entry + field->offset, &tmp, 1);
249+
break;
250+
}
251+
case 2: {
252+
u16 tmp = (u16) val;
253+
254+
memcpy(entry + field->offset, &tmp, 2);
255+
break;
256+
}
257+
case 4: {
258+
u32 tmp = (u32) val;
259+
260+
memcpy(entry + field->offset, &tmp, 4);
261+
break;
262+
}
263+
case 8:
264+
memcpy(entry + field->offset, &val, 8);
265+
break;
266+
default:
267+
return -EINVAL;
268+
}
269+
}
270+
271+
str += len;
272+
}
273+
274+
if (len < 0)
275+
return len;
276+
277+
return entry_size;
278+
}
279+
280+
static ssize_t
281+
event_inject_write(struct file *filp, const char __user *ubuf, size_t cnt,
282+
loff_t *ppos)
283+
{
284+
struct trace_event_call *call;
285+
struct trace_event_file *file;
286+
int err = -ENODEV, size;
287+
void *entry = NULL;
288+
char *buf;
289+
290+
if (cnt >= PAGE_SIZE)
291+
return -EINVAL;
292+
293+
buf = memdup_user_nul(ubuf, cnt);
294+
if (IS_ERR(buf))
295+
return PTR_ERR(buf);
296+
strim(buf);
297+
298+
mutex_lock(&event_mutex);
299+
file = event_file_data(filp);
300+
if (file) {
301+
call = file->event_call;
302+
size = parse_entry(buf, call, &entry);
303+
if (size < 0)
304+
err = size;
305+
else
306+
err = trace_inject_entry(file, entry, size);
307+
}
308+
mutex_unlock(&event_mutex);
309+
310+
kfree(entry);
311+
kfree(buf);
312+
313+
if (err < 0)
314+
return err;
315+
316+
*ppos += err;
317+
return cnt;
318+
}
319+
320+
static ssize_t
321+
event_inject_read(struct file *file, char __user *buf, size_t size,
322+
loff_t *ppos)
323+
{
324+
return -EPERM;
325+
}
326+
327+
const struct file_operations event_inject_fops = {
328+
.open = tracing_open_generic,
329+
.read = event_inject_read,
330+
.write = event_inject_write,
331+
};

0 commit comments

Comments
 (0)