1436 lines · 34.6 KB · C
RawDownload
/* * 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 list_head	files;	ssize_t			size;		/* trace entry size */	unsigned int		nr_args;	struct probe_arg	args[];}; struct event_file_link {	struct ftrace_event_file	*file;	struct list_head		list;}; #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 int 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);	INIT_LIST_HEAD(&tp->files);	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;} /* * 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; 	if (file) {		struct event_file_link *link; 		link = kmalloc(sizeof(*link), GFP_KERNEL);		if (!link) {			ret = -ENOMEM;			goto out;		} 		link->file = file;		list_add_tail_rcu(&link->list, &tp->files); 		tp->flags |= TP_FLAG_TRACE;	} else		tp->flags |= TP_FLAG_PROFILE; 	if (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:	return ret;} static struct event_file_link *find_event_file_link(struct trace_probe *tp, struct ftrace_event_file *file){	struct event_file_link *link; 	list_for_each_entry(link, &tp->files, list)		if (link->file == file)			return link; 	return NULL;} /* * 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){	struct event_file_link *link = NULL;	int wait = 0;	int ret = 0; 	if (file) {		link = find_event_file_link(tp, file);		if (!link) {			ret = -EINVAL;			goto out;		} 		list_del_rcu(&link->list);		wait = 1;		if (!list_empty(&tp->files))			goto out; 		tp->flags &= ~TP_FLAG_TRACE;	} 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);		wait = 1;	} out:	if (wait) {		/*		 * Synchronize with kprobe_trace_func/kretprobe_trace_func		 * to ensure disabled (all running handlers are finished).		 * This is not only for kfree(), but also the caller,		 * trace_remove_event_call() supposes it for releasing		 * event_call related objects, which will be accessed in		 * the kprobe_trace_func/kretprobe_trace_func.		 */		synchronize_sched();		kfree(link);	/* Ignored if link == NULL */	} 	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; 	/* Will fail if probe is being used by ftrace or perf */	if (unregister_probe_event(tp))		return -EBUSY; 	__unregister_trace_probe(tp);	list_del(&tp->list); 	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;	char *symbol = NULL, *event = NULL, *group = NULL;	char *arg;	unsigned long offset = 0;	void *addr = NULL;	char buf[MAX_EVENT_NAME_LEN]; 	/* argc must be >= 1 */	if (argv[0][0] == 'p')		is_return = false;	else if (argv[0][0] == 'r')		is_return = true;	else if (argv[0][0] == '-')		is_delete = true;	else {		pr_info("Probe definition must be started with 'p', 'r' or"			" '-'.\n");		return -EINVAL;	} 	if (argv[0][1] == ':') {		event = &argv[0][2];		if (strchr(event, '/')) {			group = event;			event = strchr(group, '/') + 1;			event[-1] = '\0';			if (strlen(group) == 0) {				pr_info("Group name is not specified\n");				return -EINVAL;			}		}		if (strlen(event) == 0) {			pr_info("Event name is not specified\n");			return -EINVAL;		}	}	if (!group)		group = KPROBE_EVENT_SYSTEM; 	if (is_delete) {		if (!event) {			pr_info("Delete command needs an event name.\n");			return -EINVAL;		}		mutex_lock(&probe_lock);

Lines 1–500 of 1436.