/* * Kprobes-based tracing events * * Created by Masami Hiramatsu <mhiramat@redhat.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/module.h>#include <linux/uaccess.h> #include "trace_probe.h" #define KPROBE_EVENT_SYSTEM "kprobes" /** * Kprobe event core functions */struct trace_probe { struct list_head list; struct kretprobe rp; /* Use rp.kp for kprobe use */ unsigned long nhit; unsigned int flags; /* For TP_FLAG_* */ const char *symbol; /* symbol name */ struct ftrace_event_class class; struct ftrace_event_call call; struct ftrace_event_file * __rcu *files; ssize_t size; /* trace entry size */ unsigned int nr_args; struct probe_arg args[];}; #define SIZEOF_TRACE_PROBE(n) \ (offsetof(struct trace_probe, args) + \ (sizeof(struct probe_arg) * (n))) static __kprobes bool trace_probe_is_return(struct trace_probe *tp){ return tp->rp.handler != NULL;} static __kprobes const char *trace_probe_symbol(struct trace_probe *tp){ return tp->symbol ? tp->symbol : "unknown";} static __kprobes unsigned long trace_probe_offset(struct trace_probe *tp){ return tp->rp.kp.offset;} static __kprobes bool trace_probe_is_enabled(struct trace_probe *tp){ return !!(tp->flags & (TP_FLAG_TRACE | TP_FLAG_PROFILE));} static __kprobes bool trace_probe_is_registered(struct trace_probe *tp){ return !!(tp->flags & TP_FLAG_REGISTERED);} static __kprobes bool trace_probe_has_gone(struct trace_probe *tp){ return !!(kprobe_gone(&tp->rp.kp));} static __kprobes bool trace_probe_within_module(struct trace_probe *tp, struct module *mod){ int len = strlen(mod->name); const char *name = trace_probe_symbol(tp); return strncmp(mod->name, name, len) == 0 && name[len] == ':';} static __kprobes bool trace_probe_is_on_module(struct trace_probe *tp){ return !!strchr(trace_probe_symbol(tp), ':');} static int register_probe_event(struct trace_probe *tp);static void unregister_probe_event(struct trace_probe *tp); static DEFINE_MUTEX(probe_lock);static LIST_HEAD(probe_list); static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs);static int kretprobe_dispatcher(struct kretprobe_instance *ri, struct pt_regs *regs); /* * Allocate new trace_probe and initialize it (including kprobes). */static struct trace_probe *alloc_trace_probe(const char *group, const char *event, void *addr, const char *symbol, unsigned long offs, int nargs, bool is_return){ struct trace_probe *tp; int ret = -ENOMEM; tp = kzalloc(SIZEOF_TRACE_PROBE(nargs), GFP_KERNEL); if (!tp) return ERR_PTR(ret); if (symbol) { tp->symbol = kstrdup(symbol, GFP_KERNEL); if (!tp->symbol) goto error; tp->rp.kp.symbol_name = tp->symbol; tp->rp.kp.offset = offs; } else tp->rp.kp.addr = addr; if (is_return) tp->rp.handler = kretprobe_dispatcher; else tp->rp.kp.pre_handler = kprobe_dispatcher; if (!event || !is_good_name(event)) { ret = -EINVAL; goto error; } tp->call.class = &tp->class; tp->call.name = kstrdup(event, GFP_KERNEL); if (!tp->call.name) goto error; if (!group || !is_good_name(group)) { ret = -EINVAL; goto error; } tp->class.system = kstrdup(group, GFP_KERNEL); if (!tp->class.system) goto error; INIT_LIST_HEAD(&tp->list); return tp;error: kfree(tp->call.name); kfree(tp->symbol); kfree(tp); return ERR_PTR(ret);} static void free_trace_probe(struct trace_probe *tp){ int i; for (i = 0; i < tp->nr_args; i++) traceprobe_free_probe_arg(&tp->args[i]); kfree(tp->call.class->system); kfree(tp->call.name); kfree(tp->symbol); kfree(tp);} static struct trace_probe *find_trace_probe(const char *event, const char *group){ struct trace_probe *tp; list_for_each_entry(tp, &probe_list, list) if (strcmp(tp->call.name, event) == 0 && strcmp(tp->call.class->system, group) == 0) return tp; return NULL;} static int trace_probe_nr_files(struct trace_probe *tp){ struct ftrace_event_file **file; int ret = 0; /* * Since all tp->files updater is protected by probe_enable_lock, * we don't need to lock an rcu_read_lock. */ file = rcu_dereference_raw(tp->files); 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 intenable_trace_probe(struct trace_probe *tp, struct ftrace_event_file *file){ int ret = 0; mutex_lock(&probe_enable_lock); if (file) { struct ftrace_event_file **new, **old; int n = trace_probe_nr_files(tp); old = rcu_dereference_raw(tp->files); /* 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)) ret = enable_kretprobe(&tp->rp); else ret = enable_kprobe(&tp->rp.kp); } out_unlock: mutex_unlock(&probe_enable_lock); return ret;} static inttrace_probe_file_index(struct trace_probe *tp, struct ftrace_event_file *file){ struct ftrace_event_file **files; int i; /* * Since all tp->files updater is protected by probe_enable_lock, * we don't need to lock an rcu_read_lock. */ files = rcu_dereference_raw(tp->files); if (files) { for (i = 0; files[i]; i++) if (files[i] == file) return i; } return -1;} /* * Disable trace_probe * if the file is NULL, disable "perf" handler, or disable "trace" handler. */static intdisable_trace_probe(struct trace_probe *tp, struct ftrace_event_file *file){ int ret = 0; mutex_lock(&probe_enable_lock); if (file) { struct ftrace_event_file **new, **old; int n = trace_probe_nr_files(tp); int i, j; old = rcu_dereference_raw(tp->files); 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 */static int __register_trace_probe(struct trace_probe *tp){ int i, ret; if (trace_probe_is_registered(tp)) return -EINVAL; for (i = 0; i < tp->nr_args; i++) traceprobe_update_arg(&tp->args[i]); /* Set/clear disabled flag according to tp->flag */ if (trace_probe_is_enabled(tp)) tp->rp.kp.flags &= ~KPROBE_FLAG_DISABLED; else tp->rp.kp.flags |= KPROBE_FLAG_DISABLED; if (trace_probe_is_return(tp)) ret = register_kretprobe(&tp->rp); else ret = register_kprobe(&tp->rp.kp); if (ret == 0) tp->flags |= TP_FLAG_REGISTERED; else { pr_warning("Could not insert probe at %s+%lu: %d\n", trace_probe_symbol(tp), trace_probe_offset(tp), ret); if (ret == -ENOENT && trace_probe_is_on_module(tp)) { pr_warning("This probe might be able to register after" "target module is loaded. Continue.\n"); ret = 0; } else if (ret == -EILSEQ) { pr_warning("Probing address(0x%p) is not an " "instruction boundary.\n", tp->rp.kp.addr); ret = -EINVAL; } } return ret;} /* Internal unregister function - just handle k*probes and flags */static void __unregister_trace_probe(struct trace_probe *tp){ if (trace_probe_is_registered(tp)) { if (trace_probe_is_return(tp)) unregister_kretprobe(&tp->rp); else unregister_kprobe(&tp->rp.kp); tp->flags &= ~TP_FLAG_REGISTERED; /* Cleanup kprobe for reuse */ if (tp->rp.kp.symbol_name) tp->rp.kp.addr = NULL; }} /* Unregister a trace_probe and probe_event: call with locking probe_lock */static int unregister_trace_probe(struct trace_probe *tp){ /* Enabled event can not be unregistered */ if (trace_probe_is_enabled(tp)) return -EBUSY; __unregister_trace_probe(tp); list_del(&tp->list); unregister_probe_event(tp); return 0;} /* Register a trace_probe and probe_event */static int register_trace_probe(struct trace_probe *tp){ struct trace_probe *old_tp; int ret; mutex_lock(&probe_lock); /* Delete old (same name) event if exist */ old_tp = find_trace_probe(tp->call.name, tp->call.class->system); if (old_tp) { ret = unregister_trace_probe(old_tp); if (ret < 0) goto end; free_trace_probe(old_tp); } /* Register new event */ ret = register_probe_event(tp); if (ret) { pr_warning("Failed to register probe event(%d)\n", ret); goto end; } /* Register k*probe */ ret = __register_trace_probe(tp); if (ret < 0) unregister_probe_event(tp); else list_add_tail(&tp->list, &probe_list); end: mutex_unlock(&probe_lock); return ret;} /* Module notifier call back, checking event on the module */static int trace_probe_module_callback(struct notifier_block *nb, unsigned long val, void *data){ struct module *mod = data; struct trace_probe *tp; int ret; if (val != MODULE_STATE_COMING) return NOTIFY_DONE; /* Update probes on coming module */ mutex_lock(&probe_lock); list_for_each_entry(tp, &probe_list, list) { if (trace_probe_within_module(tp, mod)) { /* Don't need to check busy - this should have gone. */ __unregister_trace_probe(tp); ret = __register_trace_probe(tp); if (ret) pr_warning("Failed to re-register probe %s on" "%s: %d\n", tp->call.name, mod->name, ret); } } mutex_unlock(&probe_lock); return NOTIFY_DONE;} static struct notifier_block trace_probe_module_nb = { .notifier_call = trace_probe_module_callback, .priority = 1 /* Invoked after kprobe module callback */}; static int create_trace_probe(int argc, char **argv){ /* * Argument syntax: * - Add kprobe: p[:[GRP/]EVENT] [MOD:]KSYM[+OFFS]|KADDR [FETCHARGS] * - Add kretprobe: r[:[GRP/]EVENT] [MOD:]KSYM[+0] [FETCHARGS] * Fetch args: * $retval : fetch return value * $stack : fetch stack address * $stackN : fetch Nth of stack (N:0-) * @ADDR : fetch memory at ADDR (ADDR should be in kernel) * @SYM[+|-offs] : fetch memory at SYM +|- offs (SYM is a data symbol) * %REG : fetch register REG * Dereferencing memory fetch: * +|-offs(ARG) : fetch memory at ARG +|- offs address. * Alias name of args: * NAME=FETCHARG : set NAME as alias of FETCHARG. * Type of args: * FETCHARG:TYPE : use TYPE instead of unsigned long. */ struct trace_probe *tp; int i, ret = 0; bool is_return = false, is_delete = false;Lines 1–500 of 1488. Load more
/* * Kprobes-based tracing events * * Created by Masami Hiramatsu <mhiramat@redhat.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/module.h> #include <linux/uaccess.h> #include "trace_probe.h" #define KPROBE_EVENT_SYSTEM "kprobes" /** * Kprobe event core functions */ struct trace_probe { struct list_head list; struct kretprobe rp; /* Use rp.kp for kprobe use */ unsigned long nhit; unsigned int flags; /* For TP_FLAG_* */ const char *symbol; /* symbol name */ struct ftrace_event_class class; struct ftrace_event_call call; struct ftrace_event_file * __rcu *files; ssize_t size; /* trace entry size */ unsigned int nr_args; struct probe_arg args[]; }; #define SIZEOF_TRACE_PROBE(n) \ (offsetof(struct trace_probe, args) + \ (sizeof(struct probe_arg) * (n))) static __kprobes bool trace_probe_is_return(struct trace_probe *tp) { return tp->rp.handler != NULL; } static __kprobes const char *trace_probe_symbol(struct trace_probe *tp) { return tp->symbol ? tp->symbol : "unknown"; } static __kprobes unsigned long trace_probe_offset(struct trace_probe *tp) { return tp->rp.kp.offset; } static __kprobes bool trace_probe_is_enabled(struct trace_probe *tp) { return !!(tp->flags & (TP_FLAG_TRACE | TP_FLAG_PROFILE)); } static __kprobes bool trace_probe_is_registered(struct trace_probe *tp) { return !!(tp->flags & TP_FLAG_REGISTERED); } static __kprobes bool trace_probe_has_gone(struct trace_probe *tp) { return !!(kprobe_gone(&tp->rp.kp)); } static __kprobes bool trace_probe_within_module(struct trace_probe *tp, struct module *mod) { int len = strlen(mod->name); const char *name = trace_probe_symbol(tp); return strncmp(mod->name, name, len) == 0 && name[len] == ':'; } static __kprobes bool trace_probe_is_on_module(struct trace_probe *tp) { return !!strchr(trace_probe_symbol(tp), ':'); } static int register_probe_event(struct trace_probe *tp); static void unregister_probe_event(struct trace_probe *tp); static DEFINE_MUTEX(probe_lock); static LIST_HEAD(probe_list); static int kprobe_dispatcher(struct kprobe *kp, struct pt_regs *regs); static int kretprobe_dispatcher(struct kretprobe_instance *ri, struct pt_regs *regs); /* * Allocate new trace_probe and initialize it (including kprobes). */ static struct trace_probe *alloc_trace_probe(const char *group, const char *event, void *addr, const char *symbol, unsigned long offs, int nargs, bool is_return) { struct trace_probe *tp; int ret = -ENOMEM; tp = kzalloc(SIZEOF_TRACE_PROBE(nargs), GFP_KERNEL); if (!tp) return ERR_PTR(ret); if (symbol) { tp->symbol = kstrdup(symbol, GFP_KERNEL); if (!tp->symbol) goto error; tp->rp.kp.symbol_name = tp->symbol; tp->rp.kp.offset = offs; } else tp->rp.kp.addr = addr; if (is_return) tp->rp.handler = kretprobe_dispatcher; else tp->rp.kp.pre_handler = kprobe_dispatcher; if (!event || !is_good_name(event)) { ret = -EINVAL; goto error; } tp->call.class = &tp->class; tp->call.name = kstrdup(event, GFP_KERNEL); if (!tp->call.name) goto error; if (!group || !is_good_name(group)) { ret = -EINVAL; goto error; } tp->class.system = kstrdup(group, GFP_KERNEL); if (!tp->class.system) goto error; INIT_LIST_HEAD(&tp->list); return tp; error: kfree(tp->call.name); kfree(tp->symbol); kfree(tp); return ERR_PTR(ret); } static void free_trace_probe(struct trace_probe *tp) { int i; for (i = 0; i < tp->nr_args; i++) traceprobe_free_probe_arg(&tp->args[i]); kfree(tp->call.class->system); kfree(tp->call.name); kfree(tp->symbol); kfree(tp); } static struct trace_probe *find_trace_probe(const char *event, const char *group) { struct trace_probe *tp; list_for_each_entry(tp, &probe_list, list) if (strcmp(tp->call.name, event) == 0 && strcmp(tp->call.class->system, group) == 0) return tp; return NULL; } static int trace_probe_nr_files(struct trace_probe *tp) { struct ftrace_event_file **file; int ret = 0; /* * Since all tp->files updater is protected by probe_enable_lock, * we don't need to lock an rcu_read_lock. */ file = rcu_dereference_raw(tp->files); 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; mutex_lock(&probe_enable_lock); if (file) { struct ftrace_event_file **new, **old; int n = trace_probe_nr_files(tp); old = rcu_dereference_raw(tp->files); /* 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)) ret = enable_kretprobe(&tp->rp); else ret = enable_kprobe(&tp->rp.kp); } out_unlock: mutex_unlock(&probe_enable_lock); return ret; } static int trace_probe_file_index(struct trace_probe *tp, struct ftrace_event_file *file) { struct ftrace_event_file **files; int i; /* * Since all tp->files updater is protected by probe_enable_lock, * we don't need to lock an rcu_read_lock. */ files = rcu_dereference_raw(tp->files); if (files) { for (i = 0; files[i]; i++) if (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) { int ret = 0; mutex_lock(&probe_enable_lock); if (file) { struct ftrace_event_file **new, **old; int n = trace_probe_nr_files(tp); int i, j; old = rcu_dereference_raw(tp->files); 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 */ static int __register_trace_probe(struct trace_probe *tp) { int i, ret; if (trace_probe_is_registered(tp)) return -EINVAL; for (i = 0; i < tp->nr_args; i++) traceprobe_update_arg(&tp->args[i]); /* Set/clear disabled flag according to tp->flag */ if (trace_probe_is_enabled(tp)) tp->rp.kp.flags &= ~KPROBE_FLAG_DISABLED; else tp->rp.kp.flags |= KPROBE_FLAG_DISABLED; if (trace_probe_is_return(tp)) ret = register_kretprobe(&tp->rp); else ret = register_kprobe(&tp->rp.kp); if (ret == 0) tp->flags |= TP_FLAG_REGISTERED; else { pr_warning("Could not insert probe at %s+%lu: %d\n", trace_probe_symbol(tp), trace_probe_offset(tp), ret); if (ret == -ENOENT && trace_probe_is_on_module(tp)) { pr_warning("This probe might be able to register after" "target module is loaded. Continue.\n"); ret = 0; } else if (ret == -EILSEQ) { pr_warning("Probing address(0x%p) is not an " "instruction boundary.\n", tp->rp.kp.addr); ret = -EINVAL; } } return ret; } /* Internal unregister function - just handle k*probes and flags */ static void __unregister_trace_probe(struct trace_probe *tp) { if (trace_probe_is_registered(tp)) { if (trace_probe_is_return(tp)) unregister_kretprobe(&tp->rp); else unregister_kprobe(&tp->rp.kp); tp->flags &= ~TP_FLAG_REGISTERED; /* Cleanup kprobe for reuse */ if (tp->rp.kp.symbol_name) tp->rp.kp.addr = NULL; } } /* Unregister a trace_probe and probe_event: call with locking probe_lock */ static int unregister_trace_probe(struct trace_probe *tp) { /* Enabled event can not be unregistered */ if (trace_probe_is_enabled(tp)) return -EBUSY; __unregister_trace_probe(tp); list_del(&tp->list); unregister_probe_event(tp); return 0; } /* Register a trace_probe and probe_event */ static int register_trace_probe(struct trace_probe *tp) { struct trace_probe *old_tp; int ret; mutex_lock(&probe_lock); /* Delete old (same name) event if exist */ old_tp = find_trace_probe(tp->call.name, tp->call.class->system); if (old_tp) { ret = unregister_trace_probe(old_tp); if (ret < 0) goto end; free_trace_probe(old_tp); } /* Register new event */ ret = register_probe_event(tp); if (ret) { pr_warning("Failed to register probe event(%d)\n", ret); goto end; } /* Register k*probe */ ret = __register_trace_probe(tp); if (ret < 0) unregister_probe_event(tp); else list_add_tail(&tp->list, &probe_list); end: mutex_unlock(&probe_lock); return ret; } /* Module notifier call back, checking event on the module */ static int trace_probe_module_callback(struct notifier_block *nb, unsigned long val, void *data) { struct module *mod = data; struct trace_probe *tp; int ret; if (val != MODULE_STATE_COMING) return NOTIFY_DONE; /* Update probes on coming module */ mutex_lock(&probe_lock); list_for_each_entry(tp, &probe_list, list) { if (trace_probe_within_module(tp, mod)) { /* Don't need to check busy - this should have gone. */ __unregister_trace_probe(tp); ret = __register_trace_probe(tp); if (ret) pr_warning("Failed to re-register probe %s on" "%s: %d\n", tp->call.name, mod->name, ret); } } mutex_unlock(&probe_lock); return NOTIFY_DONE; } static struct notifier_block trace_probe_module_nb = { .notifier_call = trace_probe_module_callback, .priority = 1 /* Invoked after kprobe module callback */ }; static int create_trace_probe(int argc, char **argv) { /* * Argument syntax: * - Add kprobe: p[:[GRP/]EVENT] [MOD:]KSYM[+OFFS]|KADDR [FETCHARGS] * - Add kretprobe: r[:[GRP/]EVENT] [MOD:]KSYM[+0] [FETCHARGS] * Fetch args: * $retval : fetch return value * $stack : fetch stack address * $stackN : fetch Nth of stack (N:0-) * @ADDR : fetch memory at ADDR (ADDR should be in kernel) * @SYM[+|-offs] : fetch memory at SYM +|- offs (SYM is a data symbol) * %REG : fetch register REG * Dereferencing memory fetch: * +|-offs(ARG) : fetch memory at ARG +|- offs address. * Alias name of args: * NAME=FETCHARG : set NAME as alias of FETCHARG. * Type of args: * FETCHARG:TYPE : use TYPE instead of unsigned long. */ struct trace_probe *tp; int i, ret = 0; bool is_return = false, is_delete = false;