|
|
|
@ -27,7 +27,6 @@ |
|
|
|
|
/**
|
|
|
|
|
* Kprobe event core functions |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
struct trace_probe { |
|
|
|
|
struct list_head list; |
|
|
|
|
struct kretprobe rp; /* Use rp.kp for kprobe use */ |
|
|
|
@ -36,6 +35,7 @@ struct trace_probe { |
|
|
|
|
const char *symbol; /* symbol name */ |
|
|
|
|
struct ftrace_event_class class; |
|
|
|
|
struct ftrace_event_call call; |
|
|
|
|
struct ftrace_event_file **files; |
|
|
|
|
ssize_t size; /* trace entry size */ |
|
|
|
|
unsigned int nr_args; |
|
|
|
|
struct probe_arg args[]; |
|
|
|
@ -183,12 +183,57 @@ static struct trace_probe *find_trace_probe(const char *event, |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Enable trace_probe - @flag must be TP_FLAG_TRACE or TP_FLAG_PROFILE */ |
|
|
|
|
static int enable_trace_probe(struct trace_probe *tp, int flag) |
|
|
|
|
static int trace_probe_nr_files(struct trace_probe *tp) |
|
|
|
|
{ |
|
|
|
|
struct ftrace_event_file **file = tp->files; |
|
|
|
|
int ret = 0; |
|
|
|
|
|
|
|
|
|
if (file) |
|
|
|
|
while (*(file++)) |
|
|
|
|
ret++; |
|
|
|
|
|
|
|
|
|
return ret; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static DEFINE_MUTEX(probe_enable_lock); |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Enable trace_probe |
|
|
|
|
* if the file is NULL, enable "perf" handler, or enable "trace" handler. |
|
|
|
|
*/ |
|
|
|
|
static int |
|
|
|
|
enable_trace_probe(struct trace_probe *tp, struct ftrace_event_file *file) |
|
|
|
|
{ |
|
|
|
|
int ret = 0; |
|
|
|
|
|
|
|
|
|
tp->flags |= flag; |
|
|
|
|
mutex_lock(&probe_enable_lock); |
|
|
|
|
|
|
|
|
|
if (file) { |
|
|
|
|
struct ftrace_event_file **new, **old = tp->files; |
|
|
|
|
int n = trace_probe_nr_files(tp); |
|
|
|
|
|
|
|
|
|
/* 1 is for new one and 1 is for stopper */ |
|
|
|
|
new = kzalloc((n + 2) * sizeof(struct ftrace_event_file *), |
|
|
|
|
GFP_KERNEL); |
|
|
|
|
if (!new) { |
|
|
|
|
ret = -ENOMEM; |
|
|
|
|
goto out_unlock; |
|
|
|
|
} |
|
|
|
|
memcpy(new, old, n * sizeof(struct ftrace_event_file *)); |
|
|
|
|
new[n] = file; |
|
|
|
|
/* The last one keeps a NULL */ |
|
|
|
|
|
|
|
|
|
rcu_assign_pointer(tp->files, new); |
|
|
|
|
tp->flags |= TP_FLAG_TRACE; |
|
|
|
|
|
|
|
|
|
if (old) { |
|
|
|
|
/* Make sure the probe is done with old files */ |
|
|
|
|
synchronize_sched(); |
|
|
|
|
kfree(old); |
|
|
|
|
} |
|
|
|
|
} else |
|
|
|
|
tp->flags |= TP_FLAG_PROFILE; |
|
|
|
|
|
|
|
|
|
if (trace_probe_is_enabled(tp) && trace_probe_is_registered(tp) && |
|
|
|
|
!trace_probe_has_gone(tp)) { |
|
|
|
|
if (trace_probe_is_return(tp)) |
|
|
|
@ -197,19 +242,83 @@ static int enable_trace_probe(struct trace_probe *tp, int flag) |
|
|
|
|
ret = enable_kprobe(&tp->rp.kp); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
out_unlock: |
|
|
|
|
mutex_unlock(&probe_enable_lock); |
|
|
|
|
|
|
|
|
|
return ret; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Disable trace_probe - @flag must be TP_FLAG_TRACE or TP_FLAG_PROFILE */ |
|
|
|
|
static void disable_trace_probe(struct trace_probe *tp, int flag) |
|
|
|
|
static int |
|
|
|
|
trace_probe_file_index(struct trace_probe *tp, struct ftrace_event_file *file) |
|
|
|
|
{ |
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
if (tp->files) { |
|
|
|
|
for (i = 0; tp->files[i]; i++) |
|
|
|
|
if (tp->files[i] == file) |
|
|
|
|
return i; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Disable trace_probe |
|
|
|
|
* if the file is NULL, disable "perf" handler, or disable "trace" handler. |
|
|
|
|
*/ |
|
|
|
|
static int |
|
|
|
|
disable_trace_probe(struct trace_probe *tp, struct ftrace_event_file *file) |
|
|
|
|
{ |
|
|
|
|
tp->flags &= ~flag; |
|
|
|
|
int ret = 0; |
|
|
|
|
|
|
|
|
|
mutex_lock(&probe_enable_lock); |
|
|
|
|
|
|
|
|
|
if (file) { |
|
|
|
|
struct ftrace_event_file **new, **old = tp->files; |
|
|
|
|
int n = trace_probe_nr_files(tp); |
|
|
|
|
int i, j; |
|
|
|
|
|
|
|
|
|
if (n == 0 || trace_probe_file_index(tp, file) < 0) { |
|
|
|
|
ret = -EINVAL; |
|
|
|
|
goto out_unlock; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (n == 1) { /* Remove the last file */ |
|
|
|
|
tp->flags &= ~TP_FLAG_TRACE; |
|
|
|
|
new = NULL; |
|
|
|
|
} else { |
|
|
|
|
new = kzalloc(n * sizeof(struct ftrace_event_file *), |
|
|
|
|
GFP_KERNEL); |
|
|
|
|
if (!new) { |
|
|
|
|
ret = -ENOMEM; |
|
|
|
|
goto out_unlock; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* This copy & check loop copies the NULL stopper too */ |
|
|
|
|
for (i = 0, j = 0; j < n && i < n + 1; i++) |
|
|
|
|
if (old[i] != file) |
|
|
|
|
new[j++] = old[i]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
rcu_assign_pointer(tp->files, new); |
|
|
|
|
|
|
|
|
|
/* Make sure the probe is done with old files */ |
|
|
|
|
synchronize_sched(); |
|
|
|
|
kfree(old); |
|
|
|
|
} else |
|
|
|
|
tp->flags &= ~TP_FLAG_PROFILE; |
|
|
|
|
|
|
|
|
|
if (!trace_probe_is_enabled(tp) && trace_probe_is_registered(tp)) { |
|
|
|
|
if (trace_probe_is_return(tp)) |
|
|
|
|
disable_kretprobe(&tp->rp); |
|
|
|
|
else |
|
|
|
|
disable_kprobe(&tp->rp.kp); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
out_unlock: |
|
|
|
|
mutex_unlock(&probe_enable_lock); |
|
|
|
|
|
|
|
|
|
return ret; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Internal register function - just handle k*probes and flags */ |
|
|
|
@ -724,7 +833,8 @@ static __kprobes void store_trace_args(int ent_size, struct trace_probe *tp, |
|
|
|
|
|
|
|
|
|
/* Kprobe handler */ |
|
|
|
|
static __kprobes void |
|
|
|
|
kprobe_trace_func(struct trace_probe *tp, struct pt_regs *regs) |
|
|
|
|
__kprobe_trace_func(struct trace_probe *tp, struct pt_regs *regs, |
|
|
|
|
struct ftrace_event_file *ftrace_file) |
|
|
|
|
{ |
|
|
|
|
struct kprobe_trace_entry_head *entry; |
|
|
|
|
struct ring_buffer_event *event; |
|
|
|
@ -733,14 +843,17 @@ kprobe_trace_func(struct trace_probe *tp, struct pt_regs *regs) |
|
|
|
|
unsigned long irq_flags; |
|
|
|
|
struct ftrace_event_call *call = &tp->call; |
|
|
|
|
|
|
|
|
|
WARN_ON(call != ftrace_file->event_call); |
|
|
|
|
|
|
|
|
|
local_save_flags(irq_flags); |
|
|
|
|
pc = preempt_count(); |
|
|
|
|
|
|
|
|
|
dsize = __get_data_size(tp, regs); |
|
|
|
|
size = sizeof(*entry) + tp->size + dsize; |
|
|
|
|
|
|
|
|
|
event = trace_current_buffer_lock_reserve(&buffer, call->event.type, |
|
|
|
|
size, irq_flags, pc); |
|
|
|
|
event = trace_event_buffer_lock_reserve(&buffer, ftrace_file, |
|
|
|
|
call->event.type, |
|
|
|
|
size, irq_flags, pc); |
|
|
|
|
if (!event) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
@ -753,10 +866,23 @@ kprobe_trace_func(struct trace_probe *tp, struct pt_regs *regs) |
|
|
|
|
irq_flags, pc, regs); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static __kprobes void |
|
|
|
|
kprobe_trace_func(struct trace_probe *tp, struct pt_regs *regs) |
|
|
|
|
{ |
|
|
|
|
struct ftrace_event_file **file = tp->files; |
|
|
|
|
|
|
|
|
|
/* Note: preempt is already disabled around the kprobe handler */ |
|
|
|
|
while (*file) { |
|
|
|
|
__kprobe_trace_func(tp, regs, *file); |
|
|
|
|
file++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Kretprobe handler */ |
|
|
|
|
static __kprobes void |
|
|
|
|
kretprobe_trace_func(struct trace_probe *tp, struct kretprobe_instance *ri, |
|
|
|
|
struct pt_regs *regs) |
|
|
|
|
__kretprobe_trace_func(struct trace_probe *tp, struct kretprobe_instance *ri, |
|
|
|
|
struct pt_regs *regs, |
|
|
|
|
struct ftrace_event_file *ftrace_file) |
|
|
|
|
{ |
|
|
|
|
struct kretprobe_trace_entry_head *entry; |
|
|
|
|
struct ring_buffer_event *event; |
|
|
|
@ -765,14 +891,17 @@ kretprobe_trace_func(struct trace_probe *tp, struct kretprobe_instance *ri, |
|
|
|
|
unsigned long irq_flags; |
|
|
|
|
struct ftrace_event_call *call = &tp->call; |
|
|
|
|
|
|
|
|
|
WARN_ON(call != ftrace_file->event_call); |
|
|
|
|
|
|
|
|
|
local_save_flags(irq_flags); |
|
|
|
|
pc = preempt_count(); |
|
|
|
|
|
|
|
|
|
dsize = __get_data_size(tp, regs); |
|
|
|
|
size = sizeof(*entry) + tp->size + dsize; |
|
|
|
|
|
|
|
|
|
event = trace_current_buffer_lock_reserve(&buffer, call->event.type, |
|
|
|
|
size, irq_flags, pc); |
|
|
|
|
event = trace_event_buffer_lock_reserve(&buffer, ftrace_file, |
|
|
|
|
call->event.type, |
|
|
|
|
size, irq_flags, pc); |
|
|
|
|
if (!event) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
@ -786,6 +915,19 @@ kretprobe_trace_func(struct trace_probe *tp, struct kretprobe_instance *ri, |
|
|
|
|
irq_flags, pc, regs); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static __kprobes void |
|
|
|
|
kretprobe_trace_func(struct trace_probe *tp, struct kretprobe_instance *ri, |
|
|
|
|
struct pt_regs *regs) |
|
|
|
|
{ |
|
|
|
|
struct ftrace_event_file **file = tp->files; |
|
|
|
|
|
|
|
|
|
/* Note: preempt is already disabled around the kprobe handler */ |
|
|
|
|
while (*file) { |
|
|
|
|
__kretprobe_trace_func(tp, ri, regs, *file); |
|
|
|
|
file++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Event entry printers */ |
|
|
|
|
enum print_line_t |
|
|
|
|
print_kprobe_event(struct trace_iterator *iter, int flags, |
|
|
|
@ -1041,20 +1183,19 @@ int kprobe_register(struct ftrace_event_call *event, |
|
|
|
|
enum trace_reg type, void *data) |
|
|
|
|
{ |
|
|
|
|
struct trace_probe *tp = (struct trace_probe *)event->data; |
|
|
|
|
struct ftrace_event_file *file = data; |
|
|
|
|
|
|
|
|
|
switch (type) { |
|
|
|
|
case TRACE_REG_REGISTER: |
|
|
|
|
return enable_trace_probe(tp, TP_FLAG_TRACE); |
|
|
|
|
return enable_trace_probe(tp, file); |
|
|
|
|
case TRACE_REG_UNREGISTER: |
|
|
|
|
disable_trace_probe(tp, TP_FLAG_TRACE); |
|
|
|
|
return 0; |
|
|
|
|
return disable_trace_probe(tp, file); |
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_PERF_EVENTS |
|
|
|
|
case TRACE_REG_PERF_REGISTER: |
|
|
|
|
return enable_trace_probe(tp, TP_FLAG_PROFILE); |
|
|
|
|
return enable_trace_probe(tp, NULL); |
|
|
|
|
case TRACE_REG_PERF_UNREGISTER: |
|
|
|
|
disable_trace_probe(tp, TP_FLAG_PROFILE); |
|
|
|
|
return 0; |
|
|
|
|
return disable_trace_probe(tp, NULL); |
|
|
|
|
case TRACE_REG_PERF_OPEN: |
|
|
|
|
case TRACE_REG_PERF_CLOSE: |
|
|
|
|
case TRACE_REG_PERF_ADD: |
|
|
|
@ -1190,11 +1331,24 @@ static __used int kprobe_trace_selftest_target(int a1, int a2, int a3, |
|
|
|
|
return a1 + a2 + a3 + a4 + a5 + a6; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static struct ftrace_event_file * |
|
|
|
|
find_trace_probe_file(struct trace_probe *tp, struct trace_array *tr) |
|
|
|
|
{ |
|
|
|
|
struct ftrace_event_file *file; |
|
|
|
|
|
|
|
|
|
list_for_each_entry(file, &tr->events, list) |
|
|
|
|
if (file->event_call == &tp->call) |
|
|
|
|
return file; |
|
|
|
|
|
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static __init int kprobe_trace_self_tests_init(void) |
|
|
|
|
{ |
|
|
|
|
int ret, warn = 0; |
|
|
|
|
int (*target)(int, int, int, int, int, int); |
|
|
|
|
struct trace_probe *tp; |
|
|
|
|
struct ftrace_event_file *file; |
|
|
|
|
|
|
|
|
|
target = kprobe_trace_selftest_target; |
|
|
|
|
|
|
|
|
@ -1204,31 +1358,43 @@ static __init int kprobe_trace_self_tests_init(void) |
|
|
|
|
"$stack $stack0 +0($stack)", |
|
|
|
|
create_trace_probe); |
|
|
|
|
if (WARN_ON_ONCE(ret)) { |
|
|
|
|
pr_warning("error on probing function entry.\n"); |
|
|
|
|
pr_warn("error on probing function entry.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else { |
|
|
|
|
/* Enable trace point */ |
|
|
|
|
tp = find_trace_probe("testprobe", KPROBE_EVENT_SYSTEM); |
|
|
|
|
if (WARN_ON_ONCE(tp == NULL)) { |
|
|
|
|
pr_warning("error on getting new probe.\n"); |
|
|
|
|
pr_warn("error on getting new probe.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else |
|
|
|
|
enable_trace_probe(tp, TP_FLAG_TRACE); |
|
|
|
|
} else { |
|
|
|
|
file = find_trace_probe_file(tp, top_trace_array()); |
|
|
|
|
if (WARN_ON_ONCE(file == NULL)) { |
|
|
|
|
pr_warn("error on getting probe file.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else |
|
|
|
|
enable_trace_probe(tp, file); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ret = traceprobe_command("r:testprobe2 kprobe_trace_selftest_target " |
|
|
|
|
"$retval", create_trace_probe); |
|
|
|
|
if (WARN_ON_ONCE(ret)) { |
|
|
|
|
pr_warning("error on probing function return.\n"); |
|
|
|
|
pr_warn("error on probing function return.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else { |
|
|
|
|
/* Enable trace point */ |
|
|
|
|
tp = find_trace_probe("testprobe2", KPROBE_EVENT_SYSTEM); |
|
|
|
|
if (WARN_ON_ONCE(tp == NULL)) { |
|
|
|
|
pr_warning("error on getting new probe.\n"); |
|
|
|
|
pr_warn("error on getting 2nd new probe.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else |
|
|
|
|
enable_trace_probe(tp, TP_FLAG_TRACE); |
|
|
|
|
} else { |
|
|
|
|
file = find_trace_probe_file(tp, top_trace_array()); |
|
|
|
|
if (WARN_ON_ONCE(file == NULL)) { |
|
|
|
|
pr_warn("error on getting probe file.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else |
|
|
|
|
enable_trace_probe(tp, file); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (warn) |
|
|
|
@ -1239,27 +1405,39 @@ static __init int kprobe_trace_self_tests_init(void) |
|
|
|
|
/* Disable trace points before removing it */ |
|
|
|
|
tp = find_trace_probe("testprobe", KPROBE_EVENT_SYSTEM); |
|
|
|
|
if (WARN_ON_ONCE(tp == NULL)) { |
|
|
|
|
pr_warning("error on getting test probe.\n"); |
|
|
|
|
pr_warn("error on getting test probe.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else |
|
|
|
|
disable_trace_probe(tp, TP_FLAG_TRACE); |
|
|
|
|
} else { |
|
|
|
|
file = find_trace_probe_file(tp, top_trace_array()); |
|
|
|
|
if (WARN_ON_ONCE(file == NULL)) { |
|
|
|
|
pr_warn("error on getting probe file.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else |
|
|
|
|
disable_trace_probe(tp, file); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tp = find_trace_probe("testprobe2", KPROBE_EVENT_SYSTEM); |
|
|
|
|
if (WARN_ON_ONCE(tp == NULL)) { |
|
|
|
|
pr_warning("error on getting 2nd test probe.\n"); |
|
|
|
|
pr_warn("error on getting 2nd test probe.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else |
|
|
|
|
disable_trace_probe(tp, TP_FLAG_TRACE); |
|
|
|
|
} else { |
|
|
|
|
file = find_trace_probe_file(tp, top_trace_array()); |
|
|
|
|
if (WARN_ON_ONCE(file == NULL)) { |
|
|
|
|
pr_warn("error on getting probe file.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} else |
|
|
|
|
disable_trace_probe(tp, file); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ret = traceprobe_command("-:testprobe", create_trace_probe); |
|
|
|
|
if (WARN_ON_ONCE(ret)) { |
|
|
|
|
pr_warning("error on deleting a probe.\n"); |
|
|
|
|
pr_warn("error on deleting a probe.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ret = traceprobe_command("-:testprobe2", create_trace_probe); |
|
|
|
|
if (WARN_ON_ONCE(ret)) { |
|
|
|
|
pr_warning("error on deleting a probe.\n"); |
|
|
|
|
pr_warn("error on deleting a probe.\n"); |
|
|
|
|
warn++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|